[gradle-1.12] 01/211: upstream import 0.9~rc1

Kai-Chung Yan seamlik-guest at moszumanska.debian.org
Wed Jul 1 14:17:43 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 cfe46b404afbd0cd3356ccaaf7dc10d29aa08c9e
Author: Miguel Landaeta <miguel at miguel.cc>
Date:   Sun Aug 15 14:01:55 2010 -0430

    upstream import 0.9~rc1
---
 build.gradle                                       |  490 +++++++
 buildSrc/build.gradle                              |   40 +
 .../build/docs/AssembleSampleDocsTask.groovy       |  104 ++
 .../groovy/org/gradle/build/docs/DomBuilder.groovy |   54 +
 .../gradle/build/docs/ExtractSnippetsTask.groovy   |  128 ++
 .../build/docs/SampleElementLocationHandler.groovy |   49 +
 .../build/docs/SampleElementValidator.groovy       |   45 +
 .../build/docs/UserGuideTransformTask.groovy       |  290 +++++
 .../build/samples/WrapperProjectCreator.groovy     |   52 +
 .../build/startscripts/StartScriptGenerator.groovy |   59 +
 .../build/startscripts/unixStartScriptHead.txt     |   87 ++
 .../build/startscripts/unixStartScriptTail.txt     |  110 ++
 .../build/startscripts/windowsStartScriptHead.txt  |  107 ++
 .../build/startscripts/windowsStartScriptTail.txt  |   25 +
 config/checkstyle/checkstyle-groovy.xml            |   26 +
 config/checkstyle/checkstyle.xml                   |   73 ++
 config/checkstyle/required-header.txt              |   15 +
 config/checkstyle/suppressions.xml                 |   10 +
 config/codenarc.xml                                |   36 +
 gradle.properties                                  |    4 +
 gradlew                                            |  140 ++
 gradlew.bat                                        |  126 ++
 settings.gradle                                    |   40 +
 src/toplevel/LICENSE                               |  519 ++++++++
 src/toplevel/NOTICE                                |   21 +
 src/toplevel/changelog.txt                         |    4 +
 subprojects/gradle-announce/announce.gradle        |   25 +
 .../api/plugins/announce/AnnouncePlugin.groovy     |   54 +
 .../org/gradle/api/plugins/announce/Announcer.java |   22 +
 .../api/plugins/announce/AnnouncerFactory.java     |   23 +
 .../announce/DefaultAnnouncerFactory.groovy        |   48 +
 .../org/gradle/api/plugins/announce/Growl.groovy   |   28 +
 .../gradle/api/plugins/announce/NotifySend.groovy  |   48 +
 .../org/gradle/api/plugins/announce/Snarl.groovy   |   74 ++
 .../org/gradle/api/plugins/announce/Twitter.groovy |   69 +
 .../META-INF/gradle-plugins/announce.properties    |   16 +
 .../announce/AnnouncePluginConventionTest.groovy   |   54 +
 .../api/plugins/announce/AnnouncePluginTest.groovy |   36 +
 .../announce/DefaultAnnouncerFactoryTest.groovy    |   62 +
 .../api/plugins/announce/NotifySendTest.groovy     |   60 +
 .../gradle/api/plugins/announce/SnarlTest.groovy   |   41 +
 subprojects/gradle-antlr/antlr.gradle              |   31 +
 .../org/gradle/api/plugins/antlr/AntlrPlugin.java  |  121 ++
 .../plugins/antlr/AntlrSourceVirtualDirectory.java |   57 +
 .../antlr/AntlrSourceVirtualDirectoryImpl.java     |   61 +
 .../org/gradle/api/plugins/antlr/AntlrTask.java    |  143 +++
 .../plugins/antlr/metadata/GrammarDelegate.java    |  164 +++
 .../antlr/metadata/GrammarFileMetadata.java        |   61 +
 .../plugins/antlr/metadata/GrammarMetadata.java    |  100 ++
 .../plugins/antlr/metadata/MetadataExtracter.java  |   98 ++
 .../gradle/api/plugins/antlr/metadata/XRef.java    |   97 ++
 .../api/plugins/antlr/plan/GenerationPlan.java     |   79 ++
 .../plugins/antlr/plan/GenerationPlanBuilder.java  |  148 +++
 .../META-INF/gradle-plugins/antlr.properties       |   16 +
 .../api/plugins/antlr/AntlrPluginTest.groovy       |   67 +
 .../gradle-code-quality/code-quality.gradle        |   37 +
 .../api/plugins/quality/AntCheckstyle.groovy       |   42 +
 .../gradle/api/plugins/quality/AntCodeNarc.groovy  |   42 +
 .../org/gradle/api/plugins/quality/Checkstyle.java |   92 ++
 .../org/gradle/api/plugins/quality/CodeNarc.java   |   68 +
 .../api/plugins/quality/CodeQualityPlugin.groovy   |  102 ++
 .../GroovyCodeQualityPluginConvention.groovy       |   39 +
 .../quality/JavaCodeQualityPluginConvention.groovy |   40 +
 .../gradle-plugins/code-quality.properties         |   16 +
 .../plugins/quality/CodeQualityPluginTest.groovy   |  128 ++
 subprojects/gradle-core/core.gradle                |  154 +++
 .../gradle/integtests/AbstractIntegrationTest.java |   80 ++
 .../integtests/AntProjectIntegrationTest.groovy    |  158 +++
 .../gradle/integtests/AntlrIntegrationTest.java    |   26 +
 .../integtests/ArchiveIntegrationTest.groovy       |  619 +++++++++
 .../ArtifactDependenciesIntegrationTest.groovy     |  168 +++
 .../BackwardsCompatibilityIntegrationTest.groovy   |  103 ++
 .../groovy/org/gradle/integtests/BrokenTask.java   |   31 +
 .../BuildAggregationIntegrationTest.groovy         |   98 ++
 .../BuildScriptClasspathIntegrationTest.java       |  184 +++
 .../BuildScriptErrorIntegrationTest.java           |  165 +++
 .../BuildScriptExecutionIntegrationTest.groovy     |   51 +
 .../integtests/CacheProjectIntegrationTest.groovy  |  125 ++
 ...ntModuleDependenciesResolveIntegrationTest.java |   43 +
 .../integtests/CodeQualityIntegrationTest.groovy   |  187 +++
 .../integtests/CommandLineIntegrationTest.groovy   |   55 +
 .../integtests/CopyErrorIntegrationTest.groovy     |   75 ++
 .../integtests/CopyTaskIntegrationTest.groovy      |  320 +++++
 .../DependenciesResolveIntegrationTest.java        |   44 +
 .../org/gradle/integtests/DirTransformerTask.java  |   58 +
 .../integtests/DistributionIntegrationTest.groovy  |  128 ++
 .../DistributionIntegrationTestRunner.java         |   40 +
 .../integtests/DynamicObjectIntegrationTest.groovy |  187 +++
 .../integtests/EclipseIntegrationTest.groovy       |   33 +
 .../gradle/integtests/ExecIntegrationTest.groovy   |   41 +
 .../ExternalPluginIntegrationTest.groovy           |   68 +
 .../ExternalScriptErrorIntegrationTest.groovy      |   89 ++
 .../ExternalScriptExecutionIntegrationTest.groovy  |  184 +++
 .../integtests/FileTreeCopyIntegrationTest.groovy  |   82 ++
 .../org/gradle/integtests/GeneratorTask.java       |   53 +
 .../GradleUserHomeEnvVariableIntegrationTest.java  |   66 +
 .../integtests/GroovyProjectIntegrationTest.java   |   37 +
 .../gradle/integtests/IdeaIntegrationTest.groovy   |   40 +
 .../IncrementalBuildIntegrationTest.groovy         |  374 ++++++
 .../IncrementalGroovyCompileIntegrationTest.groovy |   53 +
 .../IncrementalJavaCompileIntegrationTest.groovy   |  121 ++
 ...crementalJavaProjectBuildIntegrationTest.groovy |   68 +
 .../IncrementalScalaCompileIntegrationTest.groovy  |   53 +
 .../IncrementalTestIntegrationTest.groovy          |   95 ++
 .../integtests/InitScriptErrorIntegrationTest.java |   47 +
 .../InitScriptExecutionIntegrationTest.groovy      |   87 ++
 .../integtests/IvyPublishIntegrationTest.java      |   45 +
 .../gradle/integtests/JUnitIntegrationTest.groovy  |  349 +++++
 .../integtests/JUnitTestExecutionResult.groovy     |  150 +++
 .../integtests/JavaProjectIntegrationTest.java     |  104 ++
 .../integtests/LoggingIntegrationTest.groovy       |  214 ++++
 .../integtests/MultiprojectIntegrationTest.groovy  |   47 +
 .../OsgiProjectSampleIntegrationTest.groovy        |   59 +
 .../integtests/ProjectLayoutIntegrationTest.groovy |  175 +++
 .../integtests/ProjectLoadingIntegrationTest.java  |  253 ++++
 .../integtests/SamplesAntlrIntegrationTest.groovy  |   43 +
 .../SamplesCodeQualityIntegrationTest.groovy       |   46 +
 ...amplesCustomBuildLanguageIntegrationTest.groovy |   61 +
 .../SamplesCustomPluginIntegrationTest.groovy      |   41 +
 ...lesExcludesAndClassifiersIntegrationTest.groovy |   53 +
 ...lesGroovyCustomizedLayoutIntegrationTest.groovy |   50 +
 ...SamplesGroovyMultiProjectIntegrationTest.groovy |   81 ++
 .../SamplesGroovyOldVersionsIntegrationTest.groovy |   57 +
 .../SamplesGroovyQuickstartIntegrationTest.groovy  |   54 +
 .../SamplesJavaBaseIntegrationTest.groovy          |   59 +
 ...mplesJavaCustomizedLayoutIntegrationTest.groovy |   59 +
 .../SamplesJavaMultiProjectIntegrationTest.groovy  |  228 ++++
 .../SamplesJavaOnlyIfIntegrationTest.groovy        |   94 ++
 ...esJavaProjectWithIntTestsIntegrationTest.groovy |   47 +
 .../SamplesJavaQuickstartIntegrationTest.groovy    |   72 ++
 ...SamplesMixedJavaAndGroovyIntegrationTest.groovy |   73 ++
 .../SamplesMixedJavaAndScalaIntegrationTest.groovy |   76 ++
 .../SamplesRepositoriesIntegrationTest.groovy      |   45 +
 ...plesScalaCustomizedLayoutIntegrationTest.groovy |   53 +
 .../SamplesScalaQuickstartIntegrationTest.groovy   |   68 +
 .../SamplesWebProjectIntegrationTest.groovy        |   73 ++
 .../SamplesWebQuickstartIntegrationTest.groovy     |   67 +
 .../integtests/ScalaProjectIntegrationTest.java    |   37 +
 .../SettingsScriptErrorIntegrationTest.java        |   40 +
 .../SettingsScriptExecutionIntegrationTest.groovy  |   68 +
 .../integtests/SyncTaskIntegrationTest.groovy      |   54 +
 .../TaskAutoDependencyIntegrationTest.groovy       |   71 ++
 .../integtests/TaskDefinitionIntegrationTest.java  |  135 ++
 .../integtests/TaskExecutionIntegrationTest.java   |  161 +++
 .../groovy/org/gradle/integtests/TestTask.java     |   30 +
 .../org/gradle/integtests/TransformerTask.java     |   66 +
 .../UserGuideSamplesIntegrationTest.groovy         |   23 +
 .../integtests/UserGuideSamplesRunner.groovy       |  260 ++++
 .../integtests/UserguideIntegrationTest.groovy     |   52 +
 .../integtests/WaterProjectIntegrationTest.groovy  |   79 ++
 .../integtests/WebProjectIntegrationTest.java      |   80 ++
 .../integtests/WorkerProcessIntegrationTest.java   |  372 ++++++
 .../WrapperProjectIntegrationTest.groovy           |   47 +
 .../fixtures/AbstractExecutionResult.java          |   35 +
 .../fixtures/AbstractGradleExecuter.java           |  191 +++
 .../integtests/fixtures/ArtifactBuilder.java       |   28 +
 .../integtests/fixtures/ExecutionFailure.java      |   32 +
 .../integtests/fixtures/ExecutionResult.java       |   41 +
 .../integtests/fixtures/ForkingGradleExecuter.java |  233 ++++
 .../fixtures/GradleBackedArtifactBuilder.java      |   50 +
 .../integtests/fixtures/GradleDistribution.java    |  144 +++
 .../fixtures/GradleDistributionExecuter.java       |  154 +++
 .../gradle/integtests/fixtures/GradleExecuter.java |   87 ++
 .../gradle/integtests/fixtures/HttpServer.groovy   |   60 +
 .../fixtures/InProcessGradleExecuter.java          |  322 +++++
 .../org/gradle/integtests/fixtures/RuleHelper.java |   56 +
 .../org/gradle/integtests/fixtures/Sample.java     |   49 +
 .../fixtures/TestClassExecutionResult.java         |   50 +
 .../integtests/fixtures/TestExecutionResult.java   |   29 +
 .../gradle/integtests/fixtures/TestResources.java  |   85 ++
 .../maven/MavenProjectIntegrationTest.groovy       |   87 ++
 .../maven/MavenRepoIntegrationTest.groovy          |   46 +
 .../maven/MavenSnapshotIntegrationTest.groovy      |   35 +
 ...SamplesMavenPomGenerationIntegrationTest.groovy |  155 +++
 .../SamplesMavenQuickstartIntegrationTest.groovy   |  100 ++
 .../testng/TestNGIntegrationProject.groovy         |   67 +
 .../integtests/testng/TestNGIntegrationTest.groovy |  239 ++++
 .../integtests/testng/TestNgExecutionResult.groovy |  145 +++
 .../projectA-1.2-ivy.xml                           |   19 +
 .../projectB-1.5-ivy.xml                           |   18 +
 .../projectWithConfigurationHierarchy.gradle       |   59 +
 .../projectA-1.2-ivy.xml                           |   17 +
 .../projectB-1.5-ivy.xml                           |   33 +
 .../projectWithCyclesInDependencyGraph.gradle      |   38 +
 .../canHaveCycleInProjectDependencies/build.gradle |   37 +
 .../settings.gradle                                |    1 +
 .../canNestModules/projectWithNestedModules.gradle |   31 +
 .../canUseDynamicVersions/projectA-1.2-ivy.xml     |   17 +
 .../canUseDynamicVersions/projectB-1.5-ivy.xml     |   11 +
 .../projectWithDynamicVersions.gradle              |   33 +
 .../projectA-1.2-ivy.xml                           |   17 +
 .../projectA-2.0-ivy.xml                           |   17 +
 .../projectB-1.5-ivy.xml                           |   14 +
 .../projectB-2.1.5-ivy.xml                         |   14 +
 .../projectWithConflicts.gradle                    |   57 +
 .../dependencyReportWithConflicts/settings.gradle  |    3 +
 .../projectWithUnknownDependency.gradle            |   16 +
 .../canBuildJavaProject/build.gradle               |    3 +
 .../src/main/groovy/org/gradle/CustomTask.groovy   |   10 +
 .../src/main/java/org/gradle/Person.java           |    5 +
 .../canCreateAndDeleteMetaData/api/build.gradle    |    9 +
 .../src/integTest/java/org/gradle/SomeClass.java   |    3 +
 .../src/main/java/org/gradle/api/PersonList.java   |    5 +
 .../api/src/main/resources/someprops.properties    |    0
 .../test/java/org/gradle/shared/PersonTest.java    |   12 +
 .../api/src/test/resources/someprops.properties    |    0
 .../expectedFiles/apiClasspath.xml                 |   11 +
 .../expectedFiles/apiProject.xml                   |   21 +
 .../expectedFiles/groovyprojectClasspath.xml       |   11 +
 .../expectedFiles/groovyprojectProject.xml         |   24 +
 .../expectedFiles/masterProject.xml                |   10 +
 .../expectedFiles/webserviceClasspath.xml          |   18 +
 .../expectedFiles/webserviceProject.xml            |   39 +
 .../expectedFiles/webserviceWtpComponent.xml       |   17 +
 .../expectedFiles/webserviceWtpFacet.xml           |    6 +
 .../groovyproject/build.gradle                     |    5 +
 .../groovyproject/src/main/groovy/script.groovy    |    0
 .../src/main/java/org/gradle/api/PersonList.java   |    5 +
 .../src/main/resources/someprops.properties        |    0
 .../test/java/org/gradle/shared/PersonTest.java    |   12 +
 .../src/test/resources/someprops.properties        |    0
 .../canCreateAndDeleteMetaData/master/build.gradle |   65 +
 .../master/settings.gradle                         |    1 +
 .../webservice/build.gradle                        |   25 +
 .../main/java/org/gradle/webservice/TestTest.java  |    5 +
 .../canExecuteCommands/canExecuteCommands.gradle   |   33 +
 .../src/main/java/org/gradle/TestMain.java         |    9 +
 .../canExecuteJava/canExecuteJava.gradle           |   27 +
 .../src/main/java/org/gradle/TestMain.java         |    9 +
 .../canCreateAndDeleteMetaData/api/build.gradle    |   11 +
 .../src/main/java/org/gradle/api/PersonList.java   |    5 +
 .../test/java/org/gradle/shared/PersonTest.java    |   12 +
 .../canCreateAndDeleteMetaData/build.gradle        |   71 ++
 .../expectedFiles/expectedApiModule.xml            |   40 +
 .../expectedFiles/expectedIwsFile.xml              |  212 +++
 .../expectedFiles/expectedProjectFile.xml          |  104 ++
 .../expectedFiles/expectedRootModule.xml           |   12 +
 .../expectedFiles/expectedWebserviceModule.xml     |   76 ++
 .../canCreateAndDeleteMetaData/settings.gradle     |    4 +
 .../webservice/build.gradle                        |   19 +
 .../main/java/org/gradle/webservice/TestTest.java  |    5 +
 .../recompilesDependentClasses/NewIPerson.groovy   |    4 +
 .../recompilesDependentClasses/build.gradle        |    5 +
 .../src/main/groovy/IPerson.groovy                 |    3 +
 .../src/main/groovy/Person.groovy                  |    3 +
 .../build.gradle                                   |    6 +
 .../src/main/groovy/Person.java                    |    4 +
 .../src/main/groovy/PersonImpl.Groovy              |    4 +
 .../recompilesDependentClasses/NewIPerson.java     |    4 +
 .../recompilesDependentClasses/build.gradle        |    1 +
 .../src/main/java/IPerson.java                     |    3 +
 .../src/main/java/Person.java                      |    3 +
 .../NewIPerson.java                                |    4 +
 .../app/src/main/java/Person.java                  |    3 +
 .../build.gradle                                   |    9 +
 .../lib/src/main/java/IPerson.java                 |    3 +
 .../settings.gradle                                |    1 +
 .../build.gradle                                   |    4 +
 .../src/main/java/Test.java                        |    3 +
 .../recompilesDependentClasses/NewIPerson.scala    |    4 +
 .../recompilesDependentClasses/build.gradle        |   11 +
 .../src/main/scala/IPerson.scala                   |    3 +
 .../src/main/scala/Person.scala                    |    3 +
 .../build.gradle                                   |   12 +
 .../src/main/scala/Person.java                     |    3 +
 .../src/main/scala/PersonImpl.scala                |    3 +
 .../doesNotRunStaleTests/src/test/java/Broken.java |    8 +
 .../build.gradle                                   |   14 +
 .../src/test/java/JUnitExtra.java                  |    7 +
 .../src/test/java/JUnitTest.java                   |    7 +
 .../src/test/java/TestNGTest.java                  |    6 +
 .../NewMainClass.java                              |    6 +
 .../executesTestsWhenSourceChanges/NewOk.java      |    5 +
 .../src/main/java/MainClass.java                   |    2 +
 .../shared/build.gradle                            |    9 +
 .../shared/src/test/java/Ok.java                   |    7 +
 .../canRunSingleTests/build.gradle                 |    9 +
 .../canRunSingleTests/src/test/java/NotATest.java  |    2 +
 .../canRunSingleTests/src/test/java/Ok.java        |    7 +
 .../canRunSingleTests/src/test/java/Ok2.java       |    7 +
 .../detectsTestClasses/build.gradle                |    8 +
 .../test/java/org/gradle/AbstractHasRunWith.java   |    7 +
 .../src/test/java/org/gradle/CustomRunner.java     |   26 +
 .../test/java/org/gradle/EmptyRunWithSubclass.java |    5 +
 .../src/test/java/org/gradle/TestsOnInner.java     |    7 +
 .../executesTestsInCorrectEnvironment/build.gradle |    8 +
 .../src/test/java/org/gradle/OkTest.java           |   72 ++
 .../src/test/java/org/gradle/OtherTest.java        |   22 +
 .../build.gradle                                   |    3 +
 .../src/test/java/org/gradle/BrokenAfter.java      |   17 +
 .../src/test/java/org/gradle/BrokenAfterClass.java |   17 +
 .../src/test/java/org/gradle/BrokenBefore.java     |   17 +
 .../test/java/org/gradle/BrokenBeforeClass.java    |   17 +
 .../test/java/org/gradle/BrokenConstructor.java    |   15 +
 .../src/test/java/org/gradle/BrokenException.java  |   18 +
 .../src/test/java/org/gradle/BrokenTest.java       |   17 +
 .../src/test/java/org/gradle/Unloadable.java       |   15 +
 .../canUseANonStandardBuildDir/build.gradle        |   13 +
 .../src/main/java/Person.java                      |    2 +
 .../src/test/java/PersonTest.java                  |    8 +
 .../copyTestResources/src/one/ignore/bad.file      |    1 +
 .../integtests/copyTestResources/src/one/one.a     |    1 +
 .../integtests/copyTestResources/src/one/one.b     |    1 +
 .../copyTestResources/src/one/sub/ignore/bad.file  |    0
 .../copyTestResources/src/one/sub/onesub.a         |    0
 .../copyTestResources/src/one/sub/onesub.b         |    0
 .../gradle/integtests/copyTestResources/src/root.a |    2 +
 .../gradle/integtests/copyTestResources/src/root.b |    1 +
 .../copyTestResources/src/two/ignore/bad.file      |    1 +
 .../integtests/copyTestResources/src/two/two.a     |    3 +
 .../integtests/copyTestResources/src/two/two.b     |    1 +
 .../copyTestResources/src2/three/three.a           |    1 +
 .../copyTestResources/src2/three/three.b           |    1 +
 .../groovy/expectedClasspathFile.txt               |   19 +
 .../eclipseproject/groovy/expectedProjectFile.txt  |   17 +
 .../java/expectedApiClasspathFile.txt              |   11 +
 .../eclipseproject/java/expectedApiProjectFile.txt |   16 +
 .../java/expectedWebserviceClasspathFile.txt       |   13 +
 .../java/expectedWebserviceProjectFile.txt         |   16 +
 .../java/expectedWebserviceWtpFile.txt             |   23 +
 .../eclipseproject/scala/expectedClasspathFile.txt |   11 +
 .../eclipseproject/scala/expectedProjectFile.txt   |   17 +
 .../projectWithMavenSnapshots.gradle               |   78 ++
 .../src/main/java/org/gradle/Test.java             |    4 +
 .../maven/pomGeneration/expectedNewPom.txt         |   68 +
 .../integtests/maven/pomGeneration/expectedPom.txt |   61 +
 .../maven/pomGeneration/expectedQuickstartPom.txt  |    7 +
 .../src/main/groovy/org/gradle/BuildAdapter.java   |   42 +
 .../groovy/org/gradle/BuildExceptionReporter.java  |  176 +++
 .../src/main/groovy/org/gradle/BuildListener.java  |   66 +
 .../src/main/groovy/org/gradle/BuildLogger.java    |   79 ++
 .../src/main/groovy/org/gradle/BuildResult.java    |   53 +
 .../main/groovy/org/gradle/BuildResultLogger.java  |   41 +
 .../src/main/groovy/org/gradle/CacheUsage.java     |   35 +
 .../org/gradle/CommandLineArgumentException.java   |   31 +
 .../src/main/groovy/org/gradle/GradleLauncher.java |  142 +++
 .../groovy/org/gradle/GradleLauncherFactory.java   |   36 +
 .../src/main/groovy/org/gradle/StartParameter.java |  533 ++++++++
 .../groovy/org/gradle/TaskExecutionLogger.java     |   62 +
 .../src/main/groovy/org/gradle/api/Action.java     |   28 +
 .../src/main/groovy/org/gradle/api/AntBuilder.java |   46 +
 .../src/main/groovy/org/gradle/api/Buildable.java  |   34 +
 .../org/gradle/api/CircularReferenceException.java |   33 +
 .../main/groovy/org/gradle/api/DefaultTask.java    |   39 +
 .../org/gradle/api/DomainObjectCollection.java     |  118 ++
 .../groovy/org/gradle/api/GradleException.java     |   36 +
 .../org/gradle/api/GradleScriptException.java      |   36 +
 .../org/gradle/api/IllegalDependencyNotation.java  |   34 +
 .../IllegalOperationAtExecutionTimeException.java  |   28 +
 .../org/gradle/api/InvalidUserDataException.java   |   35 +
 .../main/groovy/org/gradle/api/JavaVersion.java    |   67 +
 .../org/gradle/api/LocationAwareException.java     |   67 +
 .../gradle/api/NamedDomainObjectCollection.java    |  114 ++
 .../org/gradle/api/NamedDomainObjectContainer.java |   53 +
 .../main/groovy/org/gradle/api/PathValidation.java |   24 +
 .../src/main/groovy/org/gradle/api/Plugin.java     |   32 +
 .../src/main/groovy/org/gradle/api/Project.java    | 1343 ++++++++++++++++++++
 .../org/gradle/api/ProjectEvaluationListener.java  |   42 +
 .../main/groovy/org/gradle/api/ProjectState.java   |   41 +
 .../src/main/groovy/org/gradle/api/Rule.java       |   39 +
 .../src/main/groovy/org/gradle/api/Script.java     |  326 +++++
 .../org/gradle/api/ScriptCompilationException.java |   46 +
 .../src/main/groovy/org/gradle/api/Task.java       |  523 ++++++++
 .../main/groovy/org/gradle/api/Transformer.java    |   29 +
 .../org/gradle/api/UncheckedIOException.java       |   39 +
 .../gradle/api/UnknownDomainObjectException.java   |   30 +
 .../org/gradle/api/UnknownProjectException.java    |   32 +
 .../org/gradle/api/UnknownTaskException.java       |   32 +
 .../org/gradle/api/artifacts/ClientModule.java     |   59 +
 .../org/gradle/api/artifacts/Configuration.java    |  409 ++++++
 .../api/artifacts/ConfigurationContainer.java      |   92 ++
 .../org/gradle/api/artifacts/Dependency.java       |   64 +
 .../gradle/api/artifacts/DependencyArtifact.java   |   60 +
 .../org/gradle/api/artifacts/ExcludeRule.java      |   39 +
 .../gradle/api/artifacts/ExcludeRuleContainer.java |   45 +
 .../gradle/api/artifacts/ExternalDependency.java   |   41 +
 .../api/artifacts/ExternalModuleDependency.java    |   45 +
 .../api/artifacts/FileCollectionDependency.java    |   23 +
 .../org/gradle/api/artifacts/IvyObjectBuilder.java |   43 +
 .../groovy/org/gradle/api/artifacts/Module.java    |   34 +
 .../org/gradle/api/artifacts/ModuleDependency.java |  105 ++
 .../ProjectDependenciesBuildInstruction.java       |   64 +
 .../gradle/api/artifacts/ProjectDependency.java    |   43 +
 .../org/gradle/api/artifacts/PublishArtifact.java  |   70 +
 .../gradle/api/artifacts/PublishInstruction.java   |   99 ++
 .../org/gradle/api/artifacts/ResolveException.java |   49 +
 .../org/gradle/api/artifacts/ResolvedArtifact.java |   33 +
 .../api/artifacts/ResolvedConfiguration.java       |   68 +
 .../gradle/api/artifacts/ResolvedDependency.java   |   95 ++
 .../gradle/api/artifacts/ResolverContainer.java    |  198 +++
 .../api/artifacts/SelfResolvingDependency.java     |   48 +
 .../artifacts/UnknownConfigurationException.java   |   27 +
 .../api/artifacts/UnknownRepositoryException.java  |   29 +
 .../gradle/api/artifacts/dsl/ArtifactHandler.java  |   33 +
 .../api/artifacts/dsl/DependencyHandler.java       |  147 +++
 .../api/artifacts/dsl/RepositoryHandler.java       |  238 ++++
 .../artifacts/dsl/RepositoryHandlerFactory.java    |   29 +
 .../org/gradle/api/artifacts/dsl/package-info.java |   20 +
 .../api/artifacts/indexing/IndexFileUtil.java      |   32 +
 .../api/artifacts/indexing/JarArtifactIndexer.java |   82 ++
 .../artifacts/indexing/JarFilePackageListener.java |   24 +
 .../artifacts/indexing/JarFilePackageLister.java   |   71 ++
 .../api/artifacts/maven/Conf2ScopeMapping.java     |   95 ++
 .../maven/Conf2ScopeMappingContainer.java          |   88 ++
 .../api/artifacts/maven/GroovyMavenDeployer.java   |   39 +
 .../artifacts/maven/GroovyPomFilterContainer.java  |   62 +
 .../gradle/api/artifacts/maven/MavenDeployer.java  |   89 ++
 .../org/gradle/api/artifacts/maven/MavenPom.java   |  205 +++
 .../gradle/api/artifacts/maven/MavenResolver.java  |   30 +
 .../api/artifacts/maven/PomFilterContainer.java    |   94 ++
 .../gradle/api/artifacts/maven/PublishFilter.java  |   34 +
 .../gradle/api/artifacts/maven/XmlProvider.java    |   28 +
 .../gradle/api/artifacts/maven/package-info.java   |   20 +
 .../org/gradle/api/artifacts/package-info.java     |   20 +
 .../artifacts/repositories/InternalRepository.java |   24 +
 .../api/artifacts/repositories/WebdavResolver.java |   44 +
 .../api/artifacts/repositories/package-info.java   |   20 +
 .../api/artifacts/specs/DependencySpecs.java       |   25 +
 .../api/artifacts/specs/DependencyTypeSpec.java    |   62 +
 .../org/gradle/api/artifacts/specs/Type.java       |   28 +
 .../gradle/api/artifacts/specs/package-info.java   |   20 +
 .../gradle/api/execution/TaskActionListener.java   |   37 +
 .../gradle/api/execution/TaskExecutionGraph.java   |  115 ++
 .../api/execution/TaskExecutionGraphListener.java  |   32 +
 .../api/execution/TaskExecutionListener.java       |   44 +
 .../org/gradle/api/execution/package-info.java     |   20 +
 .../api/file/ConfigurableFileCollection.java       |   58 +
 .../org/gradle/api/file/ConfigurableFileTree.java  |   77 ++
 .../org/gradle/api/file/ContentFilterable.java     |   78 ++
 .../groovy/org/gradle/api/file/CopyAction.java     |   24 +
 .../org/gradle/api/file/CopyProcessingSpec.java    |  122 ++
 .../groovy/org/gradle/api/file/CopySourceSpec.java |   42 +
 .../main/groovy/org/gradle/api/file/CopySpec.java  |  236 ++++
 .../groovy/org/gradle/api/file/DeleteAction.java   |   29 +
 .../org/gradle/api/file/EmptyFileVisitor.java      |   29 +
 .../groovy/org/gradle/api/file/FileCollection.java |  189 +++
 .../org/gradle/api/file/FileCopyDetails.java       |   51 +
 .../main/groovy/org/gradle/api/file/FileTree.java  |   92 ++
 .../org/gradle/api/file/FileTreeElement.java       |  101 ++
 .../org/gradle/api/file/FileVisitDetails.java      |   29 +
 .../groovy/org/gradle/api/file/FileVisitor.java    |   37 +
 .../groovy/org/gradle/api/file/RelativePath.java   |  199 +++
 .../org/gradle/api/file/SourceDirectorySet.java    |   71 ++
 .../api/initialization/ProjectDescriptor.java      |  102 ++
 .../org/gradle/api/initialization/Settings.java    |  176 +++
 .../api/initialization/dsl/ScriptHandler.java      |  109 ++
 .../gradle/api/initialization/package-info.java    |   20 +
 .../api/internal/AbstractClassGenerator.java       |  159 +++
 .../api/internal/AbstractClassPathProvider.java    |  157 +++
 .../internal/AbstractDomainObjectCollection.java   |  136 ++
 .../gradle/api/internal/AbstractDynamicObject.java |   83 ++
 .../org/gradle/api/internal/AbstractTask.java      |  457 +++++++
 .../api/internal/AsmBackedClassGenerator.java      |  581 +++++++++
 .../internal/AutoCreateDomainObjectContainer.java  |   51 +
 .../AutoCreateDomainObjectContainerDelegate.groovy |   73 ++
 .../org/gradle/api/internal/BeanDynamicObject.java |  168 +++
 .../api/internal/CachingDirectedGraphWalker.java   |  178 +++
 .../gradle/api/internal/ChainingTransformer.java   |   66 +
 .../org/gradle/api/internal/ClassGenerator.java    |   28 +
 .../org/gradle/api/internal/ClassPathProvider.java |   27 +
 .../org/gradle/api/internal/ClassPathRegistry.java |   29 +
 .../api/internal/CompositeDynamicObject.java       |  108 ++
 .../groovy/org/gradle/api/internal/Contextual.java |   32 +
 .../gradle/api/internal/ConventionAwareHelper.java |  179 +++
 .../org/gradle/api/internal/ConventionMapping.java |   51 +
 .../org/gradle/api/internal/ConventionTask.java    |   45 +
 .../api/internal/DefaultClassPathProvider.java     |   36 +
 .../api/internal/DefaultClassPathRegistry.java     |   75 ++
 .../api/internal/DefaultDomainObjectContainer.java |  110 ++
 .../DefaultNamedDomainObjectContainer.java         |  313 +++++
 .../org/gradle/api/internal/DirectedGraph.java     |   26 +
 .../api/internal/DirectedGraphWithEdgeValues.java  |   25 +
 .../gradle/api/internal/DomainObjectContext.java   |   20 +
 .../org/gradle/api/internal/DynamicObject.java     |   35 +
 .../gradle/api/internal/DynamicObjectAware.java    |   49 +
 .../gradle/api/internal/DynamicObjectHelper.java   |  167 +++
 .../org/gradle/api/internal/ExceptionAnalyser.java |   20 +
 .../api/internal/GradleDistributionLocator.java    |   22 +
 .../org/gradle/api/internal/GradleInternal.java    |   77 ++
 .../org/gradle/api/internal/GraphAggregator.java   |   91 ++
 ...GroovySourceGenerationBackedClassGenerator.java |  133 ++
 .../org/gradle/api/internal/IConventionAware.java  |   43 +
 .../api/internal/MapBackedDynamicObject.java       |   58 +
 .../gradle/api/internal/NoConventionMapping.java   |   31 +
 .../org/gradle/api/internal/NoDynamicObject.java   |   29 +
 .../org/gradle/api/internal/SettingsInternal.java  |   33 +
 .../gradle/api/internal/TaskExecutionHistory.java  |   25 +
 .../org/gradle/api/internal/TaskInternal.java      |   40 +
 .../gradle/api/internal/TaskOutputsInternal.java   |   29 +
 .../api/internal/artifacts/ArtifactContainer.java  |   48 +
 .../artifacts/CachingDependencyResolveContext.java |   73 ++
 .../artifacts/ConfigurationContainerFactory.java   |   31 +
 .../DefaultConfigurationContainerFactory.java      |   79 ++
 .../api/internal/artifacts/DefaultExcludeRule.java |   60 +
 .../artifacts/DefaultExcludeRuleContainer.java     |   46 +
 .../api/internal/artifacts/DefaultModule.java      |   57 +
 .../artifacts/DefaultResolvedArtifact.java         |   79 ++
 .../artifacts/DefaultResolvedDependency.java       |  160 +++
 .../artifacts/DefaultResolverContainer.java        |  231 ++++
 .../api/internal/artifacts/DependencyInternal.java |   23 +
 .../artifacts/DependencyResolveContext.java        |   23 +
 .../gradle/api/internal/artifacts/IvyService.java  |   35 +
 .../artifacts/ResolvedConfigurationIdentifier.java |   92 ++
 .../artifacts/configurations/Configurations.java   |   89 ++
 .../configurations/ConfigurationsProvider.java     |   27 +
 .../configurations/DefaultConfiguration.java       |  535 ++++++++
 .../DefaultConfigurationContainer.java             |   75 ++
 .../configurations/DependencyMetaDataProvider.java |   32 +
 .../DetachedConfigurationsProvider.java            |   36 +
 .../artifacts/configurations/ResolverProvider.java |   27 +
 .../artifacts/dependencies/AbstractDependency.java |   39 +
 .../dependencies/AbstractExternalDependency.java   |   36 +
 .../dependencies/AbstractModuleDependency.java     |  125 ++
 .../dependencies/DefaultClientModule.java          |  152 +++
 .../dependencies/DefaultDependencyArtifact.java    |   97 ++
 .../DefaultExternalModuleDependency.java           |  126 ++
 .../dependencies/DefaultProjectDependency.java     |  168 +++
 .../DefaultSelfResolvingDependency.java            |   80 ++
 .../artifacts/dsl/AbstractScriptTransformer.java   |   60 +
 .../dsl/BuildScriptClasspathScriptTransformer.java |   38 +
 .../artifacts/dsl/BuildScriptTransformer.java      |   40 +
 .../artifacts/dsl/ClasspathScriptTransformer.java  |  172 +++
 .../artifacts/dsl/DefaultArtifactHandler.groovy    |   66 +
 .../dsl/DefaultPublishArtifactFactory.java         |   34 +
 .../artifacts/dsl/DefaultRepositoryHandler.java    |  165 +++
 .../dsl/DefaultRepositoryHandlerFactory.java       |   42 +
 .../artifacts/dsl/FixMainScriptTransformer.java    |   48 +
 .../artifacts/dsl/PublishArtifactFactory.java      |   25 +
 .../dsl/TaskDefinitionScriptTransformer.java       |  204 +++
 .../dependencies/ClassPathDependencyFactory.java   |   49 +
 .../dependencies/DefaultClientModuleFactory.java   |   55 +
 .../dsl/dependencies/DefaultDependencyFactory.java |   85 ++
 .../dependencies/DefaultDependencyHandler.groovy   |   96 ++
 .../DefaultProjectDependencyFactory.java           |   64 +
 .../dsl/dependencies/DependencyFactory.java        |   36 +
 .../IDependencyImplementationFactory.java          |   26 +
 .../dsl/dependencies/MapModuleNotationParser.java  |   56 +
 .../dsl/dependencies/ModuleDependencyFactory.java  |   74 ++
 .../dependencies/ModuleDescriptorDelegate.groovy   |   65 +
 .../dsl/dependencies/ModuleFactoryHelper.java      |   40 +
 .../dependencies/ParsedModuleStringNotation.java   |   68 +
 .../dsl/dependencies/ProjectDependencyFactory.java |   28 +
 .../artifacts/dsl/dependencies/ProjectFinder.java  |   30 +
 .../SelfResolvingDependencyFactory.java            |   40 +
 .../artifacts/ivyservice/ClientModuleResolver.java |  126 ++
 .../ivyservice/DefaultIvyConversionResult.java     |   50 +
 .../ivyservice/DefaultIvyDependencyPublisher.java  |   61 +
 .../ivyservice/DefaultIvyDependencyResolver.java   |  157 +++
 .../artifacts/ivyservice/DefaultIvyFactory.java    |   28 +
 .../ivyservice/DefaultIvyReportConverter.java      |  329 +++++
 .../artifacts/ivyservice/DefaultIvyService.java    |  191 +++
 .../ivyservice/DefaultPublishOptionsFactory.java   |   41 +
 .../ivyservice/DefaultResolverFactory.java         |  175 +++
 .../ivyservice/DefaultSettingsConverter.java       |  204 +++
 .../ivyservice/ErrorHandlingIvyService.java        |  142 +++
 .../ivyservice/GradleIBiblioResolver.java          |  145 +++
 .../artifacts/ivyservice/IvyConversionResult.java  |   34 +
 .../ivyservice/IvyDependencyPublisher.java         |   34 +
 .../ivyservice/IvyDependencyResolver.java          |   28 +
 .../internal/artifacts/ivyservice/IvyFactory.java  |   26 +
 .../artifacts/ivyservice/IvyReportConverter.java   |   26 +
 .../api/internal/artifacts/ivyservice/IvyUtil.java |   54 +
 .../ivyservice/ModuleDescriptorConverter.java      |   30 +
 .../ivyservice/PublishOptionsFactory.java          |   28 +
 .../artifacts/ivyservice/ResolverFactory.java      |   45 +
 .../SelfResolvingDependencyResolver.java           |   78 ++
 .../artifacts/ivyservice/SettingsConverter.java    |   37 +
 .../ShortcircuitEmptyConfigsIvyService.java        |   70 +
 .../ivyservice/SnapshotVersionMatcher.java         |   36 +
 .../AbstractModuleDescriptorConverter.java         |   51 +
 .../ArtifactsExtraAttributesStrategy.java          |   27 +
 .../ArtifactsToModuleDescriptorConverter.java      |   28 +
 .../ConfigurationsToModuleDescriptorConverter.java |   28 +
 ...efaultArtifactsToModuleDescriptorConverter.java |   77 ++
 ...tConfigurationsToModuleDescriptorConverter.java |   46 +
 .../DefaultExcludeRuleConverter.java               |   41 +
 .../DefaultModuleDescriptorFactory.java            |   30 +
 .../moduleconverter/ExcludeRuleConverter.java      |   25 +
 .../moduleconverter/ModuleDescriptorFactory.java   |   26 +
 .../PublishModuleDescriptorConverter.java          |   64 +
 .../ResolveModuleDescriptorConverter.java          |   51 +
 ...bstractDependencyDescriptorFactoryInternal.java |  123 ++
 .../ClientModuleDependencyDescriptorFactory.java   |   69 +
 ...ultDependenciesToModuleDescriptorConverter.java |   93 ++
 ...aultModuleDescriptorFactoryForClientModule.java |   60 +
 .../DependenciesToModuleDescriptorConverter.java   |   29 +
 .../dependencies/DependencyDescriptorFactory.java  |   32 +
 .../DependencyDescriptorFactoryDelegate.java       |   56 +
 .../DependencyDescriptorFactoryInternal.java       |   25 +
 .../ExternalModuleDependencyDescriptorFactory.java |   55 +
 .../ModuleDescriptorFactoryForClientModule.java    |   29 +
 .../ProjectDependencyDescriptorFactory.java        |   79 ++
 .../ProjectDependencyDescriptorStrategy.java       |   27 +
 .../artifacts/publish/AbstractPublishArtifact.java |   40 +
 .../artifacts/publish/ArchivePublishArtifact.java  |   97 ++
 .../publish/DefaultArtifactContainer.java          |   47 +
 .../artifacts/publish/DefaultPublishArtifact.java  |   95 ++
 .../publish/maven/DefaultArtifactPomFactory.java   |   30 +
 .../artifacts/publish/maven/DefaultMavenPom.java   |  240 ++++
 .../publish/maven/DefaultMavenPomFactory.java      |   47 +
 .../artifacts/publish/maven/MavenPomFactory.java   |   25 +
 .../publish/maven/MavenPomMetaInfoProvider.java    |   22 +
 .../DefaultConf2ScopeMappingContainer.java         |  115 ++
 .../dependencies/DefaultExcludeRuleConverter.java  |   39 +
 .../DefaultPomDependenciesConverter.java           |  138 ++
 .../maven/dependencies/ExcludeRuleConverter.java   |   27 +
 .../dependencies/PomDependenciesConverter.java     |   29 +
 .../maven/dependencies/PomDependenciesWriter.java  |   31 +
 .../maven/deploy/AbstractMavenResolver.java        |  274 ++++
 .../publish/maven/deploy/ArtifactPom.java          |   39 +
 .../publish/maven/deploy/ArtifactPomContainer.java |   30 +
 .../publish/maven/deploy/ArtifactPomFactory.java   |   25 +
 .../publish/maven/deploy/BaseMavenDeployer.java    |  127 ++
 .../publish/maven/deploy/BaseMavenInstaller.java   |   47 +
 .../maven/deploy/BasePomFilterContainer.java       |  114 ++
 .../publish/maven/deploy/ClassifierArtifact.java   |   71 ++
 .../publish/maven/deploy/CustomDeployTask.java     |   44 +
 .../deploy/CustomInstallDeployTaskSupport.java     |   29 +
 .../publish/maven/deploy/CustomInstallTask.java    |   37 +
 .../publish/maven/deploy/DefaultArtifactPom.java   |  136 ++
 .../maven/deploy/DefaultArtifactPomContainer.java  |   78 ++
 .../maven/deploy/DefaultDeployTaskFactory.java     |   25 +
 .../maven/deploy/DefaultInstallTaskFactory.java    |   25 +
 .../publish/maven/deploy/DefaultPomFilter.java     |   56 +
 .../publish/maven/deploy/DeployTaskFactory.java    |   23 +
 .../publish/maven/deploy/DeployableFilesInfo.java  |   46 +
 .../publish/maven/deploy/InstallTaskFactory.java   |   23 +
 .../publish/maven/deploy/LoggingHelper.java        |   46 +
 .../artifacts/publish/maven/deploy/PomFilter.java  |   34 +
 .../groovy/DefaultGroovyMavenDeployer.groovy       |   69 +
 .../groovy/DefaultGroovyMavenInstaller.groovy      |   48 +
 .../groovy/DefaultGroovyPomFilterContainer.groovy  |   52 +
 .../maven/deploy/groovy/RepositoryBuilder.java     |   35 +
 .../maven/deploy/groovy/RepositoryFactory.java     |   54 +
 .../maven/pombuilder/CustomModelBuilder.java       |   83 ++
 .../publish/maven/pombuilder/ModelFactory.java     |   43 +
 .../maven/pombuilder/PlexusLoggerAdapter.java      |  101 ++
 .../repositories/DefaultInternalRepository.java    |  159 +++
 .../artifacts/repositories/WebdavRepository.java   |   80 ++
 .../internal/changedetection/CachingHasher.java    |   80 ++
 .../changedetection/DefaultFileSnapshotter.java    |  158 +++
 .../internal/changedetection/DefaultHasher.java    |   26 +
 .../DefaultTaskArtifactStateRepository.java        |  355 ++++++
 .../changedetection/FileCollectionSnapshot.java    |   59 +
 .../internal/changedetection/FileSnapshotter.java  |   35 +
 .../api/internal/changedetection/Hasher.java       |   22 +
 .../changedetection/MapMergeChangeListener.java    |   67 +
 .../changedetection/OutputFilesSnapshotter.java    |  155 +++
 .../ShortCircuitTaskArtifactStateRepository.java   |   47 +
 .../changedetection/TaskArtifactState.java         |   33 +
 .../TaskArtifactStateRepository.java               |   22 +
 .../api/internal/file/AbstractFileCollection.java  |  232 ++++
 .../api/internal/file/AbstractFileResolver.java    |  209 +++
 .../gradle/api/internal/file/AbstractFileTree.java |  143 +++
 .../api/internal/file/AbstractFileTreeElement.java |   92 ++
 .../internal/file/AntFileCollectionBuilder.groovy  |   39 +
 .../AntFileCollectionMatchingTaskBuilder.groovy    |   40 +
 .../api/internal/file/AntFileTreeBuilder.groovy    |   35 +
 .../gradle/api/internal/file/BaseDirConverter.java |   75 ++
 .../api/internal/file/CompositeFileCollection.java |  149 +++
 .../api/internal/file/CompositeFileTree.java       |   99 ++
 .../internal/file/DefaultConfigurableFileTree.java |  221 ++++
 .../api/internal/file/DefaultDirectoryWalker.java  |  149 +++
 .../api/internal/file/DefaultFileOperations.java   |  146 +++
 .../api/internal/file/DefaultFileTreeElement.java  |   60 +
 .../internal/file/DefaultSourceDirectorySet.java   |  157 +++
 .../file/DefaultTemporaryFileProvider.java         |   34 +
 .../gradle/api/internal/file/DirectoryWalker.java  |   26 +
 .../gradle/api/internal/file/FileOperations.java   |   67 +
 .../org/gradle/api/internal/file/FileResolver.java |   45 +
 .../gradle/api/internal/file/FileSetHelper.groovy  |   26 +
 .../org/gradle/api/internal/file/FileSource.java   |   23 +
 .../api/internal/file/IdentityFileResolver.java    |   39 +
 .../org/gradle/api/internal/file/MapFileTree.java  |  154 +++
 .../internal/file/PathResolvingFileCollection.java |  145 +++
 .../api/internal/file/SimpleFileCollection.java    |   50 +
 .../api/internal/file/SingletonFileCollection.java |   52 +
 .../api/internal/file/SingletonFileTree.java       |   71 ++
 .../api/internal/file/TemporaryFileProvider.java   |   28 +
 .../api/internal/file/UnionFileCollection.java     |   48 +
 .../gradle/api/internal/file/UnionFileTree.java    |   63 +
 .../api/internal/file/ant/AntFileResource.java     |   35 +
 .../api/internal/file/ant/BaseDirSelector.java     |   37 +
 .../api/internal/file/archive/TarCopyAction.java   |   23 +
 .../internal/file/archive/TarCopySpecVisitor.java  |  107 ++
 .../api/internal/file/archive/TarFileTree.java     |  162 +++
 .../internal/file/archive/ZipCopySpecVisitor.java  |   90 ++
 .../api/internal/file/archive/ZipFileTree.java     |  150 +++
 .../api/internal/file/copy/ArchiveCopyAction.java  |   24 +
 .../api/internal/file/copy/CopyActionImpl.java     |  236 ++++
 .../api/internal/file/copy/CopySpecImpl.java       |  436 +++++++
 .../api/internal/file/copy/CopySpecSource.java     |   20 +
 .../api/internal/file/copy/CopySpecVisitor.java    |   37 +
 .../file/copy/DelegatingCopySpecVisitor.java       |   55 +
 .../api/internal/file/copy/DeleteActionImpl.java   |   55 +
 .../internal/file/copy/EmptyCopySpecVisitor.java   |   40 +
 .../api/internal/file/copy/FileCopyAction.java     |   24 +
 .../api/internal/file/copy/FileCopyActionImpl.java |   39 +
 .../internal/file/copy/FileCopySpecVisitor.java    |   54 +
 .../gradle/api/internal/file/copy/FilterChain.java |  103 ++
 .../gradle/api/internal/file/copy/LineFilter.java  |  112 ++
 .../internal/file/copy/MappingCopySpecVisitor.java |  187 +++
 .../file/copy/NormalizingCopySpecVisitor.java      |  112 ++
 .../api/internal/file/copy/ReadableCopySpec.java   |   39 +
 .../api/internal/file/copy/RegExpNameMapper.java   |   47 +
 .../api/internal/file/copy/RenamingCopyAction.java |   35 +
 .../internal/file/copy/SyncCopySpecVisitor.java    |   91 ++
 .../file/pattern/DefaultPatternMatcher.java        |  159 +++
 .../internal/file/pattern/GreedyPatternStep.java   |   29 +
 .../file/pattern/NameOnlyPatternMatcher.java       |   58 +
 .../file/pattern/PatternMatcherFactory.java        |   50 +
 .../api/internal/file/pattern/PatternStep.java     |   24 +
 .../internal/file/pattern/PatternStepFactory.java  |   26 +
 .../internal/file/pattern/RegExpPatternStep.java   |   56 +
 .../initialization/AbstractScriptHandler.java      |   84 ++
 .../initialization/DefaultScriptHandler.java       |   44 +
 .../DefaultScriptHandlerFactory.java               |   90 ++
 .../NoClassLoaderUpdateScriptHandler.java          |   33 +
 .../initialization/ScriptClassLoaderProvider.java  |   22 +
 .../initialization/ScriptHandlerFactory.java       |   26 +
 .../initialization/ScriptHandlerInternal.java      |   21 +
 .../api/internal/plugins/AbstractConvention.java   |  127 ++
 .../api/internal/plugins/DefaultConvention.groovy  |   34 +
 .../plugins/DefaultObjectConfigurationAction.java  |  117 ++
 .../internal/plugins/DefaultPluginCollection.java  |   64 +
 .../internal/plugins/DefaultPluginRegistry.java    |  110 ++
 .../plugins/DefaultProjectsPluginContainer.java    |  106 ++
 .../internal/plugins/EmbeddableJavaProject.java    |   31 +
 .../api/internal/plugins/PluginRegistry.java       |   31 +
 .../api/internal/project/AbstractProject.java      |  944 ++++++++++++++
 .../api/internal/project/AntBuilderFactory.java    |   25 +
 .../api/internal/project/DefaultAntBuilder.groovy  |   96 ++
 .../internal/project/DefaultAntBuilderFactory.java |   40 +
 .../project/DefaultIsolatedAntBuilder.groovy       |  143 +++
 .../api/internal/project/DefaultProject.java       |   31 +
 .../internal/project/DefaultProjectRegistry.java   |  103 ++
 .../internal/project/DefaultServiceRegistry.java   |  244 ++++
 .../internal/project/GlobalServicesRegistry.java   |   76 ++
 .../project/GradleInternalServiceRegistry.java     |   71 ++
 .../api/internal/project/IProjectFactory.java      |   28 +
 .../api/internal/project/IProjectRegistry.java     |   40 +
 .../api/internal/project/IsolatedAntBuilder.java   |   52 +
 .../api/internal/project/ProjectFactory.java       |   66 +
 .../api/internal/project/ProjectIdentifier.java    |   31 +
 .../api/internal/project/ProjectInternal.java      |   58 +
 .../project/ProjectInternalServiceRegistry.java    |  172 +++
 .../api/internal/project/ProjectScript.groovy      |   69 +
 .../api/internal/project/ProjectStateInternal.java |   59 +
 .../api/internal/project/ServiceRegistry.java      |   21 +
 .../internal/project/ServiceRegistryFactory.java   |   29 +
 .../project/TaskInternalServiceRegistry.java       |   55 +
 .../project/TopLevelBuildServiceRegistry.java      |  359 ++++++
 .../internal/project/ant/AntLoggingAdapter.java    |   89 ++
 .../api/internal/project/ant/BasicAntBuilder.java  |   85 ++
 .../AnnotationProcessingTaskFactory.java           |  362 ++++++
 .../taskfactory/DependencyAutoWireTaskFactory.java |   50 +
 .../ExecutionShortCircuitTaskExecuter.java         |   58 +
 .../internal/project/taskfactory/ITaskFactory.java |   28 +
 .../InputDirectoryPropertyAnnotationHandler.java   |   67 +
 .../InputFilePropertyAnnotationHandler.java        |   53 +
 .../InputFilesPropertyAnnotationHandler.java       |   50 +
 .../InputPropertyAnnotationHandler.java            |   36 +
 .../NestedBeanPropertyAnnotationHandler.java       |   44 +
 .../OutputDirectoryPropertyAnnotationHandler.java  |   49 +
 .../OutputFilePropertyAnnotationHandler.java       |   56 +
 .../PostExecutionAnalysisTaskExecuter.java         |   49 +
 .../project/taskfactory/PropertyActionContext.java |   55 +
 .../taskfactory/PropertyAnnotationHandler.java     |   35 +
 .../internal/project/taskfactory/TaskFactory.java  |  131 ++
 .../internal/project/taskfactory/UpdateAction.java |   24 +
 .../project/taskfactory/ValidationAction.java      |   22 +
 .../api/internal/resource/CachingResource.java     |   45 +
 .../api/internal/resource/DelegatingResource.java  |   52 +
 .../org/gradle/api/internal/resource/Resource.java |   63 +
 .../api/internal/resource/ResourceException.java   |   29 +
 .../resource/ResourceNotFoundException.java        |   26 +
 .../api/internal/resource/StringResource.java      |   50 +
 .../gradle/api/internal/resource/UriResource.java  |   90 ++
 .../api/internal/tasks/AbstractTaskDependency.java |   29 +
 .../tasks/CachingTaskDependencyResolveContext.java |  102 ++
 .../api/internal/tasks/DefaultTaskCollection.java  |   80 ++
 .../api/internal/tasks/DefaultTaskContainer.java   |  113 ++
 .../api/internal/tasks/DefaultTaskDependency.java  |  113 ++
 .../api/internal/tasks/DefaultTaskExecuter.java    |   70 +
 .../api/internal/tasks/DefaultTaskInputs.java      |   96 ++
 .../api/internal/tasks/DefaultTaskOutputs.java     |   81 ++
 .../tasks/ExecuteAtMostOnceTaskExecuter.java       |   34 +
 .../api/internal/tasks/SkipTaskExecuter.java       |   59 +
 .../api/internal/tasks/TaskContainerInternal.java  |   23 +
 .../api/internal/tasks/TaskDependencyInternal.java |   23 +
 .../tasks/TaskDependencyResolveContext.java        |   31 +
 .../gradle/api/internal/tasks/TaskExecuter.java    |   26 +
 .../gradle/api/internal/tasks/TaskResolver.java    |   22 +
 .../api/internal/tasks/TaskStateInternal.java      |  112 ++
 .../groovy/org/gradle/api/invocation/Gradle.java   |  178 +++
 .../org/gradle/api/invocation/package-info.java    |   20 +
 .../groovy/org/gradle/api/logging/LogLevel.java    |  108 ++
 .../main/groovy/org/gradle/api/logging/Logger.java |  115 ++
 .../groovy/org/gradle/api/logging/Logging.java     |  373 ++++++
 .../org/gradle/api/logging/LoggingManager.java     |   79 ++
 .../org/gradle/api/logging/LoggingOutput.java      |   50 +
 .../gradle/api/logging/StandardOutputListener.java |   29 +
 .../org/gradle/api/logging/package-info.java       |   20 +
 .../main/groovy/org/gradle/api/package-info.java   |   26 +
 .../groovy/org/gradle/api/plugins/Convention.java  |   57 +
 .../api/plugins/ObjectConfigurationAction.java     |   62 +
 .../org/gradle/api/plugins/PluginCollection.java   |   77 ++
 .../org/gradle/api/plugins/PluginContainer.java    |  113 ++
 .../api/plugins/PluginInstantiationException.java  |   31 +
 .../gradle/api/plugins/UnknownPluginException.java |   27 +
 .../org/gradle/api/plugins/package-info.java       |   20 +
 .../main/groovy/org/gradle/api/specs/AndSpec.java  |   51 +
 .../groovy/org/gradle/api/specs/CompositeSpec.java |   64 +
 .../main/groovy/org/gradle/api/specs/NotSpec.java  |   31 +
 .../main/groovy/org/gradle/api/specs/OrSpec.java   |   43 +
 .../src/main/groovy/org/gradle/api/specs/Spec.java |   23 +
 .../main/groovy/org/gradle/api/specs/Specs.java    |   91 ++
 .../groovy/org/gradle/api/specs/package-info.java  |   20 +
 .../org/gradle/api/tasks/AbstractCopyTask.java     |  325 +++++
 .../org/gradle/api/tasks/AntBuilderAware.groovy    |   27 +
 .../org/gradle/api/tasks/ConventionValue.java      |   35 +
 .../src/main/groovy/org/gradle/api/tasks/Copy.java |   89 ++
 .../main/groovy/org/gradle/api/tasks/Delete.java   |   77 ++
 .../groovy/org/gradle/api/tasks/Directory.groovy   |   43 +
 .../src/main/groovy/org/gradle/api/tasks/Exec.java |  242 ++++
 .../groovy/org/gradle/api/tasks/GradleBuild.java   |  112 ++
 .../main/groovy/org/gradle/api/tasks/Input.java    |   31 +
 .../org/gradle/api/tasks/InputDirectory.java       |   32 +
 .../groovy/org/gradle/api/tasks/InputFile.java     |   31 +
 .../groovy/org/gradle/api/tasks/InputFiles.java    |   31 +
 .../main/groovy/org/gradle/api/tasks/JavaExec.java |  401 ++++++
 .../main/groovy/org/gradle/api/tasks/Nested.java   |   32 +
 .../main/groovy/org/gradle/api/tasks/Optional.java |   44 +
 .../org/gradle/api/tasks/OutputDirectory.java      |   31 +
 .../groovy/org/gradle/api/tasks/OutputFile.java    |   31 +
 .../groovy/org/gradle/api/tasks/SkipWhenEmpty.java |   36 +
 .../groovy/org/gradle/api/tasks/SourceTask.java    |  179 +++
 .../org/gradle/api/tasks/StopActionException.java  |   46 +
 .../gradle/api/tasks/StopExecutionException.java   |   38 +
 .../src/main/groovy/org/gradle/api/tasks/Sync.java |   47 +
 .../groovy/org/gradle/api/tasks/TaskAction.java    |   27 +
 .../org/gradle/api/tasks/TaskCollection.java       |   88 ++
 .../groovy/org/gradle/api/tasks/TaskContainer.java |  168 +++
 .../org/gradle/api/tasks/TaskDependency.java       |   39 +
 .../gradle/api/tasks/TaskExecutionException.java   |   42 +
 .../groovy/org/gradle/api/tasks/TaskInputs.java    |   88 ++
 .../api/tasks/TaskInstantiationException.java      |   27 +
 .../groovy/org/gradle/api/tasks/TaskOutputs.java   |   84 ++
 .../groovy/org/gradle/api/tasks/TaskState.java     |   65 +
 .../main/groovy/org/gradle/api/tasks/Upload.java   |  106 ++
 .../org/gradle/api/tasks/VerificationTask.java     |   36 +
 .../groovy/org/gradle/api/tasks/WorkResult.java    |   20 +
 .../groovy/org/gradle/api/tasks/ant/AntTarget.java |  110 ++
 .../org/gradle/api/tasks/ant/package-info.java     |   20 +
 .../api/tasks/bundling/AbstractArchiveTask.java    |  160 +++
 .../org/gradle/api/tasks/bundling/Compression.java |   35 +
 .../org/gradle/api/tasks/bundling/LongFile.java    |   37 +
 .../groovy/org/gradle/api/tasks/bundling/Tar.java  |   81 ++
 .../groovy/org/gradle/api/tasks/bundling/Zip.java  |   50 +
 .../gradle/api/tasks/bundling/package-info.java    |   20 +
 .../api/tasks/diagnostics/AbstractReportTask.java  |   99 ++
 .../api/tasks/diagnostics/AsciiReportRenderer.java |  194 +++
 .../diagnostics/DependencyReportRenderer.java      |   47 +
 .../tasks/diagnostics/DependencyReportTask.java    |   83 ++
 .../tasks/diagnostics/GraphvizReportRenderer.java  |   75 ++
 .../tasks/diagnostics/ProjectReportRenderer.java   |   52 +
 .../tasks/diagnostics/PropertyReportRenderer.java  |   39 +
 .../api/tasks/diagnostics/PropertyReportTask.java  |   48 +
 .../gradle/api/tasks/diagnostics/TaskDetails.java  |   28 +
 .../api/tasks/diagnostics/TaskReportModel.java     |  121 ++
 .../api/tasks/diagnostics/TaskReportRenderer.java  |  147 +++
 .../api/tasks/diagnostics/TaskReportTask.java      |   69 +
 .../diagnostics/TextProjectReportRenderer.java     |   86 ++
 .../gradle/api/tasks/diagnostics/package-info.java |   20 +
 .../groovy/org/gradle/api/tasks/package-info.java  |   20 +
 .../gradle/api/tasks/util/PatternFilterable.java   |  201 +++
 .../org/gradle/api/tasks/util/PatternSet.groovy    |  242 ++++
 .../gradle/api/tasks/util/RelativePathSpec.java    |   32 +
 .../org/gradle/api/tasks/util/package-info.java    |   20 +
 .../org/gradle/cache/AutoCloseCacheFactory.java    |   93 ++
 .../main/groovy/org/gradle/cache/CacheBuilder.java |   51 +
 .../main/groovy/org/gradle/cache/CacheFactory.java |   31 +
 .../groovy/org/gradle/cache/CacheRepository.java   |   27 +
 .../org/gradle/cache/DefaultCacheFactory.java      |   34 +
 .../org/gradle/cache/DefaultCacheRepository.java   |   92 ++
 .../cache/DefaultPersistentDirectoryCache.java     |  117 ++
 .../groovy/org/gradle/cache/DefaultSerializer.java |   34 +
 .../groovy/org/gradle/cache/PersistentCache.java   |   62 +
 .../org/gradle/cache/PersistentIndexedCache.java   |   27 +
 .../org/gradle/cache/PersistentStateCache.java     |   26 +
 .../main/groovy/org/gradle/cache/Serializer.java   |   25 +
 .../groovy/org/gradle/cache/SimpleStateCache.java  |   63 +
 .../cache/btree/BTreePersistentIndexedCache.java   |  684 ++++++++++
 .../main/groovy/org/gradle/cache/btree/Block.java  |   59 +
 .../org/gradle/cache/btree/BlockPayload.java       |   51 +
 .../org/gradle/cache/btree/BlockPointer.java       |   68 +
 .../groovy/org/gradle/cache/btree/BlockStore.java  |   68 +
 .../org/gradle/cache/btree/CachingBlockStore.java  |  100 ++
 .../cache/btree/CorruptedCacheException.java       |   22 +
 .../gradle/cache/btree/FileBackedBlockStore.java   |  352 +++++
 .../org/gradle/cache/btree/FreeListBlockStore.java |  271 ++++
 .../gradle/cache/btree/StateCheckBlockStore.java   |   78 ++
 .../gradle/configuration/BuildConfigurer.groovy    |   52 +
 .../gradle/configuration/BuildScriptProcessor.java |   45 +
 .../configuration/DefaultInitScriptProcessor.java  |   39 +
 .../configuration/DefaultProjectEvaluator.java     |   45 +
 .../configuration/DefaultScriptPluginFactory.java  |  130 ++
 .../org/gradle/configuration/ImportsReader.groovy  |   32 +
 .../gradle/configuration/ImportsScriptSource.java  |   56 +
 .../gradle/configuration/InitScriptProcessor.java  |   23 +
 .../ProjectDependencies2TaskResolver.java          |   49 +
 .../org/gradle/configuration/ProjectEvaluator.java |   23 +
 .../org/gradle/configuration/ScriptPlugin.java     |   42 +
 .../gradle/configuration/ScriptPluginFactory.java  |   22 +
 .../groovy/org/gradle/execution/BuildExecuter.java |   40 +
 .../gradle/execution/BuiltInTaskBuildExecuter.java |   75 ++
 .../org/gradle/execution/DefaultBuildExecuter.java |   54 +
 .../gradle/execution/DefaultTaskGraphExecuter.java |  200 +++
 .../gradle/execution/DelegatingBuildExecuter.java  |   56 +
 .../execution/DependencyReportBuildExecuter.java   |   33 +
 .../org/gradle/execution/DryRunBuildExecuter.java  |   35 +
 .../execution/ProjectDefaultsBuildExecuter.java    |   49 +
 .../execution/PropertyReportBuildExecuter.java     |   33 +
 .../org/gradle/execution/TaskGraphExecuter.java    |   45 +
 .../execution/TaskNameResolvingBuildExecuter.java  |  163 +++
 .../gradle/execution/TaskReportBuildExecuter.java  |   41 +
 .../gradle/execution/TaskSelectionException.java   |   28 +
 .../scripts/AsmBackedEmptyScriptGenerator.java     |   84 ++
 .../org/gradle/groovy/scripts/BasicScript.groovy   |   72 ++
 .../scripts/CachingScriptCompilationHandler.java   |   46 +
 .../gradle/groovy/scripts/CachingScriptSource.java |   51 +
 .../org/gradle/groovy/scripts/DefaultScript.groovy |  172 +++
 .../scripts/DefaultScriptCompilationHandler.java   |  196 +++
 .../scripts/DefaultScriptCompilerFactory.java      |   98 ++
 .../groovy/scripts/DefaultScriptRunnerFactory.java |   61 +
 .../groovy/scripts/DelegatingScriptSource.java     |   46 +
 .../groovy/org/gradle/groovy/scripts/Script.java   |   48 +
 .../org/gradle/groovy/scripts/ScriptAware.java     |   24 +
 .../groovy/scripts/ScriptCompilationHandler.java   |   31 +
 .../org/gradle/groovy/scripts/ScriptCompiler.java  |   42 +
 .../groovy/scripts/ScriptCompilerFactory.java      |   32 +
 .../groovy/scripts/ScriptExecutionListener.java    |   22 +
 .../org/gradle/groovy/scripts/ScriptMetaData.java  |   25 +
 .../org/gradle/groovy/scripts/ScriptRunner.java    |   38 +
 .../gradle/groovy/scripts/ScriptRunnerFactory.java |   20 +
 .../org/gradle/groovy/scripts/ScriptSource.java    |   44 +
 .../gradle/groovy/scripts/StringScriptSource.java  |   44 +
 .../org/gradle/groovy/scripts/Transformer.java     |   28 +
 .../org/gradle/groovy/scripts/UriScriptSource.java |   84 ++
 .../gradle/initialization/AbstractProjectSpec.java |   61 +
 ...AbstractSettingsFileSearchStrategyTemplate.java |   37 +
 .../org/gradle/initialization/BaseSettings.java    |  201 +++
 .../initialization/BuildFileProjectSpec.java       |   66 +
 .../org/gradle/initialization/BuildLoader.java     |  106 ++
 .../gradle/initialization/BuildProgressLogger.java |   59 +
 .../gradle/initialization/BuildSourceBuilder.java  |  145 +++
 .../gradle/initialization/ClassLoaderFactory.java  |   30 +
 .../CommandLine2StartParameterConverter.java       |   31 +
 .../initialization/DefaultClassLoaderFactory.java  |   53 +
 ...DefaultCommandLine2StartParameterConverter.java |  415 ++++++
 .../initialization/DefaultExceptionAnalyser.java   |  105 ++
 .../initialization/DefaultGradleLauncher.java      |  203 +++
 .../DefaultGradleLauncherFactory.java              |  125 ++
 .../DefaultGradlePropertiesLoader.java             |  128 ++
 .../initialization/DefaultInitScriptFinder.java    |   42 +
 .../initialization/DefaultProjectDescriptor.java   |  151 +++
 .../DefaultProjectDescriptorRegistry.java          |   30 +
 .../gradle/initialization/DefaultProjectSpec.java  |   31 +
 .../gradle/initialization/DefaultSettings.groovy   |   43 +
 .../initialization/DefaultSettingsFinder.java      |   51 +
 .../EmbeddedScriptSettingsFinder.java              |   36 +
 .../ExceptionDecoratingClassGenerator.java         |  230 ++++
 .../initialization/IGradlePropertiesLoader.java    |   34 +
 .../initialization/IProjectDescriptorRegistry.java |   25 +
 .../ISettingsFileSearchStrategy.java               |   27 +
 .../org/gradle/initialization/ISettingsFinder.java |   25 +
 .../org/gradle/initialization/InitScript.groovy    |   33 +
 .../gradle/initialization/InitScriptFinder.java    |   29 +
 .../gradle/initialization/InitScriptHandler.java   |   44 +
 .../MasterDirSettingsFinderStrategy.java           |   43 +
 .../gradle/initialization/NestedBuildTracker.java  |   41 +
 .../ParentDirSettingsFinderStrategy.java           |   39 +
 .../ProjectDirectoryProjectSpec.java               |   66 +
 .../org/gradle/initialization/ProjectSpec.java     |   40 +
 .../PropertiesLoadingSettingsProcessor.java        |   39 +
 .../SameLevelDirSettingsFinderStrategy.java        |   42 +
 .../ScriptEvaluatingSettingsProcessor.java         |   70 +
 .../org/gradle/initialization/SettingsFactory.java |   45 +
 .../org/gradle/initialization/SettingsHandler.java |  101 ++
 .../gradle/initialization/SettingsLocation.java    |   35 +
 .../gradle/initialization/SettingsProcessor.java   |   33 +
 .../gradle/initialization/SettingsScript.groovy    |   24 +
 .../initialization/UserHomeInitScriptFinder.java   |   47 +
 .../org/gradle/invocation/DefaultGradle.java       |  161 +++
 .../gradle/listener/AsyncListenerBroadcast.java    |   38 +
 .../gradle/listener/ContextClassLoaderProxy.java   |   42 +
 .../gradle/listener/DefaultListenerManager.java    |  182 +++
 .../org/gradle/listener/ListenerBroadcast.java     |  156 +++
 .../org/gradle/listener/ListenerManager.java       |  101 ++
 .../listener/ListenerNotificationException.java    |   33 +
 .../AbstractProgressLoggingAwareFormatter.java     |   93 ++
 .../groovy/org/gradle/logging/AnsiConsole.java     |  296 +++++
 .../BasicProgressLoggingAwareFormatter.java        |  101 ++
 .../main/groovy/org/gradle/logging/Console.java    |   23 +
 .../org/gradle/logging/ConsoleBackedFormatter.java |  107 ++
 .../gradle/logging/DefaultLoggingConfigurer.java   |   37 +
 .../org/gradle/logging/DefaultLoggingManager.java  |  192 +++
 .../logging/DefaultLoggingManagerFactory.java      |   37 +
 .../logging/DefaultProgressLoggerFactory.java      |   84 ++
 .../logging/DefaultStandardOutputRedirector.java   |   88 ++
 .../org/gradle/logging/IvyLoggingAdaper.java       |   47 +
 .../gradle/logging/JavaUtilLoggingConfigurer.java  |   38 +
 .../src/main/groovy/org/gradle/logging/Label.java  |   23 +
 .../org/gradle/logging/LayoutBasedFormatter.java   |   35 +
 .../org/gradle/logging/LogEventFormatter.java      |   23 +
 .../org/gradle/logging/LoggingConfigurer.java      |   25 +
 .../org/gradle/logging/LoggingManagerFactory.java  |   21 +
 .../org/gradle/logging/LoggingManagerInternal.java |   36 +
 .../org/gradle/logging/LoggingServiceRegistry.java |   31 +
 .../groovy/org/gradle/logging/LoggingSystem.java   |   43 +
 .../org/gradle/logging/LoggingSystemAdapter.java   |   66 +
 .../groovy/org/gradle/logging/MarkerFilter.java    |   67 +
 .../OutputStreamStandardOutputListenerAdapter.java |   42 +
 .../gradle/logging/PrintStreamLoggingSystem.java   |  128 ++
 .../org/gradle/logging/ProgressListener.java       |   25 +
 .../groovy/org/gradle/logging/ProgressLogger.java  |   55 +
 .../org/gradle/logging/ProgressLoggerFactory.java  |   23 +
 .../org/gradle/logging/ProgressLoggingBridge.java  |   36 +
 .../org/gradle/logging/Slf4jLoggingConfigurer.java |  244 ++++
 .../org/gradle/logging/StandardOutputCapture.java  |   35 +
 .../gradle/logging/StandardOutputRedirector.java   |   25 +
 .../org/gradle/logging/StdErrLoggingSystem.java    |   36 +
 .../org/gradle/logging/StdOutLoggingSystem.java    |   36 +
 .../org/gradle/logging/TerminalDetector.java       |   43 +
 .../main/groovy/org/gradle/logging/TextArea.java   |   21 +
 .../groovy/org/gradle/messaging/actor/Actor.java   |   50 +
 .../org/gradle/messaging/actor/ActorFactory.java   |   27 +
 .../actor/internal/DefaultActorFactory.java        |   98 ++
 .../messaging/concurrent/AsyncStoppable.java       |   30 +
 .../messaging/concurrent/CompositeStoppable.java   |  108 ++
 .../concurrent/DefaultExecutorFactory.java         |  121 ++
 .../messaging/concurrent/ExecutorFactory.java      |   27 +
 .../org/gradle/messaging/concurrent/Stoppable.java |   28 +
 .../messaging/concurrent/StoppableExecutor.java    |   35 +
 .../org/gradle/messaging/dispatch/Addressable.java |   24 +
 .../gradle/messaging/dispatch/AsyncDispatch.java   |  189 +++
 .../gradle/messaging/dispatch/AsyncReceive.java    |  141 ++
 .../messaging/dispatch/BroadcastDispatch.java      |  139 ++
 .../dispatch/ContextClassLoaderDispatch.java       |   38 +
 .../dispatch/DiscardOnFailureDispatch.java         |   36 +
 .../org/gradle/messaging/dispatch/Dispatch.java    |   29 +
 .../messaging/dispatch/DispatchException.java      |   23 +
 .../dispatch/ExceptionTrackingDispatch.java        |   37 +
 .../dispatch/ExceptionTrackingListener.java        |   49 +
 .../messaging/dispatch/MethodInvocation.java       |   60 +
 .../messaging/dispatch/ProxyDispatchAdapter.java   |   79 ++
 .../org/gradle/messaging/dispatch/Receive.java     |   28 +
 .../messaging/dispatch/ReflectionDispatch.java     |   39 +
 .../messaging/dispatch/StoppableDispatch.java      |   25 +
 .../messaging/dispatch/ThreadSafeDispatch.java     |   31 +
 .../org/gradle/messaging/remote/ConnectEvent.java  |   45 +
 .../gradle/messaging/remote/MessagingClient.java   |   33 +
 .../gradle/messaging/remote/MessagingServer.java   |   40 +
 .../gradle/messaging/remote/ObjectConnection.java  |   64 +
 .../messaging/remote/internal/ChannelMessage.java  |   52 +
 .../ChannelMessageMarshallingDispatch.java         |   47 +
 .../ChannelMessageUnmarshallingDispatch.java       |   47 +
 .../messaging/remote/internal/ChannelMetaInfo.java |   53 +
 .../messaging/remote/internal/ConnectRequest.java  |   31 +
 .../messaging/remote/internal/Connection.java      |   24 +
 .../remote/internal/DefaultMessagingClient.java    |   40 +
 .../remote/internal/DefaultMessagingServer.java    |   96 ++
 .../internal/DefaultMultiChannelConnection.java    |  174 +++
 .../internal/DefaultMultiChannelConnector.java     |   68 +
 .../remote/internal/DefaultObjectConnection.java   |   67 +
 .../remote/internal/EndOfStreamDispatch.java       |   59 +
 .../remote/internal/EndOfStreamEvent.java          |   29 +
 .../remote/internal/EndOfStreamFilter.java         |   72 ++
 .../remote/internal/EndOfStreamReceive.java        |   43 +
 .../internal/HandshakeIncomingConnector.java       |   85 ++
 .../internal/HandshakeOutgoingConnector.java       |   59 +
 .../remote/internal/IncomingConnector.java         |   32 +
 .../internal/IncomingMethodInvocationHandler.java  |   57 +
 .../gradle/messaging/remote/internal/Message.java  |  137 ++
 .../MethodInvocationMarshallingDispatch.java       |   44 +
 .../MethodInvocationUnmarshallingDispatch.java     |   53 +
 .../messaging/remote/internal/MethodMetaInfo.java  |  114 ++
 .../remote/internal/MultiChannelConnection.java    |   46 +
 .../remote/internal/MultiChannelConnector.java     |   28 +
 .../remote/internal/OutgoingConnector.java         |   25 +
 .../internal/OutgoingMethodInvocationHandler.java  |   47 +
 .../remote/internal/PlaceholderException.java      |   25 +
 .../remote/internal/RemoteMethodInvocation.java    |   54 +
 .../remote/internal/SocketConnection.java          |  209 +++
 .../remote/internal/TcpIncomingConnector.java      |  108 ++
 .../remote/internal/TcpMessagingClient.java        |   55 +
 .../remote/internal/TcpMessagingServer.java        |   58 +
 .../remote/internal/TcpOutgoingConnector.java      |   97 ++
 .../src/main/groovy/org/gradle/package-info.java   |   20 +
 .../groovy/org/gradle/process/BaseExecSpec.java    |   78 ++
 .../main/groovy/org/gradle/process/ExecResult.java |   47 +
 .../main/groovy/org/gradle/process/ExecSpec.java   |   71 ++
 .../groovy/org/gradle/process/JavaExecSpec.java    |   94 ++
 .../groovy/org/gradle/process/JavaForkOptions.java |  182 +++
 .../org/gradle/process/ProcessForkOptions.java     |  109 ++
 .../internal/AbstractExecHandleBuilder.java        |  112 ++
 .../process/internal/BadExitCodeException.java     |   25 +
 .../gradle/process/internal/DefaultExecAction.java |   43 +
 .../gradle/process/internal/DefaultExecHandle.java |  347 +++++
 .../process/internal/DefaultJavaExecAction.java    |   38 +
 .../process/internal/DefaultJavaForkOptions.java   |  229 ++++
 .../internal/DefaultProcessForkOptions.java        |  100 ++
 .../process/internal/DefaultWorkerProcess.java     |  149 +++
 .../internal/DefaultWorkerProcessFactory.java      |  108 ++
 .../org/gradle/process/internal/ExecAction.java    |   26 +
 .../org/gradle/process/internal/ExecException.java |   32 +
 .../org/gradle/process/internal/ExecHandle.java    |   47 +
 .../gradle/process/internal/ExecHandleBuilder.java |   91 ++
 .../process/internal/ExecHandleListener.java       |   28 +
 .../gradle/process/internal/ExecHandleRunner.java  |   95 ++
 .../internal/ExecHandleShutdownHookAction.java     |   47 +
 .../gradle/process/internal/ExecHandleState.java   |   29 +
 .../process/internal/ExecOutputHandleRunner.java   |   57 +
 .../gradle/process/internal/JavaExecAction.java    |   26 +
 .../process/internal/JavaExecHandleBuilder.java    |  205 +++
 .../process/internal/ProcessBuilderFactory.java    |   60 +
 .../org/gradle/process/internal/WorkerProcess.java |   32 +
 .../process/internal/WorkerProcessBuilder.java     |  105 ++
 .../process/internal/WorkerProcessContext.java     |   38 +
 .../process/internal/WorkerProcessFactory.java     |   20 +
 .../internal/child/ActionExecutionWorker.java      |   89 ++
 ...nClassesInIsolatedClassLoaderWorkerFactory.java |   83 ++
 ...ionClassesInSystemClassLoaderWorkerFactory.java |   86 ++
 .../child/ImplementationClassLoaderWorker.java     |   90 ++
 .../IsolatedApplicationClassLoaderWorker.java      |   56 +
 .../child/SystemApplicationClassLoaderWorker.java  |   64 +
 .../process/internal/child/WorkerContext.java      |   21 +
 .../process/internal/child/WorkerFactory.java      |   27 +
 .../child/WorkerProcessClassPathProvider.java      |   65 +
 .../launcher/BootstrapClassLoaderWorker.java       |   54 +
 .../internal/launcher/GradleWorkerMain.java        |   42 +
 .../org/gradle/process/internal/package.html       |   46 +
 .../shutdown/ShutdownHookActionRegister.java       |   68 +
 .../org/gradle/testfixtures/ProjectBuilder.java    |  229 ++++
 .../org/gradle/testfixtures/package-info.java      |   20 +
 .../src/main/groovy/org/gradle/util/AntUtil.java   |   46 +
 .../groovy/org/gradle/util/ChangeListener.java     |   24 +
 .../gradle/util/ClassLoaderObjectInputStream.java  |   43 +
 .../main/groovy/org/gradle/util/ClasspathUtil.java |   54 +
 .../src/main/groovy/org/gradle/util/Clock.java     |   59 +
 .../org/gradle/util/CompositeIdGenerator.java      |   66 +
 .../main/groovy/org/gradle/util/Configurable.java  |   23 +
 .../main/groovy/org/gradle/util/ConfigureUtil.java |   65 +
 .../main/groovy/org/gradle/util/DeleteOnExit.java  |   52 +
 .../groovy/org/gradle/util/DeprecationLogger.java  |   42 +
 .../src/main/groovy/org/gradle/util/DiffUtil.java  |   61 +
 .../org/gradle/util/FilteringClassLoader.java      |  177 +++
 .../main/groovy/org/gradle/util/GFileUtils.java    |  534 ++++++++
 .../src/main/groovy/org/gradle/util/GUtil.java     |  300 +++++
 .../main/groovy/org/gradle/util/GradleVersion.java |   72 ++
 .../src/main/groovy/org/gradle/util/HashUtil.java  |   69 +
 .../main/groovy/org/gradle/util/IdGenerator.java   |   29 +
 .../org/gradle/util/IgnoreInterruptHandler.java    |   26 +
 .../groovy/org/gradle/util/InterruptHandler.java   |   24 +
 .../src/main/groovy/org/gradle/util/JarUtil.java   |   60 +
 .../main/groovy/org/gradle/util/JavaMethod.java    |   61 +
 .../src/main/groovy/org/gradle/util/Jvm.java       |   63 +
 .../org/gradle/util/LineBufferingOutputStream.java |  153 +++
 .../util/LinePerThreadBufferingOutputStream.java   |  210 +++
 .../groovy/org/gradle/util/LongIdGenerator.java    |   27 +
 .../org/gradle/util/MultiParentClassLoader.java    |   97 ++
 .../main/groovy/org/gradle/util/NameMatcher.java   |  145 +++
 .../groovy/org/gradle/util/NoOpChangeListener.java |   28 +
 .../org/gradle/util/ObservableUrlClassLoader.java  |   52 +
 .../groovy/org/gradle/util/OperatingSystem.java    |   80 ++
 .../main/groovy/org/gradle/util/PathHelper.java    |   32 +
 .../src/main/groovy/org/gradle/util/PosixUtil.java |   75 ++
 .../org/gradle/util/RandomLongIdGenerator.java     |   27 +
 .../groovy/org/gradle/util/ReflectionUtil.groovy   |   41 +
 .../main/groovy/org/gradle/util/TimeProvider.java  |   22 +
 .../groovy/org/gradle/util/TrueTimeProvider.java   |   25 +
 .../groovy/org/gradle/util/UncheckedException.java |   33 +
 .../src/main/groovy/org/gradle/util/WrapUtil.java  |  120 ++
 .../org/gradle/configuration/default-imports.txt   |   25 +
 .../initialization/defaultBuildSourceScript.txt    |    5 +
 .../org/gradle/BuildExceptionReporterTest.java     |  177 +++
 .../groovy/org/gradle/BuildResultLoggerTest.java   |   68 +
 .../test/groovy/org/gradle/BuildResultTest.java    |   57 +
 .../groovy/org/gradle/StartParameterTest.groovy    |  250 ++++
 .../groovy/org/gradle/TaskExecutionLoggerTest.java |  124 ++
 .../org/gradle/api/AllGradleExceptionsTest.groovy  |   52 +
 .../groovy/org/gradle/api/JavaVersionTest.java     |   96 ++
 .../ProjectDependenciesBuildInstructionTest.java   |   78 ++
 .../api/artifacts/PublishInstructionTest.java      |   71 ++
 .../api/artifacts/maven/Conf2ScopeMappingTest.java |   55 +
 .../artifacts/specs/DependencyTypeSpecTest.java    |   46 +
 .../org/gradle/api/file/FileVisitorUtil.groovy     |   79 ++
 .../api/internal/AbstractClassGeneratorTest.java   |  552 ++++++++
 .../api/internal/AbstractDynamicObjectTest.java    |   62 +
 .../api/internal/AsmBackedClassGeneratorTest.java  |   23 +
 .../AutoCreateDomainObjectContainerTest.groovy     |  189 +++
 .../internal/CachingDirectedGraphWalkerTest.groovy |  197 +++
 .../api/internal/ChainingTransformerTest.java      |  102 ++
 .../api/internal/ConventionAwareHelperTest.java    |  166 +++
 .../internal/DefaultDomainObjectContainerTest.java |  326 +++++
 .../DefaultNamedDomainObjectContainerTest.java     |  628 +++++++++
 .../org/gradle/api/internal/DefaultTaskTest.groovy |  143 +++
 .../api/internal/DynamicObjectHelperTest.java      |  837 ++++++++++++
 .../internal/DynamicObjectHelperTestHelper.groovy  |   93 ++
 .../gradle/api/internal/GraphAggregatorTest.groovy |   65 +
 ...vySourceGenerationBackedClassGeneratorTest.java |   23 +
 .../api/internal/MapBackedDynamicObjectTest.java   |   51 +
 .../org/gradle/api/internal/TestContainer.java     |   29 +
 .../api/internal/TestDecoratedGroovyBean.groovy    |   27 +
 .../CachingDependencyResolveContextTest.groovy     |   84 ++
 .../DefaultConfigurationContainerFactoryTest.java  |   83 ++
 .../artifacts/DefaultResolvedArtifactTest.java     |  102 ++
 .../artifacts/DefaultResolvedDependencyTest.java   |  203 +++
 .../artifacts/DefaultResolverContainerTest.groovy  |  218 ++++
 .../ResolvedConfigurationIdentifierTest.groovy     |   41 +
 .../configurations/ConfigurationsTest.java         |   37 +
 .../DefaultConfigurationContainerTest.java         |  165 +++
 .../configurations/DefaultConfigurationTest.java   |  940 ++++++++++++++
 .../dependencies/AbstractModuleDependencyTest.java |  110 ++
 .../dependencies/DefaultClientModuleTest.java      |  101 ++
 .../DefaultDependencyArtifactTest.java             |   40 +
 .../DefaultExcludeRuleContainerTest.java           |   72 ++
 .../DefaultExternalModuleDependencyTest.java       |  110 ++
 .../dependencies/DefaultProjectDependencyTest.java |  296 +++++
 .../DefaultSelfResolvingDependencyTest.java        |  103 ++
 .../dsl/DefaultArtifactHandlerTest.groovy          |   97 ++
 .../dsl/DefaultConfigurationHandlerTest.groovy     |  103 ++
 .../dsl/DefaultPublishArtifactFactoryTest.groovy   |   59 +
 .../dsl/DefaultRepositoryHandlerFactoryTest.java   |   70 +
 .../dsl/DefaultRepositoryHandlerTest.groovy        |  264 ++++
 .../dependencies/AbstractModuleFactoryTest.java    |  170 +++
 .../ClassPathDependencyFactoryTest.groovy          |   63 +
 .../DefaultClientModuleFactoryTest.java            |   30 +
 .../dependencies/DefaultDependencyFactoryTest.java |  168 +++
 .../DefaultDependencyHandlerTest.groovy            |  260 ++++
 .../DefaultProjectDependencyFactoryTest.java       |   75 ++
 .../dependencies/ModuleDependencyFactoryTest.java  |   90 ++
 .../dependencies/ModuleFactoryDelegateTest.java    |   93 ++
 .../SelfResolvingDependencyFactoryTest.java        |   49 +
 .../ivyservice/ClientModuleResolverTest.groovy     |   37 +
 .../DefaultIvyDependencyPublisherTest.java         |   65 +
 .../DefaultIvyDependencyResolverTest.java          |  290 +++++
 .../ivyservice/DefaultIvyFactoryTest.java          |   36 +
 .../ivyservice/DefaultIvyServicePublishTest.java   |  175 +++
 .../ivyservice/DefaultIvyServiceResolveTest.java   |  129 ++
 .../ivyservice/DefaultIvyServiceTest.java          |   68 +
 .../DefaultPublishOptionsFactoryTest.java          |   58 +
 .../ivyservice/DefaultResolverFactoryTest.groovy   |  111 ++
 .../ivyservice/DefaultSettingsConverterTest.groovy |  139 ++
 .../ivyservice/ErrorHandlingIvyServiceTest.groovy  |  158 +++
 .../ivyservice/GradleIBiblioResolverTest.groovy    |   72 ++
 .../artifacts/ivyservice/IvyUtilTest.groovy        |   34 +
 .../ivyservice/Report2ClasspathTest.groovy         |   28 +
 .../SelfResolvingDependencyResolverTest.java       |  168 +++
 .../ShortcircuitEmptyConfigsIvyServiceTest.java    |   92 ++
 ...ltArtifactsToModuleDescriptorConverterTest.java |  124 ++
 ...figurationsToModuleDescriptorConverterTest.java |   93 ++
 .../DefaultExcludeRuleConverterTest.java           |   54 +
 .../DefaultModuleDescriptorFactoryTest.java        |   39 +
 .../moduleconverter/IvyConverterTestUtil.java      |   34 +
 .../PublishModuleDescriptorConverterTest.java      |   72 ++
 .../ResolveModuleDescriptorConverterTest.java      |   74 ++
 ...actDependencyDescriptorFactoryInternalTest.java |  137 ++
 ...lientModuleDependencyDescriptorFactoryTest.java |   98 ++
 ...ependenciesToModuleDescriptorConverterTest.java |  134 ++
 ...ModuleDescriptorFactoryForClientModuleTest.java |   92 ++
 .../DependencyDescriptorFactoryDelegateTest.java   |   67 +
 ...ernalModuleDependencyDescriptorFactoryTest.java |  127 ++
 .../ProjectDependencyDescriptorFactoryTest.java    |  128 ++
 .../publish/AbstractPublishArtifactTest.java       |   84 ++
 .../publish/ArchivePublishArtifactTest.java        |   80 ++
 .../publish/DefaultArtifactContainerTest.java      |   70 +
 .../publish/DefaultPublishArtifactTest.java        |   48 +
 .../maven/DefaultMavenPomFactoryTest.groovy        |   46 +
 .../publish/maven/DefaultMavenPomTest.groovy       |  190 +++
 .../publish/maven/DeployTaskFactoryTest.java       |   31 +
 .../DefaultConf2ScopeMappingContainerTest.java     |  123 ++
 .../DefaultExcludeRuleConverterTest.java           |   60 +
 .../DefaultPomDependenciesConverterTest.java       |  220 ++++
 .../maven/deploy/AbstractMavenResolverTest.java    |  248 ++++
 .../maven/deploy/BaseMavenDeployerTest.java        |  105 ++
 .../maven/deploy/BaseMavenInstallerTest.java       |   70 +
 .../maven/deploy/BasePomFilterContainerTest.java   |  187 +++
 .../deploy/DefaultArtifactPomContainerTest.java    |  132 ++
 .../maven/deploy/DefaultArtifactPomTest.java       |  186 +++
 .../publish/maven/deploy/DefaultPomFilterTest.java |   55 +
 .../groovy/DefaultGroovyMavenDeployerTest.groovy   |  122 ++
 .../groovy/DefaultGroovyMavenInstallerTest.groovy  |   85 ++
 .../groovy/DefaultGroovyMavenUploaderTest.groovy   |  119 ++
 .../changedetection/CachingHasherTest.java         |  116 ++
 .../DefaultFileSnapshotterTest.groovy              |  330 +++++
 .../DefaultTaskArtifactStateRepositoryTest.java    |  687 ++++++++++
 ...hortCircuitTaskArtifactStateRepositoryTest.java |  120 ++
 .../internal/file/AbstractFileCollectionTest.java  |  340 +++++
 .../internal/file/AbstractFileTreeElementTest.java |   89 ++
 .../api/internal/file/AbstractFileTreeTest.groovy  |   71 ++
 .../api/internal/file/BaseDirConverterTest.groovy  |  333 +++++
 .../internal/file/CompositeFileCollectionTest.java |  278 ++++
 .../api/internal/file/CompositeFileTreeTest.java   |  126 ++
 .../internal/file/DefaultDirectoryWalkerTest.java  |  288 +++++
 .../internal/file/DefaultFileOperationsTest.groovy |  326 +++++
 .../file/DefaultSourceDirectorySetTest.groovy      |  238 ++++
 .../file/DefaultTemporaryFileProviderTest.groovy   |   40 +
 .../gradle/api/internal/file/FileSetTest.groovy    |  304 +++++
 .../gradle/api/internal/file/MapFileTreeTest.java  |   75 ++
 .../file/PathResolvingFileCollectionTest.java      |  351 +++++
 .../gradle/api/internal/file/RelativePathTest.java |  130 ++
 .../internal/file/SimpleFileCollectionTest.groovy  |   29 +
 .../file/SingletonFileCollectionTest.groovy        |   41 +
 .../api/internal/file/SingletonFileTreeTest.groovy |  162 +++
 .../api/internal/file/UnionFileCollectionTest.java |   77 ++
 .../api/internal/file/UnionFileTreeTest.java       |   50 +
 .../file/archive/TarCopySpecVisitorTest.java       |  235 ++++
 .../api/internal/file/archive/TarFileTreeTest.java |   95 ++
 .../file/archive/ZipCopySpecVisitorTest.java       |  188 +++
 .../api/internal/file/archive/ZipFileTreeTest.java |   94 ++
 .../internal/file/copy/CopyActionImplTest.groovy   |   95 ++
 .../api/internal/file/copy/CopySpecImplTest.groovy |  333 +++++
 .../internal/file/copy/DeleteActionImplTest.groovy |   98 ++
 .../internal/file/copy/FileCopyActionImplTest.java |   54 +
 .../file/copy/FileCopySpecVisitorTest.java         |  102 ++
 .../api/internal/file/copy/FilterChainTest.java    |   89 ++
 .../api/internal/file/copy/LineFilterTest.groovy   |   74 ++
 .../file/copy/MappingCopySpecVisitorTest.java      |  318 +++++
 .../file/copy/NormalizingCopySpecVisitorTest.java  |  157 +++
 .../internal/file/copy/RegExpNameMapperTest.java   |   34 +
 .../internal/file/copy/RenamingCopyActionTest.java |   46 +
 .../file/copy/SyncCopySpecVisitorTest.java         |  146 +++
 .../file/pattern/DefaultPatternMatcherTest.java    |  218 ++++
 .../file/pattern/NameOnlyPatternMatcherTest.java   |   70 +
 .../file/pattern/PatternMatcherFactoryTest.java    |  222 ++++
 .../file/pattern/PatternStepFactoryTest.java       |   40 +
 .../file/pattern/RegExpPatternStepTest.java        |   76 ++
 .../DefaultScriptHandlerFactoryTest.groovy         |   89 ++
 .../initialization/DefaultScriptHandlerTest.groovy |  100 ++
 .../internal/plugins/DefaultConventionTest.groovy  |  131 ++
 .../DefaultObjectConfigurationActionTest.groovy    |  102 ++
 .../plugins/DefaultPluginRegistryTest.java         |  221 ++++
 .../DefaultProjectsPluginContainerTest.java        |  121 ++
 .../project/DefaultAntBuilderFactoryTest.groovy    |   56 +
 .../internal/project/DefaultAntBuilderTest.groovy  |  127 ++
 .../project/DefaultIsolatedAntBuilderTest.groovy   |  171 +++
 .../project/DefaultProjectRegistryTest.java        |  130 ++
 .../api/internal/project/DefaultProjectTest.groovy | 1053 +++++++++++++++
 .../project/DefaultServiceRegistryTest.java        |  198 +++
 .../project/GlobalServicesRegistryTest.java        |   85 ++
 .../project/GradleInternalServiceRegistryTest.java |  107 ++
 .../api/internal/project/ProjectFactoryTest.java   |  219 ++++
 .../ProjectInternalServiceRegistryTest.java        |  236 ++++
 .../project/TaskInternalServiceRegistryTest.java   |   80 ++
 .../gradle/api/internal/project/TestPlugin1.groovy |   32 +
 .../gradle/api/internal/project/TestPlugin2.groovy |   28 +
 .../project/TopLevelBuildServiceRegistryTest.java  |  235 ++++
 .../AnnotationProcessingTaskFactoryTest.java       |  714 +++++++++++
 .../DependencyAutoWireTaskFactoryTest.java         |   56 +
 .../ExecutionShortCircuitTaskExecuterTest.java     |  121 ++
 .../project/taskfactory/InputFileTask.groovy       |   24 +
 .../PostExecutionAnalysisTaskExecuterTest.groovy   |  111 ++
 .../project/taskfactory/TaskFactoryTest.java       |  222 ++++
 .../internal/resource/CachingResourceTest.groovy   |   65 +
 .../internal/resource/StringResourceTest.groovy    |   47 +
 .../api/internal/resource/UriResourceTest.groovy   |  177 +++
 .../CachingTaskDependencyResolveContextTest.groovy |  209 +++
 .../internal/tasks/DefaultTaskContainerTest.java   |  283 +++++
 .../tasks/DefaultTaskDependencyTest.groovy         |  211 +++
 .../internal/tasks/DefaultTaskExecuterTest.java    |  281 ++++
 .../internal/tasks/DefaultTaskInputsTest.groovy    |  105 ++
 .../internal/tasks/DefaultTaskOutputsTest.groovy   |  110 ++
 .../tasks/ExecuteAtMostOnceTaskExecuterTest.groovy |   52 +
 .../api/internal/tasks/SkipTaskExecuterTest.java   |  116 ++
 .../internal/tasks/TaskStateInternalTest.groovy    |  120 ++
 .../tasks/util/DefaultJavaForkOptionsTest.groovy   |  266 ++++
 .../util/DefaultProcessForkOptionsTest.groovy      |  111 ++
 .../org/gradle/api/logging/LogLevelTest.groovy     |   30 +
 .../groovy/org/gradle/api/logging/LoggingTest.java |  119 ++
 .../api/plugins/TestPluginConvention1.groovy       |   34 +
 .../api/plugins/TestPluginConvention2.groovy       |   32 +
 .../api/specs/AbstractCompositeSpecTest.java       |   74 ++
 .../groovy/org/gradle/api/specs/AndSpecTest.java   |   60 +
 .../groovy/org/gradle/api/specs/NotSpecTest.java   |   39 +
 .../groovy/org/gradle/api/specs/OrSpecTest.java    |   48 +
 .../groovy/org/gradle/api/specs/SpecsTest.groovy   |   35 +
 .../api/tasks/AbstractConventionTaskTest.java      |   45 +
 .../org/gradle/api/tasks/AbstractCopyTaskTest.java |   98 ++
 .../gradle/api/tasks/AbstractSpockTaskTest.groovy  |  375 ++++++
 .../org/gradle/api/tasks/AbstractTaskTest.java     |  341 +++++
 .../gradle/api/tasks/AntBuilderAwareUtil.groovy    |  117 ++
 .../groovy/org/gradle/api/tasks/CopyTest.groovy    |   85 ++
 .../groovy/org/gradle/api/tasks/DeleteTest.java    |   99 ++
 .../org/gradle/api/tasks/DirectoryTest.groovy      |   80 ++
 .../groovy/org/gradle/api/tasks/ExecTest.groovy    |   65 +
 .../org/gradle/api/tasks/GradleBuildTest.groovy    |   70 +
 .../gradle/api/tasks/GroovyTaskTestHelper.groovy   |   45 +
 .../org/gradle/api/tasks/SourceTaskTest.groovy     |   49 +
 .../groovy/org/gradle/api/tasks/UploadTest.java    |  118 ++
 .../org/gradle/api/tasks/ant/AntTargetTest.java    |   92 ++
 .../tasks/bundling/AbstractArchiveTaskTest.groovy  |  111 ++
 .../org/gradle/api/tasks/bundling/TarTest.groovy   |   60 +
 .../org/gradle/api/tasks/bundling/ZipTest.groovy   |   44 +
 .../tasks/diagnostics/AbstractReportTaskTest.java  |  148 +++
 .../tasks/diagnostics/AsciiReportRendererTest.java |   65 +
 .../diagnostics/DependencyReportTaskTest.java      |  137 ++
 .../diagnostics/PropertyReportRendererTest.java    |   41 +
 .../tasks/diagnostics/PropertyReportTaskTest.java  |   90 ++
 .../tasks/diagnostics/TaskReportModelTest.groovy   |  164 +++
 .../diagnostics/TaskReportRendererTest.groovy      |  175 +++
 .../api/tasks/diagnostics/TaskReportTaskTest.java  |  211 +++
 .../diagnostics/TextProjectReportRendererTest.java |  102 ++
 .../tasks/util/AbstractTestForPatternSet.groovy    |   77 ++
 .../gradle/api/tasks/util/PatternSetTest.groovy    |  234 ++++
 .../gradle/cache/AutoCloseCacheFactoryTest.groovy  |  102 ++
 .../gradle/cache/DefaultCacheFactoryTest.groovy    |   41 +
 .../gradle/cache/DefaultCacheRepositoryTest.java   |  133 ++
 .../cache/DefaultPersistentDirectoryCacheTest.java |  171 +++
 .../org/gradle/cache/SimpleStateCacheTest.groovy   |   61 +
 .../btree/BTreePersistentIndexedCacheTest.java     |  321 +++++
 .../gradle/configuration/BuildConfigurerTest.java  |   83 ++
 .../configuration/BuildScriptProcessorTest.java    |   59 +
 .../DefaultInitScriptProcessorTest.java            |   51 +
 .../configuration/DefaultProjectEvaluatorTest.java |  127 ++
 .../DefaultScriptPluginFactoryTest.java            |  194 +++
 .../gradle/configuration/ImportsReaderTest.groovy  |   45 +
 .../configuration/ImportsScriptSourceTest.java     |   89 ++
 .../ProjectDependencies2TaskResolverTest.groovy    |   50 +
 .../execution/BuiltInTaskBuildExecuterTest.java    |  158 +++
 .../gradle/execution/DefaultBuildExecuterTest.java |   95 ++
 .../execution/DefaultTaskGraphExecuterTest.java    |  402 ++++++
 .../DependencyReportBuildExecuterTest.groovy       |   29 +
 .../gradle/execution/DryRunBuildExecuterTest.java  |   61 +
 .../ProjectDefaultsBuildExecuterTest.java          |  100 ++
 .../PropertyReportBuildExecuterTest.groovy         |   29 +
 .../TaskNameResolvingBuildExecuterTest.java        |  359 ++++++
 .../execution/TaskReportBuildExecuterTest.groovy   |   29 +
 .../AsmBackedEmptyScriptGeneratorTest.groovy       |   37 +
 .../CachingScriptCompilationHandlerTest.groovy     |   91 ++
 .../groovy/scripts/CachingScriptSourceTest.java    |   45 +
 .../DefaultScriptCompilationHandlerTest.java       |  286 +++++
 .../scripts/DefaultScriptCompilerFactoryTest.java  |  272 ++++
 .../scripts/DefaultScriptRunnerFactoryTest.java    |  146 +++
 .../gradle/groovy/scripts/DefaultScriptTest.groovy |   84 ++
 .../org/gradle/groovy/scripts/EmptyScript.java     |   25 +
 .../groovy/scripts/StringScriptSourceTest.java     |   50 +
 .../gradle/groovy/scripts/UriScriptSourceTest.java |  148 +++
 .../AbstractSettingsFinderStrategyTest.java        |   70 +
 .../initialization/BuildFileProjectSpecTest.java   |  119 ++
 .../gradle/initialization/BuildLoaderTest.groovy   |  209 +++
 .../initialization/BuildProgressLoggerTest.groovy  |   69 +
 .../initialization/BuildSourceBuilderTest.groovy   |  186 +++
 ...ultCommandLine2StartParameterConverterTest.java |  397 ++++++
 .../DefaultExceptionAnalyserTest.java              |  172 +++
 .../DefaultGradleLauncherFactoryTest.java          |   75 ++
 .../initialization/DefaultGradleLauncherTest.java  |  378 ++++++
 .../DefaultGradlePropertiesLoaderTest.java         |  117 ++
 .../DefaultInitScriptFinderTest.java               |   67 +
 .../DefaultProjectDescriptorRegistryTest.java      |   51 +
 .../DefaultProjectDescriptorTest.java              |   87 ++
 .../initialization/DefaultSettingsFinderTest.java  |   90 ++
 .../initialization/DefaultSettingsTest.groovy      |  176 +++
 .../EmbeddedScriptSettingsFinderTest.java          |   76 ++
 .../ExceptionDecoratingClassGeneratorTest.groovy   |  123 ++
 .../initialization/InitScriptHandlerTest.java      |   53 +
 .../MasterDirSettingsFinderStrategyTest.java       |   55 +
 .../initialization/NestedBuildTrackerTest.groovy   |   75 ++
 .../ParentDirSettingsFinderStrategyTest.java       |   55 +
 .../ProjectDirectoryProjectSpecTest.java           |  120 ++
 .../PropertiesLoadingSettingsProcessorTest.java    |   60 +
 .../SameLevelDirSettingsFinderStrategyTest.java    |   55 +
 .../ScriptEvaluatingSettingsProcessorTest.groovy   |   97 ++
 .../gradle/initialization/SettingsFactoryTest.java |   63 +
 .../gradle/initialization/SettingsHandlerTest.java |  126 ++
 .../UserHomeInitScriptFinderTest.java              |   79 ++
 .../org/gradle/invocation/DefaultGradleTest.java   |  166 +++
 .../listener/AsyncListenerBroadcastTest.groovy     |  123 ++
 .../listener/DefaultListenerManagerTest.java       |  225 ++++
 .../org/gradle/listener/ListenerBroadcastTest.java |  334 +++++
 .../org/gradle/logging/AnsiConsoleTest.groovy      |  298 +++++
 .../BasicProgressLoggingAwareFormatterTest.groovy  |  240 ++++
 .../logging/ConsoleBackedFormatterTest.groovy      |  278 ++++
 .../gradle/logging/DefaultLoggingManagerTest.java  |  364 ++++++
 .../DefaultProgressLoggerFactoryTest.groovy        |   81 ++
 .../DefaultStandardOutputRedirectorTest.groovy     |   90 ++
 .../logging/JavaUtilLoggingConfigurerTest.groovy   |   50 +
 .../logging/LoggingServiceRegistryTest.groovy      |   29 +
 .../gradle/logging/LoggingSystemAdapterTest.groovy |   53 +
 .../org/gradle/logging/LoggingTestHelper.groovy    |   53 +
 .../logging/PrintStreamLoggingSystemTest.groovy    |  138 ++
 .../gradle/logging/Slf4jLoggingConfigurerTest.java |  311 +++++
 .../actor/internal/DefaultActorFactoryTest.groovy  |  166 +++
 .../concurrent/CompositeStoppableTest.groovy       |   87 ++
 .../concurrent/DefaultExecutorFactoryTest.groovy   |  159 +++
 .../messaging/dispatch/AsyncDispatchTest.groovy    |  212 +++
 .../messaging/dispatch/AsyncReceiveTest.groovy     |  122 ++
 .../dispatch/ContextClassLoaderDispatchTest.groovy |   81 ++
 .../dispatch/ExceptionTrackingDispatchTest.groovy  |   40 +
 .../dispatch/ExceptionTrackingListenerTest.groovy  |   61 +
 .../messaging/dispatch/MethodInvocationTest.java   |   35 +
 .../dispatch/ProxyDispatchAdapterTest.groovy       |   49 +
 .../ChannelMessageMarshallingDispatchTest.java     |   76 ++
 .../ChannelMessageUnmarshallingDispatchTest.java   |   76 ++
 .../internal/DefaultMessagingClientTest.java       |   51 +
 .../internal/DefaultMessagingServerTest.groovy     |   97 ++
 .../DefaultMultiChannelConnectionTest.groovy       |  276 ++++
 .../internal/DefaultObjectConnectionTest.java      |  233 ++++
 .../remote/internal/EndOfStreamDispatchTest.groovy |   99 ++
 .../remote/internal/EndOfStreamFilterTest.groovy   |   68 +
 .../remote/internal/EndOfStreamReceiveTest.groovy  |   50 +
 .../internal/HandshakeIncomingConnectorTest.groovy |   77 ++
 .../internal/HandshakeOutgoingConnectorTest.groovy |   60 +
 .../MethodInvocationMarshallingDispatchTest.java   |   55 +
 .../MethodInvocationUnmarshallingDispatchTest.java |   76 ++
 .../internal/RemoteMethodInvocationTest.java       |  178 +++
 .../remote/internal/TcpConnectorTest.groovy        |   41 +
 .../process/internal/DefaultExecHandleTest.java    |  157 +++
 .../internal/DefaultWorkerProcessFactoryTest.java  |   95 ++
 .../internal/DefaultWorkerProcessTest.groovy       |  174 +++
 .../process/internal/ExecHandleBuilderTest.groovy  |   41 +
 .../internal/JavaExecHandleBuilderTest.groovy      |   65 +
 .../internal/child/ActionExecutionWorkerTest.java  |   95 ++
 .../child/ImplementationClassLoaderWorkerTest.java |   84 ++
 .../internal/child/SerializableMockHelper.groovy   |   74 ++
 .../WorkerProcessClassPathProviderTest.groovy      |   73 ++
 .../gradle/testfixtures/ProjectBuilderTest.groovy  |  101 ++
 .../src/test/groovy/org/gradle/util/ClockTest.java |   86 ++
 .../gradle/util/CompositeIdGeneratorTest.groovy    |   80 ++
 .../org/gradle/util/ConfigureUtilTest.groovy       |   82 ++
 .../groovy/org/gradle/util/DiffUtilTest.groovy     |  105 ++
 .../gradle/util/FilteringClassLoaderTest.groovy    |  132 ++
 .../src/test/groovy/org/gradle/util/GUtilTest.java |   62 +
 .../org/gradle/util/GradleVersionTest.groovy       |   53 +
 .../test/groovy/org/gradle/util/HelperUtil.groovy  |  182 +++
 .../org/gradle/util/JUnit4GroovyMockery.java       |  102 ++
 .../groovy/org/gradle/util/JavaMethodTest.java     |   68 +
 .../src/test/groovy/org/gradle/util/JvmTest.groovy |   46 +
 .../gradle/util/LineBufferingOutputStreamTest.java |  138 ++
 .../LinePerThreadBufferingOutputStreamTest.groovy  |   44 +
 .../org/gradle/util/LongIdGeneratorTest.groovy     |   49 +
 .../src/test/groovy/org/gradle/util/Matchers.java  |  317 +++++
 .../gradle/util/MultiParentClassLoaderTest.groovy  |  149 +++
 .../org/gradle/util/MultithreadedTestCase.java     |  665 ++++++++++
 .../groovy/org/gradle/util/NameMatcherTest.java    |  178 +++
 .../gradle/util/ObservableUrlClassLoaderTest.java  |   44 +
 .../groovy/org/gradle/util/PathHelperTest.groovy   |   31 +
 .../org/gradle/util/RedirectStdOutAndErr.java      |   75 ++
 .../org/gradle/util/ReflectionEqualsMatcher.java   |   39 +
 .../src/test/groovy/org/gradle/util/Resources.java |   68 +
 .../org/gradle/util/SetSystemProperties.java       |   57 +
 .../groovy/org/gradle/util/TemporaryFolder.java    |  107 ++
 .../groovy/org/gradle/util/TestDirHelper.groovy    |   45 +
 .../src/test/groovy/org/gradle/util/TestFile.java  |  439 +++++++
 .../groovy/org/gradle/util/TestFileContext.java    |   23 +
 .../groovy/org/gradle/util/TestFileHelper.groovy   |  116 ++
 .../test/groovy/org/gradle/util/TestTask.groovy    |   30 +
 .../tasks/ide/eclipse/expectedClasspathFile.txt    |   13 +
 .../tasks/ide/eclipse/expectedEmptyProjectFile.txt |    9 +
 .../expectedProjectFileWithCustomBuilder.txt       |   18 +
 .../expectedProjectFileWithCustomNature.txt        |   12 +
 .../api/tasks/ide/eclipse/expectedWtpFile.txt      |   19 +
 .../gradle/testfixtures/ProjectBuilderTest.gradle  |    1 +
 .../resources/org/gradle/util/ClassLoaderTest.txt  |    1 +
 subprojects/gradle-docs/docs.gradle                |  397 ++++++
 subprojects/gradle-docs/src/docs/css/base.css      |  246 ++++
 subprojects/gradle-docs/src/docs/css/print.css     |  195 +++
 subprojects/gradle-docs/src/docs/css/style.css     |  181 +++
 .../src/docs/stylesheets/standaloneHtml.xsl        |   36 +
 .../src/docs/stylesheets/userGuideHtml.xsl         |   87 ++
 .../src/docs/stylesheets/userGuideHtmlCommon.xsl   |  179 +++
 .../src/docs/stylesheets/userGuidePdf.xsl          |   57 +
 .../src/docs/stylesheets/userGuideSingleHtml.xsl   |   20 +
 .../src/docs/stylesheets/websiteHtml.xsl           |   44 +
 .../src/docs/userguide/announcePlugin.xml          |  127 ++
 subprojects/gradle-docs/src/docs/userguide/ant.xml |  190 +++
 .../gradle-docs/src/docs/userguide/antlrPlugin.xml |  136 ++
 .../userguide/artifactDependenciesTutorial.xml     |   92 ++
 .../src/docs/userguide/artifactMngmt.xml           |   89 ++
 .../src/docs/userguide/buildLifecycle.xml          |  298 +++++
 .../src/docs/userguide/buildScriptsTutorial.xml    |  237 ++++
 .../src/docs/userguide/codeQualityPlugin.xml       |  322 +++++
 .../gradle-docs/src/docs/userguide/commandLine.xml |  153 +++
 .../src/docs/userguide/commandLineTutorial.xml     |  169 +++
 .../src/docs/userguide/customPlugins.xml           |  104 ++
 .../gradle-docs/src/docs/userguide/customTasks.xml |  144 +++
 .../gradle-docs/src/docs/userguide/depMngmt.xml    |  634 +++++++++
 .../src/docs/userguide/eclipsePlugin.xml           |  450 +++++++
 .../gradle-docs/src/docs/userguide/embedding.xml   |   22 +
 .../gradle-docs/src/docs/userguide/glossary.xml    |   37 +
 .../src/docs/userguide/gradleWrapper.xml           |  135 ++
 .../src/docs/userguide/groovyPlugin.xml            |  320 +++++
 .../src/docs/userguide/groovyTutorial.xml          |   65 +
 .../gradle-docs/src/docs/userguide/guiTutorial.xml |  126 ++
 .../gradle-docs/src/docs/userguide/ideSupport.xml  |   58 +
 .../gradle-docs/src/docs/userguide/ideaPlugin.xml  |  468 +++++++
 .../userguide/img/codeQualityPluginTasks.graphml   |  135 ++
 .../docs/userguide/img/codeQualityPluginTasks.png  |  Bin 0 -> 9278 bytes
 .../userguide/img/commandLineTutorialTasks.graphml |  123 ++
 .../userguide/img/commandLineTutorialTasks.png     |  Bin 0 -> 5469 bytes
 .../docs/userguide/img/groovyPluginTasks.graphml   |  226 ++++
 .../src/docs/userguide/img/groovyPluginTasks.png   |  Bin 0 -> 17912 bytes
 .../src/docs/userguide/img/guiSetup.png            |  Bin 0 -> 35556 bytes
 .../src/docs/userguide/img/guiTaskTree.png         |  Bin 0 -> 41227 bytes
 .../userguide/img/javaPluginConfigurations.graphml |  275 ++++
 .../userguide/img/javaPluginConfigurations.png     |  Bin 0 -> 26682 bytes
 .../src/docs/userguide/img/javaPluginTasks.graphml |  326 +++++
 .../src/docs/userguide/img/javaPluginTasks.png     |  Bin 0 -> 25445 bytes
 .../docs/userguide/img/jettyPluginTasks.graphml    |  103 ++
 .../src/docs/userguide/img/jettyPluginTasks.png    |  Bin 0 -> 5898 bytes
 .../docs/userguide/img/scalaPluginTasks.graphml    |  226 ++++
 .../src/docs/userguide/img/scalaPluginTasks.png    |  Bin 0 -> 17262 bytes
 .../src/docs/userguide/img/warPluginTasks.graphml  |   69 +
 .../src/docs/userguide/img/warPluginTasks.png      |  Bin 0 -> 2113 bytes
 .../gradle-docs/src/docs/userguide/initscripts.xml |   94 ++
 .../src/docs/userguide/installation.xml            |  125 ++
 .../src/docs/userguide/introduction.xml            |   80 ++
 .../gradle-docs/src/docs/userguide/javaPlugin.xml  | 1264 ++++++++++++++++++
 .../docs/userguide/javaProjectGenericLayout.xml    |   29 +
 .../src/docs/userguide/javaProjectMainLayout.xml   |   29 +
 .../src/docs/userguide/javaProjectTestLayout.xml   |   29 +
 .../src/docs/userguide/javaTutorial.xml            |  277 ++++
 .../gradle-docs/src/docs/userguide/jettyPlugin.xml |  151 +++
 .../gradle-docs/src/docs/userguide/logging.xml     |  205 +++
 .../gradle-docs/src/docs/userguide/mavenPlugin.xml |  372 ++++++
 .../src/docs/userguide/multiproject.xml            |  691 ++++++++++
 .../src/docs/userguide/organizeBuildLogic.xml      |  193 +++
 .../gradle-docs/src/docs/userguide/osgi.xml        |  108 ++
 .../gradle-docs/src/docs/userguide/overview.xml    |  181 +++
 .../gradle-docs/src/docs/userguide/plugins.xml     |  147 +++
 .../src/docs/userguide/potentialTraps.xml          |   50 +
 .../src/docs/userguide/projectReports.xml          |  161 +++
 .../userguide/reportingBasePluginProperties.xml    |   45 +
 .../gradle-docs/src/docs/userguide/scalaPlugin.xml |  261 ++++
 .../src/docs/userguide/standardPlugins.xml         |  246 ++++
 .../gradle-docs/src/docs/userguide/tasks.xml       |  261 ++++
 .../gradle-docs/src/docs/userguide/thisAndThat.xml |  176 +++
 .../gradle-docs/src/docs/userguide/tutorials.xml   |   72 ++
 .../gradle-docs/src/docs/userguide/userguide.xml   |   87 ++
 .../gradle-docs/src/docs/userguide/warPlugin.xml   |  189 +++
 .../gradle-docs/src/docs/userguide/webTutorial.xml |   65 +
 .../src/docs/userguide/workingWithFiles.xml        |  373 ++++++
 .../src/docs/userguide/writingBuildScripts.xml     |  145 +++
 .../gradle-docs/src/samples/announce/build.gradle  |   23 +
 .../gradle-docs/src/samples/announce/readme.xml    |    3 +
 .../gradle-docs/src/samples/antlr/build.gradle     |   16 +
 .../antlr/src/main/antlr/org/gradle/Calculator.g   |   12 +
 .../src/test/java/org/gradle/GrammarTest.java      |   14 +
 .../clientModuleDependencies/api/build.gradle      |   10 +
 .../samples/clientModuleDependencies/build.gradle  |   14 +
 .../clientModuleDependencies/settings.gradle       |    1 +
 .../clientModuleDependencies/shared/build.gradle   |   13 +
 .../shared/src/main/java/SomeClass.java            |    1 +
 .../src/samples/codeQuality/build.gradle           |   13 +
 .../codeQuality/config/checkstyle/checkstyle.xml   |   36 +
 .../codeQuality/config/codenarc/codenarc.xml       |   20 +
 .../gradle-docs/src/samples/codeQuality/readme.xml |   18 +
 .../groovy/org/gradle/sample/GroovyPerson.groovy   |    5 +
 .../src/main/java/org/gradle/sample/Person.java    |   15 +
 .../groovy/org/gradle/sample/PersonTest.groovy     |   13 +
 .../customBuildLanguage/basicEdition/build.gradle  |    9 +
 .../src/dist/end-user-license-agreement.txt        |    1 +
 .../customBuildLanguage/billing/build.gradle       |    6 +
 .../src/samples/customBuildLanguage/build.gradle   |    4 +
 .../org/gradle/samples/ProductDefinition.groovy    |   16 +
 .../org/gradle/samples/ProductModulePlugin.groovy  |   34 +
 .../groovy/org/gradle/samples/ProductPlugin.groovy |   63 +
 .../gradle/samples/ProductPluginConvention.groovy  |   12 +
 .../enterpriseEdition/build.gradle                 |   10 +
 .../src/dist/end-user-license-agreement.txt        |    1 +
 .../identityManagement/build.gradle                |    1 +
 .../src/samples/customBuildLanguage/readme.xml     |   36 +
 .../customBuildLanguage/reporting/build.gradle     |    7 +
 .../samples/customBuildLanguage/settings.gradle    |    6 +
 .../customBuildLanguage/src/dist/bin/start.sh      |    0
 .../customBuildLanguage/src/dist/readme.txt        |    1 +
 .../src/samples/customPlugin/build.gradle          |   15 +
 .../src/samples/customPlugin/readme.xml            |    3 +
 .../main/groovy/org/gradle/GreetingPlugin.groovy   |   11 +
 .../src/main/groovy/org/gradle/GreetingTask.groovy |   11 +
 .../META-INF/gradle-plugins/greeting.properties    |    1 +
 .../groovy/org/gradle/GreetingPluginTest.groovy    |   16 +
 .../test/groovy/org/gradle/GreetingTaskTest.groovy |   17 +
 .../src/samples/dependencies/build.gradle          |  177 +++
 .../repo/sea.fish/ivy-billfish-1.0.xml             |   13 +
 .../dependencies/repo/sea.fish/ivy-shark-1.0.xml   |   15 +
 .../dependencies/repo/sea.fish/ivy-tuna-1.0.xml    |   15 +
 .../dependencies/repo/sea.mammals/ivy-orca-1.0.xml |   13 +
 .../src/samples/dependencies/settings.gradle       |    1 +
 .../gradle-docs/src/samples/eclipse/build.gradle   |   36 +
 .../src/samples/gradleUserHome/build.gradle        |   13 +
 .../samples/groovy/customizedLayout/build.gradle   |   27 +
 .../src/samples/groovy/customizedLayout/readme.xml |   18 +
 .../src/groovy/org/gradle/Person.groovy            |    5 +
 .../test/groovy/org/gradle/PersonTest.groovy       |   11 +
 .../src/samples/groovy/groovy-1.5.6/build.gradle   |   10 +
 .../src/samples/groovy/groovy-1.5.6/readme.xml     |   18 +
 .../src/main/groovy/org/gradle/Person.groovy       |    5 +
 .../src/test/groovy/org/gradle/PersonTest.groovy   |   16 +
 .../src/samples/groovy/groovy-1.6.7/build.gradle   |   10 +
 .../src/samples/groovy/groovy-1.6.7/readme.xml     |   18 +
 .../src/main/groovy/org/gradle/Person.groovy       |    5 +
 .../src/test/groovy/org/gradle/PersonTest.groovy   |   31 +
 .../samples/groovy/mixedJavaAndGroovy/build.gradle |   11 +
 .../samples/groovy/mixedJavaAndGroovy/readme.xml   |   18 +
 .../src/main/groovy/org/gradle/GroovyPerson.groovy |    5 +
 .../src/main/groovy/org/gradle/JavaPerson.java     |    7 +
 .../src/main/groovy/org/gradle/PersonList.groovy   |    7 +
 .../src/main/java/org/gradle/Person.java           |    5 +
 .../src/test/groovy/org/gradle/PersonTest.groovy   |   16 +
 .../src/samples/groovy/multiproject/build.gradle   |    5 +
 .../groovy/org/gradle/buildsrc/BuildSrcClass.java  |    7 +
 .../multiproject/groovycDetector/build.gradle      |    7 +
 .../java/org/gradle/test/DetectorTransform.java    |   48 +
 ...org.codehaus.groovy.transform.ASTTransformation |    1 +
 .../src/samples/groovy/multiproject/readme.xml     |   20 +
 .../samples/groovy/multiproject/settings.gradle    |    1 +
 .../groovy/multiproject/testproject/build.gradle   |   36 +
 .../main/groovy/org/gradle/ExcludeGroovy.groovy    |    3 +
 .../main/groovy/org/gradle/ExcludeGroovyJava.java  |    3 +
 .../main/groovy/org/gradle/GroovyJavaPerson.java   |   15 +
 .../src/main/groovy/org/gradle/GroovyPerson.groovy |   12 +
 .../src/main/java/org/gradle/ExcludeJava.java      |    3 +
 .../src/main/java/org/gradle/JavaPerson.java       |   15 +
 .../src/main/resources/org/gradle/main.properties  |    1 +
 .../testproject/src/metaInfFiles/myfile            |    0
 .../groovy/org/gradle/GroovyJavaPersonTest.java    |   15 +
 .../test/groovy/org/gradle/GroovyPersonTest.groovy |   12 +
 .../src/test/groovy/org/gradle/VersionTest.groovy  |   13 +
 .../src/test/java/org/gradle/JavaPersonTest.java   |   27 +
 .../src/test/resources/org/gradle/test.properties  |    1 +
 .../src/samples/groovy/quickstart/build.gradle     |   17 +
 .../src/samples/groovy/quickstart/readme.xml       |   18 +
 .../src/main/groovy/org/gradle/Person.groovy       |   16 +
 .../quickstart/src/main/resources/resource.txt     |    1 +
 .../quickstart/src/main/resources/script.groovy    |    1 +
 .../src/test/groovy/org/gradle/PersonTest.groovy   |   26 +
 .../quickstart/src/test/resources/testResource.txt |    0
 .../src/test/resources/testScript.groovy           |    1 +
 .../gradle-docs/src/samples/idea/build.gradle      |   36 +
 .../src/samples/ivypublish/build.gradle            |   88 ++
 .../gradle-docs/src/samples/ivypublish/build.xml   |    9 +
 .../gradle-docs/src/samples/ivypublish/ivy.xml     |    6 +
 .../src/samples/ivypublish/ivysettings.xml         |    9 +
 .../src/samples/ivypublish/settings.gradle         |    1 +
 .../src/main/java/org/gradle/SomeClass.java        |    4 +
 .../src/main/java/org/gradle/shared/Person.java    |    5 +
 .../gradle-docs/src/samples/java/base/build.gradle |   23 +
 .../src/samples/java/base/prod/build.gradle        |   17 +
 .../java/base/prod/java/org/gradle/Person.java     |   16 +
 .../gradle-docs/src/samples/java/base/readme.xml   |    3 +
 .../src/samples/java/base/settings.gradle          |    1 +
 .../src/samples/java/base/test/build.gradle        |   11 +
 .../java/base/test/java/org/gradle/PersonTest.java |   12 +
 .../src/samples/java/customizedLayout/build.gradle |   32 +
 .../src/samples/java/customizedLayout/readme.xml   |    3 +
 .../src/java/org/gradle/Person.java                |   13 +
 .../test/java/org/gradle/PersonTest.java           |   12 +
 .../src/samples/java/multiproject/api/build.gradle |   65 +
 .../java/multiproject/api/src/dist/README.txt      |    1 +
 .../src/main/java/org/gradle/api/PersonList.java   |   21 +
 .../api/src/main/java/org/gradle/api/package.html  |    3 +
 .../api/src/main/java/org/gradle/apiImpl/Impl.java |   10 +
 .../src/samples/java/multiproject/build.gradle     |   20 +
 .../java/multiproject/buildSrc/build.gradle        |   14 +
 .../java/org/gradle/buildsrc/BuildSrcClass.java    |    7 +
 .../org/gradle/buildsrc/BuildSrcClassTest.java     |    8 +
 .../src/samples/java/multiproject/readme.xml       |    8 +
 .../java/multiproject/services/shared/build.gradle |    3 +
 .../java/org/gradle/services/shared/TestTest.java  |   12 +
 .../multiproject/services/webservice/build.gradle  |   15 +
 .../main/java/org/gradle/webservice/TestTest.java  |   19 +
 .../java/org/gradle/webservice/TestTestTest.java   |   16 +
 .../src/samples/java/multiproject/settings.gradle  |    4 +
 .../src/main/java/org/gradle/shared/Person.java    |   26 +
 .../main/java/org/gradle/shared/package-info.java  |    5 +
 .../resources/org/gradle/shared/main.properties    |    1 +
 .../test/java/org/gradle/shared/PersonTest.java    |   25 +
 .../resources/org/gradle/shared/test.properties    |    1 +
 .../src/samples/java/onlyif/build.gradle           |   16 +
 .../onlyif/src/main/java/org/gradle/Person.java    |   13 +
 .../src/test/java/org/gradle/PersonTest.java       |   12 +
 .../src/samples/java/quickstart/build.gradle       |   43 +
 .../src/samples/java/quickstart/readme.xml         |    3 +
 .../src/main/java/org/gradle/Person.java           |   16 +
 .../src/test/java/org/gradle/PersonTest.java       |   12 +
 .../src/samples/java/testListener/build.gradle     |   54 +
 .../src/test/java/org/gradle/DoNothingTest.java    |   18 +
 .../samples/java/withIntegrationTests/build.gradle |   36 +
 .../samples/java/withIntegrationTests/readme.xml   |    3 +
 .../java/org/gradle/PersonIntegrationTest.java     |   30 +
 .../resources/org/gradle/inttest.properties        |    1 +
 .../src/main/java/org/gradle/Person.java           |   13 +
 .../test/java/org/gradle/PersonTestFixture.java    |   10 +
 .../gradle-docs/src/samples/logging/build.gradle   |    1 +
 .../src/samples/logging/buildSrc/build.gradle      |   22 +
 .../src/samples/logging/external.gradle            |    9 +
 .../gradle-docs/src/samples/logging/init.gradle    |   46 +
 .../src/samples/logging/nestedBuild/build.gradle   |    6 +
 .../logging/nestedBuild/buildSrc/build.gradle      |   22 +
 .../src/samples/logging/project1/build.gradle      |   75 ++
 .../src/samples/logging/project2/build.gradle      |   14 +
 .../src/samples/logging/settings.gradle            |    8 +
 .../src/samples/maven/pomGeneration/build.gradle   |  105 ++
 .../src/samples/maven/pomGeneration/readme.xml     |    6 +
 .../pomGeneration/src/main/java/org/MyClass.java   |    5 +
 .../pomGeneration/src/main/webapp/WEB-INF/web.xml  |    0
 .../src/samples/maven/quickstart/build.gradle      |   30 +
 .../src/samples/maven/quickstart/readme.xml        |   18 +
 .../quickstart/src/main/java/org/MyClass.java      |   20 +
 .../gradle-docs/src/samples/mavenRepo/build.gradle |  103 ++
 .../gradle-docs/src/samples/osgi/build.gradle      |   27 +
 .../gradle-docs/src/samples/osgi/readme.xml        |    3 +
 .../main/groovy/org/gradle/GradleActivator.groovy  |   19 +
 .../samples/scala/customizedLayout/build.gradle    |   30 +
 .../src/samples/scala/customizedLayout/readme.xml  |    3 +
 .../src/scala/org/gradle/sample/api/Person.scala   |    9 +
 .../scala/org/gradle/sample/impl/PersonImpl.scala  |   10 +
 .../org/gradle/sample/impl/PersonImplTest.scala    |   14 +
 .../samples/scala/mixedJavaAndScala/build.gradle   |   15 +
 .../src/samples/scala/mixedJavaAndScala/readme.xml |    3 +
 .../src/main/java/org/gradle/sample/Person.java    |    5 +
 .../scala/org/gradle/sample/impl/JavaPerson.java   |    7 +
 .../scala/org/gradle/sample/impl/PersonImpl.scala  |   11 +
 .../scala/org/gradle/sample/impl/PersonList.scala  |    9 +
 .../test/scala/org/gradle/sample/PersonTest.scala  |   20 +
 .../src/samples/scala/quickstart/build.gradle      |   24 +
 .../src/samples/scala/quickstart/readme.xml        |    3 +
 .../main/scala/org/gradle/sample/api/Person.scala  |    9 +
 .../scala/org/gradle/sample/impl/PersonImpl.scala  |   12 +
 .../org/gradle/sample/impl/PersonImplTest.scala    |   14 +
 .../testng/groovy-jdk15-failing/build.gradle       |   17 +
 .../src/main/groovy/org/gradle/Ok.groovy           |    4 +
 .../src/test/groovy/org/gradle/BadTest.groovy      |    5 +
 .../testng/groovy-jdk15-passing/build.gradle       |   17 +
 .../src/main/groovy/org/gradle/Ok.groovy           |    4 +
 .../src/test/groovy/org/gradle/OkTest.groovy       |    5 +
 .../samples/testng/java-jdk14-failing/build.gradle |   16 +
 .../src/main/java/org/gradle/Ok.java               |    4 +
 .../src/test/java/org/gradle/BadTest.java          |    7 +
 .../samples/testng/java-jdk14-passing/build.gradle |   16 +
 .../src/main/java/org/gradle/Ok.java               |    4 +
 .../src/test/java/org/gradle/OkTest.java           |    7 +
 .../samples/testng/java-jdk15-failing/build.gradle |   15 +
 .../src/main/java/org/gradle/Ok.java               |    4 +
 .../src/test/java/org/gradle/BadTest.java          |    5 +
 .../src/test/java/org/gradle/BrokenAfterSuite.java |   26 +
 .../test/java/org/gradle/TestWithBrokenSetup.java  |   31 +
 .../java-jdk15-passing-no-report/build.gradle      |   16 +
 .../src/main/java/org/gradle/Ok.java               |    4 +
 .../src/test/java/org/gradle/OkTest.java           |    5 +
 .../samples/testng/java-jdk15-passing/build.gradle |   15 +
 .../src/main/java/org/gradle/Ok.java               |    4 +
 .../src/test/java/org/gradle/AbstractTest.java     |   25 +
 .../src/test/java/org/gradle/ConcreteTest.java     |   22 +
 .../src/test/java/org/gradle/OkTest.java           |   28 +
 .../src/test/java/org/gradle/SuiteCleanup.java     |   25 +
 .../src/test/java/org/gradle/SuiteSetup.java       |   25 +
 .../src/test/java/org/gradle/TestCleanup.java      |   25 +
 .../src/test/java/org/gradle/TestSetup.java        |   25 +
 .../samples/testng/suitexmlbuilder/build.gradle    |   25 +
 .../src/main/java/org/gradle/testng/User.java      |   11 +
 .../src/main/java/org/gradle/testng/UserImpl.java  |   26 +
 .../test/java/org/gradle/testng/UserImplTest.java  |   41 +
 .../ant/addBehaviourToAntTarget/build.gradle       |    5 +
 .../ant/addBehaviourToAntTarget/build.xml          |    5 +
 .../userguide/ant/dependsOnAntTarget/build.gradle  |    5 +
 .../userguide/ant/dependsOnAntTarget/build.xml     |    5 +
 .../userguide/ant/dependsOnTask/build.gradle       |    5 +
 .../samples/userguide/ant/dependsOnTask/build.xml  |    5 +
 .../src/samples/userguide/ant/hello/build.gradle   |    1 +
 .../src/samples/userguide/ant/hello/build.xml      |    5 +
 .../samples/userguide/ant/properties/build.gradle  |   25 +
 .../src/samples/userguide/ant/properties/build.xml |   17 +
 .../ant/taskWithNestedElements/build.gradle        |    8 +
 .../ant/taskWithNestedElements/src/test.xml        |    1 +
 .../userguide/ant/taskWithNestedText/build.gradle  |    3 +
 .../samples/userguide/ant/useAntTask/build.gradle  |    4 +
 .../samples/userguide/ant/useAntType/build.gradle  |    8 +
 .../userguide/ant/useExternalAntTask/build.gradle  |   10 +
 .../ant/useExternalAntTaskWithConfig/build.gradle  |   23 +
 .../ant/useExternalAntTaskWithConfig/pmd-rules.xml |   10 +
 .../useExternalAntTaskWithConfig/src/Source.java   |    3 +
 .../artifacts/configurationHandling/build.gradle   |   73 ++
 .../repo/sea.fish/ivy-shark-1.0.xml                |   14 +
 .../repo/sea.fish/ivy-tuna-1.0.xml                 |   13 +
 .../repo/sea.mammals/ivy-orca-1.0.xml              |   13 +
 .../artifacts/defineConfiguration/build.gradle     |   25 +
 .../artifacts/defineRepository/build.gradle        |   58 +
 .../artifacts/excludesAndClassifiers/build.gradle  |   45 +
 .../repo/org.gradle.test.excludes/api-ivy-1.0.xml  |    8 +
 .../org.gradle.test.excludes/other-api-ivy-1.0.xml |    6 +
 .../artifacts/externalDependencies/build.gradle    |   92 ++
 .../generatedFileDependencies/build.gradle         |   19 +
 .../samples/userguide/artifacts/maven/build.gradle |   83 ++
 .../userguide/artifacts/uploading/build.gradle     |   28 +
 .../userguide/buildlifecycle/basic/build.gradle    |    9 +
 .../userguide/buildlifecycle/basic/settings.gradle |    1 +
 .../buildProjectEvaluateEvents/build.gradle        |   11 +
 .../buildProjectEvaluateEvents/projectB.gradle     |    1 +
 .../buildProjectEvaluateEvents/settings.gradle     |    3 +
 .../projectEvaluateEvents/build.gradle             |   16 +
 .../projectEvaluateEvents/projectA.gradle          |    1 +
 .../projectEvaluateEvents/settings.gradle          |    3 +
 .../buildlifecycle/taskCreationEvents/build.gradle |    7 +
 .../taskExecutionEvents/build.gradle               |   18 +
 .../userguide/files/archiveNaming/build.gradle     |   11 +
 .../userguide/files/archiveNaming/settings.gradle  |    1 +
 .../userguide/files/archiveNaming/somedir/file.txt |    2 +
 .../samples/userguide/files/archives/build.gradle  |   21 +
 .../src/samples/userguide/files/copy/build.gradle  |   97 ++
 .../src/samples/userguide/files/file/build.gradle  |    8 +
 .../userguide/files/fileCollections/build.gradle   |   51 +
 .../samples/userguide/files/fileTrees/build.gradle |   50 +
 .../userguide/files/inputFiles/build.gradle        |   44 +
 .../initScripts/customLogger/build.gradle          |   10 +
 .../userguide/initScripts/customLogger/init.gradle |   16 +
 .../initScripts/externalDependency/build.gradle    |    1 +
 .../initScripts/externalDependency/init.gradle     |   14 +
 .../samples/userguide/java/sourceSets/build.gradle |   63 +
 .../addKrill/water/bluewhale/.gitignore            |    0
 .../multiproject/addKrill/water/build.gradle       |    3 +
 .../multiproject/addKrill/water/krill/.gitignore   |    0
 .../multiproject/addKrill/water/settings.gradle    |    1 +
 .../addSpecifics/water/bluewhale/.gitignore        |    0
 .../multiproject/addSpecifics/water/build.gradle   |    7 +
 .../addSpecifics/water/krill/.gitignore            |    0
 .../addSpecifics/water/settings.gradle             |    1 +
 .../addTropical/water/bluewhale/build.gradle       |    1 +
 .../multiproject/addTropical/water/build.gradle    |    9 +
 .../addTropical/water/krill/build.gradle           |    1 +
 .../multiproject/addTropical/water/settings.gradle |    1 +
 .../addTropical/water/tropicalFish/.gitignore      |    0
 .../multiproject/customLayout/settings.gradle      |   12 +
 .../firstMessages/messages/consumer/build.gradle   |    4 +
 .../firstMessages/messages/producer/build.gradle   |    4 +
 .../firstMessages/messages/settings.gradle         |    1 +
 .../main/java/org/gradle/sample/api/Person.java    |    6 +
 .../java/org/gradle/sample/apiImpl/PersonImpl.java |   34 +
 .../multiproject/dependencies/java/build.gradle    |   25 +
 .../org/gradle/sample/services/PersonService.java  |   14 +
 .../gradle/sample/services/PersonServiceTest.java  |   11 +
 .../multiproject/dependencies/java/settings.gradle |    1 +
 .../main/java/org/gradle/sample/shared/Helper.java |    8 +
 .../main/java/org/gradle/sample/api/Person.java    |    6 +
 .../java/org/gradle/sample/apiImpl/PersonImpl.java |   34 +
 .../dependencies/javaWithCustomConf/build.gradle   |   31 +
 .../org/gradle/sample/services/PersonService.java  |   14 +
 .../gradle/sample/services/PersonServiceTest.java  |   11 +
 .../javaWithCustomConf/settings.gradle             |    1 +
 .../main/java/org/gradle/sample/shared/Helper.java |    8 +
 .../messages/consumer/build.gradle                 |    7 +
 .../messages/producer/build.gradle                 |    1 +
 .../messages/settings.gradle                       |    1 +
 .../messages/consumer/build.gradle                 |    4 +
 .../messages/producer/build.gradle                 |    1 +
 .../messages/settings.gradle                       |    1 +
 .../messages/consumer/build.gradle                 |    5 +
 .../messages/producer/build.gradle                 |    1 +
 .../messages/settings.gradle                       |    1 +
 .../messages/consumer/build.gradle                 |    6 +
 .../messages/producer/build.gradle                 |    4 +
 .../messages/settings.gradle                       |    1 +
 .../messagesHack/messages/aProducer/build.gradle   |    4 +
 .../messagesHack/messages/consumer/build.gradle    |    4 +
 .../messagesHack/messages/settings.gradle          |    1 +
 .../messages/consumer/build.gradle                 |    4 +
 .../messages/producer/build.gradle                 |    4 +
 .../messages/settings.gradle                       |    1 +
 .../messages/consumer/build.gradle                 |    6 +
 .../messages/producer/build.gradle                 |    4 +
 .../messages/settings.gradle                       |    1 +
 .../multiproject/dependencies/webDist/build.gradle |   29 +
 .../main/java/org/gradle/sample/DateServlet.java   |   12 +
 .../webDist/date/src/main/webapp/web.xml           |   13 +
 .../main/java/org/gradle/sample/HelloServlet.java  |   12 +
 .../webDist/hello/src/main/webapp/web.xml          |   13 +
 .../dependencies/webDist/settings.gradle           |    1 +
 .../firstExample/water/bluewhale/.gitignore        |    0
 .../multiproject/firstExample/water/build.gradle   |    5 +
 .../firstExample/water/settings.gradle             |    1 +
 .../userguide/multiproject/flat/dolphin/.gitignore |    0
 .../multiproject/flat/master/build.gradle          |    3 +
 .../multiproject/flat/master/settings.gradle       |    1 +
 .../userguide/multiproject/flat/shark/.gitignore   |    0
 .../flatWithNoDefaultMaster/dolphin/.gitignore     |    0
 .../flatWithNoDefaultMaster/shark/.gitignore       |    0
 .../flatWithNoDefaultMaster/water/build.gradle     |    3 +
 .../flatWithNoDefaultMaster/water/settings.gradle  |    1 +
 .../partialTasks/water/bluewhale/build.gradle      |    6 +
 .../multiproject/partialTasks/water/build.gradle   |   14 +
 .../partialTasks/water/krill/build.gradle          |    6 +
 .../partialTasks/water/settings.gradle             |    1 +
 .../partialTasks/water/tropicalFish/build.gradle   |    1 +
 .../spreadSpecifics/water/bluewhale/build.gradle   |    1 +
 .../spreadSpecifics/water/build.gradle             |    6 +
 .../spreadSpecifics/water/krill/build.gradle       |    3 +
 .../spreadSpecifics/water/settings.gradle          |    1 +
 .../multiproject/standardLayouts/settings.gradle   |    7 +
 .../water/bluewhale/.gitignore                     |    0
 .../subprojectsAddFromTop/water/build.gradle       |    9 +
 .../subprojectsAddFromTop/water/krill/.gitignore   |    0
 .../subprojectsAddFromTop/water/settings.gradle    |    1 +
 .../water/bluewhale/build.gradle                   |    2 +
 .../tropicalWithProperties/water/build.gradle      |   13 +
 .../water/krill/build.gradle                       |    4 +
 .../tropicalWithProperties/water/settings.gradle   |    1 +
 .../water/tropicalFish/build.gradle                |    1 +
 .../useSubprojects/water/bluewhale/.gitignore      |    0
 .../multiproject/useSubprojects/water/build.gradle |    6 +
 .../useSubprojects/water/krill/.gitignore          |    0
 .../useSubprojects/water/settings.gradle           |    1 +
 .../userguide/organizeBuildLogic/build.gradle      |   22 +
 .../organizeBuildLogic/customPlugin/build.gradle   |    9 +
 .../build.gradle                                   |   25 +
 .../customPluginWithConvention/build.gradle        |   16 +
 .../externalDependency/build.gradle                |   17 +
 .../organizeBuildLogic/inherited/build.gradle      |    5 +
 .../inherited/child/build.gradle                   |    8 +
 .../organizeBuildLogic/inherited/settings.gradle   |    1 +
 .../organizeBuildLogic/injected/build.gradle       |   18 +
 .../injected/child1/build.gradle                   |    3 +
 .../injected/child2/build.gradle                   |    0
 .../organizeBuildLogic/injected/settings.gradle    |    1 +
 .../organizeBuildLogic/nestedBuild/build.gradle    |    9 +
 .../organizeBuildLogic/nestedBuild/other.gradle    |    3 +
 .../userguide/tasks/accessAsProperty/build.gradle  |    4 +
 .../tasks/accessFromTaskContainer/build.gradle     |    4 +
 .../userguide/tasks/accessUsingPath/build.gradle   |   10 +
 .../tasks/accessUsingPath/settings.gradle          |    1 +
 .../tasks/addDependencyUsingClosure/build.gradle   |   19 +
 .../tasks/addDependencyUsingPath/build.gradle      |   11 +
 .../tasks/addDependencyUsingPath/settings.gradle   |    1 +
 .../tasks/addDependencyUsingTask/build.gradle      |    9 +
 .../samples/userguide/tasks/addRules/build.gradle  |   13 +
 .../tasks/addToTaskContainer/build.gradle          |    8 +
 .../tasks/configureUsingClosure/build.gradle       |    9 +
 .../tasks/configureUsingConfigure/build.gradle     |    7 +
 .../tasks/configureUsingLiterateStyle/build.gradle |    4 +
 .../userguide/tasks/configureUsingVar/build.gradle |    4 +
 .../userguide/tasks/customTask/build.gradle        |   15 +
 .../tasks/customTaskWithProperty/build.gradle      |   18 +
 .../tasks/defineAndConfigure/build.gradle          |   10 +
 .../tasks/defineAsExpression/build.gradle          |    8 +
 .../tasks/defineUsingStringTaskNames/build.gradle  |    9 +
 .../userguide/tutorial/announce/build.gradle       |   11 +
 .../userguide/tutorial/antChecksum/build.gradle    |   10 +
 .../tutorial/antChecksumFiles/agile_manifesto.html |   78 ++
 .../antChecksumFiles/agile_principles.html         |   89 ++
 .../tutorial/antChecksumFiles/dylan_thomas.txt     |    9 +
 .../tutorial/antChecksumWithMethod/build.gradle    |   17 +
 .../userguide/tutorial/archiveContent/build.gradle |   36 +
 .../userguide/tutorial/configByDag/build.gradle    |   14 +
 .../tutorial/configureObject/build.gradle          |    8 +
 .../configureObjectUsingScript/build.gradle        |    7 +
 .../configureObjectUsingScript/other.gradle        |    2 +
 .../configureProjectUsingScript/build.gradle       |    1 +
 .../configureProjectUsingScript/other.gradle       |    4 +
 .../samples/userguide/tutorial/count/build.gradle  |    3 +
 .../userguide/tutorial/defaultTasks/build.gradle   |   13 +
 .../userguide/tutorial/directoryTask/build.gradle  |   10 +
 .../userguide/tutorial/disableTask/build.gradle    |    4 +
 .../userguide/tutorial/dynamic/build.gradle        |    5 +
 .../userguide/tutorial/dynamicDepends/build.gradle |    6 +
 .../tutorial/dynamicProperties/build.gradle        |    6 +
 .../userguide/tutorial/excludeTasks/build.gradle   |   15 +
 .../tutorial/groovyWithFlatDir/build.gradle        |   18 +
 .../samples/userguide/tutorial/hello/build.gradle  |    5 +
 .../userguide/tutorial/helloEnhanced/build.gradle  |   12 +
 .../userguide/tutorial/helloShortcut/build.gradle  |    3 +
 .../tutorial/helloWithShortCut/build.gradle        |    6 +
 .../samples/userguide/tutorial/intro/build.gradle  |    6 +
 .../userguide/tutorial/lazyDependsOn/build.gradle  |    6 +
 .../userguide/tutorial/makeDirectory/build.gradle  |   11 +
 .../userguide/tutorial/manifest/build.gradle       |   44 +
 .../userguide/tutorial/mkdirTrap/build.gradle      |   12 +
 .../samples/userguide/tutorial/osgi/build.gradle   |   23 +
 .../tutorial/pluginAccessConvention/build.gradle   |   11 +
 .../userguide/tutorial/pluginConfig/build.gradle   |    7 +
 .../tutorial/pluginConvention/build.gradle         |    7 +
 .../userguide/tutorial/pluginIntro/build.gradle    |   18 +
 .../userguide/tutorial/projectApi/build.gradle     |    5 +
 .../tutorial/projectCoreProperties/build.gradle    |    9 +
 .../tutorial/projectCoreProperties/settings.gradle |    1 +
 .../projectCoreProperties/subProject/build.gradle  |    0
 .../userguide/tutorial/projectReports/build.gradle |   72 ++
 .../tutorial/projectReports/settings.gradle        |    6 +
 .../userguide/tutorial/properties/build.gradle     |    7 +
 .../tutorial/properties/gradle.properties          |    4 +
 .../userguide/tutorial/replaceTask/build.gradle    |    5 +
 .../src/samples/userguide/tutorial/scope.groovy    |   22 +
 .../tutorial/selectProject/subdir/build.gradle     |    3 +
 .../tutorial/selectProject/subdir/myproject.gradle |    3 +
 .../tutorial/stopExecutionException/build.gradle   |   11 +
 .../userguide/tutorial/taskOnlyIf/build.gradle     |    5 +
 .../samples/userguide/tutorial/upper/build.gradle  |    5 +
 .../tutorial/zipWithArguments/build.gradle         |   11 +
 .../tutorial/zipWithArguments/somedir/file.txt     |    2 +
 .../tutorial/zipWithCustomName/build.gradle        |    9 +
 .../tutorial/zipWithCustomName/somedir/file.txt    |    2 +
 .../src/samples/userguide/wrapper/build.gradle     |    8 +
 .../abbreviateCamelCaseTaskName.out                |    8 +
 .../samples/userguideOutput/abbreviateTaskName.out |   12 +
 .../samples/userguideOutput/accessUsingPath.out    |    4 +
 .../userguideOutput/addBehaviourToAntTarget.out    |    7 +
 .../userguideOutput/addDependencyUsingClosure.out  |    3 +
 .../userguideOutput/addDependencyUsingPath.out     |    2 +
 .../userguideOutput/addDependencyUsingTask.out     |    2 +
 .../src/samples/userguideOutput/antChecksum.out    |    3 +
 .../userguideOutput/antChecksumWithMethod.out      |    3 +
 .../src/samples/userguideOutput/antHello.out       |    6 +
 .../src/samples/userguideOutput/archiveNaming.out  |    3 +
 .../userguideOutput/buildProjectEvaluateEvents.out |    3 +
 .../src/samples/userguideOutput/buildlifecycle.out |    9 +
 .../src/samples/userguideOutput/configByDag.out    |    2 +
 .../userguideOutput/configByDagNoRelease.out       |    1 +
 .../configurationHandlingAllFiles.out              |    5 +
 .../userguideOutput/configurationHandlingCopy.out  |    5 +
 .../configurationHandlingCopyVsFiles.out           |    5 +
 .../configurationHandlingDependencies.out          |   10 +
 .../userguideOutput/configurationHandlingFiles.out |    2 +
 .../samples/userguideOutput/configureObject.out    |    2 +
 .../userguideOutput/configureObjectUsingScript.out |    2 +
 .../configureProjectUsingScript.out                |    2 +
 .../src/samples/userguideOutput/count.out          |    1 +
 .../src/samples/userguideOutput/customPlugin.out   |    1 +
 .../customPluginWithAdvancedConvention.out         |    1 +
 .../userguideOutput/customPluginWithConvention.out |    1 +
 .../userguideOutput/customTaskWithAction.out       |    1 +
 .../userguideOutput/customTaskWithProperty.out     |    2 +
 .../samples/userguideOutput/custom_logging_ui.out  |   12 +
 .../src/samples/userguideOutput/defaultTasks.out   |    2 +
 .../userguideOutput/dependencyListReport.out       |   17 +
 .../samples/userguideOutput/dependsOnAntTarget.out |    8 +
 .../src/samples/userguideOutput/dependsOnTask.out  |    8 +
 .../src/samples/userguideOutput/directoryTask.out  |    1 +
 .../src/samples/userguideOutput/disableTask.out    |    5 +
 .../src/samples/userguideOutput/dynamic.out        |    1 +
 .../src/samples/userguideOutput/dynamicDepends.out |    3 +
 .../samples/userguideOutput/dynamicProperties.out  |    1 +
 .../src/samples/userguideOutput/excludeTask.out    |    8 +
 .../userguideOutput/externalBuildDependency.out    |    1 +
 .../userguideOutput/externalDependencies.out       |    1 +
 .../userguideOutput/externalInitDependency.out     |    1 +
 .../samples/userguideOutput/fileCollections.out    |    6 +
 .../userguideOutput/generatedFileDependencies.out  |    2 +
 .../src/samples/userguideOutput/hello.out          |    1 +
 .../src/samples/userguideOutput/helloEnhanced.out  |    4 +
 .../samples/userguideOutput/helloWithShortCut.out  |    2 +
 .../userguideOutput/inheritedBuildLogic.out        |    2 +
 .../samples/userguideOutput/injectedBuildLogic.out |    6 +
 .../src/samples/userguideOutput/intro.out          |    2 +
 .../src/samples/userguideOutput/javaQuickstart.out |   15 +
 .../src/samples/userguideOutput/lazyDependsOn.out  |    2 +
 .../src/samples/userguideOutput/makeDirectory.out  |    1 +
 .../src/samples/userguideOutput/mkdirTrap.out      |    1 +
 .../multipleTasksFromCommandLine.out               |   12 +
 .../multiprojectAbsoluteTaskPaths.out              |    7 +
 .../userguideOutput/multiprojectAddKrill.out       |    3 +
 .../userguideOutput/multiprojectAddTropical.out    |   11 +
 .../userguideOutput/multiprojectFirstExample.out   |    2 +
 .../userguideOutput/multiprojectFirstMessages.out  |    2 +
 .../samples/userguideOutput/multiprojectFlat.out   |    3 +
 .../userguideOutput/multiprojectFlatPartial.out    |    1 +
 .../multiprojectFlatPartialWithNoDefaultMaster.out |    1 +
 .../multiprojectMessagesConfigDependencies.out     |    1 +
 ...rojectMessagesConfigDependenciesAltSolution.out |    1 +
 ...ultiprojectMessagesConfigDependenciesBroken.out |    1 +
 .../multiprojectMessagesDependencies.out           |    2 +
 .../multiprojectMessagesDependenciesSubBuild.out   |    2 +
 .../multiprojectMessagesDifferentTaskNames.out     |    1 +
 .../userguideOutput/multiprojectMessagesHack.out   |    2 +
 .../multiprojectMessagesHackBroken.out             |    1 +
 .../multiprojectMessagesTaskDependencies.out       |    2 +
 .../userguideOutput/multiprojectPartialTasks.out   |    2 +
 .../multiprojectPartialTasksNotQuiet.out           |    8 +
 .../multiprojectSpreadSpecifics.out                |    7 +
 .../userguideOutput/multiprojectSubBuild.out       |    4 +
 .../multiprojectSubprojectsAddFromTop.out          |    6 +
 .../multiprojectTropicalWithProperties.out         |   11 +
 .../userguideOutput/multiprojectUseSubprojects.out |    5 +
 .../samples/userguideOutput/multitestingBuild.out  |   19 +
 .../userguideOutput/multitestingBuildDashA.out     |   15 +
 .../multitestingBuildDependents.out                |   31 +
 .../userguideOutput/multitestingBuildNeeded.out    |   27 +
 .../src/samples/userguideOutput/nestedBuild.out    |    1 +
 .../userguideOutput/pluginAccessConvention.out     |    4 +
 .../src/samples/userguideOutput/pluginConfig.out   |    2 +
 .../samples/userguideOutput/pluginConvention.out   |    2 +
 .../src/samples/userguideOutput/pluginIntro.out    |    2 +
 .../src/samples/userguideOutput/projectApi.out     |    2 +
 .../userguideOutput/projectCoreProperties.out      |   10 +
 .../userguideOutput/projectEvaluateEvents.out      |    2 +
 .../src/samples/userguideOutput/properties.out     |    5 +
 .../samples/userguideOutput/propertyListReport.out |   15 +
 .../src/samples/userguideOutput/replaceTask.out    |    1 +
 .../src/samples/userguideOutput/scope.out          |    9 +
 .../selectProjectUsingBuildFile.out                |    1 +
 .../selectProjectUsingProjectDir.out               |    1 +
 .../userguideOutput/stopExecutionException.out     |    1 +
 .../samples/userguideOutput/taskCreationEvents.out |    1 +
 .../userguideOutput/taskExecutionEvents.out        |    4 +
 .../samples/userguideOutput/taskListAllReport.out  |   11 +
 .../src/samples/userguideOutput/taskListReport.out |   10 +
 .../src/samples/userguideOutput/taskOnlyIf.out     |    5 +
 .../src/samples/userguideOutput/taskRule.out       |    1 +
 .../samples/userguideOutput/taskRuleDependsOn.out  |    2 +
 .../samples/userguideOutput/taskWithNestedText.out |    6 +
 .../src/samples/userguideOutput/upper.out          |    2 +
 .../src/samples/userguideOutput/useAntTask.out     |    6 +
 .../samples/userguideOutput/zipWithArguments.out   |    1 +
 .../samples/userguideOutput/zipWithCustomName.out  |    1 +
 .../src/samples/water/bluewhale/build.gradle       |    7 +
 .../gradle-docs/src/samples/water/build.gradle     |   16 +
 .../src/samples/water/krill/build.gradle           |    7 +
 .../src/samples/water/phytoplankton/build.gradle   |    5 +
 .../gradle-docs/src/samples/water/settings.gradle  |    1 +
 .../samples/webApplication/customised/build.gradle |   65 +
 .../samples/webApplication/customised/readme.xml   |    3 +
 .../customised/src/additionalWebInf/additional.xml |    0
 .../src/main/java/org/gradle/HelloServlet.java     |   14 +
 .../src/main/java/org/gradle/MyClass.java          |   11 +
 .../customised/src/main/webapp/WEB-INF/webapp.xml  |    0
 .../customised/src/main/webapp/webapp.html         |    0
 .../customised/src/rootContent/root.txt            |    0
 .../webApplication/customised/src/someWeb.xml      |   14 +
 .../customised/src/test/java/org/MyClassTest.java  |    7 +
 .../samples/webApplication/quickstart/build.gradle |   38 +
 .../samples/webApplication/quickstart/readme.xml   |    3 +
 .../src/main/java/org/gradle/sample/Greeter.java   |   18 +
 .../quickstart/src/main/resources/greeting.txt     |    1 +
 .../quickstart/src/main/webapp/index.jsp           |    4 +
 subprojects/gradle-eclipse/eclipse.gradle          |   30 +
 .../eclipse/AbstractXmlGeneratorTask.groovy        |   43 +
 .../gradle/plugins/eclipse/EclipseClasspath.groovy |  109 ++
 .../gradle/plugins/eclipse/EclipsePlugin.groovy    |  139 ++
 .../gradle/plugins/eclipse/EclipseProject.groovy   |  142 +++
 .../org/gradle/plugins/eclipse/EclipseWtp.groovy   |  150 +++
 .../eclipse/model/AbstractClasspathEntry.groovy    |  146 +++
 .../plugins/eclipse/model/AbstractLibrary.groovy   |   84 ++
 .../gradle/plugins/eclipse/model/AccessRule.groovy |   58 +
 .../plugins/eclipse/model/BuildCommand.groovy      |   60 +
 .../gradle/plugins/eclipse/model/Classpath.groovy  |  116 ++
 .../plugins/eclipse/model/ClasspathEntry.java      |   26 +
 .../gradle/plugins/eclipse/model/Container.groovy  |   41 +
 .../org/gradle/plugins/eclipse/model/Facet.groovy  |   67 +
 .../gradle/plugins/eclipse/model/Library.groovy    |   37 +
 .../org/gradle/plugins/eclipse/model/Link.groovy   |   65 +
 .../org/gradle/plugins/eclipse/model/Output.groovy |   65 +
 .../gradle/plugins/eclipse/model/Project.groovy    |  232 ++++
 .../plugins/eclipse/model/ProjectDependency.groovy |   47 +
 .../plugins/eclipse/model/SourceFolder.groovy      |   92 ++
 .../gradle/plugins/eclipse/model/Variable.groovy   |   37 +
 .../plugins/eclipse/model/WbDependentModule.groovy |   70 +
 .../gradle/plugins/eclipse/model/WbProperty.groovy |   67 +
 .../gradle/plugins/eclipse/model/WbResource.groovy |   69 +
 .../org/gradle/plugins/eclipse/model/Wtp.groovy    |  159 +++
 .../eclipse/model/internal/ClasspathFactory.groovy |  186 +++
 .../eclipse/model/internal/ModelFactory.groovy     |   45 +
 .../plugins/eclipse/model/internal/PathUtil.groovy |   27 +
 .../eclipse/model/internal/WtpFactory.groovy       |  117 ++
 .../org/gradle/plugins/eclipse/package-info.java   |   20 +
 .../META-INF/gradle-plugins/eclipse.properties     |   16 +
 .../plugins/eclipse/EclipseClasspathTest.groovy    |   70 +
 .../plugins/eclipse/EclipsePluginTest.groovy       |  161 +++
 .../plugins/eclipse/EclipseProjectTest.groovy      |   69 +
 .../gradle/plugins/eclipse/EclipseWtpTest.groovy   |   96 ++
 .../plugins/eclipse/model/ClasspathTest.groovy     |  176 +++
 .../plugins/eclipse/model/ContainerTest.groovy     |   66 +
 .../gradle/plugins/eclipse/model/FacetTest.groovy  |   58 +
 .../plugins/eclipse/model/LibraryTest.groovy       |   66 +
 .../gradle/plugins/eclipse/model/OutputTest.groovy |   58 +
 .../eclipse/model/ProjectDependencyTest.groovy     |   66 +
 .../plugins/eclipse/model/ProjectTest.groovy       |  198 +++
 .../plugins/eclipse/model/SourceFolderTest.groovy  |   61 +
 .../plugins/eclipse/model/VariableTest.groovy      |   68 +
 .../eclipse/model/WbDependentModuleTest.groovy     |   61 +
 .../plugins/eclipse/model/WbPropertyTest.groovy    |   58 +
 .../plugins/eclipse/model/WbResourceTest.groovy    |   58 +
 .../gradle/plugins/eclipse/model/WtpTest.groovy    |  216 ++++
 .../plugins/eclipse/model/customClasspath.xml      |   10 +
 .../model/customOrgEclipseWstCommonComponent.xml   |    9 +
 ...ustomOrgEclipseWstCommonProjectFacetCoreXml.xml |    6 +
 .../gradle/plugins/eclipse/model/customProject.xml |   29 +
 subprojects/gradle-idea/idea.gradle                |   26 +
 .../org/gradle/plugins/idea/IdeaModule.groovy      |  303 +++++
 .../org/gradle/plugins/idea/IdeaPlugin.groovy      |  121 ++
 .../org/gradle/plugins/idea/IdeaProject.groovy     |  130 ++
 .../org/gradle/plugins/idea/IdeaWorkspace.groovy   |   51 +
 .../org/gradle/plugins/idea/model/Dependency.java  |   25 +
 .../gradle/plugins/idea/model/JarDirectory.groovy  |   66 +
 .../org/gradle/plugins/idea/model/Jdk.groovy       |   93 ++
 .../org/gradle/plugins/idea/model/Module.groovy    |  345 +++++
 .../plugins/idea/model/ModuleDependency.groovy     |   91 ++
 .../gradle/plugins/idea/model/ModuleLibrary.groovy |  135 ++
 .../gradle/plugins/idea/model/ModulePath.groovy    |   73 ++
 .../org/gradle/plugins/idea/model/Path.groovy      |  117 ++
 .../org/gradle/plugins/idea/model/Project.groovy   |  144 +++
 .../org/gradle/plugins/idea/model/Workspace.groovy |   48 +
 .../META-INF/gradle-plugins/idea.properties        |   16 +
 .../gradle/plugins/idea/model/defaultModule.xml    |   12 +
 .../gradle/plugins/idea/model/defaultProject.xml   |   97 ++
 .../gradle/plugins/idea/model/defaultWorkspace.xml |  208 +++
 .../org/gradle/plugins/idea/IdeaPluginTest.groovy  |  134 ++
 .../plugins/idea/model/ModuleDependencyTest.groovy |   51 +
 .../plugins/idea/model/ModuleLibraryTest.groovy    |   54 +
 .../gradle/plugins/idea/model/ModuleTest.groovy    |  217 ++++
 .../gradle/plugins/idea/model/ProjectTest.groovy   |  141 ++
 .../gradle/plugins/idea/model/WorkspaceTest.groovy |   79 ++
 .../org/gradle/plugins/idea/model/customModule.xml |   41 +
 .../gradle/plugins/idea/model/customProject.xml    |   60 +
 .../gradle/plugins/idea/model/customWorkspace.xml  |  211 +++
 subprojects/gradle-jetty/jetty.gradle              |   34 +
 .../api/plugins/jetty/AbstractJettyRunTask.java    |  511 ++++++++
 .../api/plugins/jetty/AbstractJettyRunWarTask.java |   48 +
 .../org/gradle/api/plugins/jetty/JettyPlugin.java  |  166 +++
 .../api/plugins/jetty/JettyPluginConvention.java   |   49 +
 .../org/gradle/api/plugins/jetty/JettyRun.java     |  457 +++++++
 .../org/gradle/api/plugins/jetty/JettyRunWar.java  |  117 ++
 .../org/gradle/api/plugins/jetty/JettyStop.java    |   91 ++
 .../api/plugins/jetty/ScanTargetPattern.java       |   65 +
 .../api/plugins/jetty/internal/ConsoleScanner.java |   99 ++
 .../plugins/jetty/internal/Jetty6PluginServer.java |  164 +++
 .../plugins/jetty/internal/JettyConfiguration.java |  138 ++
 .../plugins/jetty/internal/JettyPluginServer.java  |   49 +
 .../jetty/internal/JettyPluginWebAppContext.java   |  121 ++
 .../gradle/api/plugins/jetty/internal/Monitor.java |  123 ++
 .../gradle/api/plugins/jetty/internal/Proxy.java   |   27 +
 .../org/gradle/api/plugins/jetty/package-info.java |   20 +
 .../META-INF/gradle-plugins/jetty.properties       |   16 +
 .../api/plugins/jetty/JettyPluginTest.groovy       |   70 +
 subprojects/gradle-launcher/launcher.gradle        |   33 +
 .../main/java/org/gradle/launcher/GradleMain.java  |   64 +
 .../src/main/java/org/gradle/launcher/Main.java    |  113 ++
 .../test/groovy/org/gradle/launcher/MainTest.java  |  166 +++
 subprojects/gradle-maven/maven.gradle              |   25 +
 .../groovy/org/gradle/api/plugins/MavenPlugin.java |  127 ++
 .../gradle/api/plugins/MavenPluginConvention.java  |   76 ++
 .../META-INF/gradle-plugins/maven.properties       |   16 +
 .../api/plugins/MavenPluginConventionTest.groovy   |   61 +
 .../org/gradle/api/plugins/MavenPluginTest.java    |   92 ++
 subprojects/gradle-open-api/open-api.gradle        |   52 +
 .../org/gradle/integtests/GradleRunnerTest.groovy  |  277 ++++
 .../org/gradle/integtests/OpenApiUiTest.groovy     | 1288 +++++++++++++++++++
 .../org/gradle/integtests/OutputUILordTest.groovy  |  212 +++
 .../TestAlternateUIInteractionVersion1.java        |   54 +
 .../integtests/TestSettingsNodeVersion1.java       |  247 ++++
 .../TestSingleDualPaneUIInteractionVersion1.java   |   45 +
 .../org/gradle/foundation/BootstrapLoader.java     |  193 +++
 .../gradle/foundation/ParentLastClassLoader.java   |   88 ++
 .../gradle/openapi/external/ExternalUtility.java   |  194 +++
 .../foundation/GradleInterfaceVersion1.java        |   99 ++
 .../foundation/GradleInterfaceVersion2.java        |   95 ++
 .../external/foundation/ProjectVersion1.java       |   89 ++
 .../foundation/RequestObserverVersion1.java        |   58 +
 .../external/foundation/RequestVersion1.java       |   62 +
 .../openapi/external/foundation/TaskVersion1.java  |   61 +
 .../foundation/favorites/FavoriteTaskVersion1.java |   49 +
 .../favorites/FavoritesEditorVersion1.java         |  101 ++
 .../external/runner/GradleRunnerFactory.java       |  156 +++
 .../runner/GradleRunnerInteractionVersion1.java    |   99 ++
 .../external/runner/GradleRunnerVersion1.java      |   39 +
 .../ui/AlternateUIInteractionVersion1.java         |   69 +
 .../openapi/external/ui/BasicGradleUIVersion1.java |  222 ++++
 ...ommandLineArgumentAlteringListenerVersion1.java |   36 +
 .../external/ui/DualPaneUIInteractionVersion1.java |   29 +
 .../openapi/external/ui/DualPaneUIVersion1.java    |   57 +
 .../openapi/external/ui/GradleTabVersion1.java     |   52 +
 .../external/ui/GradleUIInteractionVersion1.java   |   43 +
 .../external/ui/OutputObserverVersion1.java        |   68 +
 .../openapi/external/ui/OutputUILordVersion1.java  |   72 ++
 .../openapi/external/ui/SettingsNodeVersion1.java  |   63 +
 .../ui/SinglePaneUIInteractionVersion1.java        |   29 +
 .../openapi/external/ui/SinglePaneUIVersion1.java  |   40 +
 .../org/gradle/openapi/external/ui/UIFactory.java  |  283 +++++
 .../openapi/external/ExternalUtilityTest.groovy    |   70 +
 subprojects/gradle-osgi/osgi.gradle                |   32 +
 .../api/internal/plugins/osgi/AnalyzerFactory.java |   23 +
 .../plugins/osgi/ContainedVersionAnalyzer.java     |   22 +
 .../plugins/osgi/DefaultAnalyzerFactory.java       |   25 +
 .../internal/plugins/osgi/DefaultOsgiManifest.java |  206 +++
 .../api/internal/plugins/osgi/OsgiHelper.java      |  196 +++
 .../org/gradle/api/plugins/osgi/OsgiManifest.java  |  196 +++
 .../org/gradle/api/plugins/osgi/OsgiPlugin.groovy  |   46 +
 .../api/plugins/osgi/OsgiPluginConvention.java     |   78 ++
 .../org/gradle/api/plugins/osgi/package-info.java  |   20 +
 .../META-INF/gradle-plugins/osgi.properties        |   16 +
 .../plugins/osgi/DefaultAnalyzerFactoryTest.java   |   29 +
 .../plugins/osgi/DefaultOsgiManifestTest.java      |  224 ++++
 .../plugins/osgi/OsgiPluginConventionTest.groovy   |   59 +
 .../gradle/api/plugins/osgi/OsgiPluginTest.groovy  |   47 +
 subprojects/gradle-plugins/plugins.gradle          |   44 +
 .../api/internal/tasks/DefaultGroovySourceSet.java |   57 +
 .../api/internal/tasks/DefaultSourceSet.java       |  180 +++
 .../internal/tasks/DefaultSourceSetContainer.java  |   40 +
 .../compile/AntDependsStaleClassCleaner.groovy     |   46 +
 .../tasks/compile/AntGroovyCompiler.groovy         |   90 ++
 .../internal/tasks/compile/AntJavaCompiler.groovy  |   84 ++
 .../api/internal/tasks/compile/Compiler.java       |   31 +
 .../api/internal/tasks/compile/GroovyCompiler.java |   26 +
 .../tasks/compile/GroovyJavaJointCompiler.java     |   19 +
 .../tasks/compile/IncrementalGroovyCompiler.java   |   43 +
 .../tasks/compile/IncrementalJavaCompiler.java     |   49 +
 .../compile/IncrementalJavaSourceCompiler.java     |   77 ++
 .../api/internal/tasks/compile/JavaCompiler.java   |   22 +
 .../internal/tasks/compile/JavaSourceCompiler.java |   26 +
 .../tasks/compile/SimpleStaleClassCleaner.java     |   38 +
 .../internal/tasks/compile/StaleClassCleaner.java  |   53 +
 .../tasks/testing/AbstractTestDescriptor.java      |   45 +
 .../tasks/testing/DecoratingTestDescriptor.java    |   54 +
 .../tasks/testing/DefaultTestClassDescriptor.java  |   33 +
 .../tasks/testing/DefaultTestClassRunInfo.java     |   37 +
 .../tasks/testing/DefaultTestDescriptor.java       |   41 +
 .../tasks/testing/DefaultTestMethodDescriptor.java |   28 +
 .../tasks/testing/DefaultTestSuiteDescriptor.java  |   34 +
 .../tasks/testing/SuiteTestClassProcessor.java     |   66 +
 .../internal/tasks/testing/TestClassProcessor.java |   48 +
 .../tasks/testing/TestClassProcessorFactory.java   |   20 +
 .../internal/tasks/testing/TestClassRunInfo.java   |   25 +
 .../internal/tasks/testing/TestCompleteEvent.java  |   43 +
 .../tasks/testing/TestDescriptorInternal.java      |   23 +
 .../api/internal/tasks/testing/TestFramework.java  |   52 +
 .../internal/tasks/testing/TestOutputEvent.java    |   41 +
 .../tasks/testing/TestResultProcessor.java         |   42 +
 .../api/internal/tasks/testing/TestStartEvent.java |   48 +
 .../tasks/testing/TestSuiteExecutionException.java |   28 +
 .../testing/WorkerTestClassProcessorFactory.java   |   23 +
 .../detection/AbstractTestFrameworkDetector.java   |  179 +++
 .../detection/ClassFileExtractionManager.java      |  146 +++
 .../testing/detection/DefaultTestClassScanner.java |   84 ++
 .../testing/detection/DefaultTestExecuter.java     |   73 ++
 .../tasks/testing/detection/TestClassVisitor.java  |   44 +
 .../tasks/testing/detection/TestExecuter.java      |   27 +
 .../testing/detection/TestFrameworkDetector.java   |   29 +
 .../tasks/testing/junit/AntJUnitReport.groovy      |   34 +
 .../CaptureTestOutputTestResultProcessor.java      |   70 +
 .../tasks/testing/junit/JULRedirector.java         |   42 +
 .../junit/JUnit4TestResultProcessorAdapter.java    |   58 +
 .../tasks/testing/junit/JUnitDetector.java         |   66 +
 .../testing/junit/JUnitTestClassDetecter.java      |  119 ++
 .../testing/junit/JUnitTestClassExecuter.java      |  106 ++
 .../testing/junit/JUnitTestClassProcessor.java     |   67 +
 .../tasks/testing/junit/JUnitTestFramework.java    |  105 ++
 .../testing/junit/JUnitTestMethodDetecter.java     |   46 +
 .../junit/JUnitTestResultProcessorAdapter.java     |  107 ++
 .../testing/junit/JUnitXmlReportGenerator.java     |  165 +++
 .../processors/MaxNParallelTestClassProcessor.java |   80 ++
 .../RestartEveryNTestClassProcessor.java           |   66 +
 .../tasks/testing/processors/TestMainAction.java   |   62 +
 .../results/AttachParentTestResultProcessor.java   |   54 +
 .../tasks/testing/results/DefaultTestResult.java   |   81 ++
 .../testing/results/LoggingResultProcessor.java    |   52 +
 .../results/StateTrackingTestResultProcessor.java  |  135 ++
 .../tasks/testing/results/TestListenerAdapter.java |   51 +
 .../internal/tasks/testing/results/TestLogger.java |   75 ++
 .../tasks/testing/results/TestSummaryListener.java |   97 ++
 .../tasks/testing/testng/TestNGDetector.java       |   75 ++
 .../testing/testng/TestNGTestClassDetecter.java    |  118 ++
 .../testing/testng/TestNGTestClassProcessor.java   |   97 ++
 .../tasks/testing/testng/TestNGTestFramework.java  |   98 ++
 .../testing/testng/TestNGTestMethodDetecter.java   |   56 +
 .../testng/TestNGTestResultProcessorAdapter.java   |  121 ++
 .../testing/worker/ForkingTestClassProcessor.java  |   80 ++
 .../testing/worker/RemoteTestClassProcessor.java   |   39 +
 .../internal/tasks/testing/worker/TestWorker.java  |   96 ++
 .../testing/worker/WorkerTestClassProcessor.java   |   39 +
 .../org/gradle/api/java/archives/Attributes.java   |   27 +
 .../org/gradle/api/java/archives/Manifest.java     |  108 ++
 .../api/java/archives/ManifestException.java       |   34 +
 .../api/java/archives/ManifestMergeDetails.java    |   56 +
 .../api/java/archives/ManifestMergeSpec.java       |   54 +
 .../java/archives/internal/DefaultAttributes.java  |  100 ++
 .../java/archives/internal/DefaultManifest.java    |  238 ++++
 .../internal/DefaultManifestMergeDetails.java      |   70 +
 .../internal/DefaultManifestMergeSpec.java         |  121 ++
 .../org/gradle/api/plugins/BasePlugin.groovy       |  171 +++
 .../gradle/api/plugins/BasePluginConvention.groovy |   42 +
 .../org/gradle/api/plugins/GroovyBasePlugin.java   |  131 ++
 .../org/gradle/api/plugins/GroovyPlugin.java       |   62 +
 .../org/gradle/api/plugins/JavaBasePlugin.java     |  295 +++++
 .../groovy/org/gradle/api/plugins/JavaPlugin.java  |  209 +++
 .../gradle/api/plugins/JavaPluginConvention.groovy |  159 +++
 .../org/gradle/api/plugins/ProcessResources.java   |   30 +
 .../gradle/api/plugins/ProjectReportsPlugin.java   |   88 ++
 .../plugins/ProjectReportsPluginConvention.groovy  |   36 +
 .../gradle/api/plugins/ReportingBasePlugin.java    |   38 +
 .../api/plugins/ReportingBasePluginConvention.java |   75 ++
 .../groovy/org/gradle/api/plugins/WarPlugin.java   |  112 ++
 .../gradle/api/plugins/WarPluginConvention.groovy  |   32 +
 .../org/gradle/api/tasks/GroovySourceSet.java      |   50 +
 .../groovy/org/gradle/api/tasks/SourceSet.java     |  189 +++
 .../org/gradle/api/tasks/SourceSetContainer.java   |   46 +
 .../org/gradle/api/tasks/bundling/Jar.groovy       |   73 ++
 .../org/gradle/api/tasks/bundling/War.groovy       |   94 ++
 .../gradle/api/tasks/compile/AbstractCompile.java  |   67 +
 .../api/tasks/compile/AbstractOptions.groovy       |   79 ++
 .../org/gradle/api/tasks/compile/AntDepend.java    |   37 +
 .../org/gradle/api/tasks/compile/Compile.java      |   77 ++
 .../gradle/api/tasks/compile/CompileOptions.groovy |  104 ++
 .../gradle/api/tasks/compile/DebugOptions.groovy   |   32 +
 .../gradle/api/tasks/compile/DependOptions.groovy  |   51 +
 .../gradle/api/tasks/compile/ForkOptions.groovy    |   38 +
 .../gradle/api/tasks/compile/GroovyCompile.java    |   99 ++
 .../api/tasks/compile/GroovyCompileOptions.groovy  |   56 +
 .../api/tasks/compile/GroovyForkOptions.groovy     |   24 +
 .../org/gradle/api/tasks/compile/package-info.java |   20 +
 .../gradle/api/tasks/javadoc/AntGroovydoc.groovy   |   75 ++
 .../org/gradle/api/tasks/javadoc/AntJavadoc.groovy |   50 +
 .../org/gradle/api/tasks/javadoc/Groovydoc.java    |  328 +++++
 .../org/gradle/api/tasks/javadoc/Javadoc.java      |  233 ++++
 .../org/gradle/api/tasks/javadoc/package-info.java |   20 +
 .../groovy/org/gradle/api/tasks/testing/Test.java  |  769 +++++++++++
 .../gradle/api/tasks/testing/TestDescriptor.java   |   49 +
 .../api/tasks/testing/TestFrameworkOptions.java    |   23 +
 .../org/gradle/api/tasks/testing/TestListener.java |   54 +
 .../org/gradle/api/tasks/testing/TestResult.java   |   81 ++
 .../api/tasks/testing/junit/JUnitOptions.java      |   25 +
 .../api/tasks/testing/junit/package-info.java      |   20 +
 .../org/gradle/api/tasks/testing/package-info.java |   20 +
 .../api/tasks/testing/testng/TestNGOptions.groovy  |  212 +++
 .../api/tasks/testing/testng/package-info.java     |   20 +
 .../external/javadoc/CoreJavadocOptions.java       |  569 +++++++++
 .../external/javadoc/JavadocExecHandleBuilder.java |   90 ++
 .../external/javadoc/JavadocMemberLevel.java       |   41 +
 .../external/javadoc/JavadocOfflineLink.java       |   46 +
 .../external/javadoc/JavadocOutputLevel.java       |   40 +
 .../external/javadoc/MinimalJavadocOptions.java    |  156 +++
 .../javadoc/StandardJavadocDocletOptions.java      |  969 ++++++++++++++
 .../AbstractJavadocOptionFileOption.java           |   50 +
 .../AbstractListJavadocOptionFileOption.java       |   58 +
 .../optionfile/BooleanJavadocOptionFileOption.java |   38 +
 .../optionfile/EnumJavadocOptionFileOption.java    |   54 +
 .../optionfile/FileJavadocOptionFileOption.java    |   39 +
 .../optionfile/GroupsJavadocOptionFileOption.java  |   58 +
 .../javadoc/optionfile/JavadocOptionFile.java      |  115 ++
 .../optionfile/JavadocOptionFileOption.java        |   28 +
 .../optionfile/JavadocOptionFileWriter.java        |   56 +
 .../optionfile/JavadocOptionFileWriterContext.java |  112 ++
 .../LinksOfflineJavadocOptionFileOption.java       |   40 +
 .../MultilineStringsJavadocOptionFileOption.java   |   44 +
 .../OptionLessJavadocOptionFileOption.java         |   30 +
 .../OptionLessStringsJavadocOptionFileOption.java  |   58 +
 .../optionfile/PathJavadocOptionFileOption.java    |   40 +
 .../optionfile/StringJavadocOptionFileOption.java  |   38 +
 .../optionfile/StringsJavadocOptionFileOption.java |   38 +
 .../optionfile/TagsJavadocOptionFileOption.java    |   56 +
 .../org/gradle/external/javadoc/package-info.java  |   22 +
 .../META-INF/gradle-plugins/base.properties        |   16 +
 .../META-INF/gradle-plugins/groovy-base.properties |   16 +
 .../META-INF/gradle-plugins/groovy.properties      |   16 +
 .../META-INF/gradle-plugins/java-base.properties   |   16 +
 .../META-INF/gradle-plugins/java.properties        |   16 +
 .../gradle-plugins/project-report.properties       |   16 +
 .../gradle-plugins/project-reports.properties      |   17 +
 .../META-INF/gradle-plugins/war.properties         |   16 +
 .../tasks/DefaultGroovySourceSetTest.groovy        |   49 +
 .../tasks/DefaultSourceSetContainerTest.java       |   35 +
 .../api/internal/tasks/DefaultSourceSetTest.groovy |  137 ++
 .../IncrementalJavaSourceCompilerTest.groovy       |   48 +
 .../compile/SimpleStaleClassCleanerTest.groovy     |   57 +
 .../tasks/testing/AbstractTestFrameworkTest.java   |   68 +
 .../testing/DefaultTestClassDescriptorTest.groovy  |   29 +
 .../testing/DefaultTestSuiteDescriptorTest.groovy  |   30 +
 .../testing/SuiteTestClassProcessorTest.groovy     |  115 ++
 .../tasks/testing/TestStartEventTest.groovy        |   33 +
 .../detection/DefaultTestClassScannerTest.groovy   |   58 +
 ...CaptureTestOutputTestResultProcessorTest.groovy |   64 +
 .../junit/JUnitTestClassProcessorTest.groovy       |  608 +++++++++
 .../testing/junit/JUnitTestFrameworkTest.java      |  114 ++
 .../MaxNParallelTestClassProcessorTest.groovy      |  170 +++
 .../RestartEveryNTestClassProcessorTest.java       |  155 +++
 .../testing/processors/TestMainActionTest.groovy   |  134 ++
 .../AttachParentTestResultProcessorTest.groovy     |   80 ++
 .../testing/results/TestListenerAdapterTest.groovy |  365 ++++++
 .../tasks/testing/results/TestLoggerTest.groovy    |  131 ++
 .../testing/results/TestSummaryListenerTest.groovy |  136 ++
 .../testng/TestNGTestClassProcessorTest.groovy     |  328 +++++
 .../testing/testng/TestNGTestFrameworkTest.java    |   89 ++
 .../worker/ForkingTestClassProcessorTest.java      |  141 ++
 .../tasks/testing/worker/TestWorkerTest.groovy     |   98 ++
 .../archives/internal/DefaultAttributesTest.groovy |   70 +
 .../internal/DefaultManifestMergeSpecTest.groovy   |  155 +++
 .../archives/internal/DefaultManifestTest.groovy   |  180 +++
 .../api/plugins/BasePluginConventionTest.groovy    |   52 +
 .../org/gradle/api/plugins/BasePluginTest.groovy   |  162 +++
 .../gradle/api/plugins/GroovyBasePluginTest.groovy |   90 ++
 .../org/gradle/api/plugins/GroovyPluginTest.groovy |  107 ++
 .../gradle/api/plugins/JavaBasePluginTest.groovy   |  154 +++
 .../api/plugins/JavaPluginConventionTest.groovy    |  140 ++
 .../org/gradle/api/plugins/JavaPluginTest.groovy   |  235 ++++
 .../api/plugins/ProjectReportsPluginTest.java      |   68 +
 .../plugins/ReportingBasePluginConventionTest.java |   86 ++
 .../api/plugins/ReportingBasePluginTest.java       |   32 +
 .../org/gradle/api/plugins/WarPluginTest.groovy    |  137 ++
 .../org/gradle/api/tasks/bundling/JarTest.groovy   |   62 +
 .../org/gradle/api/tasks/bundling/WarTest.groovy   |   45 +
 .../api/tasks/compile/AbstractCompileTest.java     |   95 ++
 .../api/tasks/compile/AbstractOptionsTest.groovy   |   50 +
 .../api/tasks/compile/CompileOptionsTest.groovy    |  175 +++
 .../org/gradle/api/tasks/compile/CompileTest.java  |   93 ++
 .../api/tasks/compile/DebugOptionsTest.groovy      |   56 +
 .../api/tasks/compile/ForkOptionsTest.groovy       |   58 +
 .../tasks/compile/GroovyCompileOptionsTest.groovy  |  118 ++
 .../api/tasks/compile/GroovyCompileTest.java       |  127 ++
 .../api/tasks/compile/GroovyForkOptionsTest.groovy |   55 +
 .../gradle/api/tasks/javadoc/GroovydocTest.java    |   89 ++
 .../org/gradle/api/tasks/javadoc/JavadocTest.java  |  153 +++
 .../testing/AbstractTestFrameworkOptionsTest.java  |   37 +
 .../org/gradle/api/tasks/testing/TestTest.java     |  305 +++++
 .../tasks/testing/testng/TestNGOptionsTest.groovy  |  109 ++
 .../javadoc/JavadocExecHandleBuilderTest.groovy    |   99 ++
 .../javadoc/StandardJavadocDocletOptionsTest.java  |  514 ++++++++
 .../BooleanJavadocOptionFileOptionTest.java        |   66 +
 .../EnumJavadocOptionFileOptionTest.java           |   60 +
 .../FileJavadocOptionFileOptionTest.java           |   60 +
 .../GroupsJavadocOptionFileOptionTest.java         |   70 +
 .../javadoc/optionfile/JavadocOptionFileTest.java  |   65 +
 .../JavadocOptionFileWriterContextTest.java        |   96 ++
 .../LinksOfflineJavadocOptionFileOptionTest.java   |   64 +
 ...ultilineStringsJavadocOptionFileOptionTest.java |   81 ++
 ...tionLessStringsJavadocOptionFileOptionTest.java |   61 +
 .../PathJavadocOptionFileOptionTest.java           |   67 +
 .../StringJavadocOptionFileOptionTest.java         |   62 +
 .../StringsJavadocOptionFileOptionTest.java        |   66 +
 .../TagsJavadocOptionFileOptionTest.java           |   67 +
 subprojects/gradle-scala/scala.gradle              |   29 +
 .../api/internal/tasks/DefaultScalaSourceSet.java  |   57 +
 .../internal/tasks/scala/AntScalaCompiler.groovy   |   73 ++
 .../tasks/scala/DefaultScalaJavaJointCompiler.java |   91 ++
 .../tasks/scala/IncrementalScalaCompiler.java      |   47 +
 .../api/internal/tasks/scala/ScalaCompiler.java    |   27 +
 .../tasks/scala/ScalaJavaJointCompiler.java        |   21 +
 .../api/plugins/scala/ScalaBasePlugin.groovy       |   76 ++
 .../gradle/api/plugins/scala/ScalaPlugin.groovy    |   45 +
 .../org/gradle/api/tasks/ScalaSourceSet.java       |   50 +
 .../org/gradle/api/tasks/scala/AntScalaDoc.groovy  |   62 +
 .../org/gradle/api/tasks/scala/ScalaCompile.java   |   82 ++
 .../api/tasks/scala/ScalaCompileOptions.groovy     |  146 +++
 .../org/gradle/api/tasks/scala/ScalaDoc.java       |  109 ++
 .../gradle/api/tasks/scala/ScalaDocOptions.groovy  |   92 ++
 .../META-INF/gradle-plugins/scala-base.properties  |   16 +
 .../META-INF/gradle-plugins/scala.properties       |   16 +
 .../tasks/DefaultScalaSourceSetTest.groovy         |   49 +
 .../scala/DefaultScalaJavaJointCompilerTest.groovy |   61 +
 .../api/plugins/scala/ScalaBasePluginTest.groovy   |   96 ++
 .../api/plugins/scala/ScalaPluginTest.groovy       |  101 ++
 .../api/tasks/scala/ScalaCompileOptionsTest.groovy |  138 ++
 .../gradle/api/tasks/scala/ScalaCompileTest.java   |   97 ++
 .../api/tasks/scala/ScalaDocOptionsTest.groovy     |   97 ++
 .../org/gradle/api/tasks/scala/ScalaDocTest.java   |  113 ++
 .../gradle/foundation/CommandLineAssistant.java    |  183 +++
 .../org/gradle/foundation/PathParserPortion.java   |   60 +
 .../org/gradle/foundation/ProjectConverter.java    |  161 +++
 .../java/org/gradle/foundation/ProjectView.java    |  243 ++++
 .../main/java/org/gradle/foundation/TaskView.java  |  102 ++
 .../gradle/foundation/common/ListReorderer.java    |  354 ++++++
 .../org/gradle/foundation/common/ObserverLord.java |  174 +++
 .../gradle/foundation/common/ReorderableList.java  |  566 +++++++++
 .../gradle/foundation/ipc/basic/ClientProcess.java |  164 +++
 .../gradle/foundation/ipc/basic/ExecutionInfo.java |   48 +
 .../gradle/foundation/ipc/basic/MessageObject.java |   70 +
 .../foundation/ipc/basic/ObjectSocketWrapper.java  |  124 ++
 .../ipc/basic/ProcessLauncherServer.java           |  161 +++
 .../org/gradle/foundation/ipc/basic/Server.java    |  308 +++++
 .../ipc/gradle/AbstractGradleServerProtocol.java   |  481 +++++++
 .../gradle/ExecuteGradleCommandClientProtocol.java |  240 ++++
 .../gradle/ExecuteGradleCommandServerProtocol.java |  152 +++
 .../gradle/foundation/ipc/gradle/GradleClient.java |   96 ++
 .../gradle/foundation/ipc/gradle/IPCUtilities.java |   89 ++
 .../ipc/gradle/KillGradleClientProtocol.java       |   47 +
 .../ipc/gradle/KillGradleServerProtocol.java       |   58 +
 .../foundation/ipc/gradle/ProtocolConstants.java   |   43 +
 .../ipc/gradle/TaskListClientProtocol.java         |  185 +++
 .../ipc/gradle/TaskListServerProtocol.java         |  141 ++
 .../org/gradle/foundation/output/FileLink.java     |  114 ++
 .../foundation/output/FileLinkDefinitionLord.java  |  215 ++++
 .../gradle/foundation/output/LiveOutputParser.java |  122 ++
 .../org/gradle/foundation/output/OutputParser.java |  111 ++
 .../definitions/ExtensionFileLinkDefinition.java   |  140 ++
 .../output/definitions/FileLinkDefinition.java     |   52 +
 .../OptionalLineNumberFileLinkDefinition.java      |   39 +
 .../definitions/PrefixedFileLinkDefinition.java    |  241 ++++
 .../definitions/TestReportFileLinkDefinition.java  |   94 ++
 .../gradle/foundation/queue/ExecutionQueue.java    |  131 ++
 .../visitors/AllProjectsAndTasksVisitor.java       |  142 +++
 .../visitors/TaskTreePopulationVisitor.java        |  165 +++
 .../visitors/UniqueNameProjectAndTaskVisitor.java  |   91 ++
 .../CommandLineArgumentAlteringListener.java       |   33 +
 .../gradleplugin/foundation/DOM4JSerializer.java   |  274 ++++
 .../gradleplugin/foundation/Dom4JUtility.java      |  116 ++
 .../foundation/ExtensionFileFilter.java            |   56 +
 .../gradleplugin/foundation/GradlePluginLord.java  |  748 +++++++++++
 .../foundation/favorites/FavoriteTask.java         |   66 +
 .../foundation/favorites/FavoritesEditor.java      |  504 ++++++++
 .../favorites/FavoritesSerializable.java           |  123 ++
 .../filters/AllowAllProjectAndTaskFilter.java      |   46 +
 .../foundation/filters/BasicFilterEditor.java      |  274 ++++
 .../filters/BasicProjectAndTaskFilter.java         |  196 +++
 .../foundation/filters/ProjectAndTaskFilter.java   |   43 +
 .../foundation/request/AbstractRequest.java        |  140 ++
 .../foundation/request/ExecutionRequest.java       |   69 +
 .../foundation/request/RefreshTaskListRequest.java |  115 ++
 .../gradleplugin/foundation/request/Request.java   |   75 ++
 .../foundation/runner/GradleRunner.java            |   77 ++
 .../foundation/settings/DOM4JSettingsNode.java     |  277 ++++
 .../foundation/settings/SettingsNode.java          |  189 +++
 .../foundation/settings/SettingsSerializable.java  |   39 +
 .../userinterface/AlternateUIInteraction.java      |   57 +
 .../swing/common/BorderlessImageButton.java        |  117 ++
 .../swing/common/BorderlessImageToggleButton.java  |  132 ++
 .../swing/common/BorderlessUtility.java            |   35 +
 .../swing/common/PreferencesAssistant.java         |  177 +++
 .../swing/generic/AbstractGradleUIInstance.java    |  232 ++++
 .../userinterface/swing/generic/BasicGradleUI.java |  164 +++
 .../swing/generic/DualPaneUIInstance.java          |   62 +
 .../swing/generic/MainGradlePanel.java             |  250 ++++
 .../userinterface/swing/generic/OutputPanel.java   |  501 ++++++++
 .../swing/generic/OutputPanelLord.java             |  474 +++++++
 .../userinterface/swing/generic/OutputTab.java     |  163 +++
 .../swing/generic/OutputTextPane.java              |  340 +++++
 .../userinterface/swing/generic/OutputUILord.java  |   95 ++
 .../swing/generic/SinglePaneUIInstance.java        |  113 ++
 .../SwingAddMultipleFavoritesInteraction.java      |  219 ++++
 .../generic/SwingEditFavoriteInteraction.java      |  203 +++
 .../swing/generic/SwingExportInteraction.java      |   77 ++
 .../swing/generic/SwingImportInteraction.java      |   63 +
 .../swing/generic/TaskTreeComponent.java           |  703 ++++++++++
 .../userinterface/swing/generic/Utility.java       |  210 +++
 .../generic/filter/AbstractFilterEditorPanel.java  |  246 ++++
 .../generic/filter/ProjectAndTaskFilterDialog.java |  288 +++++
 .../swing/generic/tabs/CommandLineTab.java         |  153 +++
 .../swing/generic/tabs/FavoriteTasksTab.java       |  423 ++++++
 .../swing/generic/tabs/GradleTab.java              |   43 +
 .../userinterface/swing/generic/tabs/SetupTab.java |  626 +++++++++
 .../swing/generic/tabs/TaskTreeTab.java            |  598 +++++++++
 .../swing/standalone/Application.java              |  396 ++++++
 .../swing/standalone/BlockingApplication.java      |  106 ++
 .../openapi/wrappers/RunnerWrapperFactory.java     |   68 +
 .../gradle/openapi/wrappers/UIWrapperFactory.java  |   68 +
 .../foundation/GradleInterfaceWrapperVersion1.java |  152 +++
 .../foundation/GradleInterfaceWrapperVersion2.java |   89 ++
 .../wrappers/foundation/ProjectWrapper.java        |  156 +++
 .../foundation/RequestObserverWrapper.java         |   77 ++
 .../wrappers/foundation/RequestWrapper.java        |   98 ++
 .../openapi/wrappers/foundation/TaskWrapper.java   |   94 ++
 .../foundation/favorites/FavoriteTaskWrapper.java  |   66 +
 .../favorites/FavoritesEditorWrapper.java          |  128 ++
 .../runner/GradleRunnerInteractionWrapper.java     |  129 ++
 .../wrappers/runner/GradleRunnerWrapper.java       |   57 +
 .../wrappers/ui/AbstractOpenAPIUIWrapper.java      |  349 +++++
 .../ui/AlternateUIInteractionVersionWrapper.java   |   65 +
 ...CommandLineArgumentAlteringListenerWrapper.java |   43 +
 .../openapi/wrappers/ui/DualPaneUIWrapper.java     |   70 +
 .../wrappers/ui/GradleTabVersionWrapper.java       |   51 +
 .../openapi/wrappers/ui/OutputObserverWrapper.java |   98 ++
 .../openapi/wrappers/ui/OutputUILordWrapper.java   |   85 ++
 .../wrappers/ui/SettingsNodeVersionWrapper.java    |  149 +++
 .../openapi/wrappers/ui/SinglePaneUIWrapper.java   |   52 +
 .../ipc/gradle/execute-command-init-script.gradle  |    5 +
 .../ipc/gradle/refresh-tasks-init-script.gradle    |    5 +
 .../swing/generic/close-highlight.png              |  Bin 0 -> 3457 bytes
 .../userinterface/swing/generic/close.png          |  Bin 0 -> 202 bytes
 .../userinterface/swing/generic/tabs/add.png       |  Bin 0 -> 966 bytes
 .../userinterface/swing/generic/tabs/blank.png     |  Bin 0 -> 841 bytes
 .../swing/generic/tabs/edit-filter.png             |  Bin 0 -> 861 bytes
 .../userinterface/swing/generic/tabs/edit.png      |  Bin 0 -> 924 bytes
 .../userinterface/swing/generic/tabs/execute.png   |  Bin 0 -> 966 bytes
 .../userinterface/swing/generic/tabs/export.png    |  Bin 0 -> 984 bytes
 .../userinterface/swing/generic/tabs/filter.png    |  Bin 0 -> 783 bytes
 .../userinterface/swing/generic/tabs/import.png    |  Bin 0 -> 992 bytes
 .../userinterface/swing/generic/tabs/move-down.png |  Bin 0 -> 589 bytes
 .../userinterface/swing/generic/tabs/move-up.png   |  Bin 0 -> 598 bytes
 .../userinterface/swing/generic/tabs/refresh.png   |  Bin 0 -> 988 bytes
 .../userinterface/swing/generic/tabs/remove.png    |  Bin 0 -> 866 bytes
 .../org/gradle/foundation/BuildInformation.java    |  147 +++
 .../gradle/foundation/CommandLineParsingTest.java  |   99 ++
 .../gradle/foundation/DOM4JSettingsNodeTest.java   |  678 ++++++++++
 .../org/gradle/foundation/FavoritesTest.java       |  766 +++++++++++
 .../org/gradle/foundation/FileLinkTests.java       |  238 ++++
 .../groovy/org/gradle/foundation/FilterTest.java   |  212 +++
 .../gradle/foundation/LiveOutputParserTests.java   |  261 ++++
 .../groovy/org/gradle/foundation/TestUtility.java  |  450 +++++++
 .../integtests/FavoritesIntegrationTest.java       |  396 ++++++
 .../integtests/LiveOutputIntegrationTest.groovy    |  182 +++
 ...projectProjectAndTaskListIntegrationTest.groovy |  249 ++++
 subprojects/gradle-ui/ui.gradle                    |   53 +
 .../java/org/gradle/api/tasks/wrapper/Wrapper.java |  345 +++++
 .../api/tasks/wrapper/WrapperScriptGenerator.java  |   96 ++
 .../org/gradle/api/tasks/wrapper/package-info.java |   20 +
 .../org/gradle/wrapper/BootstrapMainStarter.java   |   39 +
 .../src/main/java/org/gradle/wrapper/Download.java |   72 ++
 .../java/org/gradle/wrapper/GradleWrapperMain.java |   69 +
 .../main/java/org/gradle/wrapper/IDownload.java    |   25 +
 .../src/main/java/org/gradle/wrapper/Install.java  |  156 +++
 .../java/org/gradle/wrapper/PathAssembler.java     |   51 +
 .../gradle/wrapper/SystemPropertiesHandler.java    |   71 ++
 .../src/main/java/org/gradle/wrapper/Wrapper.java  |   60 +
 .../api/tasks/wrapper/unixWrapperScriptHead.txt    |   63 +
 .../api/tasks/wrapper/unixWrapperScriptTail.txt    |   75 ++
 .../api/tasks/wrapper/windowsWrapperScriptHead.txt |  101 ++
 .../api/tasks/wrapper/windowsWrapperScriptTail.txt |   21 +
 .../org/gradle/api/tasks/wrapper/WrapperTest.java  |  147 +++
 .../groovy/org/gradle/wrapper/DownloadTest.groovy  |   53 +
 .../groovy/org/gradle/wrapper/InstallTest.groovy   |  164 +++
 .../org/gradle/wrapper/PathAssemblerTest.java      |   80 ++
 .../wrapper/SystemPropertiesHandlerTest.groovy     |   48 +
 .../groovy/org/gradle/wrapper/WrapperTest.java     |   86 ++
 .../org/gradle/wrapper/wrapper.properties          |    8 +
 subprojects/gradle-wrapper/wrapper.gradle          |   43 +
 wrapper/archetype.gradle                           |    5 +
 wrapper/gradle-wrapper.properties                  |   25 +
 2811 files changed, 219792 insertions(+)

diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..9f8abf7
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,490 @@
+/*
+ * 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.
+ */
+
+import java.text.SimpleDateFormat
+import java.util.jar.Attributes
+import org.gradle.api.artifacts.repositories.WebdavResolver
+
+import org.gradle.build.samples.WrapperProjectCreator
+
+/**
+ * For building Gradle you usually don't need to specify any properties. Only certain functionality of the Gradle requires
+ * setting certain properties. Those properties can be set in the gradle.properties file in the the gradle user home. The
+ * following properties can be set:
+ *
+ * Uploading distributions to Gradle's release and snapshot repository at codehaus: codehausUserName, codehausUserPassword
+ * Uploading the userguide and the javadoc to Gradle's website: websiteFtpUserName, websiteFtpUserPassword
+ * Using the build to create a new distribution and install it on the local machine: gradle_installPath
+ */
+
+version = new Version(project)
+
+defaultTasks "clean", "assemble"
+
+apply plugin: 'java-base'
+
+startScriptsDir = new File("$buildDir/startScripts")
+
+archivesBaseName = 'gradle'
+
+libraries = [
+        ant: 'org.apache.ant:ant:1.8.1 at jar',
+        ant_junit: 'org.apache.ant:ant-junit:1.8.1 at jar',
+        ant_launcher: 'org.apache.ant:ant-launcher:1.8.1 at jar',
+        ant_nodeps: 'org.apache.ant:ant-nodeps:1.8.1 at jar',
+        ant_antlr: 'org.apache.ant:ant-antlr:1.8.1 at jar',
+        antlr: 'antlr:antlr:2.7.7 at jar',
+        asm_all: 'asm:asm-all:3.2 at jar',
+        commons_cli: 'commons-cli:commons-cli:1.2 at jar',
+        commons_io: 'commons-io:commons-io:1.4 at jar',
+        commons_lang: 'commons-lang:commons-lang:2.5 at jar',
+        dom4j: 'dom4j:dom4j:1.6.1 at jar',
+        google_collections: 'com.google.collections:google-collections:1.0 at jar',
+        groovy: 'org.codehaus.groovy:groovy-all:1.7.3 at jar',
+        ivy: 'org.apache.ivy:ivy:2.2.0-rc1 at jar',
+        jaxen: 'jaxen:jaxen:1.1 at jar',
+        jopt_simple: 'net.sf.jopt-simple:jopt-simple:3.2 at jar',
+        slf4j_api: 'org.slf4j:slf4j-api:1.6.1 at jar',
+        jcl_to_slf4j: 'org.slf4j:jcl-over-slf4j:1.6.1 at jar',
+        jul_to_slf4j: 'org.slf4j:jul-to-slf4j:1.6.1 at jar',
+        log4j_to_slf4j: 'org.slf4j:log4j-over-slf4j:1.6.1 at jar',
+        logback_classic: 'ch.qos.logback:logback-classic:0.9.24 at jar',
+        logback_core: 'ch.qos.logback:logback-core:0.9.24 at jar',
+        junit: 'junit:junit:4.8.1',
+        xmlunit: 'xmlunit:xmlunit:1.3',
+]
+
+libraries.spock = ['org.spockframework:spock-core:0.4-groovy-1.7 at jar', libraries.groovy, 'cglib:cglib-nodep:2.2', 'org.objenesis:objenesis:1.2']
+libraries.groovy_depends = [libraries.groovy, libraries.commons_cli]
+libraries.jetty_depends = ["org.mortbay.jetty:jetty:6.1.22 at jar", "org.mortbay.jetty:jetty-util:6.1.22 at jar",
+        "javax.servlet:servlet-api:2.5 at jar"]
+
+allprojects {
+    group = 'org.gradle'
+
+    plugins.withType(JavaPlugin).whenPluginAdded {
+        sourceCompatibility = 1.5
+        targetCompatibility = 1.5
+    }
+
+    repositories {
+        mavenRepo(urls: 'http://gradle.artifactoryonline.com/gradle/libs')
+    }
+
+    version = this.version
+}
+
+def FIRST_LEVEL_JMOCK = ['org.hamcrest:hamcrest-core:1.1 at jar', 'org.hamcrest:hamcrest-library:1.1 at jar', 'org.jmock:jmock-junit4:2.5.1 at jar']
+
+configure(groovyProjects()) {
+    apply plugin: 'groovy'
+    apply plugin: 'code-quality'
+    apply plugin: 'eclipse'
+
+    archivesBaseName = "gradle-${name.replaceAll("\\p{Upper}") { "-${it.toLowerCase()}" } }"
+    dependencies {
+        testCompile libraries.junit, FIRST_LEVEL_JMOCK, libraries.spock
+        testCompile module("org.jmock:jmock:2.5.1") {
+            dependencies('org.jmock:jmock-legacy:2.5.1 at jar', 'org.objenesis:objenesis:1.0', 'cglib:cglib-nodep:2.1_3')
+            dependencies(FIRST_LEVEL_JMOCK as Object[])
+        }
+        // todo - these should not be here
+        testCompile 'org.jmock:jmock-legacy:2.5.1 at jar', 'org.objenesis:objenesis:1.0', 'cglib:cglib-nodep:2.1_3'
+    }
+
+    checkstyleConfigDir = "$rootDir/config/checkstyle"
+    checkstyleConfigFileName = new File(checkstyleConfigDir, "checkstyle.xml")
+    codeNarcConfigFileName = "$rootDir/config/codenarc.xml"
+    checkstyleProperties.checkstyleConfigDir = checkstyleConfigDir
+
+    sourceSets.allObjects { sourceSet ->
+        task "${sourceSet.getTaskName('checkstyle', 'groovy')}"(type: Checkstyle) {
+            configFile = new File(checkstyleConfigDir, "checkstyle-groovy.xml")
+            source sourceSet.allGroovy
+            classpath = sourceSet.compileClasspath
+            resultFile = new File(checkstyleResultsDir, "${sourceSet.name}.xml")
+        }
+    }
+
+    test {
+        maxParallelForks = guessMaxForks()
+    }
+
+    tasks.withType(Jar).each { jar ->
+        jar.manifest.mainAttributes([
+                (Attributes.Name.IMPLEMENTATION_TITLE.toString()): 'Gradle',
+                (Attributes.Name.IMPLEMENTATION_VERSION.toString()): version,
+        ])
+    }
+}
+
+allprojects {
+    apply plugin: 'idea'
+    apply plugin: 'eclipse'
+    def config = configurations.findByName('testRuntime')
+    if (!config) {
+        return
+    }
+    ideDir = file('ide')
+    task ide(type: Sync) {
+        into "$ideDir/lib"
+        from { config.copyRecursive {dep -> !(dep instanceof ProjectDependency)}.files }
+    }
+    ideaModule {
+        gradleCacheVariable = 'GRADLE_CACHE'
+        outputDir = "$rootProject.projectDir/intellij/out" as File
+        testOutputDir = "$rootProject.projectDir/intellij/testOut" as File
+    }
+}
+
+ideaModule {
+    excludeDirs += file('intTestHomeDir')
+    excludeDirs += file('buildSrc/build')
+    excludeDirs += file('buildSrc/.gradle')
+}
+
+ideaProject {
+    wildcards += ['?*.gradle']
+    javaVersion = '1.6'
+}
+
+configurations {
+    dists
+    runtime {
+        visible = false
+    }
+    plugins {
+        visible = false
+    }
+    testRuntime {
+        extendsFrom runtime
+        extendsFrom plugins
+    }
+}
+
+dependencies {
+    runtime runtimeProjects()
+    plugins pluginProjects()
+}
+
+evaluationDependsOn(':docs')
+evaluationDependsOn(':core')
+
+clean.dependsOn subprojects.collect { "$it.path:clean" }
+
+task check(overwrite: true, dependsOn: groovyProjects()*.check)
+task test(overwrite: true, dependsOn: groovyProjects()*.test)
+
+zipRootFolder = "$archivesBaseName-${-> version}"
+
+binDistImage = copySpec {
+    from 'src/toplevel'
+    from project(':docs').distDocs.destFile
+    into('bin') {
+        from startScriptsDir
+        fileMode = 0755
+    }
+    into('lib') {
+        from configurations.runtime
+        into('plugins') {
+            from configurations.plugins - configurations.runtime
+        }
+    }
+}
+
+allDistImage = copySpec {
+    with binDistImage
+    into('src') {
+        from groovyProjects().collect {project -> project.sourceSets.main.allSource }
+    }
+    into('docs') {
+        from project(':docs').docsDir
+    }
+    into('samples') {
+        from project(':docs').samplesDir
+    }
+}
+
+task allZip(type: Zip, dependsOn: ['launcher:startScripts', 'docs:docs', 'docs:samples']) {
+    classifier = 'all'
+    into(zipRootFolder) {
+        with allDistImage
+    }
+}
+
+task binZip(type: Zip, dependsOn: ['launcher:startScripts', 'docs:distDocs']) {
+    classifier = 'bin'
+    into(zipRootFolder) {
+        with binDistImage
+    }
+}
+
+task srcZip(type: Zip) {
+    classifier = 'src'
+    into(zipRootFolder) {
+        from('gradlew') {
+            fileMode = 0755
+        }
+        from(projectDir) {
+            def spec = delegate
+            ['buildSrc', 'subprojects/*'].each {
+                spec.include "$it/*.gradle"
+                spec.include "$it/src/"
+            }
+            include 'config/'
+            include 'src/'
+            include '*.gradle'
+            include 'gradle.properties'
+            include 'wrapper/'
+            include 'gradlew.bat'
+        }
+    }
+}
+
+artifacts {
+    tasks.withType(Zip).each {
+        dists it
+    }
+}
+
+task intTestImage(type: Sync) {
+    dependsOn allZip.taskDependencies
+    with allDistImage
+    integTestGradleHome = file("$buildDir/integ test")
+    into integTestGradleHome
+    doLast { task ->
+        ant.chmod(dir: "$integTestGradleHome/bin", perm: "ugo+rx", includes: "**/*")
+        WrapperProjectCreator.createProject(new File(integTestGradleHome, 'samples'), binZip.archivePath.parentFile, version.toString())
+    }
+}
+
+task integTest(type: Test, dependsOn: [intTestImage, binZip, allZip, srcZip, ':docs:userguideDocbook']) {
+    integTestUserDir = file('intTestHomeDir')
+    systemProperties['integTest.srcDir'] = file('src').absolutePath
+    systemProperties['integTest.userGuideInfoDir'] = project(':docs').docbookSrc
+    systemProperties['integTest.userGuideOutputDir'] = new File(project(':docs').samplesSrcDir, "userguideOutput").absolutePath
+    systemProperties['integTest.gradleUserHomeDir'] = integTestUserDir.absolutePath
+    include 'org/gradle/integtests/**/*IntegrationTest.*'
+    forkEvery = 15
+    maxParallelForks = guessMaxForks()
+
+    testClassesDir = project(':core').sourceSets.integTest.classesDir
+    classpath = project(':core').sourceSets.integTest.runtimeClasspath + configurations.testRuntime
+    testResultsDir = file('build/test-results')
+    testReportDir = file('build/reports/tests')
+    testSrcDirs = []
+    doFirst {
+        if (isDevBuild()) {
+            exclude 'org/gradle/integtests/DistributionIntegrationTest.*'
+        }
+        systemProperties['integTest.gradleHomeDir'] = intTestImage.integTestGradleHome.absolutePath
+        def forkArgs
+        if (noForkIntegTests()) {
+            systemProperties['org.gradle.integtest.nofork'] = "true"
+            jvmArgs '-Xmx512m', '-XX:MaxPermSize=256m', '-XX:+HeapDumpOnOutOfMemoryError'
+        } else {
+            jvmArgs '-Xmx512m', '-XX:+HeapDumpOnOutOfMemoryError'
+        }
+    }
+}
+
+boolean noForkIntegTests() {
+    if (project.hasProperty('forkIntegTests')) {
+        return !Boolean.valueOf(forkIntegTests)
+    }
+    return isDevBuild() && !OperatingSystem.current().isWindows()
+}
+
+private def isDevBuild() {
+    gradle.taskGraph.hasTask(':developerBuild')
+}
+
+def guessMaxForks() {
+    int processors = Runtime.runtime.availableProcessors()
+    return Math.max(2, (int) (processors / 2))
+}
+
+task testedDists(dependsOn: [assemble, check, integTest])
+
+task nightlyBuild(dependsOn: [clean, testedDists, ':docs:uploadDocs'])
+
+task install(type: Install, description: 'Installs the minimal distribution into directory $gradle_installPath') {
+    dependsOn binZip.taskDependencies
+    with binDistImage
+    installDirProperyName = 'gradle_installPath'
+}
+
+task installAll(type: Install, description: 'Installs the full distribution into directory $gradle_installPath') {
+    dependsOn allZip.taskDependencies
+    with allDistImage
+    installDirProperyName = 'gradle_installPath'
+}
+
+uploadDists {
+    dependsOn testedDists
+    uploadDescriptor = false
+    doFirst {
+        it.repositories.add(new WebdavResolver()) {
+            name = 'gradleReleases'
+            user = codehausUserName
+            userPassword = codehausUserPassword
+            addArtifactPattern("${version.distributionUrl}/[artifact]-[revision](-[classifier]).[ext]" as String)
+        }
+    }
+}
+
+gradle.taskGraph.whenReady {graph ->
+    if (graph.hasTask(uploadDists)) {
+        // check properties defined and fail early
+        codehausUserName
+        codehausUserPassword
+    }
+}
+
+task developerBuild(dependsOn: [clean, testedDists])
+task ciBuild(dependsOn: [clean, testedDists])
+
+task release(dependsOn: [uploadDists, ':docs:uploadDocs']) << {
+    // Update the version properties in gradle.properties
+//    Properties props = new Properties()
+//    File propsFile = file('gradle.properties')
+//    propsFile.withReader { props.load(it) }
+//    props.previousVersion = props.nextVersion
+//    def nextVersion = props.nextVersion.split('\\.')
+//    nextVersion[1] = (nextVersion[1] as Integer) +1
+//    props.nextVersion = nextVersion.join('.')
+//    propsFile.withWriter { props.store(it, 'properties') }
+// todo rewrite release functionality for Git. Until then we have to do tagging and branching manually.
+}
+
+task wrapper(type: Wrapper, dependsOn: binZip)
+wrapper.doFirst {task ->
+    task.configure {
+        gradleVersion = version
+        jarPath = 'wrapper'
+    }
+}
+
+task rebuildWrapper(dependsOn: [clean, wrapper])
+
+def groovyProjects() {
+    subprojects.findAll { project -> project.name != 'docs' }
+}
+
+def runtimeProjects() {
+    groovyProjects() - pluginProjects()
+}
+
+def pluginProjects() {
+    ['plugins', 'codeQuality', 'jetty', 'antlr', 'wrapper', 'osgi', 'maven', 'eclipse', 'idea', 'announce', 'scala'].collect {
+        project(it)
+    }
+}
+
+class Version {
+    String versionNumber
+    Date buildTime
+    Boolean release = null
+
+    def Version(project) {
+        this.versionNumber = project.nextVersion
+        File timestampFile = new File(project.buildDir, 'timestamp.txt')
+        if (!timestampFile.isFile()) {
+            timestampFile.parentFile.mkdirs()
+            timestampFile.createNewFile()
+        } else {
+            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)
+            }
+        }
+        buildTime = new Date(timestampFile.lastModified())
+
+        project.gradle.taskGraph.whenReady {graph ->
+            if (!graph.hasTask(':release')) {
+                this.versionNumber += "-" + getTimestamp()
+                release = false
+            } else {
+                release = true
+            }
+        }
+    }
+
+    String toString() {
+        versionNumber
+    }
+
+    String getTimestamp() {
+        new SimpleDateFormat('yyyyMMddHHmmssZ').format(buildTime)
+    }
+
+    boolean isRelease() {
+        if (release == null) {
+            throw new GradleException("Can't determine whether this is a release build before the task graph is populated")
+        }
+        return release
+    }
+
+    String getDistributionUrl() {
+        if (release) {
+            'https://dav.codehaus.org/dist/gradle'
+        } else {
+            'https://dav.codehaus.org/snapshots.dist/gradle'
+        }
+    }
+}
+
+class Install extends Sync {
+
+    String installDirProperyName
+    File installDir
+
+    def Install() {
+        addPropertyCheck()
+        doLast {
+            ant.chmod(file: "$installDir/bin/gradle", perm: 'ugo+x')
+        }
+    }
+
+    private void addPropertyCheck() {
+        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.")
+                }
+                installDir = project.file(this.project."$installDirProperyName")
+                if (installDir.file) {
+                    throw new RuntimeException("Install directory $installDir does not look like a Gradle installation. Cannot delete it to install.")
+                }
+                if (installDir.directory) {
+                    File libDir = new File(installDir, "lib")
+                    if (!libDir.directory || !libDir.list().findAll { it.matches('gradle.*\\.jar')}) {
+                        throw new RuntimeException("Install directory $installDir does not look like a Gradle installation. Cannot delete it to install.")
+                    }
+                }
+                into installDir
+            }
+        }
+    }
+}
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
new file mode 100644
index 0000000..afc59b1
--- /dev/null
+++ b/buildSrc/build.gradle
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+apply plugin: 'groovy'
+apply plugin: 'code-quality'
+
+repositories {
+    mavenRepo(urls: 'http://gradle.artifactoryonline.com/gradle/libs')
+}
+
+dependencies {
+    compile 'com.svnkit:svnkit:1.1.6', 'com.svnkit:svnkit-javahl:1.1.6'
+    compile gradleApi()
+    // todo Actually it should be only groovy, but without junit we get a strange error. We need to understand this.
+    groovy localGroovy()
+    testCompile "junit:junit:4.7"
+}
+
+checkstyleConfigDir = "$rootDir/../config/checkstyle"
+checkstyleConfigFileName = new File(checkstyleConfigDir, 'checkstyle.xml')
+codeNarcConfigFileName = "$rootDir/../config/codenarc.xml"
+checkstyleProperties.checkstyleConfigDir = checkstyleConfigDir
+
+task ide(type: Sync) {
+    into file('lib')
+    from { configurations.testRuntime.files(DependencySpecs.type(Type.EXTERNAL)) }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/AssembleSampleDocsTask.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/AssembleSampleDocsTask.groovy
new file mode 100644
index 0000000..cfa7ecd
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/AssembleSampleDocsTask.groovy
@@ -0,0 +1,104 @@
+package org.gradle.build.docs
+
+import groovy.xml.DOMBuilder
+import groovy.xml.dom.DOMUtil
+import org.gradle.api.file.FileVisitDetails
+import org.gradle.api.tasks.SourceTask
+import org.gradle.api.tasks.TaskAction
+import org.w3c.dom.Document
+import javax.xml.parsers.DocumentBuilderFactory
+import org.w3c.dom.Element
+import groovy.xml.dom.DOMCategory
+import org.gradle.api.tasks.OutputFile
+
+class AssembleSamplesDocTask extends SourceTask {
+    @OutputFile
+    File destFile
+
+    @TaskAction
+    def generate() {
+        List samples = []
+        use(DOMCategory) {
+
+            // Collect up the source sample.xml files
+            source.visit {FileVisitDetails fvd ->
+                if (fvd.isDirectory()) {
+                    return
+                }
+
+                Element sourceDoc;
+                fvd.file.withReader {Reader reader ->
+                    sourceDoc = DOMBuilder.parse(reader).documentElement
+                }
+
+                Element firstPara = sourceDoc.para[0]
+                if (!firstPara) {
+                    throw new RuntimeException("Source file $fvd.file does not contain any <para> elements.")
+                }
+                samples << [
+                        dir: fvd.relativePath.parent as String,
+                        firstPara: firstPara,
+                        doc: sourceDoc,
+                        include: sourceDoc['*'].size() > 1
+                ]
+            }
+
+            samples = samples.sort { it.dir }
+
+            Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument()
+
+            DomBuilder builder = new DomBuilder(doc)
+            builder.appendix(id: 'sample_list') {
+                title('Gradle Samples')
+                para {
+                    text('Listed below are some of the stand-alone samples which are included in the Gradle distribution. ')
+                    text('You can find these samples in the ')
+                    filename {
+                        replaceable('GRADLE_HOME')
+                        text('/samples')
+                    }
+                    text(' directory of the distribution.')
+                }
+                table {
+                    title('Samples included in the distribution')
+                    thead {
+                        td('Sample')
+                        td('Description')
+                    }
+                    samples.each {sample ->
+                        tr {
+                            td {
+                                if (sample.include) {
+                                    link(linkend: sample.hashCode()) { filename(sample.dir) }
+                                } else {
+                                    filename(sample.dir)
+                                }
+                            }
+                            td {
+                                appendChild sample.firstPara
+                            }
+                        }
+                    }
+                }
+                samples.each {sample ->
+                    if (!sample.include) {
+                        return
+                    }
+                    section(id: sample.hashCode()) {
+                        title {
+                            text('Sample ')
+                            filename(sample.dir)
+                        }
+                        sample.doc.childNodes.each {n ->
+                            appendChild n
+                        }
+                    }
+                }
+            }
+
+            destFile.withOutputStream {OutputStream stream ->
+                DOMUtil.serialize(doc.documentElement, stream)
+            }
+        }
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/DomBuilder.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/DomBuilder.groovy
new file mode 100644
index 0000000..f2af2da
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/DomBuilder.groovy
@@ -0,0 +1,54 @@
+package org.gradle.build.docs
+
+import org.w3c.dom.Document
+import org.w3c.dom.Element
+import org.w3c.dom.Node
+
+class DomBuilder extends BuilderSupport {
+    Document document
+
+    def DomBuilder(document) {
+        this.document = document;
+    }
+
+    protected Element createNode(Object name) {
+        Element element = document.createElement(name as String)
+        if (document.documentElement == null) {
+            document.appendChild(element)
+        }
+        return element
+    }
+
+    protected Element createNode(Object name, Map attributes) {
+        Element element = createNode(name)
+        attributes.each {key, value ->
+            element.setAttribute(key as String, value as String)
+        }
+        return element
+    }
+
+    protected Element createNode(Object name, Map attributes, Object value) {
+        Element element = createNode(name, attributes)
+        element.appendChild(document.createTextNode(value as String))
+        return element
+    }
+
+    protected Element createNode(Object name, Object value) {
+        return createNode(name, [:], value)
+    }
+
+    protected void setParent(Object parent, Object child) {
+        parent.appendChild(child)
+    }
+
+    def appendChild(Node node) {
+        current.appendChild(document.importNode(node, true))
+    }
+
+    def text(String text) {
+        current.appendChild(document.createTextNode(text))
+    }
+}
+
+
+
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/ExtractSnippetsTask.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/ExtractSnippetsTask.groovy
new file mode 100644
index 0000000..fe9e209
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/ExtractSnippetsTask.groovy
@@ -0,0 +1,128 @@
+package org.gradle.build.docs
+
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.SourceTask
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.file.FileVisitDetails
+
+public class ExtractSnippetsTask extends SourceTask {
+    @OutputDirectory
+    File destDir
+    @OutputDirectory
+    File snippetsDir
+
+    @TaskAction
+    def extract() {
+        source.visit { FileVisitDetails details ->
+            String name = details.relativePath.pathString
+            if (details.file.isDirectory()) {
+                File destDir = new File(destDir, name)
+                destDir.mkdirs()
+                destDir = new File(snippetsDir, name)
+                destDir.mkdirs()
+            }
+            else {
+                File srcFile = details.file
+                File destFile = new File(destDir, name)
+
+                destFile.parentFile.mkdirs()
+
+                if (['.jar', '.zip'].find{ name.endsWith(it) }) {
+                    destFile.withOutputStream { it.write(srcFile.readBytes()) }
+                    return
+                }
+
+                Map writers = [
+                        0: new SnippetWriter(name, destFile).start(),
+                        1: new SnippetWriter(name, new File(snippetsDir, name)).start()
+                ]
+                Pattern startSnippetPattern
+                Pattern endSnippetPattern
+                if (name.endsWith('.xml')) {
+                    startSnippetPattern = Pattern.compile('\\s*<!--\\s*START\\s+SNIPPET\\s+(\\S+)\\s*-->')
+                    endSnippetPattern = Pattern.compile('\\s*<!--\\s*END\\s+SNIPPET\\s+(\\S+)\\s*-->')
+                } else {
+                    startSnippetPattern = Pattern.compile('\\s*//\\s*START\\s+SNIPPET\\s+(\\S+)\\s*')
+                    endSnippetPattern = Pattern.compile('\\s*//\\s*END\\s+SNIPPET\\s+(\\S+)\\s*')
+                }
+
+                try {
+                    // Can't use eachLine {} because it throws away blank lines
+                    srcFile.withReader {Reader r ->
+                        BufferedReader reader = new BufferedReader(r)
+                        reader.readLines().each {String line ->
+                            Matcher m = startSnippetPattern.matcher(line)
+                            if (m.matches()) {
+                                String snippetName = m.group(1)
+                                if (!writers[snippetName]) {
+                                    File snippetFile = new File(snippetsDir, "$name-$snippetName")
+                                    writers.put(snippetName, new SnippetWriter("Snippet $snippetName in $name", snippetFile))
+                                }
+                                writers[snippetName].start()
+                                return
+                            }
+                            m = endSnippetPattern.matcher(line)
+                            if (m.matches()) {
+                                String snippetName = m.group(1)
+                                writers[snippetName].end()
+                                return
+                            }
+                            writers.values().each {SnippetWriter w ->
+                                w.println(line)
+                            }
+                        }
+                    }
+                } finally {
+                    writers.values().each {SnippetWriter w ->
+                        w.close()
+                    }
+                }
+            }
+        }
+    }
+}
+
+class SnippetWriter {
+
+    private final File dest
+    private final String displayName
+    private boolean appendToFile
+    private PrintWriter writer
+
+    def SnippetWriter(String displayName, File dest) {
+        this.dest = dest
+        this.displayName = displayName
+    }
+
+    def start() {
+        if (writer) {
+            throw new RuntimeException("$displayName is already started.")
+        }
+        dest.parentFile.mkdirs()
+        writer = new PrintWriter(dest.newWriter(appendToFile), false)
+        appendToFile = true
+        this
+    }
+
+    def println(String line) {
+        if (writer) {
+            writer.println(line)
+        }
+    }
+
+    def end() {
+        if (!writer) {
+            throw new RuntimeException("$displayName was not started.")
+        }
+        close()
+    }
+
+    def close() {
+        if (writer) {
+            writer.close()
+        }
+        writer = null
+    }
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/SampleElementLocationHandler.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/SampleElementLocationHandler.groovy
new file mode 100644
index 0000000..6b89325
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/SampleElementLocationHandler.groovy
@@ -0,0 +1,49 @@
+package org.gradle.build.docs
+
+import org.w3c.dom.Element
+import org.w3c.dom.Document
+
+/**
+ * This class is used by the UserGuideTransformTask to inject the example location into
+ * the first figure or example child of a sample block.
+ * <p>
+ * I would have included this as an inner class of UserGuideTransformTask but groovy doesn't 
+ * support them.
+ */
+class SampleElementLocationHandler {
+    
+    private Document doc
+    private Element sampleElement
+    private String srcDir
+    private boolean includeLocation = false
+    private boolean locationIncluded = false
+
+    def SampleElementLocationHandler(Document doc, Element sampleElement, String srcDir) {
+        this.doc = doc
+        this.srcDir = srcDir
+        this.includeLocation = Boolean.valueOf(sampleElement.'@includeLocation')
+    }
+
+    public void processSampleLocation(Element child) {
+        
+        if (includeLocation && !locationIncluded) {
+            Element tipElement = doc.createElement('tip')
+            tipElement.setAttribute('role', 'exampleLocation')
+            Element textElement = doc.createElement('para')
+            tipElement.appendChild(textElement)
+            Element emphasisElement = doc.createElement('emphasis')
+            textElement.appendChild(emphasisElement)
+            emphasisElement.appendChild(doc.createTextNode('Note:'))
+            textElement.appendChild(doc.createTextNode(' The code for this example can be found at '))
+            Element filenameElement = doc.createElement('filename')
+            textElement.appendChild(filenameElement)
+            textElement.appendChild(doc.createTextNode(' which is in both the binary and source distributions of Gradle.'))
+            filenameElement.appendChild(doc.createTextNode("samples/$srcDir"))
+
+            child.appendChild(tipElement)
+
+            locationIncluded = true
+        }
+    }
+
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/SampleElementValidator.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/SampleElementValidator.groovy
new file mode 100644
index 0000000..6f78685
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/SampleElementValidator.groovy
@@ -0,0 +1,45 @@
+package org.gradle.build.docs
+
+import org.w3c.dom.Element
+
+/**
+ * Validates sample element - looks for missing attributes.
+ * 
+ * @author Tomek Kaczanowski
+ */
+public class SampleElementValidator {
+
+	void validate(Element element) {
+        String id = element.'@id'
+        if (!id) {
+            throw new RuntimeException("No id attribute specified for sample.")
+        }
+        String srcDir = element.'@dir'
+        if (!srcDir) {
+            throw new RuntimeException("No dir attribute specified for sample '$id'.")
+        }
+        String title = element.'@title' 
+        if (!title) {
+        	throw new RuntimeException("No title attribute specified for sample '$id'.")
+        }
+        element.children().each {Element child ->
+        	if (child.name() == 'sourcefile') {
+        		if (!child.'@file') {
+        			throw new RuntimeException("No file attribute specified for source file in sample '$id'.")
+        		}
+
+        	} else if (child.name() == 'output' || child.name() == 'test') {
+          	  	if (child.'@args' == null) {
+          		  throw new RuntimeException("No args attribute specified for output for sample '$id'.")
+          	  	}
+        	} else if (child.name() == 'layout') {
+        		// nothing, makes no sense to do the validation here, cause I'd have to copy all the logic also
+        	}
+        	else {
+                throw new RuntimeException("Unrecognised sample type ${child.name()} found.")
+            }
+        	
+        } 
+	}
+                        
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/docs/UserGuideTransformTask.groovy b/buildSrc/src/main/groovy/org/gradle/build/docs/UserGuideTransformTask.groovy
new file mode 100644
index 0000000..284f18b
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/docs/UserGuideTransformTask.groovy
@@ -0,0 +1,290 @@
+/*
+ * 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.docs
+
+import groovy.xml.MarkupBuilder
+import groovy.xml.dom.DOMCategory
+import groovy.xml.dom.DOMUtil
+import javax.xml.parsers.DocumentBuilder
+import javax.xml.parsers.DocumentBuilderFactory
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.FileCollection
+import org.w3c.dom.Document
+import org.w3c.dom.Element
+import org.w3c.dom.Node
+import org.gradle.api.tasks.*
+
+/**
+ * Transforms userguide source into docbook, replacing custom xml elements.
+ */
+public class UserGuideTransformTask extends DefaultTask {
+    @Input
+    String getVersion() { return project.version.toString() }
+    @Input
+    String javadocUrl
+    @Input
+    String groovydocUrl
+    @Input
+    String websiteUrl
+    @InputFile
+    File sourceFile
+    @OutputFile
+    File destFile
+    @InputDirectory
+    File snippetsDir
+    @Input
+    Set<String> tags = new HashSet()
+    @InputFiles
+    FileCollection classpath;
+
+    final SampleElementValidator validator = new SampleElementValidator();
+
+    @TaskAction
+    def transform() {
+        Element root
+
+        destFile.parentFile.mkdirs()
+
+        System.setProperty("org.apache.xerces.xni.parser.XMLParserConfiguration",
+                "org.apache.xerces.parsers.XIncludeParserConfiguration")
+
+        // Set the thread context classloader to pick up the correct XML parser
+        def uris = classpath.getFiles().collect {it.toURI().toURL()}
+        def classloader = new URLClassLoader(uris as URL[], getClass().classLoader)
+        def oldClassloader = Thread.currentThread().getContextClassLoader()
+        Thread.currentThread().setContextClassLoader(classloader)
+        try {
+
+            root = loadAndTransform()
+
+        } finally {
+            Thread.currentThread().setContextClassLoader(oldClassloader)
+        }
+
+        destFile.parentFile.mkdirs()
+        destFile.withOutputStream {OutputStream stream ->
+            DOMUtil.serialize(root, stream)
+        }
+    }
+
+    private Element loadAndTransform() {
+        Document doc = parseSourceFile()
+        Element root = doc.documentElement
+        use(DOMCategory) {
+            addVersionInfo(doc)
+            applyConditionalChunks(doc)
+            transformSamples(doc)
+            transformApiLinks(doc)
+            transformWebsiteLinks(doc)
+            fixProgramListings(doc)
+        }
+
+        return root
+    }
+
+    def addVersionInfo(Document doc) {
+        Element releaseInfo = doc.createElement('releaseinfo')
+        releaseInfo.appendChild(doc.createTextNode(version.toString()))
+        if (doc.documentElement.bookinfo[0]) {
+            doc.documentElement.bookinfo[0].appendChild(releaseInfo)
+        }
+    }
+
+    def fixProgramListings(Document doc) {
+        doc.documentElement.depthFirst().findAll { it.name() == 'programlisting' || it.name() == 'screen' }.each {Element element ->
+            element.setTextContent(normalise(element.getTextContent()))
+        }
+    }
+
+    String normalise(String content) {
+        return content.trim().replace('\t', '    ').replace('\r\n', '\n')
+    }
+
+    def transformApiLinks(Document doc) {
+        File linksFile = new File(destFile.parentFile, 'links.xml')
+        linksFile.withWriter {Writer writer ->
+            MarkupBuilder xml = new MarkupBuilder(writer)
+            xml.links {
+                doc.documentElement.depthFirst().findAll { it.name() == 'apilink' }.each {Element element ->
+                    String className = element.'@class'
+                    String methodName = element.'@method'
+                    String lang = element.'@lang' ?: 'java'
+
+                    Element ulinkElement = doc.createElement('ulink')
+                    String baseUrl = lang == 'groovy' ? groovydocUrl : javadocUrl
+                    String href = "$baseUrl/${className.replace('.', '/')}.html"
+                    ulinkElement.setAttribute('url', href)
+
+                    Element classNameElement = doc.createElement('classname')
+                    ulinkElement.appendChild(classNameElement)
+
+                    String classBaseName = className.tokenize('.').last()
+                    String linkText = methodName ? "$classBaseName.$methodName()" : classBaseName
+                    classNameElement.appendChild(doc.createTextNode(linkText))
+
+                    element.parentNode.replaceChild(ulinkElement, element)
+
+                    xml.link(className: className, lang: lang)
+                }
+            }
+        }
+    }
+
+    def transformWebsiteLinks(Document doc) {
+        doc.documentElement.depthFirst().findAll { it.name() == 'ulink' }.each {Element element ->
+            String url = element.'@url'
+            if (url.startsWith('website:')) {
+                url = url.substring(8)
+                if (websiteUrl) {
+                    url = "${websiteUrl}/${url}"
+                }
+                element.setAttribute('url', url)
+            }
+        }
+    }
+
+    def transformSamples(Document doc) {
+        File samplesFile = new File(destFile.parentFile, 'samples.xml')
+        samplesFile.withWriter {Writer writer ->
+            MarkupBuilder xml = new MarkupBuilder(writer)
+            xml.samples {
+                doc.documentElement.depthFirst().findAll { it.name() == 'sample' }.each {Element element ->
+                	validator.validate(element)
+                    String sampleId = element.'@id'
+                    String srcDir = element.'@dir'
+
+                    // This class handles the responsibility of adding the location tips to the first child of first
+                    // example defined in the sample.
+                    SampleElementLocationHandler locationHandler = new SampleElementLocationHandler(doc, element, srcDir)
+                   
+                    xml.sample(id: sampleId, dir: srcDir)
+
+                    String title = element.'@title' 
+
+                    Element exampleElement = doc.createElement('example')
+                    exampleElement.setAttribute('id', sampleId)
+                    Element titleElement = doc.createElement('title')
+               	   	titleElement.appendChild(doc.createTextNode(title))
+               	   	exampleElement.appendChild(titleElement);
+                    
+                    element.children().each {Element child ->
+                        if (child.name() == 'sourcefile') {
+                            String file = child.'@file'
+                           
+                            Element sourcefileTitle = doc.createElement("para")
+                    		Element commandElement = doc.createElement('filename')
+                        	commandElement.appendChild(doc.createTextNode(file))
+                        	sourcefileTitle.appendChild(commandElement)
+                        	exampleElement.appendChild(sourcefileTitle);
+
+                            Element programListingElement = doc.createElement('programlisting')
+                            if (file.endsWith('.gradle') || file.endsWith('.groovy')) {
+                                programListingElement.setAttribute('language', 'java')
+                            }
+                            else if (file.endsWith('.xml')) {
+                                programListingElement.setAttribute('language', 'xml')
+                            }
+                            File srcFile
+                            String snippet = child.'@snippet'
+                            if (snippet) {
+                                srcFile = new File(snippetsDir, "$srcDir/$file-$snippet")
+                            } else {
+                                srcFile = new File(snippetsDir, "$srcDir/$file")
+                            }
+                            programListingElement.appendChild(doc.createTextNode(normalise(srcFile.text)))
+                            exampleElement.appendChild(programListingElement)
+                        } else if (child.name() == 'output') {
+                            String args = child.'@args'
+                            String outputFile = child.'@outputFile' ?: "${sampleId}.out"
+                            boolean ignoreExtraLines = child.'@ignoreExtraLines' ?: false
+
+                            xml.sample(id: sampleId, dir: srcDir, args: args, outputFile: outputFile, ignoreExtraLines: ignoreExtraLines)
+
+                            Element outputTitle = doc.createElement("para")
+                            outputTitle.appendChild(doc.createTextNode("Output of "))
+                            Element commandElement = doc.createElement('userinput')
+                            commandElement.appendChild(doc.createTextNode("gradle $args"))
+                            outputTitle.appendChild(commandElement)
+                            exampleElement.appendChild(outputTitle)
+
+                            Element screenElement = doc.createElement('screen')
+                            File srcFile = new File(sourceFile.parentFile, "../../../src/samples/userguideOutput/${outputFile}")
+                            screenElement.appendChild(doc.createTextNode("> gradle $args\n" + normalise(srcFile.text)))
+                            exampleElement.appendChild(screenElement)
+                        } else if (child.name() == 'test') {
+                            String args = child.'@args'
+
+                            xml.sample(id: sampleId, dir: srcDir, args: args)
+                        } else if (child.name() == 'layout') {
+                        	Element outputTitle = doc.createElement("para")
+                    		outputTitle.appendChild(doc.createTextNode("Build layout"))
+                    		exampleElement.appendChild(outputTitle)
+
+                            Element programListingElement = doc.createElement('programlisting')
+                            exampleElement.appendChild(programListingElement)
+                            StringBuilder content = new StringBuilder()
+                            content.append("${srcDir.tokenize('/').last()}/\n")
+                            List stack = []
+                            child.text().eachLine {
+                                def fileName = it.trim()
+                                if (!fileName) {
+                                    return
+                                }
+                                File file = new File(snippetsDir, "$srcDir/$fileName")
+                                if (!file.exists()) {
+                                    throw new RuntimeException("Sample file $file does not exist for sample ${sampleId}.")
+                                }
+                                List context = fileName.tokenize('/')
+
+                                int common = 0;
+                                for (;common < stack.size() && common < context.size() && stack[common] == context[common]; common++) { ; }
+                                stack = stack.subList(0, common)
+
+                                (stack.size() + 1).times { content.append("  ") }
+                                content.append("${context.subList(stack.size(), context.size()).join('/')}${file.directory ? '/' : ''}\n")
+                                if (file.directory) {
+                                    stack = context
+                                }
+                            }
+                            programListingElement.appendChild(doc.createTextNode(content.toString()))
+                        }
+
+                        locationHandler.processSampleLocation(exampleElement)
+                    }
+                    element.parentNode.insertBefore(exampleElement, element)
+                    element.parentNode.removeChild(element)
+                }
+            }
+        }
+    }
+
+    void applyConditionalChunks(Document doc) {
+        doc.documentElement.depthFirst().findAll { it.'@condition' }.each {Element element ->
+            if (!tags.contains(element.'@condition')) {
+                element.parentNode.removeChild(element)
+            }
+        }
+    }
+
+    private Document parseSourceFile() {
+        DocumentBuilderFactory factory = Class.forName('com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl', true, Thread.currentThread().contextClassLoader).newInstance()
+        factory.setNamespaceAware(true)
+        DocumentBuilder builder = factory.newDocumentBuilder()
+        return builder.parse(sourceFile)
+    }
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/samples/WrapperProjectCreator.groovy b/buildSrc/src/main/groovy/org/gradle/build/samples/WrapperProjectCreator.groovy
new file mode 100644
index 0000000..fb88d73
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/samples/WrapperProjectCreator.groovy
@@ -0,0 +1,52 @@
+/*
+ * 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.build.samples
+
+import org.gradle.api.tasks.wrapper.Wrapper
+
+/**
+ * @author Hans Dockter
+ */
+class WrapperProjectCreator {
+    final static String WRAPPER_PROJECT_NAME = 'wrapper-project'
+    final static String WRAPPER_TASK_NAME = 'wrapper'
+    final static String TEST_TASK_NAME = 'hello'
+    final static String TEST_TASK_OUTPUT = 'hello'
+
+    static void createProject(File baseDir, File downloadUrlRoot, String gradleVersion) {
+        String gradleScript = """
+createTask('$WRAPPER_TASK_NAME', type: $Wrapper.name).configure {
+    gradleVersion = '$gradleVersion'
+    urlRoot = '${downloadUrlRoot.toURI().toURL()}'
+    zipBase = Wrapper.PathBase.PROJECT
+    zipPath = 'wrapper'
+    archiveBase = Wrapper.PathBase.PROJECT
+    archivePath = 'dist'
+    distributionBase = Wrapper.PathBase.PROJECT
+    distributionPath = 'dist'
+}
+
+createTask('$TEST_TASK_NAME') {
+    println '$TEST_TASK_OUTPUT'
+}
+"""
+        File wrapperRoot = new File(baseDir, WRAPPER_PROJECT_NAME)
+        wrapperRoot.mkdirs()
+        new File(wrapperRoot, "build.gradle").withPrintWriter {PrintWriter writer -> writer.write(gradleScript)}
+    }
+
+}
diff --git a/buildSrc/src/main/groovy/org/gradle/build/startscripts/StartScriptGenerator.groovy b/buildSrc/src/main/groovy/org/gradle/build/startscripts/StartScriptGenerator.groovy
new file mode 100644
index 0000000..939afee
--- /dev/null
+++ b/buildSrc/src/main/groovy/org/gradle/build/startscripts/StartScriptGenerator.groovy
@@ -0,0 +1,59 @@
+/*
+ * 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.build.startscripts
+
+/**
+ * @author Hans Dockter
+ */
+class StartScriptsGenerator {
+    static void generate(String gradleJarName, File binDir, String projectName) {
+        String unixStartScriptHead = StartScriptsGenerator.getResourceAsStream('unixStartScriptHead.txt').text
+        String unixStartScriptTail = StartScriptsGenerator.getResourceAsStream('unixStartScriptTail.txt').text
+        String windowsStartScriptHead = StartScriptsGenerator.getResourceAsStream('windowsStartScriptHead.txt').text
+        String windowsStartScriptTail = StartScriptsGenerator.getResourceAsStream('windowsStartScriptTail.txt').text
+
+        String gradleHome = 'GRADLE_HOME'
+
+        String unixLibPath = "\$$gradleHome/lib/$gradleJarName"
+        String windowsLibPath = "%$gradleHome%\\lib\\$gradleJarName"
+
+        def unixScript = "$unixStartScriptHead\nCLASSPATH=$unixLibPath\n$unixStartScriptTail"
+        def windowsScript = "$windowsStartScriptHead\nset CLASSPATH=$windowsLibPath\n$windowsStartScriptTail"
+
+        new File(binDir, projectName).withWriter {writer ->
+            writer.write(unixScript)
+        }
+
+        new File(binDir, projectName + ".bat").withWriter {writer ->
+            writer.write(transformIntoWindowsNewLines(windowsScript))
+        }
+    }
+
+    static String transformIntoWindowsNewLines(String s) {
+        StringWriter writer = new StringWriter()
+        s.toCharArray().each {c ->
+            if (c == '\n') {
+                writer.write('\r')
+                writer.write('\n')
+            } else if (c != '\r') {
+                writer.write(c);
+            }
+        }
+        writer.toString()
+    }
+
+}
+
diff --git a/buildSrc/src/main/resources/org/gradle/build/startscripts/unixStartScriptHead.txt b/buildSrc/src/main/resources/org/gradle/build/startscripts/unixStartScriptHead.txt
new file mode 100644
index 0000000..0a316bc
--- /dev/null
+++ b/buildSrc/src/main/resources/org/gradle/build/startscripts/unixStartScriptHead.txt
@@ -0,0 +1,87 @@
+#!/bin/bash
+
+##############################################################################
+##                                                                          ##
+##  Gradle start up script for UN*X                                         ##
+##                                                                          ##
+##############################################################################
+
+# Uncomment those lines to set JVM options. GRADLE_OPTS and JAVA_OPTS can be used together.
+# GRADLE_OPTS="$GRADLE_OPTS -Xmx512"
+# JAVA_OPTS="$JAVA_OPTS -Xmx512"
+
+GRADLE_APP_NAME=Gradle
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "${PROGNAME}: $*"
+}
+
+die ( ) {
+    warn "$*"
+    exit 1
+}
+
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set JAVA_HOME if it's not already set.
+if [ -z "$JAVA_HOME" ] ; then
+    if $darwin ; then
+        [ -z "$JAVA_HOME" -a -d "/Library/Java/Home" ] && export JAVA_HOME="/Library/Java/Home"
+        [ -z "$JAVA_HOME" -a -d "/System/Library/Frameworks/JavaVM.framework/Home" ] && export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Home"
+    else
+        javaExecutable="`which javac`"
+        [ -z "$javaExecutable" -o "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ] && die "JAVA_HOME not set and cannot find javac to deduce location, please set JAVA_HOME."
+        # readlink(1) is not available as standard on Solaris 10.
+        readLink=`which readlink`
+        [ `expr "$readLink" : '\([^ ]*\)'` = "no" ] && die "JAVA_HOME not set and readlink not available, please set JAVA_HOME."
+        javaExecutable="`readlink -f \"$javaExecutable\"`"
+        javaHome="`dirname \"$javaExecutable\"`"
+        javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+        export JAVA_HOME="$javaHome"
+    fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+    [ -n "$GRADLE_HOME" ] && GRADLE_HOME=`cygpath --unix "$GRADLE_HOME"`
+    [ -n "$JAVACMD" ] && JAVACMD=`cygpath --unix "$JAVACMD"`
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set GRADLE_HOME if it is not already set.
+if [ -z "$GRADLE_HOME" -o ! -d "$GRADLE_HOME" ] ; then
+    # Resolve links: $0 may be a link to groovy's home.
+    PRG="$0"
+    # Need this for relative symlinks.
+    while [ -h "$PRG" ] ; do
+        ls=`ls -ld "$PRG"`
+        link=`expr "$ls" : '.*-> \(.*\)$'`
+        if expr "$link" : '/.*' > /dev/null; then
+            PRG="$link"
+        else
+            PRG=`dirname "$PRG"`"/$link"
+        fi
+    done
+    SAVED="`pwd`"
+    cd "`dirname \"$PRG\"`/.."
+    GRADLE_HOME="`pwd -P`"
+    cd "$SAVED"
+fi
diff --git a/buildSrc/src/main/resources/org/gradle/build/startscripts/unixStartScriptTail.txt b/buildSrc/src/main/resources/org/gradle/build/startscripts/unixStartScriptTail.txt
new file mode 100644
index 0000000..6e4ef0e
--- /dev/null
+++ b/buildSrc/src/main/resources/org/gradle/build/startscripts/unixStartScriptTail.txt
@@ -0,0 +1,110 @@
+# Determine the Java command to use to start the JVM.
+if [ -z "$JAVACMD" ] ; then
+    if [ -n "$JAVA_HOME" ] ; then
+        if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+            # IBM's JDK on AIX uses strange locations for the executables
+            JAVACMD="$JAVA_HOME/jre/sh/java"
+        else
+            JAVACMD="$JAVA_HOME/bin/java"
+        fi
+    else
+        JAVACMD="java"
+    fi
+fi
+if [ ! -x "$JAVACMD" ] ; then
+    die "JAVA_HOME is not defined correctly, can not execute: $JAVACMD"
+fi
+if [ -z "$JAVA_HOME" ] ; then
+    warn "JAVA_HOME environment variable is not set"
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; 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"
+    fi
+fi
+
+# Setup Profiler
+useprofiler=false
+if [ "$PROFILER" != "" ] ; then
+    if [ -r "$PROFILER" ] ; then
+        . $PROFILER
+        useprofiler=true
+    else
+        die "Profiler file not found: $PROFILER"
+    fi
+fi
+
+# For Darwin, add GRADLE_APP_NAME to the JAVA_OPTS as -Xdock:name
+if $darwin; then
+    JAVA_OPTS="$JAVA_OPTS -Xdock:name=$GRADLE_APP_NAME"
+# we may also want to set -Xdock:image
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    GRADLE_HOME=`cygpath --path --mixed "$GRADLE_HOME"`
+    JAVA_HOME=`cygpath --path --mixed "$JAVA_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GROOVY_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GROOVY_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done 
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+
+fi
+
+STARTER_MAIN_CLASS=org.gradle.launcher.GradleMain
+
+# Start the Profiler or the JVM
+if $useprofiler ; then
+    runProfiler
+else
+    exec "$JAVACMD" $JAVA_OPTS $GRADLE_OPTS \
+        -classpath "$CLASSPATH" \
+        $STARTER_MAIN_CLASS \
+        "$@"
+fi
diff --git a/buildSrc/src/main/resources/org/gradle/build/startscripts/windowsStartScriptHead.txt b/buildSrc/src/main/resources/org/gradle/build/startscripts/windowsStartScriptHead.txt
new file mode 100644
index 0000000..d8bcd5c
--- /dev/null
+++ b/buildSrc/src/main/resources/org/gradle/build/startscripts/windowsStartScriptHead.txt
@@ -0,0 +1,107 @@
+ at if "%DEBUG%" == "" @echo off
+ at rem ##########################################################################
+ at rem                                                                         ##
+ at rem  Gradle startup script for Windows                                      ##
+ at rem                                                                         ##
+ at rem ##########################################################################
+
+ at rem
+ at rem $Revision: 10602 $ $Date: 2008-01-25 02:49:54 +0100 (ven., 25 janv. 2008) $
+ at rem
+
+ at rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+ at rem Uncomment those lines to set JVM options. GRADLE_OPTS and JAVA_OPTS can be used together.
+ at rem set GRADLE_OPTS=%GRADLE_OPTS% -Xmx512
+ at rem set JAVA_OPTS=%JAVA_OPTS% -Xmx512
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.\
+
+ at rem Determine the command interpreter to execute the "CD" later
+set COMMAND_COM="cmd.exe"
+if exist "%SystemRoot%\system32\cmd.exe" set COMMAND_COM="%SystemRoot%\system32\cmd.exe"
+if exist "%SystemRoot%\command.com" set COMMAND_COM="%SystemRoot%\command.com"
+
+ at rem Use explicit find.exe to prevent cygwin and others find.exe from being used
+set FIND_EXE="find.exe"
+if exist "%SystemRoot%\system32\find.exe" set FIND_EXE="%SystemRoot%\system32\find.exe"
+if exist "%SystemRoot%\command\find.exe" set FIND_EXE="%SystemRoot%\command\find.exe"
+
+:check_JAVA_HOME
+ at rem Make sure we have a valid JAVA_HOME
+if not "%JAVA_HOME%" == "" goto have_JAVA_HOME
+
+echo.
+echo ERROR: Environment variable JAVA_HOME has not been set.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+echo.
+goto end
+
+:have_JAVA_HOME
+ at rem Validate JAVA_HOME
+%COMMAND_COM% /C DIR "%JAVA_HOME%" 2>&1 | %FIND_EXE% /I /C "%JAVA_HOME%" >nul
+if not errorlevel 1 goto check_GRADLE_HOME
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+echo.
+goto end
+
+:check_GRADLE_HOME
+ at rem Define GRADLE_HOME if not set
+if "%GRADLE_HOME%" == "" set GRADLE_HOME=%DIRNAME%..
+
+:init
+ at rem get name of script to launch with full path
+ at rem /// set GROOVY_SCRIPT_NAME=%~f1
+ at rem Get command-line arguments, handling Windowz variants
+
+SET _marker=%JAVA_HOME: =%
+ at rem IF NOT "%_marker%" == "%JAVA_HOME%" ECHO JAVA_HOME "%JAVA_HOME%" contains spaces. Please change to a location without spaces if this causes problems.
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%eval[2+2]" == "4" goto 4NT_args
+
+IF "%_marker%" == "%JAVA_HOME%" goto :win9xME_args
+
+set _FIXPATH=
+call :fixpath "%JAVA_HOME%"
+set JAVA_HOME=%_FIXPATH:~1%
+
+goto win9xME_args
+
+:fixpath
+if not %1.==. (
+for /f "tokens=1* delims=;" %%a in (%1) do (
+call :shortfilename "%%a" & call :fixpath "%%b"
+)
+)
+goto :EOF
+:shortfilename
+for %%i in (%1) do set _FIXPATH=%_FIXPATH%;%%~fsi
+goto :EOF
+
+:win9xME_args
+ at rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+ at rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+ at rem Setup the command line
diff --git a/buildSrc/src/main/resources/org/gradle/build/startscripts/windowsStartScriptTail.txt b/buildSrc/src/main/resources/org/gradle/build/startscripts/windowsStartScriptTail.txt
new file mode 100644
index 0000000..09d9983
--- /dev/null
+++ b/buildSrc/src/main/resources/org/gradle/build/startscripts/windowsStartScriptTail.txt
@@ -0,0 +1,25 @@
+set STARTER_MAIN_CLASS=org.gradle.launcher.GradleMain
+
+set JAVA_EXE=%JAVA_HOME%\bin\java.exe
+
+set GRADLE_OPTS=%JAVA_OPTS% %GRADLE_OPTS%
+set GRADLE_OPTS=%GRADLE_OPTS%
+
+ at rem Execute Gradle
+"%JAVA_EXE%" %GRADLE_OPTS% -classpath "%CLASSPATH%" %STARTER_MAIN_CLASS% %CMD_LINE_ARGS%
+
+:end
+ at rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+if not "%OS%"=="Windows_NT" echo 1 > nul | choice /n /c:1
+
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit "%ERRORLEVEL%"
+exit /b "%ERRORLEVEL%"
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
\ No newline at end of file
diff --git a/config/checkstyle/checkstyle-groovy.xml b/config/checkstyle/checkstyle-groovy.xml
new file mode 100644
index 0000000..321abae
--- /dev/null
+++ b/config/checkstyle/checkstyle-groovy.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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.
+  -->
+<!DOCTYPE module PUBLIC
+        "-//Puppy Crawl//DTD Check Configuration 1.2//EN"
+        "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
+<module name="Checker">
+    <module name="SuppressionFilter">
+        <property name="file" value="${checkstyleConfigDir}/suppressions.xml"/>
+    </module>
+    <module name="RegexpHeader">
+        <property name="headerFile" value="${checkstyleConfigDir}/required-header.txt"/>
+    </module>
+</module>
\ No newline at end of file
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000..a686796
--- /dev/null
+++ b/config/checkstyle/checkstyle.xml
@@ -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.
+  -->
+<!DOCTYPE module PUBLIC
+        "-//Puppy Crawl//DTD Check Configuration 1.2//EN"
+        "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
+<module name="Checker">
+    <module name="SuppressionFilter">
+        <property name="file" value="${checkstyleConfigDir}/suppressions.xml"/>
+    </module>
+    <module name="TreeWalker">
+        <!-- Blocks -->
+        <module name="EmptyBlock">
+            <property name="option" value="stmt"/>
+            <property name="tokens" value="LITERAL_DO,LITERAL_ELSE,LITERAL_FINALLY,LITERAL_IF,LITERAL_FOR,LITERAL_TRY,LITERAL_WHILE,INSTANCE_INIT,STATIC_INIT"/>
+        </module>
+        <module name="EmptyBlock">
+            <property name="option" value="text"/>
+            <property name="tokens" value="LITERAL_CATCH"/>
+        </module>
+
+        <!-- Braces -->
+        <module name="NeedBraces"/>
+
+        <!-- Coding -->
+        <module name="EmptyStatement"/>
+        <module name="EqualsHashCode"/>
+        <module name="ExplicitInitialization"/>
+        <module name="UnnecessaryParentheses"/>
+
+        <!-- Design -->
+        <module name="InterfaceIsType"/>
+
+        <!-- Imports -->
+        <module name="RedundantImport"/>
+        <module name="UnusedImports"/>
+
+        <!--<module name="JavadocMethod">-->
+        <!--<property name="scope" value="public"/>-->
+        <!--<property name="allowMissingReturnTag" value="true"/>-->
+        <!--</module>-->
+
+        <!-- Naming -->
+        <module name="ClassTypeParameterName"/>
+        <module name="ConstantName"/>
+        <module name="LocalFinalVariableName"/>
+        <module name="LocalVariableName"/>
+        <module name="MemberName"/>
+        <module name="MethodName"/>
+        <module name="MethodTypeParameterName"/>
+        <module name="PackageName">
+            <property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
+        </module>
+        <module name="ParameterName"/>
+        <module name="StaticVariableName"/>
+        <module name="TypeName"/>
+    </module>
+    <module name="RegexpHeader">
+        <property name="headerFile" value="${checkstyleConfigDir}/required-header.txt"/>
+    </module>
+</module>
\ No newline at end of file
diff --git a/config/checkstyle/required-header.txt b/config/checkstyle/required-header.txt
new file mode 100644
index 0000000..5c9e925
--- /dev/null
+++ b/config/checkstyle/required-header.txt
@@ -0,0 +1,15 @@
+^/\*$
+^ \* Copyright \d\d\d\d((\s*-\s*\d\d\d\d)|(,\s*\d\d\d\d)+)? the original author or authors.$
+^ \*$
+^ \* Licensed under the Apache License, Version 2\.0 \(the "License"\);$
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
\ No newline at end of file
diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml
new file mode 100644
index 0000000..0e3707c
--- /dev/null
+++ b/config/checkstyle/suppressions.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+
+<!DOCTYPE suppressions PUBLIC
+    "-//Puppy Crawl//DTD Suppressions 1.1//EN"
+    "http://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
+
+<suppressions>
+    <suppress checks="JavadocMethod"
+              files=".*/org/gradle/api/internal/.*"/>
+</suppressions>
\ No newline at end of file
diff --git a/config/codenarc.xml b/config/codenarc.xml
new file mode 100644
index 0000000..bc0586c
--- /dev/null
+++ b/config/codenarc.xml
@@ -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.
+  -->
+<ruleset xmlns="http://codenarc.org/ruleset/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://codenarc.org/ruleset/1.0 http://codenarc.org/ruleset-schema.xsd"
+         xsi:noNamespaceSchemaLocation="http://codenarc.org/ruleset-schema.xsd">
+    <ruleset-ref path='rulesets/braces.xml'/>
+    <ruleset-ref path='rulesets/imports.xml'/>
+    <ruleset-ref path='rulesets/naming.xml'>
+        <rule-config name='ClassName'>
+            <property name='regex' value='^[A-Z][a-zA-Z0-9]*$'/>
+        </rule-config>
+        <rule-config name='FieldName'>
+            <property name='finalRegex' value='^[a-z][a-zA-Z0-9]*$'/>
+            <property name='staticFinalRegex' value='^[A-Z][A-Z_0-9]*$'/>
+        </rule-config>
+        <rule-config name='MethodName'>
+            <property name='regex' value='^[a-z][a-zA-Z0-9_]*$'/>
+        </rule-config>
+        <rule-config name='VariableName'>
+            <property name='finalRegex' value='^[a-z][a-zA-Z0-9]*$'/>
+        </rule-config>
+    </ruleset-ref>
+</ruleset>
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..9eaa01d
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,4 @@
+#properties
+#Mon Nov 16 07:56:40 EST 2009
+previousVersion=0.9-preview-3
+nextVersion=0.9-rc-1
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..3e1b1cd
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,140 @@
+#!/bin/bash
+
+##############################################################################
+##                                                                          ##
+##  Gradle wrapper script for UN*X                                         ##
+##                                                                          ##
+##############################################################################
+
+# Uncomment those lines to set JVM options. GRADLE_OPTS and JAVA_OPTS can be used together.
+# GRADLE_OPTS="$GRADLE_OPTS -Xmx512"
+# JAVA_OPTS="$JAVA_OPTS -Xmx512"
+
+warn ( ) {
+    echo "${PROGNAME}: $*"
+}
+
+die ( ) {
+    warn "$*"
+    exit 1
+}
+
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set JAVA_HOME if it's not already set.
+if [ -z "$JAVA_HOME" ] ; then
+    if $darwin ; then
+        [ -z "$JAVA_HOME" -a -d "/Library/Java/Home" ] && export JAVA_HOME="/Library/Java/Home"
+        [ -z "$JAVA_HOME" -a -d "/System/Library/Frameworks/JavaVM.framework/Home" ] && export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Home"
+    else
+        javaExecutable="`which javac`"
+        [ -z "$javaExecutable" -o "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ] && die "JAVA_HOME not set and cannot find javac to deduce location, please set JAVA_HOME."
+        # readlink(1) is not available as standard on Solaris 10.
+        readLink=`which readlink`
+        [ `expr "$readLink" : '\([^ ]*\)'` = "no" ] && die "JAVA_HOME not set and readlink not available, please set JAVA_HOME."
+        javaExecutable="`readlink -f \"$javaExecutable\"`"
+        javaHome="`dirname \"$javaExecutable\"`"
+        javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+        export JAVA_HOME="$javaHome"
+    fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+    [ -n "$JAVACMD" ] && JAVACMD=`cygpath --unix "$JAVACMD"`
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+STARTER_MAIN_CLASS=org.gradle.wrapper.GradleWrapperMain
+CLASSPATH=`dirname "$0"`/wrapper/gradle-wrapper.jar
+WRAPPER_PROPERTIES=`dirname "$0"`/wrapper/gradle-wrapper.properties
+# Determine the Java command to use to start the JVM.
+if [ -z "$JAVACMD" ] ; then
+    if [ -n "$JAVA_HOME" ] ; then
+        if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+            # IBM's JDK on AIX uses strange locations for the executables
+            JAVACMD="$JAVA_HOME/jre/sh/java"
+        else
+            JAVACMD="$JAVA_HOME/bin/java"
+        fi
+    else
+        JAVACMD="java"
+    fi
+fi
+if [ ! -x "$JAVACMD" ] ; then
+    die "JAVA_HOME is not defined correctly, can not execute: $JAVACMD"
+fi
+if [ -z "$JAVA_HOME" ] ; then
+    warn "JAVA_HOME environment variable is not set"
+fi
+
+# For Darwin, add GRADLE_APP_NAME to the JAVA_OPTS as -Xdock:name
+if $darwin; then
+    JAVA_OPTS="$JAVA_OPTS -Xdock:name=$GRADLE_APP_NAME"
+# we may also want to set -Xdock:image
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    JAVA_HOME=`cygpath --path --mixed "$JAVA_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done 
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+"$JAVACMD" $JAVA_OPTS $GRADLE_OPTS \
+        -classpath "$CLASSPATH" \
+        -Dorg.gradle.wrapper.properties="$WRAPPER_PROPERTIES" \
+        $STARTER_MAIN_CLASS \
+        "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..644b918
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,126 @@
+ at if "%DEBUG%" == "" @echo off
+ at rem ##########################################################################
+ at rem                                                                         ##
+ at rem  Gradle startup script for Windows                                      ##
+ at rem                                                                         ##
+ at rem ##########################################################################
+
+ at rem
+ at rem $Revision: 10602 $ $Date: 2008-01-25 02:49:54 +0100 (ven., 25 janv. 2008) $
+ at rem
+
+ at rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+ at rem Uncomment those lines to set JVM options. GRADLE_OPTS and JAVA_OPTS can be used together.
+ at rem set GRADLE_OPTS=%GRADLE_OPTS% -Xmx512
+ at rem set JAVA_OPTS=%JAVA_OPTS% -Xmx512
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.\
+
+ at rem Determine the command interpreter to execute the "CD" later
+set COMMAND_COM="cmd.exe"
+if exist "%SystemRoot%\system32\cmd.exe" set COMMAND_COM="%SystemRoot%\system32\cmd.exe"
+if exist "%SystemRoot%\command.com" set COMMAND_COM="%SystemRoot%\command.com"
+
+ at rem Use explicit find.exe to prevent cygwin and others find.exe from being used
+set FIND_EXE="find.exe"
+if exist "%SystemRoot%\system32\find.exe" set FIND_EXE="%SystemRoot%\system32\find.exe"
+if exist "%SystemRoot%\command\find.exe" set FIND_EXE="%SystemRoot%\command\find.exe"
+
+:check_JAVA_HOME
+ at rem Make sure we have a valid JAVA_HOME
+if not "%JAVA_HOME%" == "" goto have_JAVA_HOME
+
+echo.
+echo ERROR: Environment variable JAVA_HOME has not been set.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+echo.
+goto end
+
+:have_JAVA_HOME
+ at rem Validate JAVA_HOME
+%COMMAND_COM% /C DIR "%JAVA_HOME%" 2>&1 | %FIND_EXE% /I /C "%JAVA_HOME%" >nul
+if not errorlevel 1 goto init
+
+echo.
+echo ERROR: JAVA_HOME might be set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation if there are problems.
+echo.
+
+:init
+ at rem get name of script to launch with full path
+ at rem Get command-line arguments, handling Windowz variants
+SET _marker=%JAVA_HOME: =%
+ at rem IF NOT "%_marker%" == "%JAVA_HOME%" ECHO JAVA_HOME "%JAVA_HOME%" contains spaces. Please change to a location without spaces if this causes problems.
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%eval[2+2]" == "4" goto 4NT_args
+
+IF "%_marker%" == "%JAVA_HOME%" goto :win9xME_args
+
+set _FIXPATH=
+call :fixpath "%JAVA_HOME%"
+set JAVA_HOME=%_FIXPATH:~1%
+
+goto win9xME_args
+
+:fixpath
+if not %1.==. (
+for /f "tokens=1* delims=;" %%a in (%1) do (
+call :shortfilename "%%a" & call :fixpath "%%b"
+)
+)
+goto :EOF
+:shortfilename
+for %%i in (%1) do set _FIXPATH=%_FIXPATH%;%%~fsi
+goto :EOF
+
+
+:win9xME_args
+ at rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+ at rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+ at rem Setup the command line
+
+set STARTER_MAIN_CLASS=org.gradle.wrapper.GradleWrapperMain
+set CLASSPATH=%DIRNAME%\wrapper\gradle-wrapper.jar
+set WRAPPER_PROPERTIES=%DIRNAME%\wrapper\gradle-wrapper.properties
+set JAVA_EXE=%JAVA_HOME%\bin\java.exe
+
+set GRADLE_OPTS=%JAVA_OPTS% %GRADLE_OPTS% -Dorg.gradle.wrapper.properties="%WRAPPER_PROPERTIES%"
+
+"%JAVA_EXE%" %GRADLE_OPTS% -classpath "%CLASSPATH%" %STARTER_MAIN_CLASS% %CMD_LINE_ARGS%
+
+:end
+ at rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+if not "%OS%"=="Windows_NT" echo 1 > nul | choice /n /c:1
+
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit "%ERRORLEVEL%"
+exit /b "%ERRORLEVEL%"
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..881e202
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+include 'core'
+include 'wrapper'
+include 'launcher'
+include 'plugins'
+include 'scala'
+include 'eclipse'
+include 'idea'
+include 'osgi'
+include 'maven'
+include 'announce'
+include 'jetty'
+include 'codeQuality'
+include 'antlr'
+include 'ui'
+include 'openApi'
+include 'docs'
+
+rootProject.name = 'gradle'
+rootProject.children.each {project ->
+    String fileBaseName = project.name.replaceAll("\\p{Upper}") { "-${it.toLowerCase()}" }
+    project.projectDir = new File(settingsDir, "subprojects/gradle-$fileBaseName")
+    project.buildFileName = "${fileBaseName}.gradle"
+    assert project.projectDir.isDirectory()
+    assert project.buildFile.isFile()
+}
diff --git a/src/toplevel/LICENSE b/src/toplevel/LICENSE
new file mode 100644
index 0000000..c9332d7
--- /dev/null
+++ b/src/toplevel/LICENSE
@@ -0,0 +1,519 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+------------------------------------------------------------------------------
+License for svnkit package
+------------------------------------------------------------------------------
+The TMate Open Source License.
+
+This license applies to all portions of TMate SVNKit library, which
+are not externally-maintained libraries (e.g. Ganymed SSH library).
+
+All the source code and compiled classes in package org.tigris.subversion.javahl
+except SvnClient class are covered by the license in JAVAHL-LICENSE file
+
+Copyright (c) 2004-2008 TMate Software. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+
+    * Redistributions in any form must be accompanied by information on how to
+      obtain complete source code for the software that uses SVNKit and any
+      accompanying software that uses the software that uses SVNKit. The source
+      code must either be included in the distribution or be available for no
+      more than the cost of distribution plus a nominal fee, and must be freely
+      redistributable under reasonable conditions. For an executable file, complete
+      source code means the source code for all modules it contains. It does not
+      include source code for modules or files that typically accompany the major
+      components of the operating system on which the executable file runs.
+
+    * Redistribution in any form without redistributing source code for software
+      that uses SVNKit is possible only when such redistribution is explictly permitted
+      by TMate Software. Please, contact TMate Software at support at svnkit.com to
+      get such permission.
+
+THIS SOFTWARE IS PROVIDED BY TMATE SOFTWARE ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT, ARE
+DISCLAIMED.
+
+IN NO EVENT SHALL TMATE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+------------------------------------------------------------------------------
+License for the logback package
+------------------------------------------------------------------------------
+Logback License
+
+
+Logback source code and binaries are distributed under the
+GNU Lesser General Public License  as published by the Free Software Foundation.
+
+Logback: the generic, reliable, fast and flexible logging library for Java.
+
+Copyright (C) 2000-2006, QOS.ch This library is free software, you can
+redistribute it and/or modify it under the terms of the GNU Lesser General Public License
+as published by the Free Software Foundation.
+
+
+------------------------------------------------------------------------------
+License for the sl4j package
+------------------------------------------------------------------------------
+SLF4J License
+
+Copyright (c) 2004-2007 QOS.ch
+All rights reserved.
+
+Permission is hereby granted, free  of charge, to any person obtaining
+a  copy  of this  software  and  associated  documentation files  (the
+"Software"), to  deal in  the Software without  restriction, including
+without limitation  the rights to  use, copy, modify,  merge, publish,
+distribute,  sublicense, and/or sell  copies of  the Software,  and to
+permit persons to whom the Software  is furnished to do so, subject to
+the following conditions:
+
+The  above  copyright  notice  and  this permission  notice  shall  be
+included in all copies or substantial portions of the Software.
+
+THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
+EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
+MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+These terms are identical to those of the MIT License, also called the X License or the X11 License,
+which is a simple, permissive non-copyleft free software license. It is deemed compatible with virtually
+all types of licenses, commercial or otherwise. In particular, the Free Software Foundation has declared it
+compatible with GNU GPL. It is also known to be approved by the Apache Software Foundation as compatible
+with Apache Software License.
+
+
+------------------------------------------------------------------------------
+License for the JUnit package
+------------------------------------------------------------------------------
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS COMMON PUBLIC
+LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
+CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+a) in the case of the initial Contributor, the initial code and
+documentation distributed under this Agreement, and
+
+b) in the case of each subsequent Contributor:
+
+i) changes to the Program, and
+
+ii) additions to the Program;
+
+where such changes and/or additions to the Program originate from and are
+distributed by that particular Contributor. A Contribution 'originates' from a
+Contributor if it was added to the Program by such Contributor itself or anyone
+acting on such Contributor's behalf. Contributions do not include additions to
+the Program which: (i) are separate modules of software distributed in
+conjunction with the Program under their own license agreement, and (ii) are not
+derivative works of the Program.
+
+"Contributor" means any person or entity that distributes the Program.
+
+"Licensed Patents " mean patent claims licensable by a Contributor which are
+necessarily infringed by the use or sale of its Contribution alone or when
+combined with the Program.
+
+"Program" means the Contributions distributed in accordance with this Agreement.
+
+"Recipient" means anyone who receives the Program under this Agreement,
+including all Contributors.
+
+2. GRANT OF RIGHTS
+
+a) Subject to the terms of this Agreement, each Contributor hereby grants
+Recipient a non-exclusive, worldwide, royalty-free copyright license to
+reproduce, prepare derivative works of, publicly display, publicly perform,
+distribute and sublicense the Contribution of such Contributor, if any, and such
+derivative works, in source code and object code form.
+
+b) Subject to the terms of this Agreement, each Contributor hereby grants
+Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed
+Patents to make, use, sell, offer to sell, import and otherwise transfer the
+Contribution of such Contributor, if any, in source code and object code form.
+This patent license shall apply to the combination of the Contribution and the
+Program if, at the time the Contribution is added by the Contributor, such
+addition of the Contribution causes such combination to be covered by the
+Licensed Patents. The patent license shall not apply to any other combinations
+which include the Contribution. No hardware per se is licensed hereunder.
+
+c) Recipient understands that although each Contributor grants the licenses
+to its Contributions set forth herein, no assurances are provided by any
+Contributor that the Program does not infringe the patent or other intellectual
+property rights of any other entity. Each Contributor disclaims any liability to
+Recipient for claims brought by any other entity based on infringement of
+intellectual property rights or otherwise. As a condition to exercising the
+rights and licenses granted hereunder, each Recipient hereby assumes sole
+responsibility to secure any other intellectual property rights needed, if any.
+For example, if a third party patent license is required to allow Recipient to
+distribute the Program, it is Recipient's responsibility to acquire that license
+before distributing the Program.
+
+d) Each Contributor represents that to its knowledge it has sufficient
+copyright rights in its Contribution, if any, to grant the copyright license set
+forth in this Agreement.
+
+3. REQUIREMENTS
+
+A Contributor may choose to distribute the Program in object code form under its
+own license agreement, provided that:
+
+a) it complies with the terms and conditions of this Agreement; and
+
+b) its license agreement:
+
+i) effectively disclaims on behalf of all Contributors all warranties and
+conditions, express and implied, including warranties or conditions of title and
+non-infringement, and implied warranties or conditions of merchantability and
+fitness for a particular purpose;
+
+ii) effectively excludes on behalf of all Contributors all liability for
+damages, including direct, indirect, special, incidental and consequential
+damages, such as lost profits;
+
+iii) states that any provisions which differ from this Agreement are offered
+by that Contributor alone and not by any other party; and
+
+iv) states that source code for the Program is available from such
+Contributor, and informs licensees how to obtain it in a reasonable manner on or
+through a medium customarily used for software exchange.
+
+When the Program is made available in source code form:
+
+a) it must be made available under this Agreement; and
+
+b) a copy of this Agreement must be included with each copy of the Program.
+
+Contributors may not remove or alter any copyright notices contained within the
+Program.
+
+Each Contributor must identify itself as the originator of its Contribution, if
+any, in a manner that reasonably allows subsequent Recipients to identify the
+originator of the Contribution.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities with
+respect to end users, business partners and the like. While this license is
+intended to facilitate the commercial use of the Program, the Contributor who
+includes the Program in a commercial product offering should do so in a manner
+which does not create potential liability for other Contributors. Therefore, if
+a Contributor includes the Program in a commercial product offering, such
+Contributor ("Commercial Contributor") hereby agrees to defend and indemnify
+every other Contributor ("Indemnified Contributor") against any losses, damages
+and costs (collectively "Losses") arising from claims, lawsuits and other legal
+actions brought by a third party against the Indemnified Contributor to the
+extent caused by the acts or omissions of such Commercial Contributor in
+connection with its distribution of the Program in a commercial product
+offering. The obligations in this section do not apply to any claims or Losses
+relating to any actual or alleged intellectual property infringement. In order
+to qualify, an Indemnified Contributor must: a) promptly notify the Commercial
+Contributor in writing of such claim, and b) allow the Commercial Contributor to
+control, and cooperate with the Commercial Contributor in, the defense and any
+related settlement negotiations. The Indemnified Contributor may participate in
+any such claim at its own expense.
+
+For example, a Contributor might include the Program in a commercial product
+offering, Product X. That Contributor is then a Commercial Contributor. If that
+Commercial Contributor then makes performance claims, or offers warranties
+related to Product X, those performance claims and warranties are such
+Commercial Contributor's responsibility alone. Under this section, the
+Commercial Contributor would have to defend claims against the other
+Contributors related to those performance claims and warranties, and if a court
+requires any other Contributor to pay any damages as a result, the Commercial
+Contributor must pay those damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
+IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each
+Recipient is solely responsible for determining the appropriateness of using and
+distributing the Program and assumes all risks associated with its exercise of
+rights under this Agreement, including but not limited to the risks and costs of
+program errors, compliance with applicable laws, damage to or loss of data,
+programs or equipment, and unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
+CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
+PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS
+GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under applicable
+law, it shall not affect the validity or enforceability of the remainder of the
+terms of this Agreement, and without further action by the parties hereto, such
+provision shall be reformed to the minimum extent necessary to make such
+provision valid and enforceable.
+
+If Recipient institutes patent litigation against a Contributor with respect to
+a patent applicable to software (including a cross-claim or counterclaim in a
+lawsuit), then any patent licenses granted by that Contributor to such Recipient
+under this Agreement shall terminate as of the date such litigation is filed. In
+addition, if Recipient institutes patent litigation against any entity
+(including a cross-claim or counterclaim in a lawsuit) alleging that the Program
+itself (excluding combinations of the Program with other software or hardware)
+infringes such Recipient's patent(s), then such Recipient's rights granted under
+Section 2(b) shall terminate as of the date such litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it fails to
+comply with any of the material terms or conditions of this Agreement and does
+not cure such failure in a reasonable period of time after becoming aware of
+such noncompliance. If all Recipient's rights under this Agreement terminate,
+Recipient agrees to cease use and distribution of the Program as soon as
+reasonably practicable. However, Recipient's obligations under this Agreement
+and any licenses granted by Recipient relating to the Program shall continue and
+survive.
+
+Everyone is permitted to copy and distribute copies of this Agreement, but in
+order to avoid inconsistency the Agreement is copyrighted and may only be
+modified in the following manner. The Agreement Steward reserves the right to
+publish new versions (including revisions) of this Agreement from time to time.
+No one other than the Agreement Steward has the right to modify this Agreement.
+IBM is the initial Agreement Steward. IBM may assign the responsibility to serve
+as the Agreement Steward to a suitable separate entity. Each new version of the
+Agreement will be given a distinguishing version number. The Program (including
+Contributions) may always be distributed subject to the version of the Agreement
+under which it was received. In addition, after a new version of the Agreement
+is published, Contributor may elect to distribute the Program (including its
+Contributions) under the new version. Except as expressly stated in Sections
+2(a) and 2(b) above, Recipient receives no rights or licenses to the
+intellectual property of any Contributor under this Agreement, whether
+expressly, by implication, estoppel or otherwise. All rights in the Program not
+expressly granted under this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and the
+intellectual property laws of the United States of America. No party to this
+Agreement will bring a legal action under this Agreement more than one year
+after the cause of action arose. Each party waives its rights to a jury trial in
+any resulting litigation.
+
diff --git a/src/toplevel/NOTICE b/src/toplevel/NOTICE
new file mode 100644
index 0000000..f049477
--- /dev/null
+++ b/src/toplevel/NOTICE
@@ -0,0 +1,21 @@
+=========================================================================
+==  NOTICE file corresponding to the section 4 d of                    ==
+==  the Apache License, Version 2.0,                                   ==
+==  in this case for the Gradle distribution.                          ==
+=========================================================================
+
+This product includes software developed by
+The Apache Software Foundation (http://www.apache.org/).
+
+It includes the following other software:
+
+Groovy (http://groovy.codehaus.org)
+Logback (http://logback.qos.ch)
+SL4J (http://www.slf4j.org)
+Svnkit (http://www.svnkit.com)
+Junit (http://www.junit.org)
+
+For licenses see the LICENSE file.
+
+If any software distributed with Gradle does not have an Apache 2 License, its license is explicitly listed in the
+LICENSE file.
\ No newline at end of file
diff --git a/src/toplevel/changelog.txt b/src/toplevel/changelog.txt
new file mode 100644
index 0000000..eb4190b
--- /dev/null
+++ b/src/toplevel/changelog.txt
@@ -0,0 +1,4 @@
+
+Release Notes - Gradle - Version 0.9-rc-1
+
+See http://docs.codehaus.org/display/GRADLE/Gradle+0.9+Release+Notes
\ No newline at end of file
diff --git a/subprojects/gradle-announce/announce.gradle b/subprojects/gradle-announce/announce.gradle
new file mode 100644
index 0000000..f842788
--- /dev/null
+++ b/subprojects/gradle-announce/announce.gradle
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+dependencies {
+    groovy libraries.groovy_depends
+
+    compile libraries.slf4j_api
+    compile project(':core')
+    compile project(':plugins')
+
+    testCompile project(path: ':core', configuration: 'testFixtures')
+    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+}
diff --git a/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncePlugin.groovy b/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncePlugin.groovy
new file mode 100644
index 0000000..5d3c258
--- /dev/null
+++ b/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncePlugin.groovy
@@ -0,0 +1,54 @@
+/*
+ * 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.announce;
+
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+/**
+ * This plugin allows to send announce messages to twitter.
+ *
+ * @author hackergarten
+ */
+class AnnouncePlugin implements Plugin<Project> {
+    public void apply(final Project project) {
+        project.convention.plugins.announce = new AnnouncePluginConvention(project)
+    }
+}
+
+class AnnouncePluginConvention {
+    String username
+    String password
+    Project project
+    AnnouncerFactory announcerFactory
+
+    def AnnouncePluginConvention(project) {
+        this.project = project;
+        this.announcerFactory = new DefaultAnnouncerFactory(this)
+    }
+
+    def announce(Closure closure) {
+        closure.delegate = this
+        closure()
+    }
+
+    def announce(String msg, def type) {
+        announcerFactory.createAnnouncer(type).send(project.name, msg)
+    }
+}
+
diff --git a/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/Announcer.java b/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/Announcer.java
new file mode 100644
index 0000000..a78863c
--- /dev/null
+++ b/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/Announcer.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.api.plugins.announce;
+
+
+public interface Announcer {
+  void send(String title, String message); 
+}
diff --git a/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncerFactory.java b/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncerFactory.java
new file mode 100644
index 0000000..2d0acc8
--- /dev/null
+++ b/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/AnnouncerFactory.java
@@ -0,0 +1,23 @@
+/*
+ * 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.announce;
+
+/**
+ * @author Hans Dockter
+ */
+public interface AnnouncerFactory {
+    Announcer createAnnouncer(String type);
+}
diff --git a/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/DefaultAnnouncerFactory.groovy b/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/DefaultAnnouncerFactory.groovy
new file mode 100644
index 0000000..2501a89
--- /dev/null
+++ b/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/DefaultAnnouncerFactory.groovy
@@ -0,0 +1,48 @@
+/*
+ * 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.announce
+
+/**
+ * @author Hans Dockter
+ */
+class DefaultAnnouncerFactory implements AnnouncerFactory {
+    AnnouncePluginConvention announcePluginConvention
+
+    def DefaultAnnouncerFactory(announcePluginConvention) {
+        this.announcePluginConvention = announcePluginConvention;
+    }
+
+    Announcer createAnnouncer(String type) {
+        if (type == "twitter") {
+            String username = announcePluginConvention.username
+            String password = announcePluginConvention.password
+            return new Twitter(username, password)
+        } else if (type == "notify-send") {
+            return new NotifySend()
+        } else if (type == "snarl") {
+            return new Snarl()
+        } else if (type == "growl") {
+            return new Growl()
+        }
+        new DoNothingAnnouncer()
+    }
+}
+
+class DoNothingAnnouncer implements Announcer {
+    void send(String title, String message) {
+        // do nothing
+    }
+}
diff --git a/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/Growl.groovy b/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/Growl.groovy
new file mode 100644
index 0000000..62c9c62
--- /dev/null
+++ b/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/Growl.groovy
@@ -0,0 +1,28 @@
+/*
+ * 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.announce
+
+import org.gradle.process.internal.DefaultExecAction
+import org.gradle.process.internal.ExecAction
+
+class Growl implements Announcer {
+    void send(String title, String message) {
+        ExecAction execAction = new DefaultExecAction()
+        execAction.executable('growlnotify')
+        execAction.args('-m', message, title)
+        execAction.execute()
+    }
+}
diff --git a/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/NotifySend.groovy b/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/NotifySend.groovy
new file mode 100644
index 0000000..10a9b57
--- /dev/null
+++ b/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/NotifySend.groovy
@@ -0,0 +1,48 @@
+/*
+ * 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.announce;
+
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+/**
+ * This class wraps the Ubuntu Notify Send functionality.
+ *
+ * @author Hackergarten
+ */
+
+class NotifySend implements Announcer {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(NotifySend)
+
+    public void send(String title, String message) {
+        def cmd = [
+                'notify-send',
+                title,
+                message
+        ]
+        try {
+            cmd.execute()
+        } catch (java.io.IOException e) {
+            LOGGER.warn('''Could not find notify-send command,
+              The programm is aviable in the libnotify-bin.
+              On ubuntu simple install it with: \\n   
+              sudo apt-get install libnotify-bin''')
+
+        }
+    }
+}
diff --git a/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/Snarl.groovy b/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/Snarl.groovy
new file mode 100644
index 0000000..6ec042a
--- /dev/null
+++ b/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/Snarl.groovy
@@ -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.api.plugins.announce
+
+class Snarl implements Announcer {
+  private static final float SNP_VERSION = 1.1f
+  private static final String HEAD = "type=SNP#?version=" + SNP_VERSION
+
+  public void send(String title, String message) {
+    send("localhost", title, message)
+  }
+
+  public void send(Collection hosts, String title, String message) {
+    hosts.each { host ->
+      send(host, title, message)
+    }
+  }
+
+  public void send(String host, String title, String message) {
+    with(new Socket(InetAddress.getByName(host), 9887)) { sock ->
+      with(new PrintWriter(sock.getOutputStream(), true)) { out ->
+        out.println(formatMessage(title, message))
+      }
+    }
+  }
+
+  private String formatMessage(String title, String message) {
+    def properties = [
+            formatProperty("action", "notification"),
+            formatProperty("app", "Gradle Snarl Notifier"),
+            formatProperty("class", "alert"),
+            formatProperty("title", title),
+            formatProperty("text", message),
+            formatProperty("icon", null),
+            formatProperty("timeout", "10")]
+
+    HEAD + properties.join('') + "\r\n"
+  }
+
+  private String formatProperty(String name, String value) {
+    if (!value) {
+      return ""
+    }
+    else {
+      return "#?" + name + "=" + value
+    }
+  }
+
+  private with(closable, closure) {
+    try {
+      closure(closable)
+    } finally {
+      try {
+        closable.close()
+      } catch (Exception e) {
+
+      }
+    }
+  }
+}
diff --git a/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/Twitter.groovy b/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/Twitter.groovy
new file mode 100644
index 0000000..914a025
--- /dev/null
+++ b/subprojects/gradle-announce/src/main/groovy/org/gradle/api/plugins/announce/Twitter.groovy
@@ -0,0 +1,69 @@
+/*
+ * 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.announce;
+
+
+
+import org.slf4j.LoggerFactory
+import sun.misc.BASE64Encoder
+import org.slf4j.Logger
+
+/**
+ * This class allows to send announce messages to twitter.
+ *
+ * @author hackergarten
+ */
+class Twitter implements Announcer {
+
+  private static final String TWITTER_UPDATE_URL = "https://twitter.com/statuses/update.xml"
+
+  def username
+  def password
+
+  private static Logger logger = LoggerFactory.getLogger(Twitter)
+
+  Twitter(String username, String password) {
+    this.username = username
+    this.password = password
+  }
+
+  public void send(String title, String message) {
+    OutputStreamWriter out
+    URLConnection connection
+    try {
+      connection = new URL(TWITTER_UPDATE_URL).openConnection()
+      connection.doInput = true
+      connection.doOutput = true
+      connection.useCaches = false
+      String encoded = new BASE64Encoder().encodeBuffer("$username:$password".toString().bytes).trim()
+      connection.setRequestProperty "Authorization", "Basic " + encoded
+      out = new OutputStreamWriter(connection.outputStream)
+      out.write "status=" + URLEncoder.encode(message, "UTF-8")
+      out.close()
+       def result = ''
+       connection.inputStream.eachLine { result += it }
+      logger.info result
+      logger.info("Successfully send message: [$message] to twitter [$username]")
+    } catch (Exception e) {
+      logger.warn('Could not send message to twitter', e)
+    } finally {
+      connection?.disconnect()
+
+    }
+  
+  }
+}
diff --git a/subprojects/gradle-announce/src/main/resources/META-INF/gradle-plugins/announce.properties b/subprojects/gradle-announce/src/main/resources/META-INF/gradle-plugins/announce.properties
new file mode 100644
index 0000000..f236da0
--- /dev/null
+++ b/subprojects/gradle-announce/src/main/resources/META-INF/gradle-plugins/announce.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+implementation-class=org.gradle.api.plugins.announce.AnnouncePlugin
diff --git a/subprojects/gradle-announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginConventionTest.groovy b/subprojects/gradle-announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginConventionTest.groovy
new file mode 100644
index 0000000..6d223d6
--- /dev/null
+++ b/subprojects/gradle-announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginConventionTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * 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.announce
+
+import org.gradle.api.Project
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+
+class AnnouncePluginConventionTest extends Specification {
+    Project project = HelperUtil.createRootProject()
+    AnnouncePluginConvention announcePluginConvention = new AnnouncePluginConvention(project)
+
+    def announceConfigureMethod() {
+        when:
+        announcePluginConvention.announce {
+            username = 'someUser'
+            password = 'somePassword'
+        }
+
+        then:
+        announcePluginConvention.username == 'someUser'
+        announcePluginConvention.password == 'somePassword'
+    }
+
+    def announce() {
+        AnnouncerFactory announcerFactory = Mock()
+        announcePluginConvention.announcerFactory = announcerFactory
+        Announcer announcer = Mock()
+        announcerFactory.createAnnouncer("someType") >> announcer
+
+        when:
+        announcePluginConvention.announce('someMessage', "someType")
+
+        then:
+        1 * announcer.send(project.name, 'someMessage')
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginTest.groovy b/subprojects/gradle-announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginTest.groovy
new file mode 100644
index 0000000..38b8c9f
--- /dev/null
+++ b/subprojects/gradle-announce/src/test/groovy/org/gradle/api/plugins/announce/AnnouncePluginTest.groovy
@@ -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.api.plugins.announce
+
+import spock.lang.Specification
+import org.gradle.util.HelperUtil
+import org.gradle.api.Project
+
+/**
+ * @author Hans Dockter
+ */
+class AnnouncePluginTest extends Specification {
+    AnnouncePlugin announcePlugin = new AnnouncePlugin()
+    Project project = HelperUtil.createRootProject()
+
+    def addConventionObject() {
+        when:
+        announcePlugin.apply project
+
+        then:
+        project.convention.plugins.announce instanceof AnnouncePluginConvention
+    }
+}
diff --git a/subprojects/gradle-announce/src/test/groovy/org/gradle/api/plugins/announce/DefaultAnnouncerFactoryTest.groovy b/subprojects/gradle-announce/src/test/groovy/org/gradle/api/plugins/announce/DefaultAnnouncerFactoryTest.groovy
new file mode 100644
index 0000000..266754e
--- /dev/null
+++ b/subprojects/gradle-announce/src/test/groovy/org/gradle/api/plugins/announce/DefaultAnnouncerFactoryTest.groovy
@@ -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.api.plugins.announce
+
+import org.gradle.api.Project
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+
+class DefaultAnnouncerFactoryTest extends Specification {
+    AnnouncePluginConvention announcePluginConvention = new AnnouncePluginConvention(project)
+    DefaultAnnouncerFactory announcerFactory = new DefaultAnnouncerFactory(announcePluginConvention)
+    Project project = HelperUtil.createRootProject()
+
+    def createForTwitter() {
+        announcePluginConvention.username = 'username'
+        announcePluginConvention.password = 'password'
+        
+        when:
+        Twitter twitter = announcerFactory.createAnnouncer('twitter')
+
+        then:
+        twitter.username == announcePluginConvention.username
+        twitter.password == announcePluginConvention.password
+    }
+
+    def createForSnarl() {
+        expect:
+        announcerFactory.createAnnouncer('snarl') instanceof Snarl
+    }
+
+    def createForNotifySend() {
+        expect:
+        announcerFactory.createAnnouncer('notify-send') instanceof NotifySend
+    }
+
+    def createForGrowl() {
+        expect:
+        announcerFactory.createAnnouncer('growl') instanceof Growl
+    }
+
+    def createWithUnknownType() {
+        expect:
+        announcerFactory.createAnnouncer('unknown') instanceof DoNothingAnnouncer
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-announce/src/test/groovy/org/gradle/api/plugins/announce/NotifySendTest.groovy b/subprojects/gradle-announce/src/test/groovy/org/gradle/api/plugins/announce/NotifySendTest.groovy
new file mode 100644
index 0000000..61f804f
--- /dev/null
+++ b/subprojects/gradle-announce/src/test/groovy/org/gradle/api/plugins/announce/NotifySendTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * 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.announce
+
+import org.junit.Ignore
+
+class NotifySendTest extends GroovyTestCase {
+
+  public void testWithException() {
+    use(ExceptionCategory) {
+      def notifier = new NotifySend()
+      notifier.send("title", "body")
+    }
+  }
+
+ /* @Ignore
+  public void testCanSendMessage() {
+
+    use(MockCategory) {
+      def notifier = new NotifySend()
+      notifier.send("title", "body")
+      assert ['notify-send', 'title', 'body'] == MockCategory.capture, "nothing was executed"
+    }
+  }*/
+
+  public void testIntegrationTest() {
+    def notifier = new NotifySend()
+    notifier.send("title", "body")
+  }
+}
+
+
+private static class ExceptionCategory {
+  void execute(List list) {
+    throw new IOException()
+  }
+}
+
+private static class MockCategory {
+  def static capture
+
+  void execute(List list) {
+    capture = list
+  }
+}
+
+
diff --git a/subprojects/gradle-announce/src/test/groovy/org/gradle/api/plugins/announce/SnarlTest.groovy b/subprojects/gradle-announce/src/test/groovy/org/gradle/api/plugins/announce/SnarlTest.groovy
new file mode 100644
index 0000000..4c49026
--- /dev/null
+++ b/subprojects/gradle-announce/src/test/groovy/org/gradle/api/plugins/announce/SnarlTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * 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.announce;
+
+
+class SnarlTest extends GroovyTestCase {
+//	public void testSend() {
+//		new Snarl().send("JUnit Test", "Hello from Groovy!");
+//	}
+//	public void testSend_MultipleHosts() {
+//		new Snarl().send(["localhost", "localhost", "localhost"], "JUnit Test", "Hello from Groovy!");
+//	}
+
+	void testMockSend() {
+//		use(PrintWriterCapture) {
+//			new Snarl().send("some title", "some message")
+//			assert PrintWriterCapture.capture == "type=SNP#?version=1.1#?action=notification#?app=Gradle Snarl Notifier#?class=alert#?title=some title#?text=some message#?timeout=10\r\n"
+//		}
+	}
+	
+}
+class PrintWriterCapture {
+  static capture
+
+  static void println(PrintWriter p, String input) {
+    capture = input
+  }
+}
diff --git a/subprojects/gradle-antlr/antlr.gradle b/subprojects/gradle-antlr/antlr.gradle
new file mode 100644
index 0000000..d6b6446
--- /dev/null
+++ b/subprojects/gradle-antlr/antlr.gradle
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+apply plugin: 'groovy'
+
+dependencies {
+    groovy libraries.groovy_depends
+
+    compile project(':core')
+    compile project(':plugins')
+
+    compile libraries.slf4j_api,
+            libraries.ant,
+            libraries.ant_antlr,
+            libraries.antlr
+
+    testCompile project(path: ':core', configuration: 'testFixtures')
+    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+}
diff --git a/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrPlugin.java b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrPlugin.java
new file mode 100644
index 0000000..82f0bac
--- /dev/null
+++ b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrPlugin.java
@@ -0,0 +1,121 @@
+/*
+ * 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.antlr;
+
+import java.io.File;
+
+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.internal.DynamicObjectAware;
+import org.gradle.api.internal.IConventionAware;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.DefaultSourceSet;
+import org.gradle.api.plugins.Convention;
+import org.gradle.api.plugins.JavaPlugin;
+import static org.gradle.api.plugins.JavaPlugin.COMPILE_CONFIGURATION_NAME;
+import org.gradle.api.plugins.JavaPluginConvention;
+import org.gradle.api.tasks.ConventionValue;
+import org.gradle.api.tasks.SourceSet;
+
+
+/**
+ * A plugin for adding Antlr support to {@link JavaPlugin java projects}
+ *
+ * @author Steve Ebersole
+ */
+public class AntlrPlugin implements Plugin<Project> {
+	public static final String ANTLR_CONFIGURATION_NAME = "antlr";
+
+    public void apply(final Project project) {
+        project.getPlugins().apply( JavaPlugin.class );
+
+		// set up a configuration named 'antlr' for the user to specify the antlr libs to use in case
+		// they want a specific version etc.
+        Configuration antlrConfiguration = project.getConfigurations().add( ANTLR_CONFIGURATION_NAME )
+				.setVisible( false )
+				.setTransitive( false )
+				.setDescription( "The Antlr libraries to be used for this project." );
+        project.getConfigurations().getByName( COMPILE_CONFIGURATION_NAME ).extendsFrom( antlrConfiguration );
+
+        final ProjectInternal projectInternal = (ProjectInternal) project;
+		project.getConvention().getPlugin( JavaPluginConvention.class ).getSourceSets().allObjects(
+				new Action<SourceSet>() {
+					public void execute(SourceSet sourceSet) {
+						// for each source set we will:
+						// 		1) Add a new 'antlr' virtual directory mapping
+						final AntlrSourceVirtualDirectoryImpl antlrDirectoryDelegate = new AntlrSourceVirtualDirectoryImpl(
+								( (DefaultSourceSet) sourceSet ).getDisplayName(),
+								projectInternal.getFileResolver()
+						);
+						( (DynamicObjectAware) sourceSet ).getConvention().getPlugins().put(
+								AntlrSourceVirtualDirectory.NAME,
+								antlrDirectoryDelegate
+						);
+						final String srcDir = String.format( "src/%s/antlr", sourceSet.getName() );
+						antlrDirectoryDelegate.getAntlr().srcDir( srcDir );
+						sourceSet.getAllSource().add( antlrDirectoryDelegate.getAntlr() );
+
+						//		2) create an AntlrTask for this sourceSet following the gradle
+						//			naming conventions via call to sourceSet.getTaskName()
+						final String taskName = sourceSet.getTaskName("generate", "GrammarSource");
+						AntlrTask antlrTask = project.getTasks().add( taskName, AntlrTask.class );
+						antlrTask.setDescription(
+								String.format( "Processes the %s Antlr grammars.", sourceSet.getName() )
+						);
+
+						//		3) set up convention mapping for default sources (allows user to not have to specify)
+						antlrTask.conventionMapping(
+								"defaultSource",
+								new ConventionValue() {
+									public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+										return antlrDirectoryDelegate.getAntlr();
+									}
+								}
+						);
+
+						//		4) set up convention mapping for handling the 'antlr' dependency configuration
+						antlrTask.getConventionMapping().map(
+								"antlrClasspath",
+								new ConventionValue() {
+									public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+										return project.getConfigurations()
+												.getByName( ANTLR_CONFIGURATION_NAME )
+												.copy()
+												.setTransitive( true );
+                    				}
+                				}
+						);
+
+						//		5) Set up the Antlr output directory (adding to javac inputs!)
+						final String outputDirectoryName = String.format(
+								"%s/generated-src/antlr/%s",
+								project.getBuildDir(),
+								sourceSet.getName()
+						);
+						final File outputDirectory = new File( outputDirectoryName );
+						antlrTask.setOutputDirectory( outputDirectory );
+						sourceSet.getJava().srcDir( outputDirectory );
+
+						//		6) register fact that antlr should be run before compiling
+						project.getTasks().getByName( sourceSet.getCompileJavaTaskName() ).dependsOn( taskName );
+					}
+				}
+		);
+    }
+}
diff --git a/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrSourceVirtualDirectory.java b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrSourceVirtualDirectory.java
new file mode 100644
index 0000000..ea987fa
--- /dev/null
+++ b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrSourceVirtualDirectory.java
@@ -0,0 +1,57 @@
+/*
+ * 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.antlr;
+
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.file.FileTree;
+import groovy.lang.Closure;
+
+/**
+ * Contract for a Gradle "convention object" that acts as a handler for what I call a virtual directory mapping,
+ * injecting a virtual directory named 'antlr' into the project's various
+ * {@link org.gradle.api.tasks.SourceSet source sets}.  Its implementation gets pushed onto the
+ * {@link org.gradle.api.internal.DynamicObjectAware} portion of the source set under the name 'antlr'.
+ *
+ * @author Steve Ebersole
+ */
+public interface AntlrSourceVirtualDirectory {
+	public static final String NAME = "antlr";
+
+	/**
+	 * All Antlr source for this source set.
+	 *
+	 * @return The Antlr source.  Never returns null.
+	 */
+	public SourceDirectorySet getAntlr();
+
+	/**
+	 * Configures the Antlr source for this set. The given closure is used to configure the {@code SourceDirectorySet}
+	 * (see {@link #getAntlr}) which contains the Antlr source.
+	 *
+	 * @param configureClosure The closure to use to configure the Antlr source.
+	 *
+	 * @return this
+	 */
+	public AntlrSourceVirtualDirectory antlr(Closure configureClosure);
+
+	/**
+	 * All Antlr source for this source set.
+	 *
+	 * @return The Antlr source. Never returns null.
+	 */
+	public FileTree getAllAntlr();
+}
diff --git a/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrSourceVirtualDirectoryImpl.java b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrSourceVirtualDirectoryImpl.java
new file mode 100644
index 0000000..fa3a968
--- /dev/null
+++ b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrSourceVirtualDirectoryImpl.java
@@ -0,0 +1,61 @@
+/*
+ * 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.antlr;
+
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.internal.file.UnionFileTree;
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.tasks.util.PatternFilterable;
+import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.util.ConfigureUtil;
+import groovy.lang.Closure;
+
+/**
+ * The implementation of the {@link AntlrSourceVirtualDirectory} contract.
+ *
+ * @author Steve Ebersole
+ */
+public class AntlrSourceVirtualDirectoryImpl implements AntlrSourceVirtualDirectory {
+	private final SourceDirectorySet antlr;
+    private final UnionFileTree allAntlr;
+    private final PatternFilterable antlrPatterns = new PatternSet();
+
+	public AntlrSourceVirtualDirectoryImpl(String parentDisplayName, FileResolver fileResolver) {
+		final String displayName = String.format( "%s Antlr source", parentDisplayName );
+		antlr = new DefaultSourceDirectorySet( displayName, fileResolver );
+		antlr.getFilter().include( "**/*.g" );
+        antlrPatterns.include( "**/*.g" );
+        allAntlr = new UnionFileTree( displayName, antlr.matching( antlrPatterns ) );
+	}
+
+	public SourceDirectorySet getAntlr() {
+		return antlr;
+	}
+
+	public AntlrSourceVirtualDirectory antlr(Closure configureClosure) {
+        ConfigureUtil.configure( configureClosure, getAntlr() );
+        return this;
+    }
+
+	public UnionFileTree getAllAntlr() {
+		return allAntlr;
+	}
+
+	public PatternFilterable getAntlrSourcePatterns() {
+		return antlrPatterns;
+	}
+}
diff --git a/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrTask.java b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrTask.java
new file mode 100644
index 0000000..8aa3783
--- /dev/null
+++ b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/AntlrTask.java
@@ -0,0 +1,143 @@
+/*
+ * 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.antlr;
+
+import java.io.File;
+import java.util.List;
+
+import org.apache.tools.ant.taskdefs.optional.ANTLR;
+import org.apache.tools.ant.types.Path;
+import org.gradle.api.file.FileCollection;
+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.api.plugins.antlr.metadata.MetadataExtracter;
+import org.gradle.api.plugins.antlr.metadata.XRef;
+import org.gradle.api.plugins.antlr.plan.GenerationPlan;
+import org.gradle.api.plugins.antlr.plan.GenerationPlanBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Gradle task for executing Antlr generations.  Wrapper around the Ant {@link ANTLR} task.
+ * <p/>
+ * Most properties here are self-evident, but I wanted to highlight one in particular:
+ * {@link #setAntlrClasspath} is used to define the classpath that should be passed along to the
+ * Ant {@link ANTLR} task as its classpath.  That is the classpath it uses to perform generation
+ * execution.  This <b>should<b> really only require the antlr jar.  In {@link AntlrPlugin}
+ * usage, this would happen simply by adding your antlr jar into the 'antlr' dependency configuration
+ * created and exposed by the {@link AntlrPlugin} itself.
+ *
+ * @author Steve Ebersole
+ */
+public class AntlrTask extends SourceTask {
+	private static final Logger LOGGER = LoggerFactory.getLogger( AntlrTask.class );
+
+    private boolean trace;
+    private boolean traceLexer;
+    private boolean traceParser;
+    private boolean traceTreeWalker;
+
+    private FileCollection antlrClasspath;
+
+	private File outputDirectory;
+
+	public boolean isTrace() {
+		return trace;
+	}
+
+	public void setTrace(boolean trace) {
+		this.trace = trace;
+	}
+
+	public boolean isTraceLexer() {
+		return traceLexer;
+	}
+
+	public void setTraceLexer(boolean traceLexer) {
+		this.traceLexer = traceLexer;
+	}
+
+	public boolean isTraceParser() {
+		return traceParser;
+	}
+
+	public void setTraceParser(boolean traceParser) {
+		this.traceParser = traceParser;
+	}
+
+	public boolean isTraceTreeWalker() {
+		return traceTreeWalker;
+	}
+
+	public void setTraceTreeWalker(boolean traceTreeWalker) {
+		this.traceTreeWalker = traceTreeWalker;
+	}
+
+    @OutputDirectory
+	public File getOutputDirectory() {
+		return outputDirectory;
+	}
+
+	public void setOutputDirectory(File outputDirectory) {
+		this.outputDirectory = outputDirectory;
+	}
+
+    @InputFiles
+	public FileCollection getAntlrClasspath() {
+		return antlrClasspath;
+	}
+
+	public void setAntlrClasspath(FileCollection antlrClasspath) {
+		this.antlrClasspath = antlrClasspath;
+	}
+
+	@TaskAction
+	public void generate() {
+		// Determine the grammar files and the proper ordering amongst them
+		XRef xref = new MetadataExtracter().extractMetadata( getSource() );
+		List<GenerationPlan> generationPlans = new GenerationPlanBuilder( outputDirectory ).buildGenerationPlans( xref );
+
+		for ( GenerationPlan generationPlan : generationPlans ) {
+			if ( ! generationPlan.isOutOfDate() ) {
+				LOGGER.info( "grammar [" + generationPlan.getId() + "] was up-to-date; skipping" );
+				continue;
+			}
+
+			LOGGER.info( "performing grammar generation [" + generationPlan.getId() + "]" );
+
+			//noinspection ResultOfMethodCallIgnored
+			generationPlan.getGenerationDirectory().mkdirs();
+
+			ANTLR antlr = new ANTLR();
+			antlr.setProject( getAnt().getAntProject() );
+			Path antlrTaskClasspath = antlr.createClasspath();
+			for ( File dep: getAntlrClasspath() ) {
+				antlrTaskClasspath.createPathElement().setLocation( dep );
+			}
+			antlr.setTrace( trace );
+			antlr.setTraceLexer( traceLexer );
+			antlr.setTraceParser( traceParser );
+			antlr.setTraceTreeWalker( traceTreeWalker );
+			antlr.setOutputdirectory( generationPlan.getGenerationDirectory() );
+			antlr.setTarget( generationPlan.getSource() );
+
+			antlr.execute();
+		}
+	}
+}
diff --git a/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/metadata/GrammarDelegate.java b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/metadata/GrammarDelegate.java
new file mode 100644
index 0000000..09d6052
--- /dev/null
+++ b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/metadata/GrammarDelegate.java
@@ -0,0 +1,164 @@
+/*
+ * 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.antlr.metadata;
+
+import java.lang.reflect.Method;
+import java.util.Enumeration;
+import java.util.ArrayList;
+import java.util.List;
+
+import antlr.collections.impl.IndexedVector;
+import antlr.preprocessor.GrammarFile;
+
+/**
+ * Antlr defines its {@link antlr.preprocessor.Grammar} class as package-protected for some unfortunate reason.
+ * So this class acts as a delegate to the Antlr {@link antlr.preprocessor.Grammar} class, hiding all the
+ * ugly necessary reflection code.
+ *
+ * @author Steve Ebersole
+ */
+public class GrammarDelegate {
+	public static List<GrammarDelegate> extractGrammarDelegates(GrammarFile antlrGrammarFile) {
+		List<GrammarDelegate> grammarDelegates = new ArrayList<GrammarDelegate>();
+		Enumeration grammarFileGramars = antlrGrammarFile.getGrammars().elements();
+		while ( grammarFileGramars.hasMoreElements() ) {
+			grammarDelegates.add( new GrammarDelegate( grammarFileGramars.nextElement() ) );
+		}
+		return grammarDelegates;
+	}
+
+	private final String className;
+	private final String importVocab;
+	private final String exportVocab;
+	private final GrammarDelegate superGrammarDelegate;
+
+	public GrammarDelegate(Object antlrGrammarMetadata) {
+		try {
+			final Method getNameMethod = ANTLR_GRAMMAR_CLASS.getDeclaredMethod( "getName", NO_ARG_SIGNATURE );
+			getNameMethod.setAccessible( true );
+			this.className = ( String ) getNameMethod.invoke( antlrGrammarMetadata, NO_ARGS );
+
+			final Method getSuperGrammarMethod = ANTLR_GRAMMAR_CLASS.getMethod( "getSuperGrammar", NO_ARG_SIGNATURE );
+			getSuperGrammarMethod.setAccessible( true );
+			final Object antlrSuperGrammarGrammarMetadata = getSuperGrammarMethod.invoke( antlrGrammarMetadata, NO_ARGS );
+			this.superGrammarDelegate = antlrSuperGrammarGrammarMetadata == null
+					? null
+					: new GrammarDelegate( antlrSuperGrammarGrammarMetadata );
+
+			Method getOptionsMethod = ANTLR_GRAMMAR_CLASS.getMethod( "getOptions", NO_ARG_SIGNATURE );
+			getOptionsMethod.setAccessible( true );
+			IndexedVector options = ( IndexedVector ) getOptionsMethod.invoke( antlrGrammarMetadata, NO_ARGS );
+
+			Method getRHSMethod = ANTLR_OPTION_CLASS.getMethod( "getRHS", NO_ARG_SIGNATURE );
+			getRHSMethod.setAccessible( true );
+
+			final Object importVocabOption = options == null
+					? null
+					: options.getElement( "importVocab" );
+			this.importVocab = importVocabOption == null
+					? null
+					: vocabName( (String) getRHSMethod.invoke( importVocabOption, NO_ARGS ) );
+
+			final Object exportVocabOption = options == null
+					? null
+					: options.getElement( "exportVocab" );
+			this.exportVocab = exportVocabOption == null
+					? null
+					: vocabName( (String) getRHSMethod.invoke( exportVocabOption, NO_ARGS ) );
+		}
+		catch ( Throwable t ) {
+			throw new IllegalStateException( "Error accessing  Antlr grammar metadata", t );
+		}
+	}
+
+	/**
+	 * Retrieves the unqualified name of the lexer/parser class.
+	 *
+	 * @return The unqualified lexer/parser class name.
+	 */
+	public String getClassName() {
+		return className;
+	}
+
+	/**
+	 * Retrieves the name of this vocabulary imported by this grammar.
+	 *
+	 * @return The gammar's imported vocabulary name.
+	 */
+	public String getImportVocab() {
+		return importVocab;
+	}
+
+	/**
+	 * Retrieves the name of this vocabulary exported by this grammar.
+	 *
+	 * @return The gammar's exported vocabulary name.
+	 */
+	public String getExportVocab() {
+		return exportVocab;
+	}
+
+	/**
+	 * Retrieves the grammar delegate associated with this grammars super grammar deduced during preprocessing
+	 * from its extends clause.
+	 *
+	 * @return The super-grammar grammar delegate
+	 */
+	public GrammarDelegate getSuperGrammarDelegate() {
+		return superGrammarDelegate;
+	}
+
+	private GrammarMetadata associatedGrammarMetadata;
+
+	public void associateWith(GrammarMetadata associatedGrammarMetadata) {
+		this.associatedGrammarMetadata = associatedGrammarMetadata;
+	}
+
+	public GrammarMetadata getAssociatedGrammarMetadata() {
+		return associatedGrammarMetadata;
+	}
+
+	private String vocabName(String vocabName) {
+		if ( vocabName == null ) {
+			return null;
+		}
+		vocabName = vocabName.trim();
+		if ( vocabName.endsWith( ";" ) ) {
+			vocabName = vocabName.substring( 0, vocabName.length() - 1 );
+		}
+		return vocabName;
+	}
+
+	private static final Class ANTLR_GRAMMAR_CLASS;
+	private static final Class ANTLR_OPTION_CLASS;
+
+	static {
+		ANTLR_GRAMMAR_CLASS = loadAntlrClass( "antlr.preprocessor.Grammar" );
+		ANTLR_OPTION_CLASS = loadAntlrClass( "antlr.preprocessor.Option" );
+	}
+
+	public static final Class[] NO_ARG_SIGNATURE = new Class[0];
+	public static final Object[] NO_ARGS = new Object[0];
+
+	private static Class loadAntlrClass(String className) {
+		try {
+			return Class.forName( className, true, GrammarDelegate.class.getClassLoader() );
+		}
+		catch ( ClassNotFoundException e ) {
+			throw new IllegalStateException( "Unable to locate Antlr class [" + className + "]", e );
+		}
+	}
+}
diff --git a/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/metadata/GrammarFileMetadata.java b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/metadata/GrammarFileMetadata.java
new file mode 100644
index 0000000..be07239
--- /dev/null
+++ b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/metadata/GrammarFileMetadata.java
@@ -0,0 +1,61 @@
+/*
+ * 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.antlr.metadata;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.io.File;
+
+/**
+ * Models information about an Antlr grammar file, including the inidividual {@link #getGrammars grammars}
+ * (lexers, parsers, etc) contained within it.
+ *
+ * @author Steve Ebersole
+ */
+public class GrammarFileMetadata {
+	private final File filePath;
+	private final antlr.preprocessor.GrammarFile antlrGrammarFile;
+	private final String packageName;
+	private List<GrammarMetadata> grammarMetadatas = new ArrayList<GrammarMetadata>();
+
+	public GrammarFileMetadata(File filePath, antlr.preprocessor.GrammarFile antlrGrammarFile, String packageName) {
+		this.filePath = filePath;
+		this.antlrGrammarFile = antlrGrammarFile;
+		this.packageName = packageName;
+
+		List<GrammarDelegate> antlrGrammarDelegates = GrammarDelegate.extractGrammarDelegates( antlrGrammarFile );
+		for ( GrammarDelegate antlrGrammarDelegate : antlrGrammarDelegates ) {
+			GrammarMetadata grammarMetadata = new GrammarMetadata( this, antlrGrammarDelegate );
+			grammarMetadatas.add( grammarMetadata );
+		}
+	}
+
+	public File getFilePath() {
+		return filePath;
+	}
+
+	public antlr.preprocessor.GrammarFile getAntlrGrammarFile() {
+		return antlrGrammarFile;
+	}
+
+	public String getPackageName() {
+		return packageName;
+	}
+
+	public List<GrammarMetadata> getGrammars() {
+		return grammarMetadatas;
+	}
+}
diff --git a/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/metadata/GrammarMetadata.java b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/metadata/GrammarMetadata.java
new file mode 100644
index 0000000..5df1c0c
--- /dev/null
+++ b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/metadata/GrammarMetadata.java
@@ -0,0 +1,100 @@
+/*
+ * 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.antlr.metadata;
+
+import java.io.File;
+
+import antlr.TreeParser;
+import antlr.Parser;
+
+/**
+ * Models a grammar defined within an Antlr grammar file.
+ *
+ * @author Steve Ebersole
+ */
+public class GrammarMetadata {
+	private final GrammarFileMetadata grammarFileMetadata;
+	private final GrammarDelegate grammarDelegate;
+
+	public GrammarMetadata(GrammarFileMetadata grammarFileMetadata, GrammarDelegate grammarDelegate) {
+		this.grammarFileMetadata = grammarFileMetadata;
+		this.grammarDelegate = grammarDelegate;
+		grammarDelegate.associateWith( this );
+	}
+
+	public GrammarFileMetadata getGrammarFile() {
+		return grammarFileMetadata;
+	}
+
+	public String getClassName() {
+		return grammarDelegate.getClassName();
+	}
+
+	public String getQualifiedClassName() {
+		if ( isEmpty( getPackageName() ) ) {
+			return getClassName();
+		}
+		else {
+			return getPackageName() + '.' + getClassName();
+		}
+	}
+
+	public GrammarDelegate getSuperGrammarDelegate() {
+		return grammarDelegate.getSuperGrammarDelegate();
+	}
+
+	public boolean extendsStandardGrammar() {
+		final String superGrammarClassName = getSuperGrammarDelegate().getClassName();
+		return Parser.class.getName().equals( superGrammarClassName )
+				|| Parser.class.getSimpleName().equals( superGrammarClassName )
+				|| TreeParser.class.getName().equals( superGrammarClassName )
+				|| TreeParser.class.getSimpleName().equals( superGrammarClassName )
+				|| "Lexer".equals( superGrammarClassName );
+	}
+
+	public String getImportVocab() {
+		return grammarDelegate.getImportVocab();
+	}
+
+	public String getExportVocab() {
+		return grammarDelegate.getExportVocab();
+	}
+
+	public String getPackageName() {
+		return getGrammarFile().getPackageName();
+	}
+
+	/**
+	 * Determine the relative path of the generated parser java file.
+	 * 
+	 * @return The relative generated parser file path.
+	 */
+	public String determineGeneratedParserPath() {
+		if ( isEmpty( getPackageName() ) ) {
+			return getClassName() + ".java";
+		}
+		else {
+			return getPackageName().replace( '.', File.separatorChar )
+					+ File.separatorChar
+					+ getClassName()
+					+ ".java";
+		}
+	}
+
+	private boolean isEmpty(String packageName) {
+		return packageName == null || packageName.trim().length() == 0;
+	}
+}
diff --git a/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/metadata/MetadataExtracter.java b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/metadata/MetadataExtracter.java
new file mode 100644
index 0000000..0dd1d73
--- /dev/null
+++ b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/metadata/MetadataExtracter.java
@@ -0,0 +1,98 @@
+/*
+ * 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.antlr.metadata;
+
+import java.io.File;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.FileNotFoundException;
+
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.file.FileTree;
+
+/**
+ * Preprocess an Antlr grammar file so that dependencies between grammars
+ * can be properly determined such that they can be processed for generation
+ * in proper order later.
+ *
+ * @author Steve Ebersole
+ */
+public class MetadataExtracter {
+
+	public XRef extractMetadata(FileTree source) {
+		antlr.Tool tool = new antlr.Tool();
+		antlr.preprocessor.Hierarchy hierarchy = new antlr.preprocessor.Hierarchy( tool );
+
+		// first let antlr preprocess the grammars...
+		for ( File grammarFileFile : source.getFiles() ) {
+			final String grammarFilePath = grammarFileFile.getPath();
+
+			try {
+				hierarchy.readGrammarFile( grammarFilePath );
+			}
+			catch ( FileNotFoundException e ) {
+				// should never happen here
+				throw new IllegalStateException( "Received FileNotFoundException on already read file", e );
+			}
+		}
+
+		// now, do our processing using the antlr preprocessor results whenever possible.
+		XRef xref = new XRef( hierarchy );
+		for ( File grammarFileFile : source.getFiles() ) {
+
+			// determine the package name :(
+			String grammarPackageName = null;
+			try {
+				BufferedReader in = new BufferedReader( new FileReader( grammarFileFile ) );
+				try {
+					String line;
+					while ( ( line = in.readLine() ) != null ) {
+						line = line.trim();
+						if ( line.startsWith( "package" ) && line.endsWith( ";" ) ) {
+							grammarPackageName = line.substring( 8, line.length() -1 );
+							break;
+						}
+					}
+				}
+				finally {
+					try {
+						in.close();
+					}
+					catch ( IOException e ) {
+                        throw new UncheckedIOException(e);
+                    }
+				}
+			}
+			catch ( IOException e ) {
+				e.printStackTrace();
+			}
+
+			final String grammarFilePath = grammarFileFile.getPath();
+			antlr.preprocessor.GrammarFile antlrGrammarFile = hierarchy.getFile( grammarFilePath );
+
+			GrammarFileMetadata grammarFileMetadata = new GrammarFileMetadata(
+					grammarFileFile,
+					antlrGrammarFile,
+					grammarPackageName
+			);
+
+			xref.addGrammarFile( grammarFileMetadata );
+		}
+
+		return xref;
+	}
+}
diff --git a/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/metadata/XRef.java b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/metadata/XRef.java
new file mode 100644
index 0000000..40a4a55
--- /dev/null
+++ b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/metadata/XRef.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.api.plugins.antlr.metadata;
+
+import java.util.LinkedHashMap;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import antlr.preprocessor.Hierarchy;
+
+/**
+ * Models cross-reference (x-ref) info about {@link GrammarFileMetadata grammar files} such as {@link #filesByPath},
+ * {@link #filesByExportVocab} and {@link #filesByClassName}.
+ *
+ * @author Steve Ebersole
+ */
+public class XRef {
+	private final Hierarchy antlrHierarchy;
+
+	private LinkedHashMap<String, GrammarFileMetadata> filesByPath = new LinkedHashMap<String, GrammarFileMetadata>();
+	private HashMap<String, GrammarFileMetadata> filesByExportVocab = new HashMap<String, GrammarFileMetadata>();
+	private HashMap<String, GrammarFileMetadata> filesByClassName = new HashMap<String, GrammarFileMetadata>();
+
+	public XRef(Hierarchy antlrHierarchy) {
+		this.antlrHierarchy = antlrHierarchy;
+	}
+
+	public Object getAntlrHierarchy() {
+		return antlrHierarchy;
+	}
+
+	/**
+	 * Adds a grammar file to this cross-reference.
+	 * 
+	 * @param grammarFileMetadata The grammar file to add (and to be cross referenced).
+	 */
+	void addGrammarFile(GrammarFileMetadata grammarFileMetadata) {
+		filesByPath.put( grammarFileMetadata.getFilePath().getPath(), grammarFileMetadata );
+		for ( GrammarMetadata grammarMetadata : grammarFileMetadata.getGrammars() ) {
+			filesByClassName.put( grammarMetadata.getClassName(), grammarFileMetadata );
+			if ( grammarMetadata.getExportVocab() != null ) {
+				GrammarFileMetadata old = filesByExportVocab.put( grammarMetadata.getExportVocab(), grammarFileMetadata );
+				if ( old != null && old != grammarFileMetadata ) {
+					System.out.println( "[WARNING] : multiple grammars defined the same exportVocab : " + grammarMetadata
+							.getExportVocab() );
+				}
+			}
+		}
+	}
+
+	public Iterator<GrammarFileMetadata> iterateGrammarFiles() {
+		return filesByPath.values().iterator();
+	}
+
+	/**
+	 * Locate the grammar file metadata by grammar file path.
+	 *
+	 * @param path The grammar file path.
+	 * @return The grammar file metadata.  May be null if none found.
+	 */
+	public GrammarFileMetadata getGrammarFileByPath(String path) {
+		return filesByPath.get( path );
+	}
+
+	/**
+	 * Locate the grammar file metadata by the name of a class generated from one of its included grammars.
+	 *
+	 * @param className The generated class name.
+	 * @return The grammar file metadata.  May be null if none found.
+	 */
+	public GrammarFileMetadata getGrammarFileByClassName(String className) {
+		return filesByClassName.get( className );
+	}
+
+	/**
+	 * Locate the grammar file metadata by the name of a vocabulary exported from one of its included grammars.
+	 *
+	 * @param vocabName The vocabulary name
+	 * @return The grammar file metadata.  May be null if none found.
+	 */
+	public GrammarFileMetadata getGrammarFileByExportVocab(String vocabName) {
+		return filesByExportVocab.get( vocabName );
+	}
+}
diff --git a/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/plan/GenerationPlan.java b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/plan/GenerationPlan.java
new file mode 100644
index 0000000..ba176ac
--- /dev/null
+++ b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/plan/GenerationPlan.java
@@ -0,0 +1,79 @@
+/*
+ * 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.antlr.plan;
+
+import java.io.File;
+
+/**
+ * Models information relevant to generation of a particular Antlr grammar file.
+ *
+ * @author Steve Ebersole
+ */
+public class GenerationPlan {
+	private final File source;
+	private final File generationDirectory;
+
+	private File importVocabTokenTypesDirectory;
+	private boolean outOfDate;
+
+	/**
+	 * Instantiates a generation plan.
+	 *
+	 * @param source The grammar file.
+	 * @param generationDirectory The directory into which generated lexers and parsers should be written, accounting
+	 * for delared package.
+	 */
+	GenerationPlan(File source, File generationDirectory) {
+		this.source = source;
+		this.generationDirectory = generationDirectory;
+	}
+
+	public String getId() {
+		return getSource().getPath();
+	}
+
+	public File getSource() {
+		return source;
+	}
+
+	public File getGenerationDirectory() {
+		return generationDirectory;
+	}
+
+	public File getImportVocabTokenTypesDirectory() {
+		return importVocabTokenTypesDirectory;
+	}
+
+	void setImportVocabTokenTypesDirectory(File importVocabTokenTypesDirectory) {
+		this.importVocabTokenTypesDirectory = importVocabTokenTypesDirectory;
+	}
+
+	/**
+	 * Is the grammar file modeled by this plan out of considered out of date?
+	 *
+	 * @return True if the grammar generation is out of date (needs regen); false otherwise.
+	 */
+	public boolean isOutOfDate() {
+		return outOfDate;
+	}
+
+	/**
+	 * Marks the plan as out of date.
+	 */
+	void markOutOfDate() {
+		this.outOfDate = true;
+	}
+}
diff --git a/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/plan/GenerationPlanBuilder.java b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/plan/GenerationPlanBuilder.java
new file mode 100644
index 0000000..4701564
--- /dev/null
+++ b/subprojects/gradle-antlr/src/main/groovy/org/gradle/api/plugins/antlr/plan/GenerationPlanBuilder.java
@@ -0,0 +1,148 @@
+/*
+ * 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.antlr.plan;
+
+import java.io.File;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Iterator;
+import java.util.ArrayList;
+
+import org.gradle.api.plugins.antlr.metadata.XRef;
+import org.gradle.api.plugins.antlr.metadata.GrammarFileMetadata;
+import org.gradle.api.plugins.antlr.metadata.GrammarMetadata;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Builder for the properly order list of {@link GenerationPlan generation plans}.
+ * <p/>
+ * IMPL NOTE : Uses recursive calls to achieve ordering.
+ *
+ * @author Steve Ebersole
+ */
+public class GenerationPlanBuilder {
+	private static final Logger LOGGER = LoggerFactory.getLogger( GenerationPlanBuilder.class );
+
+	private final LinkedHashMap<String,GenerationPlan> generationPlans = new LinkedHashMap<String,GenerationPlan>();
+	private final File outputDirectory;
+
+	private XRef metadataXRef;
+
+	public GenerationPlanBuilder(File outputDirectory) {
+		this.outputDirectory = outputDirectory;
+	}
+
+	public synchronized List<GenerationPlan> buildGenerationPlans(XRef metadataXRef) {
+		this.metadataXRef = metadataXRef;
+
+		Iterator<GrammarFileMetadata> grammarFiles = metadataXRef.iterateGrammarFiles();
+		while ( grammarFiles.hasNext() ) {
+			final GrammarFileMetadata grammarFileMetadata = grammarFiles.next();
+			// NOTE : loacteOrBuildGenerationPlan populates the generationPlans map
+			loacteOrBuildGenerationPlan( grammarFileMetadata );
+		}
+
+		return new ArrayList<GenerationPlan>( generationPlans.values() );
+	}
+
+	private GenerationPlan loacteOrBuildGenerationPlan(GrammarFileMetadata grammarFileMetadata) {
+		GenerationPlan generationPlan = generationPlans.get( grammarFileMetadata.getFilePath().getPath() );
+		if ( generationPlan == null ) {
+			generationPlan = buildGenerationPlan( grammarFileMetadata );
+		}
+		return generationPlan;
+	}
+
+	private GenerationPlan buildGenerationPlan(GrammarFileMetadata grammarFileMetadata) {
+		File generationDirectory = isEmpty( grammarFileMetadata.getPackageName() )
+				? outputDirectory
+				: new File( outputDirectory, grammarFileMetadata.getPackageName().replace( '.', File.separatorChar ) );
+
+		GenerationPlan generationPlan = new GenerationPlan(
+				grammarFileMetadata.getFilePath(),
+				generationDirectory
+		);
+
+		for ( GrammarMetadata grammarMetadata : grammarFileMetadata.getGrammars() ) {
+			final File generatedParserFile = new File(
+					outputDirectory,
+					grammarMetadata.determineGeneratedParserPath()
+			);
+
+			if ( !generatedParserFile.exists() ) {
+				generationPlan.markOutOfDate();
+			}
+			else if ( generatedParserFile.lastModified() < generationPlan.getSource().lastModified() ) {
+				generationPlan.markOutOfDate();
+			}
+
+			// see if the grammar if out-of-date by way of its super-grammar(s) as gleaned from parsing the grammar file
+			if ( !grammarMetadata.extendsStandardGrammar() ) {
+				final GrammarFileMetadata superGrammarGrammarFileMetadata = grammarMetadata.getSuperGrammarDelegate()
+						.getAssociatedGrammarMetadata()
+						.getGrammarFile();
+				if ( superGrammarGrammarFileMetadata != null ) {
+					final GenerationPlan superGrammarGenerationPlan = loacteOrBuildGenerationPlan(
+							superGrammarGrammarFileMetadata
+					);
+					if ( superGrammarGenerationPlan.isOutOfDate() ) {
+						generationPlan.markOutOfDate();
+					}
+					else if ( superGrammarGenerationPlan.getSource().lastModified()
+							> generatedParserFile.lastModified() ) {
+						generationPlan.markOutOfDate();
+					}
+				}
+			}
+
+			// see if the grammar if out-of-date by way of its importVocab
+			if ( isNotEmpty( grammarMetadata.getImportVocab() ) ) {
+				final GrammarFileMetadata importVocabGrammarFileMetadata = metadataXRef.getGrammarFileByExportVocab( grammarMetadata.getImportVocab() );
+				if ( importVocabGrammarFileMetadata == null ) {
+					LOGGER.warn( "unable to locate grammar exporting specifcied import vocab [" + grammarMetadata.getImportVocab() + "]" );
+				}
+				else if ( !importVocabGrammarFileMetadata.getFilePath().equals( grammarFileMetadata.getFilePath() ) ) {
+					final GenerationPlan importVocabGrammarGenerationPlan = loacteOrBuildGenerationPlan(
+							importVocabGrammarFileMetadata
+					);
+					generationPlan.setImportVocabTokenTypesDirectory(
+							importVocabGrammarGenerationPlan.getGenerationDirectory()
+					);
+					if ( importVocabGrammarGenerationPlan.isOutOfDate() ) {
+						generationPlan.markOutOfDate();
+					}
+					else if ( importVocabGrammarGenerationPlan.getSource()
+							.lastModified() > generatedParserFile.lastModified() ) {
+						generationPlan.markOutOfDate();
+					}
+				}
+			}
+		}
+
+		generationPlans.put( generationPlan.getId(), generationPlan );
+		return generationPlan;
+	}
+
+	private boolean isEmpty(String string) {
+		return string == null || string.trim().length() == 0;
+	}
+
+	private boolean isNotEmpty(String string) {
+		return ! isEmpty( string );
+	}
+
+}
diff --git a/subprojects/gradle-antlr/src/main/resources/META-INF/gradle-plugins/antlr.properties b/subprojects/gradle-antlr/src/main/resources/META-INF/gradle-plugins/antlr.properties
new file mode 100644
index 0000000..f31f7fa
--- /dev/null
+++ b/subprojects/gradle-antlr/src/main/resources/META-INF/gradle-plugins/antlr.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+implementation-class=org.gradle.api.plugins.antlr.AntlrPlugin
diff --git a/subprojects/gradle-antlr/src/test/groovy/org/gradle/api/plugins/antlr/AntlrPluginTest.groovy b/subprojects/gradle-antlr/src/test/groovy/org/gradle/api/plugins/antlr/AntlrPluginTest.groovy
new file mode 100644
index 0000000..632cd09
--- /dev/null
+++ b/subprojects/gradle-antlr/src/test/groovy/org/gradle/api/plugins/antlr/AntlrPluginTest.groovy
@@ -0,0 +1,67 @@
+/*
+ * 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.antlr
+
+import spock.lang.Specification
+import org.gradle.api.Project
+import org.gradle.util.HelperUtil
+
+class AntlrPluginTest extends Specification {
+    private final Project project = HelperUtil.createRootProject()
+    private final AntlrPlugin plugin = new AntlrPlugin()
+
+    def addsAntlrPropertiesToEachSourceSet() {
+        when:
+        plugin.apply(project)
+
+        then:
+        def sourceSet = project.sourceSets.main
+        sourceSet.antlr.srcDirs == [project.file('src/main/antlr')] as Set
+
+        sourceSet = project.sourceSets.test
+        sourceSet.antlr.srcDirs == [project.file('src/test/antlr')] as Set
+
+        when:
+        project.sourceSets.add('custom')
+
+        then:
+        sourceSet = project.sourceSets.custom
+        sourceSet.antlr.srcDirs == [project.file('src/custom/antlr')] as Set
+    }
+    
+    def addsTaskForEachSourceSet() {
+        when:
+        plugin.apply(project)
+
+        then:
+        def task = project.tasks.generateGrammarSource
+        task instanceof AntlrTask
+        project.tasks.compileJava.taskDependencies.getDependencies(null).contains(task)
+
+        task = project.tasks.generateTestGrammarSource
+        task instanceof AntlrTask
+        project.tasks.compileTestJava.taskDependencies.getDependencies(null).contains(task)
+
+        when:
+        project.sourceSets.add('custom')
+
+        then:
+        task = project.tasks.generateCustomGrammarSource
+        task instanceof AntlrTask
+        project.tasks.compileCustomJava.taskDependencies.getDependencies(null).contains(task)
+    }
+}
diff --git a/subprojects/gradle-code-quality/code-quality.gradle b/subprojects/gradle-code-quality/code-quality.gradle
new file mode 100644
index 0000000..591e09d
--- /dev/null
+++ b/subprojects/gradle-code-quality/code-quality.gradle
@@ -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.
+ */
+dependencies {
+    groovy libraries.groovy_depends
+
+    compile project(':core')
+    compile project(':plugins')
+
+    compile "org.codenarc:CodeNarc:0.9 at jar"
+    compile libraries.slf4j_api
+ 
+    runtime libraries.log4j_to_slf4j,
+            "org.gmetrics:GMetrics:0.2 at jar"
+
+    def BEAN_UTILS = ["commons-beanutils:commons-beanutils-core:1.7.0 at jar", "commons-collections:commons-collections:2.0 at jar", libraries.jcl_to_slf4j]
+    runtime "checkstyle:checkstyle:5.1 at jar",
+            libraries.antlr,
+            libraries.jcl_to_slf4j,
+            libraries.google_collections,
+            BEAN_UTILS
+    
+    testCompile project(path: ':core', configuration: 'testFixtures')
+    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+}
diff --git a/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/AntCheckstyle.groovy b/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/AntCheckstyle.groovy
new file mode 100644
index 0000000..b91b518
--- /dev/null
+++ b/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/AntCheckstyle.groovy
@@ -0,0 +1,42 @@
+/*
+ * 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.plugins.quality
+
+import org.gradle.api.AntBuilder
+import org.gradle.api.GradleException
+import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.AntBuilderAware
+
+class AntCheckstyle {
+    def checkstyle(AntBuilder ant, FileCollection source, File configFile, File resultFile, AntBuilderAware classpath, Map<String, ?> properties, boolean ignoreFailures) {
+        String propertyName = "org.gradle.checkstyle.violations"
+
+        ant.project.addTaskDefinition('checkstyle', getClass().classLoader.loadClass('com.puppycrawl.tools.checkstyle.CheckStyleTask'))
+        ant.checkstyle(config: configFile, failOnViolation: false, failureProperty: propertyName) {
+            source.addToAntBuilder(ant, 'fileset', FileCollection.AntType.FileSet)
+            classpath.addToAntBuilder(ant, 'classpath')
+            formatter(type: 'plain', useFile: false)
+            formatter(type: 'xml', toFile: resultFile)
+            properties.each {key, value ->
+                property(key: key, value: value.toString())
+            }
+        }
+
+        if (!ignoreFailures && ant.properties[propertyName]) {
+            throw new GradleException("Checkstyle check violations were found in $source. See the report at $resultFile.")
+        }
+    }
+}
diff --git a/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/AntCodeNarc.groovy b/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/AntCodeNarc.groovy
new file mode 100644
index 0000000..953e745
--- /dev/null
+++ b/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/AntCodeNarc.groovy
@@ -0,0 +1,42 @@
+/*
+ * 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.quality
+
+import org.apache.tools.ant.BuildException
+import org.codenarc.ant.CodeNarcTask
+import org.gradle.api.AntBuilder
+import org.gradle.api.GradleException
+import org.gradle.api.file.FileCollection
+
+class AntCodeNarc {
+    def execute(AntBuilder ant, FileCollection source, File configFile, File reportFile, boolean ignoreFailures) {
+        ant.project.addTaskDefinition('codenarc', CodeNarcTask)
+        try {
+            ant.codenarc(ruleSetFiles: "file:$configFile", maxPriority1Violations: 0, maxPriority2Violations: 0, maxPriority3Violations: 0) {
+                report(type: 'html', toFile: reportFile)
+                source.addToAntBuilder(ant, 'fileset', FileCollection.AntType.FileSet)
+            }
+        } catch (BuildException e) {
+            if (e.message.matches('Exceeded maximum number of priority \\d* violations.*')) {
+                if (ignoreFailures) {
+                    return
+                }
+                throw new GradleException("CodeNarc check violations were found in $source. See the report at $reportFile.", e)
+            }
+            throw e
+        }
+    }
+}
diff --git a/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.java b/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.java
new file mode 100644
index 0000000..b1d060b
--- /dev/null
+++ b/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.java
@@ -0,0 +1,92 @@
+/*
+ * 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.plugins.quality;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.tasks.*;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+public class Checkstyle extends SourceTask implements VerificationTask {
+    private File configFile;
+
+    private File resultFile;
+
+    private FileCollection classpath;
+
+    private Map<String, Object> properties = new HashMap<String, Object>();
+
+    private AntCheckstyle antCheckstyle = new AntCheckstyle();
+
+    private boolean ignoreFailures;
+
+    @TaskAction
+    public void check() {
+        antCheckstyle.checkstyle(getAnt(), getSource(), getConfigFile(), getResultFile(), getClasspath(), getProperties(), isIgnoreFailures());
+    }
+
+    @InputFile
+    public File getConfigFile() {
+        return configFile;
+    }
+
+    public void setConfigFile(File configFile) {
+        this.configFile = configFile;
+    }
+
+    @OutputFile
+    public File getResultFile() {
+        return resultFile;
+    }
+
+    public void setResultFile(File resultFile) {
+        this.resultFile = resultFile;
+    }
+
+    @InputFiles
+    public FileCollection getClasspath() {
+        return classpath;
+    }
+
+    public void setClasspath(FileCollection classpath) {
+        this.classpath = classpath;
+    }
+
+    public Map<String, Object> getProperties() {
+        return properties;
+    }
+
+    public void setProperties(Map<String, Object> properties) {
+        this.properties = properties;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isIgnoreFailures() {
+        return ignoreFailures;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Checkstyle setIgnoreFailures(boolean ignoreFailures) {
+        this.ignoreFailures = ignoreFailures;
+        return this;
+    }
+}
diff --git a/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarc.java b/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarc.java
new file mode 100644
index 0000000..c5e239e
--- /dev/null
+++ b/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeNarc.java
@@ -0,0 +1,68 @@
+/*
+ * 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.plugins.quality;
+
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.tasks.*;
+
+import java.io.File;
+
+public class CodeNarc extends SourceTask implements VerificationTask {
+    private AntCodeNarc antCodeNarc = new AntCodeNarc();
+
+    private File reportFile;
+    private File configFile;
+    private boolean ignoreFailures;
+
+    @TaskAction
+    public void check() {
+        getLogging().captureStandardOutput(LogLevel.INFO);
+        antCodeNarc.execute(getAnt(), getSource(), getConfigFile(), getReportFile(), isIgnoreFailures());
+    }
+
+    @InputFile
+    public File getConfigFile() {
+        return configFile;
+    }
+
+    public void setConfigFile(File configFile) {
+        this.configFile = configFile;
+    }
+
+    @OutputFile
+    public File getReportFile() {
+        return reportFile;
+    }
+
+    public void setReportFile(File reportFile) {
+        this.reportFile = reportFile;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isIgnoreFailures() {
+        return ignoreFailures;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public CodeNarc setIgnoreFailures(boolean ignoreFailures) {
+        this.ignoreFailures = ignoreFailures;
+        return this;
+    }
+}
diff --git a/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeQualityPlugin.groovy b/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeQualityPlugin.groovy
new file mode 100644
index 0000000..6c42702
--- /dev/null
+++ b/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/CodeQualityPlugin.groovy
@@ -0,0 +1,102 @@
+/*
+ * 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.quality
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.plugins.GroovyBasePlugin
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.plugins.JavaPluginConvention
+import org.gradle.api.plugins.ReportingBasePlugin
+import org.gradle.api.tasks.GroovySourceSet
+import org.gradle.api.tasks.SourceSet
+
+/**
+ * A {@link Plugin} which measures and enforces code quality for Java and Groovy projects.
+ */
+public class CodeQualityPlugin implements Plugin<Project> {
+    public static final String CHECKSTYLE_MAIN_TASK = "checkstyleMain";
+    public static final String CHECKSTYLE_TEST_TASK = "checkstyleTest";
+    public static final String CODE_NARC_MAIN_TASK = "codenarcMain";
+    public static final String CODE_NARC_TEST_TASK = "codenarcTest";
+
+    public void apply(final Project project) {
+        project.plugins.apply(ReportingBasePlugin.class);
+
+        JavaCodeQualityPluginConvention javaPluginConvention = new JavaCodeQualityPluginConvention(project)
+        project.convention.plugins.javaCodeQuality = javaPluginConvention;
+
+        GroovyCodeQualityPluginConvention groovyPluginConvention = new GroovyCodeQualityPluginConvention(project)
+        project.convention.plugins.groovyCodeQuality = groovyPluginConvention;
+
+        configureCheckstyleDefaults(project, javaPluginConvention)
+        configureCodeNarcDefaults(project, groovyPluginConvention)
+
+        project.plugins.withType(JavaBasePlugin.class).allPlugins {
+            configureForJavaPlugin(project, javaPluginConvention);
+        }
+        project.plugins.withType(GroovyBasePlugin.class).allPlugins {
+            configureForGroovyPlugin(project, groovyPluginConvention);
+        }
+    }
+
+    private void configureCheckstyleDefaults(Project project, JavaCodeQualityPluginConvention pluginConvention) {
+        project.tasks.withType(Checkstyle.class).allTasks {Checkstyle checkstyle ->
+            checkstyle.conventionMapping.configFile = { pluginConvention.checkstyleConfigFile }
+            checkstyle.conventionMapping.map('properties') { pluginConvention.checkstyleProperties }
+        }
+    }
+
+    private void configureCodeNarcDefaults(Project project, GroovyCodeQualityPluginConvention pluginConvention) {
+        project.tasks.withType(CodeNarc.class).allTasks {CodeNarc codenarc ->
+            codenarc.conventionMapping.configFile = { pluginConvention.codeNarcConfigFile }
+        }
+    }
+
+    private void configureCheckTask(Project project) {
+        Task task = project.tasks[JavaBasePlugin.CHECK_TASK_NAME]
+        task.setDescription("Executes all quality checks");
+        task.dependsOn { project.tasks.withType(Checkstyle.class).all; }
+        task.dependsOn { project.tasks.withType(CodeNarc.class).all; }
+    }
+
+    private void configureForJavaPlugin(Project project, JavaCodeQualityPluginConvention pluginConvention) {
+        configureCheckTask(project);
+
+        project.convention.getPlugin(JavaPluginConvention.class).sourceSets.allObjects {SourceSet set ->
+            Checkstyle checkstyle = project.tasks.add(set.getTaskName("checkstyle", null), Checkstyle.class);
+            checkstyle.description = "Runs Checkstyle against the $set.name Java source code."
+            checkstyle.conventionMapping.defaultSource = { set.allJava; }
+            checkstyle.conventionMapping.configFile = { pluginConvention.checkstyleConfigFile }
+            checkstyle.conventionMapping.resultFile = { new File(pluginConvention.checkstyleResultsDir, "${set.name}.xml") }
+            checkstyle.conventionMapping.classpath = { set.compileClasspath; }
+        }
+    }
+
+    private void configureForGroovyPlugin(Project project, GroovyCodeQualityPluginConvention pluginConvention) {
+        project.convention.getPlugin(JavaPluginConvention.class).sourceSets.allObjects {SourceSet set ->
+            GroovySourceSet groovySourceSet = set.convention.getPlugin(GroovySourceSet.class)
+            CodeNarc codeNarc = project.tasks.add(set.getTaskName("codenarc", null), CodeNarc.class);
+            codeNarc.setDescription("Runs CodeNarc against the $set.name Groovy source code.");
+            codeNarc.conventionMapping.defaultSource = { groovySourceSet.allGroovy; }
+            codeNarc.conventionMapping.configFile = { pluginConvention.codeNarcConfigFile; }
+            codeNarc.conventionMapping.reportFile = { new File(pluginConvention.codeNarcReportsDir, "${set.name}.html"); }
+        }
+    }
+}
diff --git a/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/GroovyCodeQualityPluginConvention.groovy b/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/GroovyCodeQualityPluginConvention.groovy
new file mode 100644
index 0000000..0097be9
--- /dev/null
+++ b/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/GroovyCodeQualityPluginConvention.groovy
@@ -0,0 +1,39 @@
+/*
+ * 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.plugins.quality
+
+import org.gradle.api.Project
+import org.gradle.api.internal.project.ProjectInternal
+
+class GroovyCodeQualityPluginConvention {
+    String codeNarcConfigFileName
+    String codeNarcReportsDirName
+    private final ProjectInternal project
+
+    def GroovyCodeQualityPluginConvention(Project project) {
+        this.project = project
+        codeNarcConfigFileName = 'config/codenarc/codenarc.xml'
+        codeNarcReportsDirName = 'codenarc'
+    }
+
+    File getCodeNarcConfigFile() {
+        project.file(codeNarcConfigFileName)
+    }
+
+    File getCodeNarcReportsDir() {
+        project.fileResolver.withBaseDir(project.reportsDir).resolve(codeNarcReportsDirName)
+    }
+}
diff --git a/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/JavaCodeQualityPluginConvention.groovy b/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/JavaCodeQualityPluginConvention.groovy
new file mode 100644
index 0000000..88bb31f
--- /dev/null
+++ b/subprojects/gradle-code-quality/src/main/groovy/org/gradle/api/plugins/quality/JavaCodeQualityPluginConvention.groovy
@@ -0,0 +1,40 @@
+/*
+ * 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.plugins.quality
+
+import org.gradle.api.Project
+import org.gradle.api.internal.project.ProjectInternal
+
+class JavaCodeQualityPluginConvention {
+    String checkstyleConfigFileName
+    String checkstyleResultsDirName
+    Map<String, Object> checkstyleProperties = [:]
+    private ProjectInternal project
+
+    def JavaCodeQualityPluginConvention(Project project) {
+        this.project = project
+        checkstyleConfigFileName = 'config/checkstyle/checkstyle.xml'
+        checkstyleResultsDirName = 'checkstyle'
+    }
+
+    File getCheckstyleConfigFile() {
+        project.file(checkstyleConfigFileName)
+    }
+
+    File getCheckstyleResultsDir() {
+        project.fileResolver.withBaseDir(project.buildDir).resolve(checkstyleResultsDirName)
+    }
+}
diff --git a/subprojects/gradle-code-quality/src/main/resources/META-INF/gradle-plugins/code-quality.properties b/subprojects/gradle-code-quality/src/main/resources/META-INF/gradle-plugins/code-quality.properties
new file mode 100644
index 0000000..cb7c257
--- /dev/null
+++ b/subprojects/gradle-code-quality/src/main/resources/META-INF/gradle-plugins/code-quality.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+implementation-class=org.gradle.api.plugins.quality.CodeQualityPlugin
diff --git a/subprojects/gradle-code-quality/src/test/groovy/org/gradle/api/plugins/quality/CodeQualityPluginTest.groovy b/subprojects/gradle-code-quality/src/test/groovy/org/gradle/api/plugins/quality/CodeQualityPluginTest.groovy
new file mode 100644
index 0000000..2479b54
--- /dev/null
+++ b/subprojects/gradle-code-quality/src/test/groovy/org/gradle/api/plugins/quality/CodeQualityPluginTest.groovy
@@ -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.api.plugins.quality
+
+import org.gradle.api.Project
+import org.gradle.api.plugins.GroovyPlugin
+import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.plugins.ReportingBasePlugin
+import org.gradle.util.HelperUtil
+import org.junit.Test
+import static org.gradle.util.Matchers.*
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.gradle.api.plugins.JavaBasePlugin
+
+class CodeQualityPluginTest {
+    private final Project project = HelperUtil.createRootProject()
+    private final CodeQualityPlugin plugin = new CodeQualityPlugin()
+
+    @Test public void appliesReportingBasePlugin() {
+        plugin.apply(project)
+
+        assertTrue(project.plugins.hasPlugin(ReportingBasePlugin))
+    }
+
+    @Test public void addsConventionObjectsToProject() {
+        plugin.apply(project)
+
+        assertThat(project.convention.plugins.javaCodeQuality, instanceOf(JavaCodeQualityPluginConvention))
+        assertThat(project.convention.plugins.groovyCodeQuality, instanceOf(GroovyCodeQualityPluginConvention))
+    }
+
+    @Test public void createsTasksAndAppliesMappingsForEachJavaSourceSet() {
+        plugin.apply(project)
+
+        project.plugins.apply(JavaPlugin)
+        project.checkstyleProperties.someProp = 'someValue'
+
+        def task = project.tasks[CodeQualityPlugin.CHECKSTYLE_MAIN_TASK]
+        assertThat(task, instanceOf(Checkstyle))
+        assertThat(task.defaultSource, equalTo(project.sourceSets.main.allJava))
+        assertThat(task.configFile, equalTo(project.checkstyleConfigFile))
+        assertThat(task.resultFile, equalTo(project.file("build/checkstyle/main.xml")))
+        assertThat(task.properties, equalTo(project.checkstyleProperties))
+        assertThat(task, dependsOn())
+
+        task = project.tasks[CodeQualityPlugin.CHECKSTYLE_TEST_TASK]
+        assertThat(task, instanceOf(Checkstyle))
+        assertThat(task.defaultSource, equalTo(project.sourceSets.test.allJava))
+        assertThat(task.configFile, equalTo(project.checkstyleConfigFile))
+        assertThat(task.resultFile, equalTo(project.file("build/checkstyle/test.xml")))
+        assertThat(task.properties, equalTo(project.checkstyleProperties))
+        assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
+
+        project.sourceSets.add('custom')
+        task = project.tasks['checkstyleCustom']
+        assertThat(task, instanceOf(Checkstyle))
+        assertThat(task.defaultSource, equalTo(project.sourceSets.custom.allJava))
+        assertThat(task.configFile, equalTo(project.checkstyleConfigFile))
+        assertThat(task.resultFile, equalTo(project.file("build/checkstyle/custom.xml")))
+        assertThat(task.properties, equalTo(project.checkstyleProperties))
+        assertThat(task, dependsOn())
+
+        task = project.tasks[JavaBasePlugin.CHECK_TASK_NAME]
+        assertThat(task, dependsOn(hasItems(CodeQualityPlugin.CHECKSTYLE_MAIN_TASK, CodeQualityPlugin.CHECKSTYLE_TEST_TASK, 'checkstyleCustom')))
+    }
+
+    @Test public void createsTasksAndAppliesMappingsForEachGroovySourceSet() {
+        plugin.apply(project)
+
+        project.plugins.apply(GroovyPlugin)
+
+        def task = project.tasks[CodeQualityPlugin.CODE_NARC_MAIN_TASK]
+        assertThat(task, instanceOf(CodeNarc))
+        assertThat(task.defaultSource, equalTo(project.sourceSets.main.allGroovy))
+        assertThat(task.configFile, equalTo(project.codeNarcConfigFile))
+        assertThat(task.reportFile, equalTo(project.file("build/reports/codenarc/main.html")))
+        assertThat(task, dependsOn())
+
+        task = project.tasks[CodeQualityPlugin.CODE_NARC_TEST_TASK]
+        assertThat(task, instanceOf(CodeNarc))
+        assertThat(task.defaultSource, equalTo(project.sourceSets.test.allGroovy))
+        assertThat(task.configFile, equalTo(project.codeNarcConfigFile))
+        assertThat(task.reportFile, equalTo(project.file("build/reports/codenarc/test.html")))
+        assertThat(task, dependsOn())
+
+        project.sourceSets.add('custom')
+        task = project.tasks['codenarcCustom']
+        assertThat(task, instanceOf(CodeNarc))
+        assertThat(task.defaultSource, equalTo(project.sourceSets.custom.allGroovy))
+        assertThat(task.configFile, equalTo(project.codeNarcConfigFile))
+        assertThat(task.reportFile, equalTo(project.file("build/reports/codenarc/custom.html")))
+        assertThat(task, dependsOn())
+
+        task = project.tasks[JavaBasePlugin.CHECK_TASK_NAME]
+        assertThat(task, dependsOn(hasItem(CodeQualityPlugin.CODE_NARC_MAIN_TASK)))
+        assertThat(task, dependsOn(hasItem(CodeQualityPlugin.CODE_NARC_TEST_TASK)))
+        assertThat(task, dependsOn(hasItem('codenarcCustom')))
+    }
+
+    @Test public void configuresAdditionalTasksDefinedByTheBuildScript() {
+        plugin.apply(project)
+
+        def task = project.tasks.add('customCheckstyle', Checkstyle)
+        assertThat(task.source, isEmpty())
+        assertThat(task.configFile, equalTo(project.checkstyleConfigFile))
+        assertThat(task.resultFile, nullValue())
+        assertThat(task.classpath, nullValue())
+
+        task = project.tasks.add('customCodeNarc', CodeNarc)
+        assertThat(task.source, isEmpty())
+        assertThat(task.configFile, equalTo(project.codeNarcConfigFile))
+        assertThat(task.reportFile, nullValue())
+    }
+}
diff --git a/subprojects/gradle-core/core.gradle b/subprojects/gradle-core/core.gradle
new file mode 100644
index 0000000..c2de68c
--- /dev/null
+++ b/subprojects/gradle-core/core.gradle
@@ -0,0 +1,154 @@
+/*
+ * 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.
+ */
+
+import java.text.DateFormat
+import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency
+
+apply plugin: 'groovy'
+
+configurations {
+    testFixtures
+    testFixturesRuntime {
+        extendsFrom testFixtures, testRuntime
+    }
+    integTestCompile {
+        extendsFrom testCompile
+    }
+    integTestRuntime {
+        extendsFrom intTestCompile, testRuntime
+    }
+    integTestFixtures {
+        extendsFrom testFixtures
+    }
+    integTestFixturesRuntime {
+        extendsFrom integTestFixtures, integTestRuntime
+    }
+}
+
+sourceSets {
+    integTest {
+        compileClasspath = sourceSets.test.classes + sourceSets.main.classes + configurations.integTestCompile
+        runtimeClasspath = classes + compileClasspath + configurations.integTestRuntime
+    }
+}
+
+dependencies {
+    groovy libraries.groovy_depends
+
+    compile "commons-httpclient:commons-httpclient:3.0 at jar", "commons-codec:commons-codec:1.2 at jar", libraries.jcl_to_slf4j
+
+    compile libraries.ivy, "com.jcraft:jsch:0.1.42 at jar", 'com.jcraft:jzlib:1.0.7 at jar'
+
+    compile libraries.jopt_simple,
+            libraries.ant,
+            libraries.ant_nodeps,
+            libraries.logback_classic,
+            libraries.logback_core,
+            libraries.slf4j_api,
+            libraries.jul_to_slf4j,
+            libraries.commons_io,
+            libraries.commons_lang,
+            "commons-codec:commons-codec:1.2 at jar",
+            libraries.google_collections,
+            "commons-collections:commons-collections:3.2.1 at jar",
+            "slide:webdavlib:2.0 at jar",
+            "org.apache.maven:maven-ant-tasks:2.1.0 at jar",
+            libraries.asm_all,
+            'org.fusesource.jansi:jansi:1.2.1',
+            'org.jruby.ext.posix:jna-posix:1.0.3',
+            'org.sonatype.pmaven:pmaven-common:0.8-20100325 at jar',
+            'org.sonatype.pmaven:pmaven-groovy:0.8-20100325 at jar',
+            'org.codehaus.plexus:plexus-component-annotations:1.5.2'
+
+    runtime 'net.java.dev.jna:jna:3.2.2'
+
+    runtime libraries.log4j_to_slf4j, libraries.jcl_to_slf4j
+
+    testCompile libraries.xmlunit
+
+    runtime libraries.ant_launcher
+    testCompile libraries.ant_launcher
+
+    integTestCompile libraries.jetty_depends
+
+    testFixtures sourceSets.test.classes, sourceSets.main.classes
+    integTestFixtures sourceSets.integTest.classes
+}
+
+task versionProperties(type: WriteVersionProperties) {
+    propertiesFile = new File(sourceSets.main.classesDir, GradleVersion.FILE_NAME)
+}
+
+classes.dependsOn(versionProperties)
+
+task ideVersionProperties(type: WriteVersionProperties) {
+    propertiesFile = new File(ideDir, "resources/test/$GradleVersion.FILE_NAME")
+}
+
+ide.dependsOn ideVersionProperties
+
+ideaModule {
+    dependsOn ideVersionProperties
+    testSourceDirs += project.sourceSets.integTest.groovy.srcDirs
+    scopes.COMPILE.plus.add(configurations.detachedConfiguration(new DefaultSelfResolvingDependency(files(new File(ideDir, "resources/test/")))))
+    scopes.TEST.plus.add(configurations.detachedConfiguration(new DefaultSelfResolvingDependency(files(project.sourceSets.integTest.resources.srcDirs))))
+    scopes.TEST.plus.add(configurations.integTestCompile)
+    scopes.TEST.plus.add(configurations.integTestRuntime)
+}
+
+eclipseClasspath {
+    dependsOn ideVersionProperties
+    plusConfigurations.add(configurations.detachedConfiguration(new DefaultSelfResolvingDependency(files(new File(ideDir, "resources/test/")))))
+    plusConfigurations.add(configurations.detachedConfiguration(new DefaultSelfResolvingDependency(files(project.sourceSets.integTest.resources.srcDirs))))
+    plusConfigurations.add(configurations.integTestCompile)
+    plusConfigurations.add(configurations.integTestRuntime)
+	whenConfigured { classpath ->
+		classpath.entries.removeAll { entry -> entry.kind == 'src' && entry.path.startsWith('src/integTest/resources')}
+	}
+}
+
+
+
+[compileGroovy, compileTestGroovy]*.groovyOptions*.fork(memoryInitialSize: '128M', memoryMaximumSize: '1G')
+
+test {
+    jvmArgs '-Xms128m', '-Xmx512m', '-XX:MaxPermSize=128m', '-XX:+HeapDumpOnOutOfMemoryError'
+}
+
+class WriteVersionProperties extends DefaultTask {
+    @Input
+    String getVersion() { return project.version.toString() }
+
+    @Input
+    Date getBuildTime() { return project.version.buildTime }
+
+    @OutputFile
+    File propertiesFile
+
+    @TaskAction
+    def void generate() {
+        logger.info('Write version properties to: {}', propertiesFile)
+        Properties versionProperties = new Properties()
+        versionProperties.putAll([
+                (GradleVersion.VERSION): version,
+                (GradleVersion.BUILD_TIME): DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL).format(buildTime)
+        ])
+        propertiesFile.withOutputStream {
+            versionProperties.store(it, '')
+        }
+
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/AbstractIntegrationTest.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/AbstractIntegrationTest.java
new file mode 100644
index 0000000..b37251f
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/AbstractIntegrationTest.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.integtests;
+
+import org.gradle.CacheUsage;
+import org.gradle.StartParameter;
+import org.gradle.integtests.fixtures.*;
+import org.gradle.util.TestFile;
+import org.gradle.util.TestFileContext;
+import org.junit.Rule;
+
+import java.io.File;
+
+public abstract class AbstractIntegrationTest implements TestFileContext {
+    @Rule public GradleDistribution distribution = new GradleDistribution();
+
+    public TestFile getTestDir() {
+        return distribution.getTestDir();
+    }
+
+    public TestFile file(Object... path) {
+        return getTestDir().file(path);
+    }
+
+    public TestFile testFile(String name) {
+        return file(name);
+    }
+
+    private StartParameter startParameter() {
+        StartParameter parameter = new StartParameter();
+        parameter.setGradleUserHomeDir(distribution.getUserHomeDir());
+
+        parameter.setSearchUpwards(false);
+        parameter.setCacheUsage(CacheUsage.ON);
+        parameter.setCurrentDir(getTestDir());
+
+        return parameter;
+    }
+
+    protected GradleExecuter inTestDirectory() {
+        return inDirectory(getTestDir());
+    }
+
+    protected GradleExecuter inDirectory(File directory) {
+        return new InProcessGradleExecuter(startParameter()).inDirectory(directory);
+    }
+
+    protected GradleExecuter usingBuildFile(File file) {
+        StartParameter parameter = startParameter();
+        parameter.setBuildFile(file);
+        return new InProcessGradleExecuter(parameter);
+    }
+
+    protected GradleExecuter usingBuildScript(String script) {
+        return new InProcessGradleExecuter(startParameter()).usingBuildScript(script);
+    }
+
+    protected GradleExecuter usingProjectDir(File projectDir) {
+        StartParameter parameter = startParameter();
+        parameter.setProjectDir(projectDir);
+        return new InProcessGradleExecuter(parameter);
+    }
+
+    protected ArtifactBuilder artifactBuilder() {
+        return new GradleBackedArtifactBuilder(new InProcessGradleExecuter(startParameter()), getTestDir().file("artifacts"));
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/AntProjectIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/AntProjectIntegrationTest.groovy
new file mode 100644
index 0000000..7f29fd8
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/AntProjectIntegrationTest.groovy
@@ -0,0 +1,158 @@
+/*
+ * 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
+
+import org.gradle.integtests.fixtures.ExecutionFailure
+import org.gradle.util.TestFile
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+
+public class AntProjectIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void antTargetsAndGradleTasksCanDependOnEachOther() {
+        testFile('build.xml') << """
+<project>
+    <target name='target1' depends='target2,init'>
+        <touch file='build/target1.txt'/>
+    </target>
+    <target name='target2' depends='init'>
+        <touch file='build/target2.txt'/>
+    </target>
+</project>
+"""
+        testFile('build.gradle') << """
+ant.importBuild(file('build.xml'))
+task init << { buildDir.mkdirs() }
+task ant(dependsOn: target1)
+"""
+        TestFile target1File = testFile('build/target1.txt')
+        TestFile target2File = testFile('build/target2.txt')
+        target1File.assertDoesNotExist()
+        target2File.assertDoesNotExist()
+
+        inTestDirectory().withTasks('ant').run().assertTasksExecuted(':init', ':target2', ':target1', ':ant')
+
+        target1File.assertExists()
+        target2File.assertExists()
+    }
+
+    @Test
+    public void canImportMultipleBuildFilesWithDifferentBaseDirs() {
+        testFile('project1/build.xml') << """
+<project>
+    <target name='target1'>
+        <mkdir dir='build'/>
+        <touch file='build/target1.txt'/>
+    </target>
+</project>
+"""
+        testFile('project2/build.xml') << """
+<project>
+    <target name='target2'>
+        <mkdir dir='build'/>
+        <touch file='build/target2.txt'/>
+    </target>
+</project>
+"""
+        testFile('build.gradle') << """
+ant.importBuild('project1/build.xml')
+ant.importBuild('project2/build.xml')
+task ant(dependsOn: [target1, target2])
+"""
+        TestFile target1File = testFile('project1/build/target1.txt')
+        TestFile target2File = testFile('project2/build/target2.txt')
+        target1File.assertDoesNotExist()
+        target2File.assertDoesNotExist()
+
+        inTestDirectory().withTasks('ant').run().assertTasksExecuted(':target1', ':target2', ':ant')
+
+        target1File.assertExists()
+        target2File.assertExists()
+    }
+
+    @Test
+    public void handlesAntImportsOk() {
+        testFile('imported.xml') << """
+<project>
+    <target name='target1'>
+        <mkdir dir='build'/>
+        <touch file='build/target1.txt'/>
+    </target>
+</project>
+"""
+        testFile('build.xml') << """
+<project>
+    <import file="imported.xml"/>
+    <target name='target2'>
+        <mkdir dir='build'/>
+        <touch file='build/target2.txt'/>
+    </target>
+</project>
+"""
+        testFile('build.gradle') << """
+ant.importBuild('build.xml')
+task ant(dependsOn: [target1, target2])
+"""
+        TestFile target1File = testFile('build/target1.txt')
+        TestFile target2File = testFile('build/target2.txt')
+        target1File.assertDoesNotExist()
+        target2File.assertDoesNotExist()
+
+        inTestDirectory().withTasks('ant').run().assertTasksExecuted(':target1', ':target2', ':ant')
+
+        target1File.assertExists()
+        target2File.assertExists()
+    }
+
+    @Test
+    public void reportsAntBuildParseFailure() {
+        TestFile antBuildFile = testFile('build.xml')
+        antBuildFile << """
+<project>
+    <target name='target1'
+        <unknown/>
+    </target>
+</project>
+"""
+        TestFile buildFile = testFile('build.gradle')
+        buildFile << """
+ant.importBuild('build.xml')
+"""
+        ExecutionFailure failure = inTestDirectory().withTasks('target1').runWithFailure()
+        failure.assertHasFileName("Build file '$buildFile'")
+        failure.assertThatDescription(startsWith('A problem occurred evaluating root project'))
+        failure.assertHasCause("Could not import Ant build file '$antBuildFile'.")
+    }
+
+    @Test
+    public void reportsAntTaskExecutionFailure() {
+        testFile('build.xml') << """
+<project>
+    <target name='target1'>
+        <fail>broken</fail>
+    </target>
+</project>
+"""
+        TestFile buildFile = testFile('build.gradle')
+        buildFile << """
+ant.importBuild('build.xml')
+"""
+        ExecutionFailure failure = inTestDirectory().withTasks('target1').runWithFailure()
+        failure.assertHasFileName("Build file '$buildFile'")
+        failure.assertHasDescription('Execution failed for task \':target1\'.')
+        failure.assertHasCause('broken')
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/AntlrIntegrationTest.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/AntlrIntegrationTest.java
new file mode 100644
index 0000000..d35f052
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/AntlrIntegrationTest.java
@@ -0,0 +1,26 @@
+/*
+ * 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.junit.Test;
+
+public class AntlrIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void handlesEmptyProject() {
+        testFile("build.gradle").writelns("apply plugin: 'antlr'");
+        inTestDirectory().withTasks("build").run();
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ArchiveIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ArchiveIntegrationTest.groovy
new file mode 100644
index 0000000..997d0d2
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ArchiveIntegrationTest.groovy
@@ -0,0 +1,619 @@
+/*
+ * 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.util.TestFile
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+
+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 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]'))
+
+        expandDir.file('prefix/dir1').assertPermissions(equalTo("rwxr-xr-x"))
+        expandDir.file('prefix/dir1/renamed_file1.txt').assertPermissions(equalTo("rw-r--r--"))
+        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') << '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]'))
+
+        expandDir.file('dir1').assertPermissions(equalTo("rwxr-xr-x"))
+        expandDir.file('dir1/file1.txt').assertPermissions(equalTo("rw-r--r--"))
+        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 canMergeArchivesIntoAnotherArchive() {
+        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')
+    }
+
+    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)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ArtifactDependenciesIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ArtifactDependenciesIntegrationTest.groovy
new file mode 100644
index 0000000..c59bd17
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ArtifactDependenciesIntegrationTest.groovy
@@ -0,0 +1,168 @@
+/*
+ * 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.ExecutionFailure
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.util.TestFile
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+
+class ArtifactDependenciesIntegrationTest extends AbstractIntegrationTest {
+    @Rule
+    public final TestResources testResources = new TestResources()
+
+    @Before
+    public void setup() {
+        distribution.requireOwnUserHomeDir()
+    }
+    
+    @Test
+    public void canHaveConfigurationHierarchy() {
+        File buildFile = testFile("projectWithConfigurationHierarchy.gradle");
+        usingBuildFile(buildFile).run();
+    }
+
+    @Test
+    public void dependencyReportWithConflicts() {
+        File buildFile = testFile("projectWithConflicts.gradle");
+        usingBuildFile(buildFile).run();
+        usingBuildFile(buildFile).withDependencyList().run();
+    }
+
+    @Test
+    public void canNestModules() throws IOException {
+        File buildFile = testFile("projectWithNestedModules.gradle");
+        usingBuildFile(buildFile).run();
+    }
+
+    @Test
+    public void canHaveCycleInDependencyGraph() throws IOException {
+        File buildFile = testFile("projectWithCyclesInDependencyGraph.gradle");
+        usingBuildFile(buildFile).run();
+    }
+
+    @Test
+    public void canUseDynamicVersions() throws IOException {
+        File buildFile = testFile("projectWithDynamicVersions.gradle");
+        usingBuildFile(buildFile).run();
+    }
+
+    @Test
+    public void reportsUnknownDependencyError() {
+        File buildFile = testFile("projectWithUnknownDependency.gradle");
+        ExecutionFailure failure = usingBuildFile(buildFile).runWithFailure();
+        failure.assertHasFileName("Build file '" + buildFile.getPath() + "'");
+        failure.assertHasDescription("Execution failed for task ':listJars'");
+        failure.assertThatCause(startsWith("Could not resolve all dependencies for configuration ':compile'"));
+        failure.assertThatCause(containsString("unresolved dependency: test#unknownProjectA;1.2: not found"));
+        failure.assertThatCause(containsString("unresolved dependency: test#unknownProjectB;2.1.5: not found"));
+    }
+
+    @Test
+    public void reportsProjectDependsOnSelfError() {
+        TestFile buildFile = testFile("build.gradle");
+        buildFile << '''
+            configurations { compile }
+            dependencies { compile project(':') }
+            defaultTasks 'listJars'
+            task listJars << { configurations.compile.each { println it } }
+'''
+        ExecutionFailure failure = usingBuildFile(buildFile).runWithFailure();
+        failure.assertHasFileName("Build file '" + buildFile.getPath() + "'");
+        failure.assertHasDescription("Execution failed for task ':listJars'");
+        failure.assertThatCause(startsWith("Could not resolve all dependencies for configuration ':compile'"));
+        failure.assertThatCause(containsString("a module is not authorized to depend on itself"));
+    }
+
+    @Test
+    public void canSpecifyProducerTasksForFileDependency() {
+        testFile("settings.gradle").write("include 'sub'");
+        testFile("build.gradle") << '''
+            configurations { compile }
+            dependencies { compile project(path: ':sub', configuration: 'compile') }
+            task test(dependsOn: configurations.compile) << {
+                assert file('sub/sub.jar').isFile()
+            }
+'''
+        testFile("sub/build.gradle") << '''
+            configurations { compile }
+            dependencies { compile files('sub.jar') { builtBy 'jar' } }
+            task jar << { file('sub.jar').text = 'content' }
+'''
+
+        inTestDirectory().withTasks("test").run().assertTasksExecuted(":sub:jar", ":test");
+    }
+
+    @Test
+    public void resolvedProjectArtifactsContainProjectVersionInTheirNames() {
+        testFile('settings.gradle').write("include 'a', 'b'");
+        testFile('a/build.gradle') << '''
+            apply plugin: 'base'
+            configurations { compile }
+            task aJar(type: Jar) { }
+            artifacts { compile aJar }
+'''
+        testFile('b/build.gradle') << '''
+            apply plugin: 'base'
+            version = 'early'
+            configurations { compile }
+            task bJar(type: Jar) { }
+            gradle.taskGraph.whenReady { project.version = 'late' }
+            artifacts { compile bJar }
+'''
+        testFile('build.gradle') << '''
+            configurations { compile }
+            dependencies { compile project(path: ':a', configuration: 'compile'), project(path: ':b', configuration: 'compile') }
+            task test(dependsOn: configurations.compile) << {
+                assert configurations.compile.collect { it.name } == ['a.jar', 'b-late.jar']
+            }
+'''
+        inTestDirectory().withTasks('test').run()
+    }
+
+    @Test
+    public void canUseArtifactSelectorForProjectDependencies() {
+        testFile('settings.gradle').write("include 'a', 'b'");
+        testFile('a/build.gradle') << '''
+            apply plugin: 'base'
+            configurations { 'default' {} }
+            task aJar(type: Jar) { }
+            artifacts { 'default' aJar }
+'''
+        testFile('b/build.gradle') << '''
+            configurations { compile }
+            dependencies { compile(project(':a')) { artifact { name = 'a'; type = 'jar' } } }
+            task test {
+                inputs.files configurations.compile
+                doFirst {
+                    assert [project(':a').tasks.aJar.archivePath] as Set == configurations.compile.files
+                }
+            }
+'''
+        inTestDirectory().withTasks('test').run()
+    }
+
+    @Test
+    public void canHaveCycleInProjectDependencies() {
+        inTestDirectory().withTasks('listJars').run();
+    }
+}
+
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BackwardsCompatibilityIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BackwardsCompatibilityIntegrationTest.groovy
new file mode 100644
index 0000000..4ec4c6c
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BackwardsCompatibilityIntegrationTest.groovy
@@ -0,0 +1,103 @@
+/*
+ * 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.junit.runner.RunWith
+import org.junit.Test
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.junit.Rule
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.AbstractGradleExecuter
+import org.gradle.integtests.fixtures.ExecutionResult
+import org.gradle.integtests.fixtures.ExecutionFailure
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.GradleExecuter
+import org.gradle.util.TestFile
+import org.gradle.integtests.fixtures.ForkingGradleExecuter
+
+ at RunWith(DistributionIntegrationTestRunner.class)
+class BackwardsCompatibilityIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final TestResources resources = new TestResources()
+    private final GradleExecuter gradle08 = new PreviousGradleVersionExecuter(version: '0.8', dist: dist)
+    private final GradleExecuter gradle09preview3 = new PreviousGradleVersionExecuter(version: '0.9-preview-3', dist: dist)
+
+    @Test
+    public void canBuildJavaProject() {
+        dist.testFile('buildSrc/src/main/groovy').assertIsDir()
+
+        // Upgrade and downgrade
+        eachVersion([gradle08, gradle09preview3, executer, gradle09preview3, gradle08]) { version ->
+            version.inDirectory(dist.testDir).withTasks('build').run()
+        }
+    }
+
+    def eachVersion(Iterable<GradleExecuter> versions, Closure cl) {
+        versions.each { version ->
+            try {
+                System.out.println("building using $version");
+                cl.call(version)
+            } catch (Throwable t) {
+                throw new RuntimeException("Could not build test project using $version.", t)
+            }
+        }
+    }
+}
+
+class PreviousGradleVersionExecuter extends AbstractGradleExecuter {
+    def GradleDistribution dist
+    def String version
+
+    def String toString() {
+        "Gradle $version"
+    }
+
+    protected ExecutionResult doRun() {
+        TestFile gradleHome = findGradleHome()
+
+        ForkingGradleExecuter executer = new ForkingGradleExecuter(gradleHome)
+        copyTo(executer)
+        return executer.run()
+    }
+
+    private TestFile findGradleHome() {
+        // maybe download and unzip distribution
+        TestFile versionsDir = dist.distributionsDir.parentFile.file('previousVersions')
+        TestFile gradleHome = versionsDir.file("gradle-$version")
+        TestFile markerFile = gradleHome.file('ok.txt')
+        if (!markerFile.isFile()) {
+            TestFile zipFile = dist.userHomeDir.parentFile.file("gradle-$version-bin.zip")
+            if (!zipFile.isFile()) {
+                try {
+                    URL url = new URL("http://dist.codehaus.org/gradle/${zipFile.name}")
+                    System.out.println("downloading $url");
+                    zipFile.copyFrom(url)
+                } catch (Throwable t) {
+                    zipFile.delete()
+                    throw t
+                }
+            }
+            zipFile.usingNativeTools().unzipTo(versionsDir)
+            markerFile.touch()
+        }
+        return gradleHome
+    }
+
+    protected ExecutionFailure doRunWithFailure() {
+        throw new UnsupportedOperationException();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BrokenTask.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BrokenTask.java
new file mode 100644
index 0000000..0591d51
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BrokenTask.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+import org.gradle.api.Action;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Task;
+
+public class BrokenTask extends DefaultTask {
+    public BrokenTask() {
+        doFirst(new Action<Task>() {
+            public void execute(Task task) {
+                throw new RuntimeException("broken action");
+            }
+        });
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BuildAggregationIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BuildAggregationIntegrationTest.groovy
new file mode 100644
index 0000000..97e772b
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BuildAggregationIntegrationTest.groovy
@@ -0,0 +1,98 @@
+/*
+ * 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
+
+import org.gradle.integtests.fixtures.ExecutionFailure
+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 org.junit.runner.RunWith
+import static org.hamcrest.Matchers.*
+
+ at RunWith(DistributionIntegrationTestRunner.class)
+class BuildAggregationIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+
+    @Test
+    public void canExecuteAnotherBuildFromBuild() {
+        dist.testFile('build.gradle') << '''
+            assert gradle.parent == null
+            task build(type: GradleBuild) {
+                dir = 'other'
+                tasks = ['dostuff']
+                startParameter.searchUpwards = false
+            }
+'''
+
+        dist.testFile('other/build.gradle') << '''
+            assert gradle.parent != null
+            task dostuff << {
+                assert gradle.parent != null
+            }
+'''
+
+        executer.withTasks('build').run()
+    }
+
+    @Test
+    public void treatsBuildSrcProjectAsANestedBuild() {
+        dist.testFile('build.gradle') << '''
+            assert gradle.parent == null
+            task build
+'''
+
+        dist.testFile('buildSrc/build.gradle') << '''
+            apply plugin: 'java'
+            assert gradle.parent != null
+            classes << {
+                assert gradle.parent != null
+            }
+'''
+
+        executer.withTasks('build').run()
+    }
+
+    @Test
+    public void reportsNestedBuildFailure() {
+        TestFile other = dist.testFile('other.gradle') << '''
+            1/0
+'''
+
+        dist.testFile('build.gradle') << '''
+            task build(type: GradleBuild) {
+                buildFile = 'other.gradle'
+                startParameter.searchUpwards = false
+            }
+'''
+
+        ExecutionFailure failure = executer.withTasks('build').runWithFailure()
+        failure.assertHasFileName("Build file '${other}'")
+        failure.assertHasLineNumber(2)
+        failure.assertHasDescription('A problem occurred evaluating root project')
+        failure.assertThatCause(containsString('Division by zero'))
+    }
+
+    @Test
+    public void reportsBuildSrcFailure() {
+        dist.testFile('buildSrc/src/main/java/Broken.java') << 'broken!'
+        ExecutionFailure failure = executer.runWithFailure()
+        failure.assertHasFileName('Default buildSrc build script')
+        failure.assertHasDescription('Execution failed for task \':compileJava\'')
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BuildScriptClasspathIntegrationTest.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BuildScriptClasspathIntegrationTest.java
new file mode 100644
index 0000000..c85fcca
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BuildScriptClasspathIntegrationTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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;
+
+import org.gradle.integtests.fixtures.ArtifactBuilder;
+import org.gradle.integtests.fixtures.ExecutionFailure;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class BuildScriptClasspathIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void providesADefaultBuildForBuildSrcProject() {
+        testFile("buildSrc/src/main/java/BuildClass.java").writelns("public class BuildClass { }");
+        testFile("build.gradle").writelns("new BuildClass()");
+        inTestDirectory().withTaskList().run();
+    }
+
+    @Test
+    public void buildSrcProjectCanReferToSourceOutsideBuildSrcDir() {
+        testFile("gradle/src/BuildClass.java").writelns("public class BuildClass { }");
+        testFile("buildSrc/build.gradle").writelns(
+                "apply plugin: 'java'",
+                "sourceSets.main.java.srcDirs = ['../gradle/src']"
+        );
+        testFile("build.gradle").writelns(
+                "task test << { new BuildClass() }"
+        );
+
+        inTestDirectory().withTasks("test").run();
+
+        testFile("gradle/src/BuildClass.java").writelns("public class BuildClass { public BuildClass(String value) { throw new RuntimeException(\"broken\"); } }");
+
+        ExecutionFailure failure = inTestDirectory().withTasks("test").runWithFailure();
+        failure.assertHasCause("broken");
+    }
+
+    @Test
+    public void canDeclareClasspathInBuildScript() {
+        ArtifactBuilder builder = artifactBuilder();
+        builder.sourceFile("org/gradle/test/ImportedClass.java").writelns(
+                "package org.gradle.test;",
+                "public class ImportedClass { }"
+        );
+        builder.sourceFile("org/gradle/test/StaticImportedClass.java").writelns(
+                "package org.gradle.test;",
+                "public class StaticImportedClass { public static int someValue = 12; }"
+        );
+        builder.sourceFile("org/gradle/test/StaticImportedFieldClass.java").writelns(
+                "package org.gradle.test;",
+                "public class StaticImportedFieldClass { public static int anotherValue = 4; }"
+        );
+        builder.sourceFile("org/gradle/test2/OnDemandImportedClass.java").writelns(
+                "package org.gradle.test2;",
+                "public class OnDemandImportedClass { }"
+        );
+        builder.buildJar(testFile("repo/test-1.3.jar"));
+
+        testFile("build.gradle").writelns(
+                "import org.gradle.test.ImportedClass",
+                "import static org.gradle.test.StaticImportedClass.*",
+                "import static org.gradle.test.StaticImportedFieldClass.anotherValue",
+                "import org.gradle.test2.*",
+                "buildscript {",
+                "  repositories {",
+                "    flatDir dirs: file('repo')",
+                "  }",
+                "  dependencies {",
+                "    classpath name: 'test', version: '1.+'",
+                "  }",
+                "}",
+                "task hello << {",
+                "  new org.gradle.test.ImportedClass()",
+                "  println someValue",
+                "  println anotherValue",
+                "  new ImportedClass()",
+                "  new OnDemandImportedClass()",
+                "}",
+                "a = new ImportedClass()",
+                "b = OnDemandImportedClass",
+                "c = someValue",
+                "d = anotherValue",
+                "class TestClass extends ImportedClass { }",
+                "def aMethod() { return new OnDemandImportedClass() }"
+        );
+        inTestDirectory().withTasks("hello").run();
+    }
+
+    @Test
+    public void canUseBuildSrcAndSystemClassesInClasspathDeclaration() {
+        testFile("buildSrc/src/main/java/org/gradle/buildsrc/test/ImportedClass.java").writelns(
+                "package org.gradle.buildsrc.test;",
+                "public class ImportedClass { }"
+        );
+        testFile("buildSrc/src/main/java/org/gradle/buildsrc/test/StaticImportedClass.java").writelns(
+                "package org.gradle.buildsrc.test;",
+                "public class StaticImportedClass { public static int someValue = 12; }"
+        );
+        testFile("buildSrc/src/main/java/org/gradle/buildsrc/test/StaticImportedFieldClass.java").writelns(
+                "package org.gradle.buildsrc.test;",
+                "public class StaticImportedFieldClass { public static int anotherValue = 4; }"
+        );
+        testFile("buildSrc/src/main/java/org/gradle/buildsrc/test2/OnDemandImportedClass.java").writelns(
+                "package org.gradle.buildsrc.test2;",
+                "public class OnDemandImportedClass { }"
+        );
+
+        testFile("build.gradle").writelns(
+                "import org.gradle.buildsrc.test.ImportedClass",
+                "import org.gradle.buildsrc.test2.*",
+                "import static org.gradle.buildsrc.test.StaticImportedClass.*",
+                "import static org.gradle.buildsrc.test.StaticImportedFieldClass.anotherValue",
+                "buildscript {",
+                "    new ImportedClass()",
+                "    new org.gradle.buildsrc.test.ImportedClass()",
+                "    new org.gradle.buildsrc.test2.OnDemandImportedClass()",
+                "    println someValue",
+                "    println anotherValue",
+                "    List l = new ArrayList()",
+                "    Project p = project",
+                "    Closure cl = { }",
+                "}",
+                "task hello"
+        );
+        inTestDirectory().withTasks("hello").run();
+    }
+
+    @Test
+    public void inheritsClassPathOfParentProject() {
+        ArtifactBuilder builder = artifactBuilder();
+        builder.sourceFile("org/gradle/test/BuildClass.java").writelns(
+                "package org.gradle.test;",
+                "public class BuildClass { }"
+        );
+        builder.buildJar(testFile("repo/test-1.3.jar"));
+        testFile("settings.gradle").writelns(
+                "include 'child'"
+        );
+        testFile("build.gradle").writelns(
+                "assert gradle.scriptClassLoader == buildscript.classLoader.parent",
+                "buildscript {",
+                "    repositories { flatDir(dirs: file('repo')) }",
+                "    dependencies { classpath name: 'test', version: '1.3' }",
+                "}"
+        );
+        testFile("child/build.gradle").writelns(
+                "assert parent.buildscript.classLoader == buildscript.classLoader.parent",
+                "task hello << ",
+                "{",
+                "    new org.gradle.test.BuildClass()",
+                "}"
+        );
+        inTestDirectory().withTasks("hello").run();
+    }
+
+    @Test @Ignore
+    public void reportsFailureDuringClasspathDeclaration() {
+        fail("implement me");
+    }
+
+    @Test @Ignore
+    public void canInjectClassPathIntoSubProjects() {
+        fail("implement me");
+    }
+    
+    @Test @Ignore
+    public void canReuseClassPathRepositories() {
+        fail("implement me");
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BuildScriptErrorIntegrationTest.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BuildScriptErrorIntegrationTest.java
new file mode 100644
index 0000000..784ac41
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BuildScriptErrorIntegrationTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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;
+
+import org.gradle.integtests.fixtures.ExecutionFailure;
+import org.gradle.util.TestFile;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.File;
+
+public class BuildScriptErrorIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void reportsProjectEvaluationFailsWithGroovyException() {
+        ExecutionFailure failure = usingBuildScript("\ncreateTakk('do-stuff')").runWithFailure();
+
+        failure.assertHasFileName("Embedded build file");
+        failure.assertHasLineNumber(2);
+        failure.assertHasDescription("A problem occurred evaluating root project 'reportsProjectEvaluationFailsWithGroovyException");
+        failure.assertHasCause("Could not find method createTakk() for arguments [do-stuff] on root project 'reportsProjectEvaluationFailsWithGroovyException");
+    }
+
+    @Test
+    public void reportsScriptCompilationException() {
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns(
+            "// a comment",
+            "import org.gradle.unknown.Unknown",
+            "new Unknown()");
+        ExecutionFailure failure = inTestDirectory().runWithFailure();
+        failure.assertHasFileName(String.format("Build file '%s'", buildFile));
+        failure.assertHasLineNumber(2);
+        failure.assertHasDescription(String.format("Could not compile build file '%s'.", buildFile));
+    }
+
+    @Test
+    public void reportsNestedProjectEvaluationFailsWithRuntimeException() {
+        testFile("settings.gradle").write("include 'child'");
+
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns(
+                "dependsOn 'child'",
+                "task t");
+
+        TestFile childBuildFile = testFile("child/build.gradle");
+        childBuildFile.writelns(
+                "def broken = { ->",
+                "    throw new RuntimeException('failure') }",
+                "broken()");
+        ExecutionFailure failure = inTestDirectory().withTasks("t").runWithFailure();
+
+        failure.assertHasFileName(String.format("Build file '%s'", childBuildFile));
+        failure.assertHasLineNumber(2);
+        failure.assertHasDescription("A problem occurred evaluating project ':child'");
+        failure.assertHasCause("failure");
+    }
+
+    @Test
+    public void reportsTaskActionExecutionFailsWithError() {
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns(
+                "task('do-stuff').doFirst",
+                "{",
+                "1/0",
+                "}");
+        ExecutionFailure failure = usingBuildFile(buildFile).withTasks("do-stuff").runWithFailure();
+
+        failure.assertHasFileName(String.format("Build file '%s'", buildFile));
+        failure.assertHasLineNumber(3);
+        failure.assertHasDescription("Execution failed for task ':do-stuff'");
+        failure.assertHasCause("Division by zero");
+    }
+
+    @Test
+    public void reportsTaskActionExecutionFailsWithRuntimeException() {
+        File buildFile = testFile("build.gradle").writelns(
+                "task brokenClosure << {",
+                "    throw new RuntimeException('broken closure')",
+                "}");
+
+        ExecutionFailure failure = usingBuildFile(buildFile).withTasks("brokenClosure").runWithFailure();
+
+        failure.assertHasFileName(String.format("Build file '%s'", buildFile));
+        failure.assertHasLineNumber(2);
+        failure.assertHasDescription("Execution failed for task ':brokenClosure'");
+        failure.assertHasCause("broken closure");
+    }
+
+    @Test
+    public void reportsTaskActionExecutionFailsFromJavaWithRuntimeException() {
+        File buildFile = testFile("build.gradle").write("task brokenJavaTask(type: org.gradle.integtests.BrokenTask)");
+
+        ExecutionFailure failure = usingBuildFile(buildFile).withTasks("brokenJavaTask").runWithFailure();
+
+        failure.assertHasFileName(String.format("Build file '%s'", buildFile));
+        failure.assertHasDescription("Execution failed for task ':brokenJavaTask'");
+        failure.assertHasCause("broken action");
+    }
+
+    @Test
+    public void reportsTaskInjectedByOtherProjectFailsWithRuntimeException() {
+        testFile("settings.gradle").write("include 'a', 'b'");
+        TestFile buildFile = testFile("b/build.gradle");
+        buildFile.writelns(
+                "project(':a') {",
+                "    task a << {",
+                "        throw new RuntimeException('broken')",
+                "    }",
+                "}");
+
+        ExecutionFailure failure = inTestDirectory().withTasks("a").runWithFailure();
+
+        failure.assertHasFileName(String.format("Build file '%s'", buildFile));
+        failure.assertHasLineNumber(3);
+        failure.assertHasDescription("Execution failed for task ':a:a");
+        failure.assertHasCause("broken");
+    }
+
+    @Test
+    public void reportsTaskGraphReadyEventFailsWithRuntimeException() {
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns(
+                "gradle.taskGraph.whenReady {",
+                "throw new RuntimeException('broken closure')",
+                "}",
+                "task a");
+
+        ExecutionFailure failure = usingBuildFile(buildFile).withTasks("a").runWithFailure();
+
+        failure.assertHasFileName(String.format("Build file '%s'", buildFile));
+        failure.assertHasLineNumber(2);
+        failure.assertHasDescription("Failed to notify task execution graph listener");
+        failure.assertHasCause("broken closure");
+    }
+
+    @Test @Ignore
+    public void reportsTaskDependencyClosureFailsWithRuntimeException() {
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns(
+                "task a",
+                "a.dependsOn {",
+                "throw new RuntimeException('broken')",
+                "}");
+
+        ExecutionFailure failure = usingBuildFile(buildFile).withTasks("a").runWithFailure();
+
+        failure.assertHasFileName(String.format("Build file '%s'", buildFile));
+        failure.assertHasLineNumber(3);
+        failure.assertHasDescription("??");
+        failure.assertHasCause("broken");
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BuildScriptExecutionIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BuildScriptExecutionIntegrationTest.groovy
new file mode 100644
index 0000000..d3b223a
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/BuildScriptExecutionIntegrationTest.groovy
@@ -0,0 +1,51 @@
+/*
+ * 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.ExecutionResult
+import org.gradle.util.TestFile
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+class BuildScriptExecutionIntegrationTest extends AbstractIntegrationTest {
+
+    @Test
+    public void executesBuildScriptWithCorrectEnvironment() {
+        TestFile buildScript = testFile('build.gradle')
+        buildScript << """
+            println 'quiet message'
+            captureStandardOutput(LogLevel.ERROR)
+            println 'error message'
+            assert project != null
+            assert "${buildScript.absolutePath.replace("\\", "\\\\")}" == buildscript.sourceFile as String
+            assert "${buildScript.toURI()}" == buildscript.sourceURI as String
+            assert buildscript.classLoader == getClass().classLoader.parent
+            assert buildscript.classLoader == Thread.currentThread().contextClassLoader
+            assert gradle.scriptClassLoader == buildscript.classLoader.parent
+
+            task doStuff
+"""
+
+        ExecutionResult result = inTestDirectory().withTasks('doStuff').run()
+        assertThat(result.output, containsString('quiet message'))
+        assertThat(result.output, not(containsString('error message')))
+        assertThat(result.error, containsString('error message'))
+        assertThat(result.error, not(containsString('quiet message')))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/CacheProjectIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/CacheProjectIntegrationTest.groovy
new file mode 100644
index 0000000..efbe0db
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/CacheProjectIntegrationTest.groovy
@@ -0,0 +1,125 @@
+/*
+ * 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
+
+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.util.GradleVersion
+import org.gradle.util.TestFile
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(DistributionIntegrationTestRunner.class)
+class CacheProjectIntegrationTest {
+    static final String TEST_FILE = "build/test.txt"
+
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+
+    TestFile projectDir
+    TestFile userHomeDir
+    TestFile buildFile
+    TestFile propertiesFile
+    TestFile classFile
+    TestFile artifactsCache
+
+    @Before
+    public void setUp() {
+        String version = new GradleVersion().version
+        projectDir = dist.getTestDir().file("project")
+        projectDir.mkdirs()
+        userHomeDir = dist.getUserHomeDir()
+        buildFile = projectDir.file('build.gradle')
+        ScriptSource source = new UriScriptSource("build file", buildFile)
+        propertiesFile = userHomeDir.file("caches/$version/scripts/$source.className/cache.properties")
+        classFile = userHomeDir.file("caches/$version/scripts/$source.className/no_buildscript_ProjectScript/${source.className}.class")
+        artifactsCache = projectDir.file(".gradle/$version/taskArtifacts/cache.bin")
+    }
+
+    @Test
+    public void cachesBuildScript() {
+        createLargeBuildScript()
+        testBuild("hello1", "Hello 1")
+        TestFile.Snapshot classFileSnapshot = classFile.snapshot()
+        TestFile.Snapshot artifactsCacheSnapshot = artifactsCache.snapshot()
+
+        testBuild("hello2", "Hello 2")
+        classFile.assertHasNotChangedSince(classFileSnapshot)
+        artifactsCache.assertHasNotChangedSince(artifactsCacheSnapshot)
+
+        modifyLargeBuildScript()
+        testBuild("newTask", "I am new")
+        classFile.assertHasChangedSince(classFileSnapshot)
+        artifactsCache.assertHasNotChangedSince(artifactsCacheSnapshot)
+        classFileSnapshot = classFile.snapshot()
+        artifactsCacheSnapshot = artifactsCache.snapshot()
+
+        testBuild("newTask", "I am new", "-Crebuild")
+        classFile.assertHasChangedSince(classFileSnapshot)
+        artifactsCache.assertHasChangedSince(artifactsCacheSnapshot)
+    }
+
+    private def testBuild(String taskName, String expected, String... args) {
+        executer.inDirectory(projectDir).withTasks(taskName).withArguments(args).withQuietLogging().run()
+        assertEquals(expected, projectDir.file(TEST_FILE).text)
+        classFile.assertIsFile()
+        propertiesFile.assertIsFile()
+        artifactsCache.assertIsFile()
+    }
+
+    // We once ran into a cache problem under windows, which was not reproducible with small build scripts. Therefore we
+    // create a larger one here.
+
+    def createLargeBuildScript() {
+        File buildFile = projectDir.file('build.gradle')
+        String content = ""
+        50.times {i ->
+            content += """task 'hello$i' << {
+    File file = file('$TEST_FILE')
+    file.parentFile.mkdirs()
+    file.write('Hello $i')
+}
+
+void someMethod$i() {
+    println('Some message')
+}
+
+"""
+        }
+        buildFile.write(content)
+    }
+
+    def void modifyLargeBuildScript() {
+        File buildFile = projectDir.file('build.gradle')
+        String newContent = buildFile.text + """
+task newTask << {
+    File file = file('$TEST_FILE')
+    file.parentFile.mkdirs()
+    file.write('I am new')
+}
+"""
+        buildFile.write(newContent)
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ClientModuleDependenciesResolveIntegrationTest.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ClientModuleDependenciesResolveIntegrationTest.java
new file mode 100644
index 0000000..497441c
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ClientModuleDependenciesResolveIntegrationTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+import org.gradle.integtests.fixtures.GradleDistribution;
+import org.gradle.integtests.fixtures.GradleDistributionExecuter;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith (DistributionIntegrationTestRunner.class)
+public class ClientModuleDependenciesResolveIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution();
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter();
+
+    @Test
+    public void testResolve() {
+        // the actual testing is done in the build script.
+        File projectDir = new File(dist.getSamplesDir(), "clientModuleDependencies/shared");
+        executer.inDirectory(projectDir).withTasks("testDeps").run();
+
+        projectDir = new File(dist.getSamplesDir(), "clientModuleDependencies/api");
+        executer.inDirectory(projectDir).withTasks("testDeps").run();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/CodeQualityIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/CodeQualityIntegrationTest.groovy
new file mode 100644
index 0000000..6b3d8a2
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/CodeQualityIntegrationTest.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
+
+import org.gradle.integtests.fixtures.ExecutionFailure
+import org.gradle.util.TestFile
+import org.hamcrest.Matcher
+import org.junit.Test
+import static org.gradle.util.Matchers.*
+import static org.hamcrest.Matchers.*
+
+class CodeQualityIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void handlesEmptyProjects() {
+        testFile('build.gradle') << '''
+apply plugin: 'groovy'
+apply plugin: 'code-quality'
+'''
+        inTestDirectory().withTasks('check').run()
+    }
+
+    @Test
+    public void generatesReportForJavaSource() {
+        testFile('build.gradle') << '''
+apply plugin: 'java'
+apply plugin: 'code-quality'
+'''
+        writeCheckstyleConfig()
+
+        testFile('src/main/java/org/gradle/Class1.java') << 'package org.gradle; class Class1 { }'
+        testFile('src/test/java/org/gradle/TestClass1.java') << 'package org.gradle; class TestClass1 { }'
+
+        inTestDirectory().withTasks('check').run()
+
+        testFile('build/checkstyle/main.xml').assertContents(containsClass('org.gradle.Class1'))
+        testFile('build/checkstyle/test.xml').assertContents(containsClass('org.gradle.TestClass1'))
+    }
+
+    @Test
+    public void generatesReportForJavaSourceInGroovySourceDirs() {
+        testFile('build.gradle') << '''
+apply plugin: 'groovy'
+apply plugin: 'code-quality'
+dependencies { groovy localGroovy() }
+'''
+        writeCheckstyleConfig()
+
+        testFile('src/main/groovy/org/gradle/Class1.java') << 'package org.gradle; class Class1 { }'
+        testFile('src/test/groovy/org/gradle/TestClass1.java') << 'package org.gradle; class TestClass1 { }'
+
+        inTestDirectory().withTasks('check').run()
+
+        testFile('build/checkstyle/main.xml').assertContents(containsClass('org.gradle.Class1'))
+        testFile('build/checkstyle/test.xml').assertContents(containsClass('org.gradle.TestClass1'))
+    }
+
+    private Matcher<String> containsClass(String classname) {
+        return containsLine(containsString(classname.replace('.', File.separator) + '.java'))
+    }
+
+    @Test
+    public void checkstyleOnlyChecksJavaSource() {
+        testFile('build.gradle') << '''
+apply plugin: 'groovy'
+apply plugin: 'code-quality'
+'''
+        writeCheckstyleConfig()
+
+        testFile('src/main/groovy/org/gradle/Class1.java') << 'package org.gradle; class Class1 { }'
+        testFile('src/main/groovy/org/gradle/Class2.java') << 'package org.gradle; class Class2 { }'
+        testFile('src/main/groovy/org/gradle/class3.groovy') << 'package org.gradle; class class3 { }'
+
+        inTestDirectory().withTasks('checkstyleMain').run()
+
+        testFile('build/checkstyle/main.xml').assertExists()
+        testFile('build/checkstyle/main.xml').assertContents(not(containsClass('org.gradle.class3')))
+    }
+
+    @Test
+    public void checkstyleViolationBreaksBuild() {
+        testFile('build.gradle') << '''
+apply plugin: 'groovy'
+apply plugin: 'code-quality'
+'''
+        writeCheckstyleConfig()
+
+        testFile('src/main/java/org/gradle/class1.java') << 'package org.gradle; class class1 { }'
+        testFile('src/main/groovy/org/gradle/class2.java') << 'package org.gradle; class class2 { }'
+
+        ExecutionFailure failure = inTestDirectory().withTasks('check').runWithFailure()
+        failure.assertHasDescription('Execution failed for task \':checkstyleMain\'')
+        failure.assertThatCause(startsWith('Checkstyle check violations were found in main Java source. See the report at'))
+
+        testFile('build/checkstyle/main.xml').assertExists()
+    }
+
+    @Test
+    public void generatesReportForGroovySource() {
+        testFile('build.gradle') << '''
+apply plugin: 'groovy'
+apply plugin: 'code-quality'
+dependencies { groovy localGroovy() }
+'''
+        writeCodeNarcConfigFile()
+
+        testFile('src/main/groovy/org/gradle/Class1.groovy') << 'package org.gradle; class Class1 { }'
+        testFile('src/test/groovy/org/gradle/TestClass1.groovy') << 'package org.gradle; class TestClass1 { }'
+
+        inTestDirectory().withTasks('check').run()
+
+        testFile('build/reports/codenarc/main.html').assertExists()
+        testFile('build/reports/codenarc/test.html').assertExists()
+    }
+
+    @Test
+    public void codeNarcOnlyChecksGroovySource() {
+        testFile('build.gradle') << '''
+apply plugin: 'groovy'
+apply plugin: 'code-quality'
+'''
+
+        writeCodeNarcConfigFile()
+
+        testFile('src/main/groovy/org/gradle/class1.java') << 'package org.gradle; class class1 { }'
+        testFile('src/main/groovy/org/gradle/Class2.groovy') << 'package org.gradle; class Class2 { }'
+
+        inTestDirectory().withTasks('codenarcMain').run()
+
+        testFile('build/reports/codenarc/main.html').assertExists()
+    }
+
+    @Test
+    public void codeNarcViolationBreaksBuild() {
+        testFile('build.gradle') << '''
+apply plugin: 'groovy'
+apply plugin: 'code-quality'
+dependencies { groovy localGroovy() }
+'''
+
+        writeCodeNarcConfigFile()
+
+        testFile('src/main/groovy/org/gradle/class1.groovy') << 'package org.gradle; class class1 { }'
+
+        ExecutionFailure failure = inTestDirectory().withTasks('check').runWithFailure()
+        failure.assertHasDescription('Execution failed for task \':codenarcMain\'')
+        failure.assertThatCause(startsWith('CodeNarc check violations were found in main Groovy source. See the report at '))
+
+        testFile('build/reports/codenarc/main.html').assertExists()
+    }
+
+    private TestFile writeCheckstyleConfig() {
+        return testFile('config/checkstyle/checkstyle.xml') << '''
+<!DOCTYPE module PUBLIC
+        "-//Puppy Crawl//DTD Check Configuration 1.2//EN"
+        "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
+<module name="Checker">
+    <module name="TreeWalker">
+        <module name="TypeName"/>
+    </module>
+</module>'''
+    }
+
+    private TestFile writeCodeNarcConfigFile() {
+        return testFile('config/codenarc/codenarc.xml') << '''
+<ruleset xmlns="http://codenarc.org/ruleset/1.0"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://codenarc.org/ruleset/1.0 http://codenarc.org/ruleset-schema.xsd"
+        xsi:noNamespaceSchemaLocation="http://codenarc.org/ruleset-schema.xsd">
+    <ruleset-ref path='rulesets/naming.xml'/>
+</ruleset>
+'''
+    }
+
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/CommandLineIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/CommandLineIntegrationTest.groovy
new file mode 100644
index 0000000..996eb6a
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/CommandLineIntegrationTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * 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.ExecutionFailure
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.util.OperatingSystem
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+ at RunWith(DistributionIntegrationTestRunner.class)
+public class CommandLineIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+
+    @Test
+    public void hasNonZeroExitCodeOnBuildFailure() {
+        File javaprojectDir = new File(dist.samplesDir, 'java/quickstart')
+        ExecutionFailure failure = executer.inDirectory(javaprojectDir).withTasks('unknown').runWithFailure()
+        failure.assertHasDescription("Task 'unknown' not found in root project 'quickstart'.")
+    }
+
+    @Test
+    public void canonicalisesWorkingDirectory() {
+        File javaprojectDir;
+        if (OperatingSystem.current().isWindows()) {
+            javaprojectDir = new File(dist.samplesDir, 'java/QUICKS~1')
+        } else if (!OperatingSystem.current().isCaseSensitiveFileSystem()) {
+            javaprojectDir = new File(dist.samplesDir, 'JAVA/QuickStart')
+        } else {
+            javaprojectDir = new File(dist.samplesDir, 'java/multiproject/../quickstart')
+        }
+        executer.inDirectory(javaprojectDir).withTasks('classes').run()
+    }
+
+    @Test
+    public void canUseVersionCommandLineOption() {
+        executer.withArguments('-v').run()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/CopyErrorIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/CopyErrorIntegrationTest.groovy
new file mode 100644
index 0000000..bd292dc
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/CopyErrorIntegrationTest.groovy
@@ -0,0 +1,75 @@
+/*
+ * 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.ExecutionFailure
+import org.gradle.util.OperatingSystem
+import org.gradle.util.TestFile
+import org.junit.Assert
+import org.junit.Test
+
+class CopyErrorIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void reportsSymLinkWhichPointsToNothing() {
+        if (OperatingSystem.current().isWindows()) {
+            return
+        }
+
+        TestFile link = testFile('src/file')
+        link.linkTo(testFile('missing'))
+
+        Assert.assertFalse(link.isDirectory())
+        Assert.assertFalse(link.isFile())
+        Assert.assertFalse(link.exists())
+
+        testFile('build.gradle') << '''
+            task copy(type: Copy) {
+                from 'src'
+                into 'dest'
+            }
+'''
+
+        ExecutionFailure failure = inTestDirectory().withTasks('copy').runWithFailure()
+        failure.assertHasDescription("Could not list contents of '${link}'.")
+    }
+
+    @Test
+    public void reportsUnreadableSourceDir() {
+        if (OperatingSystem.current().isWindows()) {
+            return
+        }
+
+        TestFile dir = testFile('src').createDir()
+        dir.permissions = '-w-r--r--'
+
+        Assert.assertTrue(dir.isDirectory())
+        Assert.assertTrue(dir.exists())
+        Assert.assertFalse(dir.canRead())
+        Assert.assertTrue(dir.canWrite())
+
+        testFile('build.gradle') << '''
+            task copy(type: Copy) {
+                from 'src'
+                into 'dest'
+            }
+'''
+
+        ExecutionFailure failure = inTestDirectory().withTasks('copy').runWithFailure()
+        failure.assertHasDescription("Could not list contents of directory '${dir}' as it is not readable.")
+
+        dir.permissions = 'rwxr--r--'
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/CopyTaskIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/CopyTaskIntegrationTest.groovy
new file mode 100644
index 0000000..8692d21
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/CopyTaskIntegrationTest.groovy
@@ -0,0 +1,320 @@
+/*
+ * 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.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+public class CopyTaskIntegrationTest extends AbstractIntegrationTest {
+    @Rule
+    public final TestResources resources = new TestResources("copyTestResources")
+
+    @Test
+    public void testSingleSourceWithIncludeAndExclude() {
+        TestFile buildFile = testFile("build.gradle") << '''
+            task (copy, type:Copy) {
+               from 'src'
+               into 'dest'
+               include '**/sub/**'
+               exclude '**/ignore/**'
+            }
+'''
+        usingBuildFile(buildFile).withTasks("copy").run()
+        testFile('dest').assertHasDescendants(
+                'one/sub/onesub.a',
+                'one/sub/onesub.b'
+        )
+    }
+
+   @Test
+   public void testSingleSourceWithSpecClosures() {
+       TestFile buildFile = testFile("build.gradle").writelns(
+               "task (copy, type:Copy) {",
+               "   from 'src'",
+               "   into 'dest'",
+               "   include { fte -> !fte.file.name.endsWith('b') }",
+               "   exclude { fte -> fte.file.name == 'bad.file' }",
+               "}"
+       )
+       usingBuildFile(buildFile).withTasks("copy").run()
+       testFile('dest').assertHasDescendants(
+               'root.a',
+               'one/one.a',
+               'two/two.a',
+       )
+   }
+
+    @Test
+    public void testMultipleSourceWithInheritedPatterns() {
+        TestFile buildFile = testFile("build.gradle") << '''
+            task (copy, type:Copy) {
+               into 'dest'
+               from('src/one') {
+                  into '1'
+                  include '**/*.a'
+               }
+               from('src/two') {
+                  into '2'
+                  include '**/*.b'
+               }
+               exclude '**/ignore/**'
+            }
+'''
+        usingBuildFile(buildFile).withTasks("copy").run()
+        testFile('dest').assertHasDescendants(
+                '1/one.a',
+                '1/sub/onesub.a',
+                '2/two.b',
+        )
+    }
+
+    @Test
+    public void testMultipleSourcesWithInheritedDestination() {
+        TestFile buildFile = testFile("build.gradle") << '''
+            task (copy, type:Copy) {
+               into 'dest'
+               into('common') {
+                  from('src/one') {
+                     into 'a/one'
+                     include '*.a'
+                  }
+                  into('b') {
+                     from('src/two') {
+                        into 'two'
+                        include '**/*.b'
+                     }
+                  }
+               }
+            }
+'''
+        usingBuildFile(buildFile).withTasks("copy").run()
+        testFile('dest').assertHasDescendants(
+                'common/a/one/one.a',
+                'common/b/two/two.b',
+        )
+    }
+
+    @Test void testRename() {
+        TestFile buildFile = testFile("build.gradle") << '''
+            task (copy, type:Copy) {
+               from 'src'
+               into 'dest'
+               exclude '**/ignore/**'
+               rename '(.*).a', '\$1.renamed'
+               rename { it.startsWith('one.') ? "renamed_$it" : it }
+            }
+'''
+        usingBuildFile(buildFile).withTasks("copy").run()
+        testFile('dest').assertHasDescendants(
+                'root.renamed',
+                'root.b',
+                'one/renamed_one.renamed',
+                'one/renamed_one.b',
+                'one/sub/onesub.renamed',
+                'one/sub/onesub.b',
+                'two/two.renamed',
+                'two/two.b'
+        )
+    }
+
+    @Test
+    public void testCopyAction() {
+        TestFile buildFile = testFile("build.gradle").writelns(
+                "task copyIt << {",
+                "   copy {",
+                "      from 'src'",
+                "      into 'dest'",
+                "      exclude '**/ignore/**'",
+                "   }",
+                "}"
+        )
+        usingBuildFile(buildFile).withTasks("copyIt").run()
+        testFile('dest').assertHasDescendants(
+                'root.a',
+                'root.b',
+                'one/one.a',
+                'one/one.b',
+                'one/sub/onesub.a',
+                'one/sub/onesub.b',
+                'two/two.a',
+                'two/two.b',
+        )
+    }
+
+    @Test public void copySingleFiles() {
+        TestFile buildFile = testFile("build.gradle").writelns(
+                "task copyIt << {",
+                "   copy {",
+                "      from 'src/one/one.a', 'src/two/two.a'",
+                "      into 'dest/two'",
+                "   }",
+                "}"
+        )
+        usingBuildFile(buildFile).withTasks("copyIt").run()
+        testFile('dest').assertHasDescendants(
+                'two/one.a',
+                'two/two.a',
+        )
+    }
+
+    /*
+     * two.a starts off with "$one\n${one+1}\n${one+1+1}\n"
+     * If these filters are chained in the correct order, you should get 6, 11, and 16
+     */
+    @Test public void copyMultipleFilterTest() {
+        TestFile buildFile = testFile('build.gradle').writelns(
+                """task (copy, type:Copy) {
+                   into 'dest'
+                   expand(one: 1)
+                   filter { (Integer.parseInt(it) * 10) as String }
+                   filter { (Integer.parseInt(it) + 2) as String }
+                   from('src/two/two.a') {
+                     filter { (Integer.parseInt(it) / 2) as String }
+                   }
+                }
+                """
+        )
+        usingBuildFile(buildFile).withTasks("copy").run()
+        Iterator<String> it = testFile('dest/two.a').readLines().iterator()
+        assertThat(it.next(), startsWith('6'))
+        assertThat(it.next(), startsWith('11'))
+        assertThat(it.next(), startsWith('16'))
+    }
+
+    @Test public void chainedTransformations() {
+        def buildFile = testFile('build.gradle') << '''
+            task copy(type: Copy) {
+                into 'dest'
+                rename '(.*).a', '\$1.renamed'
+                eachFile { fcd -> if (fcd.path.contains('/ignore/')) { fcd.exclude() } }
+                eachFile { fcd -> if (fcd.relativePath.segments.length > 1) { fcd.relativePath = fcd.relativePath.prepend('prefix') }}
+                filter(org.apache.tools.ant.filters.PrefixLines, prefix: 'line: ')
+                eachFile { fcd -> fcd.filter { it.replaceAll('^line:', 'prefix:') } }
+                from ('src') {
+                    rename '(.*).renamed', '\$1.renamed_twice'
+                    eachFile { fcd -> fcd.path = fcd.path.replaceAll('/one/sub/', '/one_sub/') }
+                    eachFile { fcd -> if (fcd.path.contains('/two/')) { fcd.exclude() } }
+                    eachFile { fcd -> fcd.filter { "[$it]" } }
+                }
+            }
+'''
+        usingBuildFile(buildFile).withTasks('copy').run()
+        testFile('dest').assertHasDescendants(
+                'root.renamed_twice',
+                'root.b',
+                'prefix/one/one.renamed_twice',
+                'prefix/one/one.b',
+                'prefix/one_sub/onesub.renamed_twice',
+                'prefix/one_sub/onesub.b'
+        )
+
+        Iterator<String> it = testFile('dest/root.renamed_twice').readLines().iterator()
+        assertThat(it.next(), equalTo('[prefix: line 1]'))
+        assertThat(it.next(), equalTo('[prefix: line 2]'))
+    }
+
+    @Test public void testCopyFromFileTree() {
+        TestFile buildFile = testFile("build.gradle").writelns(
+                """task cpy << {
+                   copy {
+                        from fileTree(dir: 'src', excludes: ['**/ignore/**'], includes: ['*', '*/*'])
+                        into 'dest'
+                    }
+                }"""
+        )
+        usingBuildFile(buildFile).withTasks("cpy").run()
+        testFile('dest').assertHasDescendants(
+                'root.a',
+                'root.b',
+                'one/one.a',
+                'one/one.b',
+                'two/two.a',
+                'two/two.b',
+        )
+    }
+
+    @Test public void testCopyFromFileCollection() {
+        TestFile buildFile = testFile("build.gradle").writelns(
+                """task copy << {
+                   copy {
+                        from files('src')
+                        into 'dest'
+                        exclude '**/ignore/**'
+                        exclude '*/*/*/**'
+                    }
+                }"""
+        )
+        usingBuildFile(buildFile).withTasks("copy").run()
+        testFile('dest').assertHasDescendants(
+                'root.a',
+                'root.b',
+                'one/one.a',
+                'one/one.b',
+                'two/two.a',
+                'two/two.b',
+        )
+    }
+
+    @Test public void testCopyFromCompositeFileCollection() {
+        testFile('a.jar').touch()
+
+        TestFile buildFile = testFile("build.gradle").writelns(
+                """
+                configurations { compile }
+                dependencies { compile files('a.jar') }
+                task copy << {
+                   copy {
+                        from files('src2') + fileTree { from 'src'; exclude '**/ignore/**' } + configurations.compile
+                        into 'dest'
+                        include { fte -> fte.relativePath.segments.length < 3 && (fte.file.directory || fte.file.name.contains('a')) }
+                    }
+                }"""
+        )
+        usingBuildFile(buildFile).withTasks("copy").run()
+        testFile('dest').assertHasDescendants(
+                'root.a',
+                'one/one.a',
+                'two/two.a',
+                'three/three.a',
+                'a.jar'
+        )
+    }
+
+    @Test public void testCopyWithCopyspec() {
+        TestFile buildFile = testFile("build.gradle").writelns(
+                """
+                def spec = copySpec {
+                    from 'src'
+                    exclude '**/ignore/**'
+                    include '*/*.a'
+                    into 'subdir'
+                }
+                task copy(type: Copy) {
+                    into 'dest'
+                    with spec
+                }"""
+        )
+        usingBuildFile(buildFile).withTasks("copy").run()
+        testFile('dest').assertHasDescendants(
+                'subdir/one/one.a',
+                'subdir/two/two.a'
+        )
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/DependenciesResolveIntegrationTest.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/DependenciesResolveIntegrationTest.java
new file mode 100644
index 0000000..14dbb52
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/DependenciesResolveIntegrationTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+import org.gradle.integtests.fixtures.GradleDistribution;
+import org.gradle.integtests.fixtures.GradleDistributionExecuter;
+import org.gradle.integtests.fixtures.Sample;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith (DistributionIntegrationTestRunner.class)
+public class DependenciesResolveIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution();
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter();
+    @Rule public final Sample sample = new Sample("dependencies");
+
+    @Test
+    public void testResolve() {
+        dist.requireOwnUserHomeDir();
+
+        // the actual testing is done in the build script.
+        File projectDir = sample.getDir();
+        executer.inDirectory(projectDir).withTasks("test").run();
+    }   
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/DirTransformerTask.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/DirTransformerTask.java
new file mode 100644
index 0000000..77659dd
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/DirTransformerTask.java
@@ -0,0 +1,58 @@
+/*
+ * 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;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.tasks.InputDirectory;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.util.TestFile;
+
+import java.io.File;
+
+public class DirTransformerTask extends DefaultTask {
+    private File inputDir;
+    private File outputDir;
+
+    @InputDirectory
+    public File getInputDir() {
+        return inputDir;
+    }
+
+    public void setInputDir(File inputDir) {
+        this.inputDir = inputDir;
+    }
+
+    @OutputDirectory
+    public File getOutputDir() {
+        return outputDir;
+    }
+
+    public void setOutputDir(File outputDir) {
+        this.outputDir = outputDir;
+    }
+
+    @TaskAction
+    public void transform() {
+        TestFile inputDir = new TestFile(this.inputDir);
+        for (File file : inputDir.listFiles()) {
+            TestFile inputFile = new TestFile(file);
+            TestFile outputFile = new TestFile(outputDir, inputFile.getName());
+            outputFile.write(String.format("[%s]", inputFile.getText()));
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/DistributionIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/DistributionIntegrationTest.groovy
new file mode 100644
index 0000000..a66a37c
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/DistributionIntegrationTest.groovy
@@ -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.integtests
+
+import org.apache.tools.ant.taskdefs.Expand
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.util.AntUtil
+import org.gradle.util.GradleVersion
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+ at RunWith(DistributionIntegrationTestRunner)
+class DistributionIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    private String version = new GradleVersion().version
+
+    @Test
+    public void binZipContents() {
+        TestFile binZip = dist.distributionsDir.file("gradle-$version-bin.zip")
+        binZip.usingNativeTools().unzipTo(dist.testDir)
+        TestFile contentsDir = dist.testDir.file("gradle-$version")
+
+        checkMinimalContents(contentsDir)
+
+        // Extra stuff
+        contentsDir.file('src').assertDoesNotExist()
+        contentsDir.file('samples').assertDoesNotExist()
+        contentsDir.file('docs').assertDoesNotExist()
+    }
+
+    @Test
+    public void allZipContents() {
+        TestFile binZip = dist.distributionsDir.file("gradle-$version-all.zip")
+        binZip.usingNativeTools().unzipTo(dist.testDir)
+        TestFile contentsDir = dist.testDir.file("gradle-$version")
+
+        checkMinimalContents(contentsDir)
+
+        // Source
+        contentsDir.file('src/org/gradle/api/Project.java').assertIsFile()
+        contentsDir.file('src/org/gradle/initialization/defaultBuildSourceScript.txt').assertIsFile()
+        contentsDir.file('src/org/gradle/gradleplugin/userinterface/swing/standalone/BlockingApplication.java').assertIsFile()
+        contentsDir.file('src/org/gradle/wrapper/Wrapper.java').assertIsFile()
+
+        // Samples
+        contentsDir.file('samples/java/quickstart/build.gradle').assertIsFile()
+
+        // Docs
+        contentsDir.file('docs/javadoc/index.html').assertIsFile()
+        contentsDir.file('docs/javadoc/org/gradle/api/Project.html').assertIsFile()
+        contentsDir.file('docs/groovydoc/index.html').assertIsFile()
+        contentsDir.file('docs/groovydoc/org/gradle/api/Project.html').assertIsFile()
+        contentsDir.file('docs/groovydoc/org/gradle/api/tasks/bundling/Zip.html').assertIsFile()
+        contentsDir.file('docs/userguide/userguide.html').assertIsFile()
+        contentsDir.file('docs/userguide/userguide_single.html').assertIsFile()
+//        contentsDir.file('docs/userguide/userguide.pdf').assertIsFile()
+    }
+
+    private def checkMinimalContents(TestFile contentsDir) {
+        // Check it can be executed
+        executer.inDirectory(contentsDir).usingExecutable('bin/gradle').withTaskList().run()
+
+        // Scripts
+        contentsDir.file('bin/gradle').assertIsFile()
+        contentsDir.file('bin/gradle.bat').assertIsFile()
+
+        // Top level files
+        contentsDir.file('LICENSE').assertIsFile()
+
+        // Libs
+        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/plugins/gradle-code-quality-${version}.jar"))
+        assertIsGradleJar(contentsDir.file("lib/plugins/gradle-plugins-${version}.jar"))
+        assertIsGradleJar(contentsDir.file("lib/plugins/gradle-jetty-${version}.jar"))
+        assertIsGradleJar(contentsDir.file("lib/plugins/gradle-wrapper-${version}.jar"))
+
+        // Docs
+        contentsDir.file('getting-started.html').assertIsFile()
+    }
+
+    private def assertIsGradleJar(TestFile jar) {
+        jar.assertIsFile()
+        assertThat(jar.manifest.mainAttributes.getValue('Implementation-Version'), equalTo(version))
+        assertThat(jar.manifest.mainAttributes.getValue('Implementation-Title'), equalTo('Gradle'))
+    }
+
+    @Test
+    public void sourceZipContents() {
+        TestFile srcZip = dist.distributionsDir.file("gradle-$version-src.zip")
+        srcZip.usingNativeTools().unzipTo(dist.testDir)
+        TestFile contentsDir = dist.testDir.file("gradle-$version")
+
+        // Build self using wrapper in source distribution
+        executer.inDirectory(contentsDir).usingExecutable('gradlew').withTasks('binZip').run()
+
+        File binZip = contentsDir.file('build/distributions').listFiles()[0]
+        Expand unpack = new Expand()
+        unpack.src = binZip
+        unpack.dest = contentsDir.file('build/distributions/unzip')
+        AntUtil.execute(unpack)
+        TestFile unpackedRoot = new TestFile(contentsDir.file('build/distributions/unzip').listFiles()[0])
+
+        // Make sure the build distribution does something useful
+        unpackedRoot.file("bin/gradle").assertIsFile()
+        // todo run something with the gradle build by the source dist
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/DistributionIntegrationTestRunner.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/DistributionIntegrationTestRunner.java
new file mode 100644
index 0000000..3e98fbd
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/DistributionIntegrationTestRunner.java
@@ -0,0 +1,40 @@
+/*
+ * 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.junit.runner.Description;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.InitializationError;
+
+public class DistributionIntegrationTestRunner extends BlockJUnit4ClassRunner {
+    private static final String IGNORE_SYS_PROP = "org.gradle.integtest.ignore";
+
+    public DistributionIntegrationTestRunner(Class<?> testClass) throws InitializationError {
+        super(testClass);
+    }
+
+    @Override
+    public void run(final RunNotifier notifier) {
+        if (System.getProperty(IGNORE_SYS_PROP) != null) {
+            notifier.fireTestIgnored(Description.createTestDescription(getTestClass().getJavaClass(),
+                    "System property to ignore integration tests is set."));
+        } else {
+            super.run(notifier);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/DynamicObjectIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/DynamicObjectIntegrationTest.groovy
new file mode 100644
index 0000000..8fbe9a9
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/DynamicObjectIntegrationTest.groovy
@@ -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.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 org.junit.runner.RunWith
+
+ at RunWith(DistributionIntegrationTestRunner.class)
+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(
+                "rootProperty = 'root'",
+                "sharedProperty = 'ignore me'",
+                "convention.plugins.test = new ConventionBean()",
+                "task rootTask",
+                "task testTask",
+                "class ConventionBean { def getConventionProperty() { 'convention' } }"
+        );
+        testDir.file("child/build.gradle").writelns(
+                "childProperty = 'child'",
+                "sharedProperty = 'shared'",
+                "task testTask << {",
+                "  new Reporter().checkProperties(project)",
+                "}",
+                // 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 ':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 canAddDynamicPropertiesToCoreDomainObjects() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file('build.gradle') << '''
+            class Extension { def doStuff() { 'method' } }
+            class GroovyTask extends DefaultTask { }
+
+            task defaultTask {
+                custom = 'value'
+                convention.plugins.custom = new Extension()
+            }
+            task javaTask(type: Copy) {
+                custom = 'value'
+                convention.plugins.custom = new Extension()
+            }
+            task groovyTask(type: GroovyTask) {
+                custom = 'value'
+                convention.plugins.custom = new Extension()
+            }
+            configurations {
+                test {
+                    custom = 'value'
+                    convention.plugins.custom = new Extension()
+                }
+            }
+            dependencies {
+                test('::name:') {
+                    custom = 'value';
+                    convention.plugins.custom = new Extension()
+                }
+                test(module('::other')) {
+                    custom = 'value';
+                    convention.plugins.custom = new Extension()
+                }
+                test(project(':')) {
+                    custom = 'value';
+                    convention.plugins.custom = new Extension()
+                }
+                test(files('src')) {
+                    custom = 'value';
+                    convention.plugins.custom = new Extension()
+                }
+            }
+            repositories {
+                custom = 'repository'
+                convention.plugins.custom = new Extension()
+            }
+            defaultTask.custom = 'another value'
+            javaTask.custom = 'another value'
+            groovyTask.custom = 'another value'
+            assert !project.hasProperty('custom')
+            assert defaultTask.custom == 'another value'
+            assert defaultTask.doStuff() == 'method'
+            assert javaTask.doStuff() == 'method'
+            assert groovyTask.doStuff() == 'method'
+            assert configurations.test.custom == 'value'
+            assert configurations.test.doStuff() == 'method'
+            configurations.test.dependencies.each { assert it.custom == 'value' }
+            assert repositories.custom == 'repository'
+            assert repositories.doStuff() == 'method'
+            repositories {
+                assert custom == 'repository'
+                assert doStuff() == 'method'
+            }
+'''
+
+        executer.inDirectory(testDir).withTasks("defaultTask").run();
+    }
+
+    @Test
+    public void canInjectMethodsFromParentProject() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file("settings.gradle").writelns("include 'child'");
+        testDir.file("build.gradle").writelns(
+                "subprojects {",
+                "  injectedMethod = { project.name }",
+                "}"
+        );
+        testDir.file("child/build.gradle").writelns(
+                "task testTask << {",
+                "   assert injectedMethod() == 'child'",
+                "}"
+        );
+
+        executer.inDirectory(testDir).withTasks("testTask").run();
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/EclipseIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/EclipseIntegrationTest.groovy
new file mode 100644
index 0000000..319d436
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/EclipseIntegrationTest.groovy
@@ -0,0 +1,33 @@
+/*
+ * 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.junit.Rule
+import org.junit.Test
+
+class EclipseIntegrationTest extends AbstractIntegrationTest {
+    @Rule
+    public final TestResources testResources = new TestResources()
+
+    @Test
+    public void canCreateAndDeleteMetaData() {
+        File buildFile = testFile("master/build.gradle");
+        usingBuildFile(buildFile).run();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ExecIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ExecIntegrationTest.groovy
new file mode 100644
index 0000000..e2e8932
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ExecIntegrationTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * 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.junit.Rule
+import org.junit.Test
+
+class ExecIntegrationTest extends AbstractIntegrationTest {
+    @Rule
+    public final TestResources testResources = new TestResources()
+
+    @Test
+    public void canExecuteJava() {
+        File buildFile = testFile("canExecuteJava.gradle");
+        usingBuildFile(buildFile).run();
+    }
+
+    @Test
+    public void canExecuteCommands() {
+        File buildFile = testFile("canExecuteCommands.gradle");
+        usingBuildFile(buildFile).run();
+    }
+
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ExternalPluginIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ExternalPluginIntegrationTest.groovy
new file mode 100644
index 0000000..f539583
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ExternalPluginIntegrationTest.groovy
@@ -0,0 +1,68 @@
+/*
+ * 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.ArtifactBuilder
+import org.junit.Test
+
+public class ExternalPluginIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void canReferencePluginInBuildSrcProjectById() {
+        testFile('buildSrc/src/main/java/CustomPlugin.java') << '''
+import org.gradle.api.*;
+public class CustomPlugin implements Plugin<Project> {
+    public void apply(Project p) { p.setProperty("prop", "value"); }
+}
+'''
+        testFile('buildSrc/src/main/resources/META-INF/gradle-plugins/custom.properties') << '''
+implementation-class=CustomPlugin
+'''
+
+        testFile('build.gradle') << '''
+apply plugin: 'custom'
+assert 'value' == prop
+task test
+'''
+        inTestDirectory().withTasks('test').run()
+    }
+    
+    @Test
+    public void canReferencePluginInExternalJarById() {
+        ArtifactBuilder builder = artifactBuilder()
+        builder.sourceFile('CustomPlugin.java') << '''
+import org.gradle.api.*;
+public class CustomPlugin implements Plugin<Project> {
+    public void apply(Project p) { p.setProperty("prop", "value"); }
+}
+'''
+        builder.resourceFile('META-INF/gradle-plugins/custom.properties') << '''
+implementation-class=CustomPlugin
+'''
+        builder.buildJar(testFile('external.jar'))
+
+        testFile('build.gradle') << '''
+buildscript {
+    dependencies {
+        classpath files('external.jar')
+    }
+}
+apply plugin: 'custom'
+assert 'value' == prop
+task test
+'''
+        inTestDirectory().withTasks('test').run()
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ExternalScriptErrorIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ExternalScriptErrorIntegrationTest.groovy
new file mode 100644
index 0000000..533f7f4
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ExternalScriptErrorIntegrationTest.groovy
@@ -0,0 +1,89 @@
+/*
+ * 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.ExecutionFailure
+import org.gradle.util.TestFile
+import org.junit.Test
+
+class ExternalScriptErrorIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void reportsScriptEvaluationFailsWithGroovyException() {
+        testFile('build.gradle') << '''
+apply { from 'other.gradle' }
+'''
+        TestFile script = testFile('other.gradle') << '''
+
+doStuff()
+'''
+
+        ExecutionFailure failure = inTestDirectory().runWithFailure()
+
+        failure.assertHasFileName("Script '${script}'");
+        failure.assertHasLineNumber(3);
+        failure.assertHasDescription('A problem occurred evaluating script.');
+        failure.assertHasCause('Could not find method doStuff() for arguments [] on root project');
+    }
+
+    @Test
+    public void reportsScriptCompilationException() {
+        testFile('build.gradle') << '''
+apply { from 'other.gradle' }
+'''
+        TestFile script = testFile('other.gradle')
+        script.text = 'import org.gradle()'
+
+        ExecutionFailure failure = inTestDirectory().runWithFailure()
+        failure.assertHasFileName("Script '${script}'");
+        failure.assertHasLineNumber(1);
+        failure.assertHasDescription("Could not compile script '${script}'");
+    }
+
+    @Test
+    public void reportsMissingScript() {
+        TestFile buildScript = testFile('build.gradle') << '''
+apply { from 'unknown.gradle' }
+'''
+        TestFile script = testFile('unknown.gradle')
+
+        ExecutionFailure failure = inTestDirectory().runWithFailure()
+        failure.assertHasFileName("Build file '${buildScript}");
+        failure.assertHasLineNumber(2);
+        failure.assertHasDescription("A problem occurred evaluating root project");
+        failure.assertHasCause("Could not read script '${script}' as it does not exist.");
+    }
+
+    @Test
+    public void reportsTaskExecutionFailsWithRuntimeException() {
+        testFile('build.gradle') << '''
+apply { from 'other.gradle' }
+'''
+        TestFile script = testFile('other.gradle') << '''
+task doStuff << {
+    throw new RuntimeException('fail')
+}
+'''
+
+        ExecutionFailure failure = inTestDirectory().withTasks('doStuff').runWithFailure()
+
+        failure.assertHasFileName("Script '${script}'");
+        failure.assertHasLineNumber(3);
+        failure.assertHasDescription('Execution failed for task \':doStuff\'');
+        failure.assertHasCause('fail');
+    }
+
+}
+
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ExternalScriptExecutionIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ExternalScriptExecutionIntegrationTest.groovy
new file mode 100644
index 0000000..e7cae95
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ExternalScriptExecutionIntegrationTest.groovy
@@ -0,0 +1,184 @@
+/*
+ * 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.ArtifactBuilder
+import org.gradle.integtests.fixtures.ExecutionResult
+import org.gradle.integtests.fixtures.HttpServer
+import org.gradle.util.TestFile
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+public class ExternalScriptExecutionIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void executesExternalScriptAgainstAProjectWithCorrectEnvironment() {
+        createExternalJar()
+        createBuildSrc()
+
+        TestFile externalScript = testFile('external.gradle')
+        externalScript << """
+            buildscript {
+                dependencies { classpath files('repo/test-1.3.jar') }
+            }
+            new org.gradle.test.BuildClass()
+            new BuildSrcClass()
+            println 'quiet message'
+            captureStandardOutput(LogLevel.ERROR)
+            println 'error message'
+            assert project != null
+            assert "${externalScript.absolutePath.replace("\\", "\\\\")}" == buildscript.sourceFile as String
+            assert "${externalScript.toURI()}" == buildscript.sourceURI as String
+            assert buildscript.classLoader == getClass().classLoader.parent
+            assert buildscript.classLoader == Thread.currentThread().contextClassLoader
+            assert gradle.scriptClassLoader == buildscript.classLoader.parent
+            assert project.buildscript.classLoader != buildscript.classLoader
+            task doStuff
+            someProp = 'value'
+"""
+        testFile('build.gradle') << '''
+apply { from 'external.gradle' }
+assert 'value' == someProp
+'''
+
+        ExecutionResult result = inTestDirectory().withTasks('doStuff').run()
+        assertThat(result.output, containsString('quiet message'))
+        assertThat(result.output, not(containsString('error message')))
+        assertThat(result.error, containsString('error message'))
+        assertThat(result.error, not(containsString('quiet message')))
+    }
+
+    @Test
+    public void canExecuteExternalScriptAgainstAnArbitraryObject() {
+        createBuildSrc()
+
+        testFile('external.gradle') << '''
+println 'quiet message'
+captureStandardOutput(LogLevel.ERROR)
+println 'error message'
+new BuildSrcClass()
+assert 'doStuff' == name
+assert buildscript.classLoader == getClass().classLoader.parent
+assert buildscript.classLoader == Thread.currentThread().contextClassLoader
+assert project.gradle.scriptClassLoader == buildscript.classLoader.parent
+assert project.buildscript.classLoader != buildscript.classLoader
+someProp = 'value'
+'''
+        testFile('build.gradle') << '''
+task doStuff
+apply {
+    to doStuff
+    from 'external.gradle'
+}
+assert 'value' == doStuff.someProp
+'''
+
+        ExecutionResult result = inTestDirectory().withTasks('doStuff').run()
+        assertThat(result.output, containsString('quiet message'))
+        assertThat(result.output, not(containsString('error message')))
+        assertThat(result.error, containsString('error message'))
+        assertThat(result.error, not(containsString('quiet message')))
+    }
+
+    @Test
+    public void canExecuteExternalScriptFromSettingsScript() {
+        testFile('settings.gradle') << ''' apply { from 'other.gradle' } '''
+        testFile('other.gradle') << ''' include 'child' '''
+        testFile('build.gradle') << ''' assert ['child'] == subprojects*.name '''
+
+        inTestDirectory().withTaskList().run()
+    }
+
+    @Test
+    public void canExecuteExternalScriptFromInitScript() {
+        TestFile initScript = testFile('init.gradle') << ''' apply { from 'other.gradle' } '''
+        testFile('other.gradle') << '''
+addListener(new ListenerImpl())
+class ListenerImpl extends BuildAdapter {
+    public void projectsEvaluated(Gradle gradle) {
+        gradle.rootProject.task('doStuff')
+    }
+}
+'''
+        inTestDirectory().usingInitScript(initScript).withTasks('doStuff').run()
+    }
+
+    @Test
+    public void canExecuteExternalScriptFromExternalScript() {
+        testFile('build.gradle') << ''' apply { from 'other1.gradle' } '''
+        testFile('other1.gradle') << ''' apply { from 'other2.gradle' } '''
+        testFile('other2.gradle') << ''' task doStuff '''
+
+        inTestDirectory().withTasks('doStuff').run()
+    }
+
+    @Test
+    public void canFetchScriptViaHttp() {
+        TestFile script = testFile('external.gradle')
+
+        HttpServer server = new HttpServer()
+        server.add('/external.gradle', script)
+        server.start()
+
+        script << """
+            task doStuff
+            assert buildscript.sourceFile == null
+            assert "http://localhost:$server.port/external.gradle" == buildscript.sourceURI as String
+"""
+
+        testFile('build.gradle') << """
+            apply from: 'http://localhost:$server.port/external.gradle'
+            defaultTasks 'doStuff'
+"""
+
+        inTestDirectory().run()
+
+        server.stop()
+    }
+
+    @Test
+    public void cachesScriptClassForAGivenScript() {
+        testFile('settings.gradle') << 'include \'a\', \'b\''
+        testFile('external.gradle') << 'appliedScript = this'
+        testFile('build.gradle') << '''
+allprojects {
+   apply from: "$rootDir/external.gradle"
+}
+subprojects {
+    assert appliedScript.class == rootProject.appliedScript.class
+}
+task doStuff
+'''
+        inTestDirectory().withTasks('doStuff').run()
+    }
+
+    private TestFile createBuildSrc() {
+        return testFile('buildSrc/src/main/java/BuildSrcClass.java') << '''
+            public class BuildSrcClass { }
+'''
+    }
+
+    private def createExternalJar() {
+        ArtifactBuilder builder = artifactBuilder();
+        builder.sourceFile('org/gradle/test/BuildClass.java') << '''
+            package org.gradle.test;
+            public class BuildClass { }
+'''
+        builder.buildJar(testFile("repo/test-1.3.jar"))
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/FileTreeCopyIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/FileTreeCopyIntegrationTest.groovy
new file mode 100644
index 0000000..4176690
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/FileTreeCopyIntegrationTest.groovy
@@ -0,0 +1,82 @@
+/*
+ * 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
+
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+
+public class FileTreeCopyIntegrationTest extends AbstractIntegrationTest {
+    @Rule
+    public final TestResources resources = new TestResources("copyTestResources")
+
+    @Test public void testCopyWithClosure() {
+        TestFile buildFile = testFile("build.gradle").writelns(
+                """task cpy << {
+                   fileTree {
+                      from 'src'
+                      exclude '**/ignore/**'
+                   }.copy { into 'dest'}
+                }"""
+        )
+        usingBuildFile(buildFile).withTasks("cpy").run()
+        testFile('dest').assertHasDescendants(
+                'root.a',
+                'root.b',
+                'one/one.a',
+                'one/one.b',
+                'one/sub/onesub.a',
+                'one/sub/onesub.b',
+                'two/two.a',
+                'two/two.b',
+        )
+    }
+
+    @Test public void testCopyWithMap() {
+        TestFile buildFile = testFile("build.gradle").writelns(
+                """task cpy << {
+                   fileTree(dir:'src', excludes:['**/ignore/**', '**/sub/**']).copy { into 'dest'}
+                }"""
+        )
+        usingBuildFile(buildFile).withTasks("cpy").run()
+        testFile('dest').assertHasDescendants(
+                'root.a',
+                'root.b',
+                'one/one.a',
+                'one/one.b',
+                'two/two.a',
+                'two/two.b',
+        )
+    }
+
+    @Test public void testCopyFluent() {
+        TestFile buildFile = testFile("build.gradle").writelns(
+                """task cpy << {
+                   fileTree(dir:'src').exclude('**/ignore/**', '**/sub/*.?').copy { into 'dest' }
+                }"""
+        )
+        usingBuildFile(buildFile).withTasks("cpy").run()
+        testFile('dest').assertHasDescendants(
+                'root.a',
+                'root.b',
+                'one/one.a',
+                'one/one.b',
+                'two/two.a',
+                'two/two.b',
+        )
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/GeneratorTask.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/GeneratorTask.java
new file mode 100644
index 0000000..3daf2ac
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/GeneratorTask.java
@@ -0,0 +1,53 @@
+/*
+ * 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.api.DefaultTask;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+
+public class GeneratorTask extends DefaultTask {
+    @Input
+    private String text;
+    @OutputFile
+    private File outputFile;
+
+    public String getText() {
+        return text;
+    }
+
+    public void setText(String text) {
+        this.text = text;
+    }
+
+    public File getOutputFile() {
+        return outputFile;
+    }
+
+    public void setOutputFile(File outputFile) {
+        this.outputFile = outputFile;
+    }
+
+    @TaskAction
+    public void generate() {
+        GFileUtils.writeStringToFile(outputFile, text);
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/GradleUserHomeEnvVariableIntegrationTest.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/GradleUserHomeEnvVariableIntegrationTest.java
new file mode 100644
index 0000000..95b377a
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/GradleUserHomeEnvVariableIntegrationTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+import org.gradle.integtests.fixtures.GradleDistribution;
+import org.gradle.integtests.fixtures.GradleDistributionExecuter;
+import org.gradle.util.WrapUtil;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+ at RunWith(DistributionIntegrationTestRunner.class)
+public class GradleUserHomeEnvVariableIntegrationTest  {
+    @Rule
+    public final GradleDistribution dist = new GradleDistribution();
+    @Rule
+    public final GradleDistributionExecuter executer = new GradleDistributionExecuter();
+
+    @Test
+    public void canDefineGradleUserHomeViaEnvVariable() {
+        // the actual testing is done in the build script.
+        File projectDir = new File(dist.getSamplesDir(), "gradleUserHome");
+        File gradleUserHomeDir = new File(dist.getSamplesDir(), "gradleUserHome/customUserHome");
+        executer.withUserHomeDir(null);
+        executer.withEnvironmentVars(WrapUtil.toMap("GRADLE_USER_HOME", gradleUserHomeDir.getAbsolutePath())).
+                inDirectory(projectDir).withTasks("checkGradleUserHomeViaSystemEnv").run();
+    }
+
+    @Test
+    public void checkDefaultGradleUserHome() {
+        // the actual testing is done in the build script.
+        File projectDir = new File(dist.getSamplesDir(), "gradleUserHome");
+        executer.withUserHomeDir(null);
+        executer.inDirectory(projectDir).withTasks("checkDefaultGradleUserHome").run();
+    }
+
+    @Ignore
+    public void systemPropGradleUserHomeHasPrecedenceOverEnvVariable() {
+        // the actual testing is done in the build script.
+        File projectDir = new File(dist.getSamplesDir(), "gradleUserHome");
+        File gradleUserHomeDir = new File(dist.getSamplesDir(), "gradleUserHome/customUserHome");
+        File systemPropGradleUserHomeDir = new File(dist.getSamplesDir(), "gradleUserHome/systemPropCustomUserHome");
+        executer.withUserHomeDir(null);
+        executer.withArguments("-Dgradle.user.home=" + systemPropGradleUserHomeDir.getAbsolutePath()).
+                withEnvironmentVars(WrapUtil.toMap("GRADLE_USER_HOME", gradleUserHomeDir.getAbsolutePath())).
+                inDirectory(projectDir).withTasks("checkSystemPropertyGradleUserHomeHasPrecedence").run();
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/GroovyProjectIntegrationTest.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/GroovyProjectIntegrationTest.java
new file mode 100644
index 0000000..7c79eca
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/GroovyProjectIntegrationTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+import org.junit.Test;
+
+public class GroovyProjectIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void handlesEmptyProject() {
+        testFile("build.gradle").writelns(
+                "apply plugin: 'groovy'"
+        );
+        inTestDirectory().withTasks("build").run();
+    }
+
+    @Test
+    public void handlesJavaSourceOnly() {
+        testFile("src/main/java/somepackage/SomeClass.java").writelns("public class SomeClass { }");
+        testFile("build.gradle").writelns("apply plugin: 'groovy'");
+        testFile("settings.gradle").write("rootProject.name='javaOnly'");
+        inTestDirectory().withTasks("build").run();
+        testFile("build/libs/javaOnly.jar").assertExists();
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IdeaIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IdeaIntegrationTest.groovy
new file mode 100644
index 0000000..e3825ca
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IdeaIntegrationTest.groovy
@@ -0,0 +1,40 @@
+/*
+ * 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.integtests.fixtures.TestResources
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+ at RunWith(DistributionIntegrationTestRunner)
+class IdeaIntegrationTest {
+    @Rule
+    public final GradleDistribution dist = new GradleDistribution()
+    @Rule
+    public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule
+    public final TestResources testResources = new TestResources()
+
+    @Test
+    public void canCreateAndDeleteMetaData() {
+        executer.withTasks('idea', 'cleanIdea').run()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IncrementalBuildIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IncrementalBuildIntegrationTest.groovy
new file mode 100644
index 0000000..77984a8
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IncrementalBuildIntegrationTest.groovy
@@ -0,0 +1,374 @@
+/*
+ * 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.util.TestFile
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+class IncrementalBuildIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void skipsTaskWhenOutputFileIsUpToDate() {
+        testFile('build.gradle') << '''
+task a(type: org.gradle.integtests.TransformerTask) {
+    inputFile = file('src.txt')
+    outputFile = file('src.a.txt')
+}
+task b(type: org.gradle.integtests.TransformerTask, dependsOn: a) {
+    inputFile = a.outputFile
+    outputFile = file('src.b.txt')
+}
+'''
+        TestFile inputFile = testFile('src.txt')
+        TestFile outputFileA = testFile('src.a.txt')
+        TestFile outputFileB = testFile('src.b.txt')
+
+        inputFile.text = 'content'
+
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped()
+
+        TestFile.Snapshot aSnapshot = outputFileA.snapshot()
+        TestFile.Snapshot bSnapshot = outputFileB.snapshot()
+        assertThat(outputFileA.text, equalTo('[content]'))
+        assertThat(outputFileB.text, equalTo('[[content]]'))
+
+        // No changes
+
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped(':a', ':b')
+
+        outputFileA.assertHasNotChangedSince(aSnapshot)
+        outputFileB.assertHasNotChangedSince(bSnapshot)
+
+        // Update timestamp, no content changes
+
+        inputFile.setLastModified(inputFile.lastModified() - 10000);
+
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped(':a', ':b')
+
+        outputFileA.assertHasNotChangedSince(aSnapshot)
+        outputFileB.assertHasNotChangedSince(bSnapshot)
+
+        // Change content
+
+        inputFile.text = 'new content'
+
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped()
+
+        outputFileA.assertHasChangedSince(aSnapshot)
+        outputFileB.assertHasChangedSince(bSnapshot)
+        assertThat(outputFileA.text, equalTo('[new content]'))
+        assertThat(outputFileB.text, equalTo('[[new content]]'))
+
+        // Delete intermediate output file
+
+        outputFileA.delete()
+
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped(':b')
+
+        assertThat(outputFileA.text, equalTo('[new content]'))
+        assertThat(outputFileB.text, equalTo('[[new content]]'))
+
+        // Delete final output file
+
+        outputFileB.delete()
+
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped(':a')
+
+        assertThat(outputFileA.text, equalTo('[new content]'))
+        assertThat(outputFileB.text, equalTo('[[new content]]'))
+
+        // Change build file in a way which does not affect the task
+
+        testFile('build.gradle').text += '''
+task c
+'''
+
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped(':a', ':b')
+
+        // Change an input property of the first task (the content format)
+
+        testFile('build.gradle').text += '''
+a.format = ' %s '
+'''
+
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped()
+
+        assertThat(outputFileA.text, equalTo(' new content '))
+        assertThat(outputFileB.text, equalTo('[ new content ]'))
+
+        // Change final output file destination
+
+        testFile('build.gradle').text += '''
+b.outputFile = file('new-output.txt')
+'''
+
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped(':a')
+        outputFileB = testFile('new-output.txt')
+        outputFileB.assertIsFile()
+
+        // Run with --no-opt command-line options
+        inTestDirectory().withTasks('b').withArguments('--no-opt').run().assertTasksExecuted(':a', ':b').assertTasksSkipped()
+
+        // Output files already exist before using this version of Gradle
+        // delete .gradle dir to simulate this
+        testFile('.gradle').assertIsDir().deleteDir()
+
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped()
+
+        outputFileB.delete()
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped(':a')
+
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped(':a', ':b')
+    }
+
+    @Test
+    public void skipsTaskWhenOutputDirContentsAreUpToDate() {
+        testFile('build.gradle') << '''
+task a(type: org.gradle.integtests.DirTransformerTask) {
+    inputDir = file('src')
+    outputDir = file('build/a')
+}
+task b(type: org.gradle.integtests.DirTransformerTask, dependsOn: a) {
+    inputDir = a.outputDir
+    outputDir = file('build/b')
+}
+'''
+
+        testFile('src').createDir()
+        testFile('src/file1.txt').write('content')
+
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped()
+
+        TestFile outputAFile = testFile('build/a/file1.txt')
+        TestFile outputBFile = testFile('build/b/file1.txt')
+        TestFile.Snapshot aSnapshot = outputAFile.snapshot()
+        TestFile.Snapshot bSnapshot = outputBFile.snapshot()
+
+        outputAFile.assertContents(equalTo('[content]'))
+        outputBFile.assertContents(equalTo('[[content]]'))
+
+        // No changes
+
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped(':a', ':b')
+
+        outputAFile.assertHasNotChangedSince(aSnapshot)
+        outputBFile.assertHasNotChangedSince(bSnapshot)
+
+        // Change content
+
+        testFile('src/file1.txt').write('new content')
+
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped()
+
+        outputAFile.assertHasChangedSince(aSnapshot)
+        outputBFile.assertHasChangedSince(bSnapshot)
+        outputAFile.assertContents(equalTo('[new content]'))
+        outputBFile.assertContents(equalTo('[[new content]]'))
+
+        // Add file
+
+        testFile('src/file2.txt').write('content2')
+
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped()
+
+        testFile('build/a/file2.txt').assertContents(equalTo('[content2]'))
+        testFile('build/b/file2.txt').assertContents(equalTo('[[content2]]'))
+
+        // Remove file
+
+        testFile('src/file1.txt').delete()
+
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped(':b')
+
+        // Output files already exist before using this version of Gradle
+        // delete .gradle dir to simulate this
+        testFile('.gradle').assertIsDir().deleteDir()
+
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped()
+
+        testFile('build/b').deleteDir()
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped(':a')
+
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped(':a', ':b')
+    }
+
+    @Test
+    public void skipsTaskWhenInputPropertiesHaveNotChanged() {
+        testFile('build.gradle') << '''
+task a(type: org.gradle.integtests.GeneratorTask) {
+    text = project.text
+    outputFile = file('dest.txt')
+}
+'''
+
+        inTestDirectory().withTasks('a').withArguments('-Ptext=text').run().assertTasksExecuted(':a').assertTasksSkipped()
+
+        inTestDirectory().withTasks('a').withArguments('-Ptext=text', '-i').run().assertTasksExecuted(':a').assertTasksSkipped(':a')
+
+        inTestDirectory().withTasks('a').withArguments('-Ptext=newtext').run().assertTasksExecuted(':a').assertTasksSkipped()
+    }
+
+    @Test
+    public void multipleTasksCanGenerateIntoOverlappingOutputDirectories() {
+        testFile('build.gradle') << '''
+task a(type: org.gradle.integtests.DirTransformerTask) {
+    inputDir = file('src/a')
+    outputDir = file('build')
+}
+task b(type: org.gradle.integtests.DirTransformerTask) {
+    inputDir = file('src/b')
+    outputDir = file('build')
+}
+'''
+
+        testFile('src/a/file1.txt') << 'content'
+        testFile('src/b/file2.txt') << 'content'
+
+        inTestDirectory().withTasks('a', 'b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped()
+
+        // No changes
+
+        inTestDirectory().withTasks('a', 'b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped(':a', ':b')
+
+        // Delete an output file
+
+        testFile('build/file1.txt').delete()
+
+        inTestDirectory().withTasks('a', 'b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped(':b')
+
+        // Change an output file
+
+        testFile('build/file2.txt').write('something else')
+
+        inTestDirectory().withTasks('a', 'b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped(':a')
+
+        // Change to new version of Gradle
+        // Simulate this by removing the .gradle dir
+        testFile('.gradle').assertIsDir().deleteDir()
+
+        inTestDirectory().withTasks('a', 'b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped()
+
+        testFile('build').deleteDir()
+
+        inTestDirectory().withTasks('a').run().assertTasksExecuted(':a').assertTasksSkipped()
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':b').assertTasksSkipped()
+    }
+
+    @Test
+    public void canUseUpToDatePredicateToForceTaskToExecute() {
+        testFile('build.gradle') << '''
+task inputsAndOutputs {
+    inputs.files 'src.txt'
+    outputs.files 'src.a.txt'
+    outputs.upToDateWhen { project.hasProperty('uptodate') }
+    doFirst {
+        outputs.files.singleFile.text = "[${inputs.files.singleFile.text}]"
+    }
+}
+task noOutputs {
+    inputs.files 'src.txt'
+    outputs.upToDateWhen { project.hasProperty('uptodate') }
+    doFirst { }
+}
+task nothing {
+    outputs.upToDateWhen { project.hasProperty('uptodate') }
+    doFirst { }
+}
+'''
+        TestFile srcFile = testFile('src.txt')
+        srcFile.text = 'content'
+
+        // Task with input files, output files and a predicate
+        inTestDirectory().withTasks('inputsAndOutputs').run().assertTasksExecuted(':inputsAndOutputs').assertTasksSkipped()
+
+        // Is up to date
+        inTestDirectory().withArguments('-Puptodate').withTasks('inputsAndOutputs').run().assertTasksExecuted(':inputsAndOutputs').assertTasksSkipped(':inputsAndOutputs')
+
+        // Changed input file
+        srcFile.text = 'different'
+        inTestDirectory().withArguments('-Puptodate').withTasks('inputsAndOutputs').run().assertTasksExecuted(':inputsAndOutputs').assertTasksSkipped()
+
+        // Predicate is false
+        inTestDirectory().withTasks('inputsAndOutputs').run().assertTasksExecuted(':inputsAndOutputs').assertTasksSkipped()
+
+        // Task with input files and a predicate
+        inTestDirectory().withTasks('noOutputs').run().assertTasksExecuted(':noOutputs').assertTasksSkipped()
+
+        // Is up to date
+        inTestDirectory().withArguments('-Puptodate').withTasks('noOutputs').run().assertTasksExecuted(':noOutputs').assertTasksSkipped(':noOutputs')
+
+        // Changed input file
+        srcFile.text = 'different again'
+        inTestDirectory().withArguments('-Puptodate').withTasks('noOutputs').run().assertTasksExecuted(':noOutputs').assertTasksSkipped()
+
+        // Predicate is false
+        inTestDirectory().withTasks('noOutputs').run().assertTasksExecuted(':noOutputs').assertTasksSkipped()
+
+        // Task a predicate only
+        inTestDirectory().withTasks('nothing').run().assertTasksExecuted(':nothing').assertTasksSkipped()
+
+        // Is up to date
+        inTestDirectory().withArguments('-Puptodate').withTasks('nothing').run().assertTasksExecuted(':nothing').assertTasksSkipped(':nothing')
+
+        // Predicate is false
+        inTestDirectory().withTasks('nothing').run().assertTasksExecuted(':nothing').assertTasksSkipped()
+    }
+
+    @Test
+    public void lifecycleTaskIsUpToDateWhenAllDependenciesAreSkipped() {
+        testFile('build.gradle') << '''
+task a(type: org.gradle.integtests.TransformerTask) {
+    inputFile = file('src.txt')
+    outputFile = file('out.txt')
+}
+task b(dependsOn: a)
+'''
+
+        testFile('src.txt').text = 'content'
+
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped()
+        inTestDirectory().withTasks('b').run().assertTasksExecuted(':a', ':b').assertTasksSkipped(':a', ':b')
+    }
+
+    @Test
+    public void canShareArtifactsBetweenBuilds() {
+        testFile('build.gradle') << '''
+task otherBuild(type: GradleBuild) {
+    buildFile = 'other.gradle'
+    tasks = ['generate']
+    startParameter.searchUpwards = false
+}
+task transform(type: org.gradle.integtests.TransformerTask) {
+    dependsOn otherBuild
+    inputFile = file('generated.txt')
+    outputFile = file('out.txt')
+}
+'''
+        testFile('other.gradle') << '''
+task generate(type: org.gradle.integtests.TransformerTask) {
+    inputFile = file('src.txt')
+    outputFile = file('generated.txt')
+}
+'''
+        testFile('src.txt').text = 'content'     
+        inTestDirectory().withTasks('transform').run().assertTasksExecuted(':otherBuild', ':transform').assertTasksSkipped()
+        inTestDirectory().withTasks('transform').run().assertTasksExecuted(':otherBuild', ':transform').assertTasksSkipped(':transform')
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest.groovy
new file mode 100644
index 0000000..08963dd
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * 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.junit.Rule
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.junit.Test
+import org.gradle.integtests.fixtures.ExecutionFailure
+
+class IncrementalGroovyCompileIntegrationTest {
+    @Rule public final GradleDistribution distribution = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final TestResources resources = new TestResources()
+
+    @Test
+    public void recompilesSourceWhenPropertiesChange() {
+        executer.withTasks('compileGroovy').run().assertTasksSkipped()
+
+        distribution.testFile('build.gradle').text += '''
+            compileGroovy.options.debug = false
+'''
+
+        executer.withTasks('compileGroovy').run().assertTasksSkipped(':compileJava')
+
+        executer.withTasks('compileGroovy').run().assertTasksSkipped(':compileJava', ':compileGroovy')
+    }
+
+    @Test
+    public void recompilesDependentClasses() {
+        executer.withTasks("classes").run();
+
+        // Update interface, compile should fail
+        distribution.testFile('src/main/groovy/IPerson.groovy').assertIsFile().copyFrom(distribution.testFile('NewIPerson.groovy'))
+
+        ExecutionFailure failure = executer.withTasks("classes").runWithFailure();
+        failure.assertHasDescription("Execution failed for task ':compileGroovy'.");
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IncrementalJavaCompileIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IncrementalJavaCompileIntegrationTest.groovy
new file mode 100644
index 0000000..9ee05ab
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IncrementalJavaCompileIntegrationTest.groovy
@@ -0,0 +1,121 @@
+/*
+ * 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.junit.Rule
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.junit.Test
+import org.gradle.integtests.fixtures.ExecutionFailure
+
+class IncrementalJavaCompileIntegrationTest {
+    @Rule public final GradleDistribution distribution = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final TestResources resources = new TestResources()
+
+    @Test
+    public void recompilesSourceWhenPropertiesChange() {
+        executer.withTasks('compileJava').run().assertTasksSkipped()
+
+        distribution.testFile('build.gradle').text += '''
+            sourceCompatibility = 1.4
+'''
+
+        executer.withTasks('compileJava').run().assertTasksSkipped()
+
+        distribution.testFile('build.gradle').text += '''
+            compileJava.options.debug = false
+'''
+
+        executer.withTasks('compileJava').run().assertTasksSkipped()
+
+        executer.withTasks('compileJava').run().assertTasksSkipped(':compileJava')
+    }
+
+    @Test
+    public void recompilesDependentClasses() {
+        executer.withTasks("classes").run();
+
+        // Update interface, compile should fail
+        distribution.testFile('src/main/java/IPerson.java').assertIsFile().copyFrom(distribution.testFile('NewIPerson.java'))
+        
+        ExecutionFailure failure = executer.withTasks("classes").runWithFailure();
+        failure.assertHasDescription("Execution failed for task ':compileJava'.");
+    }
+
+    @Test
+    public void recompilesDependentClassesAcrossProjectBoundaries() {
+        executer.withTasks("app:classes").run();
+
+        // Update interface, compile should fail
+        distribution.testFile('lib/src/main/java/IPerson.java').assertIsFile().copyFrom(distribution.testFile('NewIPerson.java'))
+
+        ExecutionFailure failure = executer.withTasks("app:classes").runWithFailure();
+        failure.assertHasDescription("Execution failed for task ':app:compileJava'.");
+    }
+
+    @Test
+    public void recompilesDependentClassesWhenUsingAntDepend() {
+        distribution.testFile("build.gradle").writelns(
+                "apply plugin: 'java'",
+                "compileJava.options.depend()"
+        );
+        writeShortInterface();
+        writeTestClass();
+
+        executer.withTasks("classes").run();
+
+        // file system time stamp may not see change without this wait
+        Thread.sleep(1000L);
+
+        // Update interface, compile should fail because depend deletes old class
+        writeLongInterface();
+        ExecutionFailure failure = executer.withTasks("classes").runWithFailure();
+        failure.assertHasDescription("Execution failed for task ':compileJava'.");
+
+        // assert that dependency caching is on
+        distribution.testFile("build/dependency-cache/dependencies.txt").assertExists();
+    }
+
+    private void writeShortInterface() {
+        distribution.testFile("src/main/java/IPerson.java").writelns(
+                "interface IPerson {",
+                "    String getName();",
+                "}"
+        );
+    }
+
+    private void writeLongInterface() {
+        distribution.testFile("src/main/java/IPerson.java").writelns(
+                "interface IPerson {",
+                "    String getName();",
+                "    String getAddress();",
+                "}"
+        );
+    }
+
+    private void writeTestClass() {
+        distribution.testFile("src/main/java/Person.java").writelns(
+                "public class Person implements IPerson {",
+                "    private final String name = \"never changes\";",
+                "    public String getName() {",
+                "        return name;\n" +
+                "    }",
+                "}"
+        );
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IncrementalJavaProjectBuildIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IncrementalJavaProjectBuildIntegrationTest.groovy
new file mode 100644
index 0000000..245119e
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IncrementalJavaProjectBuildIntegrationTest.groovy
@@ -0,0 +1,68 @@
+/*
+ * 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.junit.Rule
+import org.junit.Test
+import org.gradle.util.TestFile
+
+class IncrementalJavaProjectBuildIntegrationTest {
+    @Rule public final GradleDistribution distribution = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+
+    @Test
+    public void removesStateResources() {
+        distribution.testFile('build.gradle') << 'apply plugin: \'java\''
+        distribution.testFile('src/main/resources/org/gradle/resource.txt').createFile()
+
+        executer.withTasks('classes').run()
+        distribution.testFile('build/classes/main').assertHasDescendants('org/gradle/resource.txt')
+
+        distribution.testFile('src/main/resources/org/gradle/resource.txt').assertIsFile().delete()
+        distribution.testFile('src/main/resources/org/gradle/resource2.txt').createFile()
+
+        executer.withTasks('classes').run()
+        distribution.testFile('build/classes/main').assertHasDescendants('org/gradle/resource2.txt')
+    }
+
+    @Test
+    public void doesNotRebuildJarIfSourceHasNotChanged() {
+        distribution.testFile("src/main/java/BuildClass.java") << 'public class BuildClass { }'
+        distribution.testFile("build.gradle") << "apply plugin: 'java'"
+        distribution.testFile("settings.gradle") << "rootProject.name = 'project'"
+
+        executer.withTasks("jar").run();
+
+        TestFile jar = distribution.testFile("build/libs/project.jar");
+        jar.assertIsFile();
+        TestFile.Snapshot snapshot = jar.snapshot();
+
+        executer.withTasks("jar").run();
+
+        jar.assertHasNotChangedSince(snapshot);
+
+        executer.withArguments("-Crebuild").withTasks("jar").run();
+
+        jar.assertHasChangedSince(snapshot);
+        snapshot = jar.snapshot();
+
+        executer.withTasks("jar").run();
+        jar.assertHasNotChangedSince(snapshot);
+    }
+
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IncrementalScalaCompileIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IncrementalScalaCompileIntegrationTest.groovy
new file mode 100644
index 0000000..5392cd2
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IncrementalScalaCompileIntegrationTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * 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.integtests.fixtures.TestResources
+import org.junit.Rule
+import org.junit.Test
+import org.gradle.integtests.fixtures.ExecutionFailure
+
+class IncrementalScalaCompileIntegrationTest {
+    @Rule public final GradleDistribution distribution = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final TestResources resources = new TestResources()
+
+    @Test
+    public void recompilesSourceWhenPropertiesChange() {
+        executer.withTasks('compileScala').run().assertTasksSkipped()
+
+        distribution.testFile('build.gradle').text += '''
+            compileScala.options.debug = false
+'''
+
+        executer.withTasks('compileScala').run().assertTasksSkipped(':compileJava')
+
+        executer.withTasks('compileScala').run().assertTasksSkipped(':compileJava', ':compileScala')
+    }
+
+    @Test
+    public void recompilesDependentClasses() {
+        executer.withTasks("classes").run();
+
+        // Update interface, compile should fail
+        distribution.testFile('src/main/scala/IPerson.scala').assertIsFile().copyFrom(distribution.testFile('NewIPerson.scala'))
+
+        ExecutionFailure failure = executer.withTasks("classes").runWithFailure();
+        failure.assertHasDescription("Execution failed for task ':compileScala'.");
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy
new file mode 100644
index 0000000..44b7272
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IncrementalTestIntegrationTest.groovy
@@ -0,0 +1,95 @@
+/*
+ * 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.integtests.fixtures.TestResources
+import org.junit.Assert
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import org.gradle.integtests.testng.TestNgExecutionResult
+
+class IncrementalTestIntegrationTest {
+    @Rule public final GradleDistribution distribution = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final TestResources resources = new TestResources()
+
+    @Test
+    public void doesNotRunStaleTests() {
+        def failure = executer.withTasks('test').runWithFailure()
+        failure.assertThatCause(startsWith('There were failing tests.'))
+
+        distribution.testFile('src/test/java/Broken.java').assertIsFile().delete()
+
+        executer.withTasks('test').run()
+    }
+
+    @Test
+    public void executesTestsWhenSourceChanges() {
+        executer.withTasks('test').run()
+
+        // Change a production class
+        distribution.testFile('src/main/java/MainClass.java').assertIsFile().copyFrom(distribution.testFile('NewMainClass.java'))
+
+        executer.withTasks('test').run().assertTasksNotSkipped(':compileJava', ':classes', ':compileTestJava', ':testClasses', ':test')
+        executer.withTasks('test').run().assertTasksNotSkipped()
+        
+        // Change a test class
+        distribution.testFile('src/test/java/Ok.java').assertIsFile().copyFrom(distribution.testFile('NewOk.java'))
+
+        executer.withTasks('test').run().assertTasksNotSkipped(':compileTestJava', ':testClasses', ':test')
+        executer.withTasks('test').run().assertTasksNotSkipped()
+    }
+
+    @Test
+    public void executesTestsWhenSelectedTestsChange() {
+        executer.withTasks('test').run()
+
+        def result = new JUnitTestExecutionResult(distribution.testDir)
+        result.assertTestClassesExecuted('JUnitTest')
+
+        // Include more tests
+        distribution.testFile('build.gradle').append 'test.include "**/*Extra*"\n'
+
+        executer.withTasks('test').run().assertTasksNotSkipped(':test')
+        result.assertTestClassesExecuted('JUnitTest', 'JUnitExtra')
+
+        executer.withTasks('test').run().assertTasksNotSkipped()
+
+        // Use single test execution
+        executer.withTasks('test').withArguments('-Dtest.single=Ok').run().assertTasksNotSkipped(':test')
+        executer.withTasks('test').run().assertTasksNotSkipped(':test')
+        executer.withTasks('test').run().assertTasksNotSkipped()
+
+        // Switch test framework
+        distribution.testFile('build.gradle').append 'test.useTestNG()\n'
+
+        executer.withTasks('test').run().assertTasksNotSkipped(':test')
+
+        result = new TestNgExecutionResult(distribution.testDir)
+        result.assertTestClassesExecuted('TestNGTest')
+
+        executer.withTasks('test').run().assertTasksNotSkipped()
+    }
+
+    @Test @Ignore
+    public void executesTestsWhenPropertiesChange() {
+        Assert.fail()
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/InitScriptErrorIntegrationTest.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/InitScriptErrorIntegrationTest.java
new file mode 100644
index 0000000..f23c3ae
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/InitScriptErrorIntegrationTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+import org.gradle.integtests.fixtures.ExecutionFailure;
+import org.gradle.util.TestFile;
+import org.junit.Test;
+
+public class InitScriptErrorIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void reportsInitScriptEvaluationFailsWithGroovyException() {
+        TestFile initScript = testFile("init.gradle");
+        initScript.write("\ncreateTakk('do-stuff')");
+        ExecutionFailure failure = inTestDirectory().usingInitScript(initScript).runWithFailure();
+
+        failure.assertHasFileName(String.format("Initialization script '%s'", initScript));
+        failure.assertHasLineNumber(2);
+        failure.assertHasDescription("A problem occurred evaluating initialization script.");
+        failure.assertHasCause("No signature of method: org.gradle.invocation.DefaultGradle.createTakk() is applicable for argument types: (java.lang.String) values: [do-stuff]");
+    }
+
+    @Test
+    public void reportsGroovyCompilationException() {
+        TestFile initScript = testFile("init.gradle");
+        initScript.writelns(
+            "// a comment",
+            "import org.gradle.unknown.Unknown",
+            "new Unknown()");
+        ExecutionFailure failure = inTestDirectory().usingInitScript(initScript).runWithFailure();
+        failure.assertHasFileName(String.format("Initialization script '%s'", initScript));
+        failure.assertHasLineNumber(2);
+        failure.assertHasDescription(String.format("Could not compile initialization script '%s'.", initScript));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/InitScriptExecutionIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/InitScriptExecutionIntegrationTest.groovy
new file mode 100644
index 0000000..e068ffe
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/InitScriptExecutionIntegrationTest.groovy
@@ -0,0 +1,87 @@
+/*
+ * 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
+
+import org.gradle.integtests.fixtures.ArtifactBuilder
+import org.gradle.integtests.fixtures.ExecutionResult
+import org.gradle.util.TestFile
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+class InitScriptExecutionIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void executesInitScriptWithCorrectEnvironment() {
+        createExternalJar();
+
+        TestFile initScript = testFile('init.gradle')
+        initScript << '''
+initscript {
+    dependencies { classpath files('repo/test-1.3.jar') }
+}
+new org.gradle.test.BuildClass()
+println 'quiet message'
+captureStandardOutput(LogLevel.ERROR)
+println 'error message'
+assert gradle != null
+assert initscript.classLoader == getClass().classLoader.parent
+assert initscript.classLoader == Thread.currentThread().contextClassLoader
+assert scriptClassLoader == initscript.classLoader.parent
+assert Gradle.class.classLoader == scriptClassLoader.parent.parent
+'''
+        testFile('build.gradle') << 'task doStuff'
+
+        ExecutionResult result = inTestDirectory().usingInitScript(initScript).withTasks('doStuff').run()
+        assertThat(result.output, containsString('quiet message'))
+        assertThat(result.output, not(containsString('error message')))
+        assertThat(result.error, containsString('error message'))
+        assertThat(result.error, not(containsString('quiet message')))
+    }
+
+    @Test
+    public void eachScriptHasIndependentClassLoader() {
+        createExternalJar()
+
+        TestFile initScript1 = testFile('init1.gradle')
+        initScript1 << '''
+initscript {
+    dependencies { classpath files('repo/test-1.3.jar') }
+}
+new org.gradle.test.BuildClass()
+'''
+        TestFile initScript2 = testFile('init2.gradle')
+        initScript2 << '''
+try {
+    Class.forName('org.gradle.test.BuildClass')
+    fail()
+} catch (ClassNotFoundException e) {
+}
+'''
+
+        testFile('build.gradle') << 'task doStuff'
+
+       inTestDirectory().usingInitScript(initScript1).usingInitScript(initScript2)
+    }
+
+    private def createExternalJar() {
+        ArtifactBuilder builder = artifactBuilder();
+        builder.sourceFile('org/gradle/test/BuildClass.java') << '''
+            package org.gradle.test;
+            public class BuildClass { }
+'''
+        builder.buildJar(testFile("repo/test-1.3.jar"))
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IvyPublishIntegrationTest.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IvyPublishIntegrationTest.java
new file mode 100644
index 0000000..ef4e6cf
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/IvyPublishIntegrationTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import org.gradle.integtests.fixtures.GradleDistribution;
+import org.gradle.integtests.fixtures.GradleDistributionExecuter;
+import org.gradle.integtests.fixtures.Sample;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(DistributionIntegrationTestRunner.class)
+public class IvyPublishIntegrationTest {
+    @Rule
+    public final GradleDistribution dist = new GradleDistribution();
+    @Rule
+    public final GradleDistributionExecuter executer = new GradleDistributionExecuter();
+    @Rule
+    public final Sample sample = new Sample("ivypublish");
+
+    @Test
+    public void testResolve() {
+        // the actual testing is done in the build script.
+        File projectDir = sample.getDir();
+        executer.inDirectory(projectDir).withTasks("clean", "uploadArchives").run();
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/JUnitIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/JUnitIntegrationTest.groovy
new file mode 100644
index 0000000..b3758a7
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/JUnitIntegrationTest.groovy
@@ -0,0 +1,349 @@
+/*
+ * 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.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.gradle.integtests.fixtures.*
+import static org.gradle.util.Matchers.*
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+ at RunWith(DistributionIntegrationTestRunner.class)
+public class JUnitIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final TestResources resources = new TestResources()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+
+    @Test
+    public void executesTestsInCorrectEnvironment() {
+        TestFile testDir = dist.testDir;
+        executer.withTasks('build').run();
+
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir)
+        result.assertTestClassesExecuted('org.gradle.OkTest', 'org.gradle.OtherTest')
+        result.testClass('org.gradle.OkTest').assertTestPassed('ok')
+        result.testClass('org.gradle.OkTest').assertStdout(containsString('This is test stdout'))
+        result.testClass('org.gradle.OkTest').assertStdout(containsString('no EOL'))
+        result.testClass('org.gradle.OkTest').assertStdout(containsString('class loaded'))
+        result.testClass('org.gradle.OkTest').assertStdout(containsString('test constructed'))
+        result.testClass('org.gradle.OkTest').assertStdout(containsString('stdout from another thread'))
+        result.testClass('org.gradle.OkTest').assertStderr(containsString('This is test stderr'))
+        result.testClass('org.gradle.OkTest').assertStderr(containsString('this is a warning'))
+        result.testClass('org.gradle.OtherTest').assertTestPassed('ok')
+        result.testClass('org.gradle.OtherTest').assertStdout(containsString('This is other stdout'))
+        result.testClass('org.gradle.OtherTest').assertStdout(containsString('other class loaded'))
+        result.testClass('org.gradle.OtherTest').assertStdout(containsString('other test constructed'))
+        result.testClass('org.gradle.OtherTest').assertStderr(containsString('This is other stderr'))
+        result.testClass('org.gradle.OtherTest').assertStderr(containsString('this is another warning'))
+    }
+
+    @Test
+    public void reportsAndBreaksBuildWhenTestFails() {
+        TestFile testDir = dist.getTestDir();
+        TestFile buildFile = testDir.file('build.gradle');
+        ExecutionFailure failure = executer.withTasks('build').runWithFailure();
+
+        failure.assertHasFileName("Build file '${buildFile}'");
+        failure.assertHasDescription("Execution failed for task ':test'.");
+        failure.assertThatCause(startsWith('There were failing tests.'));
+
+        assertThat(failure.getError(), containsLine('Test org.gradle.BrokenTest FAILED'));
+        assertThat(failure.getError(), containsLine('Test org.gradle.BrokenBefore FAILED'));
+        assertThat(failure.getError(), containsLine('Test org.gradle.BrokenAfter FAILED'));
+        assertThat(failure.getError(), containsLine('Test org.gradle.BrokenBeforeClass FAILED'));
+        assertThat(failure.getError(), containsLine('Test org.gradle.BrokenAfterClass FAILED'));
+        assertThat(failure.getError(), containsLine('Test org.gradle.BrokenConstructor FAILED'));
+        assertThat(failure.getError(), containsLine('Test org.gradle.BrokenException FAILED'));
+        assertThat(failure.getError(), containsLine('Test org.gradle.Unloadable FAILED'));
+
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir)
+        result.assertTestClassesExecuted(
+                'org.gradle.BrokenTest',
+                'org.gradle.BrokenBefore',
+                'org.gradle.BrokenAfter',
+                'org.gradle.BrokenBeforeClass',
+                'org.gradle.BrokenAfterClass',
+                'org.gradle.BrokenConstructor',
+                'org.gradle.BrokenException',
+                'org.gradle.Unloadable')
+        result.testClass('org.gradle.BrokenTest').assertTestFailed('failure', equalTo('java.lang.AssertionError: failed'))
+        result.testClass('org.gradle.BrokenTest').assertTestFailed('broken', equalTo('java.lang.IllegalStateException'))
+        result.testClass('org.gradle.BrokenBeforeClass').assertTestFailed('classMethod', equalTo('java.lang.AssertionError: failed'))
+        result.testClass('org.gradle.BrokenAfterClass').assertTestFailed('classMethod', equalTo('java.lang.AssertionError: failed'))
+        result.testClass('org.gradle.BrokenBefore').assertTestFailed('ok', equalTo('java.lang.AssertionError: failed'))
+        result.testClass('org.gradle.BrokenAfter').assertTestFailed('ok', equalTo('java.lang.AssertionError: failed'))
+        result.testClass('org.gradle.BrokenConstructor').assertTestFailed('ok', equalTo('java.lang.AssertionError: failed'))
+        result.testClass('org.gradle.BrokenException').assertTestFailed('broken', startsWith('Could not determine failure message for exception of type org.gradle.BrokenException$BrokenRuntimeException: '))
+        result.testClass('org.gradle.Unloadable').assertTestFailed('initializationError', equalTo('java.lang.AssertionError: failed'))
+    }
+
+    @Test
+    public void canRunSingleTests() {
+        executer.withTasks('test').withArguments('-Dtest.single=Ok2').run()
+        def result = new JUnitTestExecutionResult(dist.testDir)
+        result.assertTestClassesExecuted('Ok2')
+
+        executer.withTasks('cleanTest', 'test').withArguments('-Dtest.single=Ok').run()
+        result.assertTestClassesExecuted('Ok', 'Ok2')
+
+        def failure = executer.withTasks('test').withArguments('-Dtest.single=DoesNotMatchAClass').runWithFailure()
+        failure.assertHasCause('Could not find matching test for pattern: DoesNotMatchAClass')
+
+        failure = executer.withTasks('test').withArguments('-Dtest.single=NotATest').runWithFailure()
+        failure.assertHasCause('Could not find matching test for pattern: NotATest')
+    }
+    
+    @Test
+    public void canUseTestSuperClassesFromAnotherProject() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file('settings.gradle').write("include 'a', 'b'");
+        testDir.file('b/build.gradle') << '''
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { compile 'junit:junit:4.7' }
+        '''
+        testDir.file('b/src/main/java/org/gradle/AbstractTest.java') << '''
+            package org.gradle;
+            public abstract class AbstractTest {
+                @org.junit.Test public void ok() { }
+            }
+        '''
+        TestFile buildFile = testDir.file('a/build.gradle');
+        buildFile << '''
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile project(':b') }
+        '''
+        testDir.file('a/src/test/java/org/gradle/SomeTest.java') << '''
+            package org.gradle;
+            public class SomeTest extends AbstractTest {
+            }
+        '''
+
+        executer.withTasks('a:test').run();
+
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir.file('a'))
+        result.assertTestClassesExecuted('org.gradle.SomeTest')
+        result.testClass('org.gradle.SomeTest').assertTestPassed('ok')
+    }
+
+    @Test
+    public void canExcludeSuperClassesFromExecution() {
+        TestFile testDir = dist.getTestDir();
+        TestFile buildFile = testDir.file('build.gradle');
+        buildFile << '''
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:4.7' }
+            test { exclude '**/BaseTest.*' }
+        '''
+        testDir.file('src/test/java/org/gradle/BaseTest.java') << '''
+            package org.gradle;
+            public class BaseTest {
+                @org.junit.Test public void ok() { }
+            }
+        '''
+        testDir.file('src/test/java/org/gradle/SomeTest.java') << '''
+            package org.gradle;
+            public class SomeTest extends BaseTest {
+            }
+        '''
+
+        executer.withTasks('test').run();
+
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(testDir)
+        result.assertTestClassesExecuted('org.gradle.SomeTest')
+        result.testClass('org.gradle.SomeTest').assertTestPassed('ok')
+    }
+
+    @Test
+    public void detectsTestClasses() {
+        executer.withTasks('test').run()
+
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(dist.testDir)
+        result.assertTestClassesExecuted('org.gradle.EmptyRunWithSubclass', 'org.gradle.TestsOnInner$SomeInner')
+        result.testClass('org.gradle.EmptyRunWithSubclass').assertTestsExecuted('ok')
+        result.testClass('org.gradle.EmptyRunWithSubclass').assertTestPassed('ok')
+        result.testClass('org.gradle.TestsOnInner$SomeInner').assertTestPassed('ok')        
+    }
+
+    @Test
+    public void runsAllTestsInTheSameForkedJvm() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file('build.gradle').writelns(
+                "apply plugin: 'java'",
+                "repositories { mavenCentral() }",
+                "dependencies { compile 'junit:junit:4.7' }"
+        );
+        testDir.file('src/test/java/org/gradle/AbstractTest.java').writelns(
+                "package org.gradle;",
+                "public abstract class AbstractTest {",
+                "    @org.junit.Test public void ok() {",
+                "        long time = java.lang.management.ManagementFactory.getRuntimeMXBean().getStartTime();",
+                "        System.out.println(String.format(\"VM START TIME = %s\", time));",
+                "    }",
+                "}");
+        testDir.file('src/test/java/org/gradle/SomeTest.java').writelns(
+                "package org.gradle;",
+                "public class SomeTest extends AbstractTest {",
+                "}");
+        testDir.file('src/test/java/org/gradle/SomeTest2.java').writelns(
+                "package org.gradle;",
+                "public class SomeTest2 extends AbstractTest {",
+                "}");
+
+        executer.withTasks('test').run();
+
+        TestFile results1 = testDir.file('build/test-results/TEST-org.gradle.SomeTest.xml');
+        TestFile results2 = testDir.file('build/test-results/TEST-org.gradle.SomeTest2.xml');
+        results1.assertIsFile();
+        results2.assertIsFile();
+        assertThat(results1.linesThat(containsString('VM START TIME =')).get(0), equalTo(results2.linesThat(containsString('VM START TIME =')).get(0)));
+    }
+
+    @Test
+    public void canSpecifyMaximumNumberOfTestClassesToExecuteInAForkedJvm() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file('build.gradle').writelns(
+                "apply plugin: 'java'",
+                "repositories { mavenCentral() }",
+                "dependencies { compile 'junit:junit:4.7' }",
+                "test.forkEvery = 1"
+        );
+        testDir.file('src/test/java/org/gradle/AbstractTest.java').writelns(
+                "package org.gradle;",
+                "public abstract class AbstractTest {",
+                "    @org.junit.Test public void ok() {",
+                "        long time = java.lang.management.ManagementFactory.getRuntimeMXBean().getStartTime();",
+                "        System.out.println(String.format(\"VM START TIME = %s\", time));",
+                "    }",
+                "}");
+        testDir.file('src/test/java/org/gradle/SomeTest.java').writelns(
+                "package org.gradle;",
+                "public class SomeTest extends AbstractTest {",
+                "}");
+        testDir.file('src/test/java/org/gradle/SomeTest2.java').writelns(
+                "package org.gradle;",
+                "public class SomeTest2 extends AbstractTest {",
+                "}");
+
+        executer.withTasks('test').run();
+
+        TestFile results1 = testDir.file('build/test-results/TEST-org.gradle.SomeTest.xml');
+        TestFile results2 = testDir.file('build/test-results/TEST-org.gradle.SomeTest2.xml');
+        results1.assertIsFile();
+        results2.assertIsFile();
+        assertThat(results1.linesThat(containsString('VM START TIME =')).get(0), not(equalTo(results2.linesThat(
+                containsString('VM START TIME =')).get(0))));
+    }
+
+    @Test
+    public void canListenForTestResults() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file('src/main/java/AppException.java').writelns(
+                "public class AppException extends Exception { }"
+        );
+
+        testDir.file('src/test/java/SomeTest.java').writelns(
+                "public class SomeTest {",
+                "@org.junit.Test public void fail() { org.junit.Assert.fail(\"message\"); }",
+                "@org.junit.Test public void knownError() { throw new RuntimeException(\"message\"); }",
+                "@org.junit.Test public void unknownError() throws AppException { throw new AppException(); }",
+                "}"
+        );
+        testDir.file('src/test/java/SomeOtherTest.java').writelns(
+                "public class SomeOtherTest {",
+                "@org.junit.Test public void pass() { }",
+                "}"
+        );
+
+        testDir.file('build.gradle') << '''
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:4.7' }
+            def listener = new TestListenerImpl()
+            test.addTestListener(listener)
+            test.ignoreFailures = true
+            class TestListenerImpl implements TestListener {
+                void beforeSuite(TestDescriptor suite) { println "START [$suite] [$suite.name]" }
+                void afterSuite(TestDescriptor suite, TestResult result) { println "FINISH [$suite] [$suite.name] [$result.resultType] [$result.testCount]" }
+                void beforeTest(TestDescriptor test) { println "START [$test] [$test.name]" }
+                void afterTest(TestDescriptor test, TestResult result) { println "FINISH [$test] [$test.name] [$result.resultType] [$result.testCount] [$result.error]" }
+            }
+        '''
+
+        ExecutionResult result = executer.withTasks("test").run();
+        assertThat(result.getOutput(), containsLine("START [tests] []"));
+        assertThat(result.getOutput(), containsLine("FINISH [tests] [] [FAILURE] [4]"));
+
+        assertThat(result.getOutput(), containsLine("START [test process 'Gradle Worker 1'] [Gradle Worker 1]"));
+        assertThat(result.getOutput(), containsLine("FINISH [test process 'Gradle Worker 1'] [Gradle Worker 1] [FAILURE] [4]"));
+
+        assertThat(result.getOutput(), containsLine("START [test class SomeOtherTest] [SomeOtherTest]"));
+        assertThat(result.getOutput(), containsLine("FINISH [test class SomeOtherTest] [SomeOtherTest] [SUCCESS] [1]"));
+        assertThat(result.getOutput(), containsLine("START [test pass(SomeOtherTest)] [pass]"));
+        assertThat(result.getOutput(), containsLine("FINISH [test pass(SomeOtherTest)] [pass] [SUCCESS] [1] [null]"));
+
+        assertThat(result.getOutput(), containsLine("START [test class SomeTest] [SomeTest]"));
+        assertThat(result.getOutput(), containsLine("FINISH [test class SomeTest] [SomeTest] [FAILURE] [3]"));
+        assertThat(result.getOutput(), containsLine("START [test fail(SomeTest)] [fail]"));
+        assertThat(result.getOutput(), containsLine("FINISH [test fail(SomeTest)] [fail] [FAILURE] [1] [java.lang.AssertionError: message]"));
+        assertThat(result.getOutput(), containsLine("START [test knownError(SomeTest)] [knownError]"));
+        assertThat(result.getOutput(), containsLine("FINISH [test knownError(SomeTest)] [knownError] [FAILURE] [1] [java.lang.RuntimeException: message]"));
+        assertThat(result.getOutput(), containsLine("START [test unknownError(SomeTest)] [unknownError]"));
+        assertThat(result.getOutput(), containsLine("FINISH [test unknownError(SomeTest)] [unknownError] [FAILURE] [1] [org.gradle.messaging.remote.internal.PlaceholderException: AppException: null]"));
+    }
+
+    @Test
+    public void canListenForTestResultsWhenJUnit3IsUsed() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file('src/test/java/SomeTest.java').writelns(
+                "public class SomeTest extends junit.framework.TestCase {",
+                "public void testPass() { }",
+                "public void testFail() { junit.framework.Assert.fail(\"message\"); }",
+                "public void testError() { throw new RuntimeException(\"message\"); }",
+                "}"
+        );
+
+        testDir.file('build.gradle') << '''
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'junit:junit:3.8' }
+            def listener = new TestListenerImpl()
+            test.addTestListener(listener)
+            test.ignoreFailures = true
+            class TestListenerImpl implements TestListener {
+                void beforeSuite(TestDescriptor suite) { println "START [$suite] [$suite.name]" }
+                void afterSuite(TestDescriptor suite, TestResult result) { println "FINISH [$suite] [$suite.name]" }
+                void beforeTest(TestDescriptor test) { println "START [$test] [$test.name]" }
+                void afterTest(TestDescriptor test, TestResult result) { println "FINISH [$test] [$test.name] [$result.error]" }
+            }
+        '''
+
+        ExecutionResult result = executer.withTasks("test").run();
+        assertThat(result.getOutput(), containsLine("START [test class SomeTest] [SomeTest]"));
+        assertThat(result.getOutput(), containsLine("FINISH [test class SomeTest] [SomeTest]"));
+        assertThat(result.getOutput(), containsLine("START [test testPass(SomeTest)] [testPass]"));
+        assertThat(result.getOutput(), containsLine("FINISH [test testPass(SomeTest)] [testPass] [null]"));
+        assertThat(result.getOutput(), containsLine("START [test testFail(SomeTest)] [testFail]"));
+        assertThat(result.getOutput(), containsLine("FINISH [test testFail(SomeTest)] [testFail] [junit.framework.AssertionFailedError: message]"));
+        assertThat(result.getOutput(), containsLine("START [test testError(SomeTest)] [testError]"));
+        assertThat(result.getOutput(), containsLine("FINISH [test testError(SomeTest)] [testError] [java.lang.RuntimeException: message]"));
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/JUnitTestExecutionResult.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/JUnitTestExecutionResult.groovy
new file mode 100644
index 0000000..5228053
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/JUnitTestExecutionResult.groovy
@@ -0,0 +1,150 @@
+/*
+ * 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 groovy.util.slurpersupport.GPathResult
+import org.gradle.integtests.fixtures.TestClassExecutionResult
+import org.gradle.integtests.fixtures.TestExecutionResult
+import org.gradle.util.TestFile
+import org.hamcrest.Matcher
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+class JUnitTestExecutionResult implements TestExecutionResult {
+    private final TestFile buildDir
+
+    def JUnitTestExecutionResult(TestFile projectDir, String buildDirName = 'build') {
+        this.buildDir = projectDir.file(buildDirName)
+    }
+
+    TestExecutionResult assertTestClassesExecuted(String... testClasses) {
+        Map<String, File> classes = findClasses()
+        assertThat(classes.keySet(), equalTo(testClasses as Set));
+        this
+    }
+
+    TestClassExecutionResult testClass(String testClass) {
+        return new JUnitTestClassExecutionResult(findTestClass(testClass), testClass)
+    }
+
+    private def findTestClass(String testClass) {
+        def classes = findClasses()
+        assertThat(classes.keySet(), hasItem(testClass))
+        def classFile = classes.get(testClass)
+        assertThat(classFile, notNullValue())
+        return new XmlSlurper().parse(classFile)
+    }
+
+    private def findClasses() {
+        buildDir.file('test-results').assertIsDir()
+        buildDir.file('test-results/TESTS-TestSuites.xml').assertIsFile()
+        buildDir.file('reports/tests/index.html').assertIsFile()
+
+        Map<String, File> classes = [:]
+        buildDir.file('test-results').eachFile { File file ->
+            def matcher = (file.name =~ /TEST-(.+)\.xml/)
+            if (matcher.matches()) {
+                classes[matcher.group(1)] = file
+            }
+        }
+        return classes
+    }
+}
+
+class JUnitTestClassExecutionResult implements TestClassExecutionResult {
+    GPathResult testClassNode
+    String testClassName
+    boolean checked
+
+    def JUnitTestClassExecutionResult(GPathResult testClassNode, String testClassName) {
+        this.testClassNode = testClassNode
+        this.testClassName = testClassName
+    }
+
+    TestClassExecutionResult assertTestsExecuted(String... testNames) {
+        Map<String, Node> testMethods = findTests()
+        assertThat(testMethods.keySet(), equalTo(testNames as Set))
+        this
+    }
+
+    TestClassExecutionResult assertTestPassed(String name) {
+        Map<String, Node> testMethods = findTests()
+        assertThat(testMethods.keySet(), hasItem(name))
+        assertThat(testMethods[name].failure.size(), equalTo(0))
+        this
+    }
+
+    TestClassExecutionResult assertTestFailed(String name, Matcher<? super String> messageMatcher) {
+        Map<String, Node> testMethods = findTests()
+        assertThat(testMethods.keySet(), hasItem(name))
+        assertThat(testMethods[name].failure.size(), equalTo(1))
+        def failure = testMethods[name].failure[0]
+        assertThat(failure. at message.text(), messageMatcher)
+        this
+    }
+
+    TestClassExecutionResult assertConfigMethodPassed(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    TestClassExecutionResult assertConfigMethodFailed(String name) {
+        throw new UnsupportedOperationException();
+    }
+
+    TestClassExecutionResult assertStdout(Matcher<? super String> matcher) {
+        def stdout = testClassNode.'system-out'[0].text();
+        assertThat(stdout, matcher)
+        this
+    }
+
+    TestClassExecutionResult assertStderr(Matcher<? super String> matcher) {
+        def stderr = testClassNode.'system-err'[0].text();
+        assertThat(stderr, matcher)
+        this
+    }
+
+    private def findTests() {
+        if (!checked) {
+            assertThat(testClassNode.name(), equalTo('testsuite'))
+            assertThat(testClassNode. at name.text(), equalTo(testClassName))
+            assertThat(testClassNode. at tests.text(), not(equalTo('')))
+            assertThat(testClassNode. at failures.text(), not(equalTo('')))
+            assertThat(testClassNode. at errors.text(), not(equalTo('')))
+            assertThat(testClassNode. at time.text(), not(equalTo('')))
+            assertThat(testClassNode. at timestamp.text(), not(equalTo('')))
+            assertThat(testClassNode. at hostname.text(), not(equalTo('')))
+            assertThat(testClassNode.properties.size(), equalTo(1))
+            testClassNode.testcase.each { node ->
+                assertThat(node. at classname.text(), equalTo(testClassName))
+                assertThat(node. at name.text(), not(equalTo('')))
+                assertThat(node. at time.text(), not(equalTo('')))
+                assertThat(node.failure.size(), not(greaterThan(1)))
+                node.failure.each { failure ->
+                    assertThat(failure. at message.size(), equalTo(1))
+                    assertThat(failure. at type.text(), not(equalTo('')))
+                    assertThat(failure.text(), not(equalTo('')))
+                }
+            }
+            assertThat(testClassNode.'system-out'.size(), equalTo(1))
+            assertThat(testClassNode.'system-err'.size(), equalTo(1))
+            checked = true
+        }
+        Map testMethods = [:]
+        testClassNode.testcase.each { testMethods[it. at name.text()] = it }
+        return testMethods
+    }
+
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.java
new file mode 100644
index 0000000..e8af5bf
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/JavaProjectIntegrationTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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;
+
+import org.gradle.integtests.fixtures.ExecutionFailure;
+import org.gradle.util.TestFile;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class JavaProjectIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void handlesEmptyProject() {
+        testFile("build.gradle").writelns("apply plugin: 'java'");
+        inTestDirectory().withTasks("build").run();
+    }
+
+    @Test
+    public void compilationFailureBreaksBuild() {
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns("apply plugin: 'java'");
+        testFile("src/main/java/org/gradle/broken.java").write("broken");
+
+        ExecutionFailure failure = usingBuildFile(buildFile).withTasks("build").runWithFailure();
+
+        failure.assertHasFileName(String.format("Build file '%s'", buildFile));
+        failure.assertHasDescription("Execution failed for task ':compileJava'");
+        failure.assertHasCause("Compile failed; see the compiler error output for details.");
+    }
+
+    @Test
+    public void testCompilationFailureBreaksBuild() {
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns("apply plugin: 'java'");
+        testFile("src/main/java/org/gradle/ok.java").write("package org.gradle; class ok { }");
+        testFile("src/test/java/org/gradle/broken.java").write("broken");
+
+        ExecutionFailure failure = usingBuildFile(buildFile).withTasks("build").runWithFailure();
+
+        failure.assertHasFileName(String.format("Build file '%s'", buildFile));
+        failure.assertHasDescription("Execution failed for task ':compileTestJava'");
+        failure.assertHasCause("Compile failed; see the compiler error output for details.");
+    }
+
+    @Test
+    public void handlesTestSrcWhichDoesNotContainAnyTestCases() {
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns("apply plugin: 'java'");
+        testFile("src/test/java/org/gradle/NotATest.java").writelns("package org.gradle;", "public class NotATest {}");
+
+        usingBuildFile(buildFile).withTasks("build").run();
+    }
+
+    @Test
+    public void javadocGenerationFailureBreaksBuild() throws IOException {
+        TestFile buildFile = testFile("javadocs.gradle");
+        buildFile.write("apply plugin: 'java'");
+        testFile("src/main/java/org/gradle/broken.java").write("class Broken { }");
+
+        ExecutionFailure failure = usingBuildFile(buildFile).withTasks("javadoc").runWithFailure();
+
+        failure.assertHasFileName(String.format("Build file '%s'", buildFile));
+        failure.assertHasDescription("Execution failed for task ':javadoc'");
+        failure.assertHasCause("Javadoc generation failed.");
+    }
+
+    @Test
+    public void handlesResourceOnlyProject() throws IOException {
+        TestFile buildFile = testFile("resources.gradle");
+        buildFile.write("apply plugin: 'java'");
+        testFile("src/main/resources/org/gradle/resource.file").write("test resource");
+
+        usingBuildFile(buildFile).withTasks("build").run();
+        testFile("build/classes/main/org/gradle/resource.file").assertExists();
+    }
+
+    @Test
+    public void generatesArtifactsWhenVersionIsEmpty() {
+        testFile("settings.gradle").write("rootProject.name = 'empty'");
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns(
+                "apply plugin: 'java'",
+                "version = ''"
+        );
+        testFile("src/main/resources/org/gradle/resource.file").write("some resource");
+
+        usingBuildFile(buildFile).withTasks("jar").run();
+        testFile("build/libs/empty.jar").assertIsFile();
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/LoggingIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/LoggingIntegrationTest.groovy
new file mode 100644
index 0000000..b6b977a
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/LoggingIntegrationTest.groovy
@@ -0,0 +1,214 @@
+/*
+ * 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
+
+import org.gradle.integtests.fixtures.ExecutionResult
+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 org.junit.runner.RunWith
+import static org.gradle.util.Matchers.*
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+// todo To make this test stronger, we should check against the output of a file appender. Right now GradleLauncher does not provided this easily but eventually will.
+ at RunWith(DistributionIntegrationTestRunner.class)
+class LoggingIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+
+    List quietMessages = [
+            'An info log message which is always logged.',
+            'A message 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',
+            'buildSrc quiet',
+            'nestedBuild/buildSrc quiet',
+            'nestedBuild quiet',
+            'nestedBuild task quiet',
+            'external QUIET message'
+    ]
+    List errorMessages = [
+            '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'
+    ]
+    List warningMessages = [
+            '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'
+    ]
+    List lifecycleMessages = [
+            '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',
+            ':buildSrc:classes',
+            ':nestedBuild:log'
+    ]
+    List infoMessages = [
+            'An info log message.',
+            'A message 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: executed task',
+            'LOGGER: task starting work',
+            'LOGGER: task completed work',
+            'buildSrc info',
+            'nestedBuild/buildSrc info',
+            'nestedBuild info',
+            'external INFO message'
+    ]
+    List debugMessages = [
+            'A debug log message.',
+            '[ant:echo] A debug message logged from Ant',
+            'A fine log message logged using JUL'
+    ]
+    List traceMessages = [
+            'A trace log message.'
+    ]
+    List forbiddenMessages = [
+            // the default message generated by JUL
+            'INFO: An info log message logged using JUL',
+            // the custom logger should override this
+            'BUILD SUCCESSFUL'
+    ]
+    List allOuts = [
+            errorMessages,
+            quietMessages,
+            warningMessages,
+            lifecycleMessages,
+            infoMessages,
+            debugMessages,
+            traceMessages,
+            forbiddenMessages
+    ]
+
+    LogLevel quiet = new LogLevel(
+            args: ['-q'],
+            includeMessages: [quietMessages, errorMessages]
+    )
+    LogLevel lifecycle = new LogLevel(
+            args: [],
+            includeMessages: [quietMessages, errorMessages, warningMessages, lifecycleMessages]
+    )
+    LogLevel info = new LogLevel(
+            args: ['-i'],
+            includeMessages: [quietMessages, errorMessages, warningMessages, lifecycleMessages, infoMessages]
+    )
+    LogLevel debug = new LogLevel(
+            args: ['-d'],
+            includeMessages: [quietMessages, errorMessages, warningMessages, lifecycleMessages, infoMessages, debugMessages],
+            matchPartialLine: true
+    )
+
+    @Test
+    public void quietLogging() {
+        checkOutput(quiet)
+    }
+
+    @Test
+    public void lifecycleLogging() {
+        checkOutput(lifecycle)
+    }
+
+    @Test
+    public void infoLogging() {
+        checkOutput(info)
+    }
+
+    @Test
+    public void debugLogging() {
+        checkOutput(debug)
+    }
+
+    void checkOutput(LogLevel level) {
+        TestFile loggingDir = dist.samplesDir.file('logging')
+        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]
+
+        ExecutionResult result = executer.inDirectory(loggingDir).withArguments(allArgs).withTasks('log').run()
+        level.includeMessages.each {List messages ->
+            if (messages == errorMessages) {
+                checkOuts(true, result.error, messages, level.matchPartialLine)
+            }
+            else {
+                checkOuts(true, result.output, messages, level.matchPartialLine)
+            }
+        }
+        (allOuts- level.includeMessages).each {List messages ->
+            checkOuts(false, result.output, messages, true)
+            checkOuts(false, result.error, messages, true)
+        }
+    }
+
+    void checkOuts(boolean shouldContain, String result, List outs, boolean partialLine) {
+        outs.each {String expectedOut ->
+            def matcher = partialLine ? containsLine(containsString(expectedOut)) : containsLine(expectedOut)
+            if (!shouldContain) {
+                matcher = not(matcher)
+            }
+            assertThat(result, matcher)
+        }
+    }
+}
+
+class LogLevel {
+    List args
+    List includeMessages
+    boolean matchPartialLine
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/MultiprojectIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/MultiprojectIntegrationTest.groovy
new file mode 100644
index 0000000..d3bca15
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/MultiprojectIntegrationTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * 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
+
+import org.junit.Test
+
+class MultiProjectIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void canInjectConfigurationFromParentProject() {
+        testFile('settings.gradle') << 'include "a", "b"'
+        testFile('build.gradle') << '''
+            allprojects {
+                def destDir = buildDir
+                task test << {
+                    destDir.mkdirs()
+                    new File(destDir, 'test.txt') << 'content'
+                }
+                gradle.taskGraph.whenReady {
+                    destDir.mkdirs()
+                    new File(destDir, 'whenReady.txt') << 'content'
+                }
+                afterEvaluate {
+                    destDir.mkdirs()
+                    new File(destDir, 'afterEvaluate.txt') << 'content'
+                }
+            }
+'''
+        inTestDirectory().withTasks('test').run()
+
+        testFile('build').assertHasDescendants('test.txt', 'whenReady.txt', 'afterEvaluate.txt')
+        testFile('a/build').assertHasDescendants('test.txt', 'whenReady.txt', 'afterEvaluate.txt')
+        testFile('b/build').assertHasDescendants('test.txt', 'whenReady.txt', 'afterEvaluate.txt')
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy
new file mode 100644
index 0000000..3367bf8
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/OsgiProjectSampleIntegrationTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * 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
+
+import java.util.jar.Manifest
+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 org.junit.runner.RunWith
+import static org.junit.Assert.*
+import org.gradle.integtests.fixtures.Sample
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(DistributionIntegrationTestRunner.class)
+class OsgiProjectSampleIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('osgi')
+
+    @Test
+    public void osgiProjectSamples() {
+        long start = System.currentTimeMillis()
+        TestFile osgiProjectDir = sample.dir
+        executer.inDirectory(osgiProjectDir).withTasks('clean', 'assemble').run()
+        TestFile tmpDir = dist.testDir
+        osgiProjectDir.file('build/libs/osgi-1.0.jar').unzipTo(tmpDir)
+        tmpDir.file('META-INF/MANIFEST.MF').withInputStream { InputStream instr ->
+            Manifest manifest = new Manifest(instr)
+            checkManifest(manifest, start)
+        }
+    }
+
+    static void checkManifest(Manifest manifest, start) {
+        assertEquals('Example Gradle Activator', manifest.mainAttributes.getValue('Bundle-Name'))
+        assertEquals('2', manifest.mainAttributes.getValue('Bundle-ManifestVersion'))
+        assertEquals('Bnd-0.0.384', manifest.mainAttributes.getValue('Tool'))
+        assertTrue(start <= Long.parseLong(manifest.mainAttributes.getValue('Bnd-LastModified')))
+        assertEquals('1.0', manifest.mainAttributes.getValue('Bundle-Version'))
+        assertEquals('gradle_tooling.osgi', manifest.mainAttributes.getValue('Bundle-SymbolicName'))
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ProjectLayoutIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ProjectLayoutIntegrationTest.groovy
new file mode 100644
index 0000000..0ea489a
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ProjectLayoutIntegrationTest.groovy
@@ -0,0 +1,175 @@
+/*
+ * 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
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.TestResources
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+ at RunWith(DistributionIntegrationTestRunner.class)
+class ProjectLayoutIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final TestResources resources = new TestResources()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+
+    @Test
+    public void canHaveSomeSourceAndResourcesInSameDirectoryAndSomeInDifferentDirectories() {
+        dist.testFile('settings.gradle') << 'rootProject.name = "sharedSource"'
+        dist.testFile('build.gradle') << '''
+apply plugin: 'java'
+apply plugin: 'groovy'
+apply plugin: 'scala'
+
+repositories {
+    mavenRepo urls: 'http://scala-tools.org/repo-releases/'
+    mavenCentral()
+}
+dependencies {
+    groovy group: 'org.codehaus.groovy', name: 'groovy-all', version: '1.6.0'
+    scalaTools group: 'org.scala-lang', name: 'scala-compiler', version: '2.7.6'
+    scalaTools group: 'org.scala-lang', name: 'scala-library', version: '2.7.6'
+
+    compile group: 'org.scala-lang', name: 'scala-library', version: '2.7.6'
+}
+
+sourceSets.each {
+    configure(it) {
+        resources.srcDir 'src'
+        resources.srcDir 'src/resources'
+        resources.include "org/gradle/$name/**"
+        java.srcDir 'src'
+        java.srcDir 'src/java'
+        java.include "org/gradle/$name/**"
+        groovy.srcDir 'src'
+        groovy.srcDir 'src/groovy'
+        groovy.include "org/gradle/$name/**"
+        scala.srcDir 'src'
+        scala.srcDir 'src/scala'
+        scala.include "org/gradle/$name/**"
+    }
+}
+'''
+        dist.testFile('src/org/gradle/main/resource.txt') << 'some text'
+        dist.testFile('src/org/gradle/test/resource.txt') << 'some text'
+        dist.testFile('src/resources/org/gradle/main/resource2.txt') << 'some text'
+        dist.testFile('src/resources/org/gradle/test/resource2.txt') << 'some text'
+        dist.testFile('src/org/gradle/main/JavaClass.java') << 'package org.gradle; public class JavaClass { }'
+        dist.testFile('src/org/gradle/test/JavaClassTest.java') << 'package org.gradle; class JavaClassTest { JavaClass c = new JavaClass(); }'
+        dist.testFile('src/java/org/gradle/main/JavaClass2.java') << 'package org.gradle; class JavaClass2 { }'
+        dist.testFile('src/java/org/gradle/test/JavaClassTest2.java') << 'package org.gradle; class JavaClassTest2 { JavaClass c = new JavaClass(); }'
+        dist.testFile('src/org/gradle/main/GroovyClass.groovy') << 'package org.gradle; class GroovyClass { }'
+        dist.testFile('src/org/gradle/test/GroovyClassTest.groovy') << 'package org.gradle; class GroovyClassTest { GroovyClass c = new GroovyClass() }'
+        dist.testFile('src/groovy/org/gradle/main/GroovyClass2.groovy') << 'package org.gradle; class GroovyClass2 { }'
+        dist.testFile('src/groovy/org/gradle/test/GroovyClassTest2.groovy') << 'package org.gradle; class GroovyClassTest2 { GroovyClass c = new GroovyClass() }'
+        dist.testFile('src/org/gradle/main/ScalaClass.scala') << 'package org.gradle; class ScalaClass { }'
+        dist.testFile('src/org/gradle/test/ScalaClassTest.scala') << 'package org.gradle; class ScalaClassTest { val c: ScalaClass = new ScalaClass() }'
+        dist.testFile('src/scala/org/gradle/main/ScalaClass2.scala') << 'package org.gradle; class ScalaClass2 { }'
+        dist.testFile('src/scala/org/gradle/test/ScalaClassTest2.scala') << 'package org.gradle; class ScalaClassTest2 { val c: ScalaClass = new ScalaClass() }'
+
+        executer.withTasks('build').run()
+
+        File buildDir = dist.testFile('build')
+
+        buildDir.file('classes/main').assertHasDescendants(
+                'org/gradle/main/resource.txt',
+                'org/gradle/main/resource2.txt',
+                'org/gradle/JavaClass.class',
+                'org/gradle/JavaClass2.class',
+                'org/gradle/GroovyClass.class',
+                'org/gradle/GroovyClass2.class',
+                'org/gradle/ScalaClass.class',
+                'org/gradle/ScalaClass2.class'
+        )
+
+        buildDir.file('classes/test').assertHasDescendants(
+                'org/gradle/test/resource.txt',
+                'org/gradle/test/resource2.txt',
+                'org/gradle/JavaClassTest.class',
+                'org/gradle/JavaClassTest2.class',
+                'org/gradle/GroovyClassTest.class',
+                'org/gradle/GroovyClassTest2.class',
+                'org/gradle/ScalaClassTest.class',
+                'org/gradle/ScalaClassTest2.class'
+        )
+
+        TestFile tmpDir = dist.testFile('jarContents')
+        buildDir.file('libs/sharedSource.jar').unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/main/resource.txt',
+                'org/gradle/main/resource2.txt',
+                'org/gradle/JavaClass.class',
+                'org/gradle/JavaClass2.class',
+                'org/gradle/GroovyClass.class',
+                'org/gradle/GroovyClass2.class',
+                'org/gradle/ScalaClass.class',
+                'org/gradle/ScalaClass2.class'
+        )
+
+        executer.withTasks('javadoc', 'groovydoc', 'scaladoc').run()
+
+        buildDir.file('docs/javadoc/index.html').assertIsFile()
+        buildDir.file('docs/groovydoc/index.html').assertIsFile()
+        buildDir.file('docs/scaladoc/index.html').assertIsFile()
+    }
+
+    @Test
+    public void multipleProjectsCanShareTheSameSourceDirectory() {
+        dist.testFile('settings.gradle') << 'include "a", "b"'
+        dist.testFile('a/build.gradle') << '''
+apply plugin: 'java'
+sourceSets.main.java {
+    srcDirs '../src'
+    include 'org/gradle/a/**'
+}
+'''
+        dist.testFile('b/build.gradle') << '''
+apply plugin: 'java'
+dependencies { compile project(':a') }
+sourceSets.main.java {
+    srcDirs '../src'
+    include 'org/gradle/b/**'
+}
+'''
+
+        dist.testFile('src/org/gradle/a/ClassA.java') << 'package org.gradle.a; public class ClassA { }'
+        dist.testFile('src/org/gradle/b/ClassB.java') << 'package org.gradle.b; public class ClassB { private org.gradle.a.ClassA field; }'
+
+        executer.withTasks('clean', 'assemble').run()
+
+        dist.testFile('a/build/classes/main').assertHasDescendants(
+                'org/gradle/a/ClassA.class'
+        )
+        dist.testFile('b/build/classes/main').assertHasDescendants(
+                'org/gradle/b/ClassB.class'
+        )
+    }
+
+    @Test
+    public void canUseANonStandardBuildDir() {
+        executer.withTasks('build').withArguments('-i').run()
+
+        dist.testFile('build').assertDoesNotExist()
+
+        JUnitTestExecutionResult results = new JUnitTestExecutionResult(dist.testFile(), 'target')
+        results.assertTestClassesExecuted('PersonTest')
+        results.testClass('PersonTest').assertTestsExecuted('ok')
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ProjectLoadingIntegrationTest.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ProjectLoadingIntegrationTest.java
new file mode 100644
index 0000000..fee6710
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ProjectLoadingIntegrationTest.java
@@ -0,0 +1,253 @@
+/*
+ * 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.ExecutionFailure;
+import org.gradle.util.TestFile;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.hamcrest.Matchers.*;
+
+public class ProjectLoadingIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void handlesSimilarlyNamedBuildFilesInSameDirectory() {
+        TestFile buildFile1 = testFile("similarly-named build.gradle").write("task build");
+        TestFile buildFile2 = testFile("similarly_named_build_gradle").write("task 'other-build'");
+
+        usingBuildFile(buildFile1).withTasks("build").run();
+
+        usingBuildFile(buildFile2).withTasks("other-build").run();
+
+        usingBuildFile(buildFile1).withTasks("build").run();
+    }
+
+    @Test
+    public void handlesWhitespaceOnlySettingsAndBuildFiles() {
+        testFile("settings.gradle").write("   \n  ");
+        testFile("build.gradle").write("   ");
+        inTestDirectory().withTaskList().run();
+    }
+
+    @Test
+    public void embeddedBuildFileIgnoresBuildAndScriptFiles() {
+        File rootDir = getTestDir();
+        testFile("settings.gradle").write("throw new RuntimeException()");
+        testFile("build.gradle").write("throw new RuntimeException()");
+        inDirectory(rootDir).usingBuildScript("Task task = task('do-stuff')").withTasks("do-stuff").run();
+    }
+
+    @Test
+    public void canDetermineRootProjectAndDefaultProjectBasedOnCurrentDirectory() {
+        File rootDir = getTestDir();
+        File childDir = new File(rootDir, "child");
+
+        testFile("settings.gradle").write("include('child')");
+        testFile("build.gradle").write("task('do-stuff')");
+        testFile("child/build.gradle").write("task('do-stuff')");
+
+        inDirectory(rootDir).withSearchUpwards().withTasks("do-stuff").run().assertTasksExecuted(":do-stuff", ":child:do-stuff");
+        inDirectory(rootDir).withSearchUpwards().withTasks(":do-stuff").run().assertTasksExecuted(":do-stuff");
+
+        inDirectory(childDir).withSearchUpwards().withTasks("do-stuff").run().assertTasksExecuted(":child:do-stuff");
+        inDirectory(childDir).withSearchUpwards().withTasks(":do-stuff").run().assertTasksExecuted(":do-stuff");
+    }
+
+    @Test
+    public void canDetermineRootProjectAndDefaultProjectBasedOnProjectDirectory() {
+        File rootDir = getTestDir();
+        File childDir = new File(rootDir, "child");
+
+        testFile("settings.gradle").write("include('child')");
+        testFile("build.gradle").write("task('do-stuff')");
+        testFile("child/build.gradle").write("task('do-stuff')");
+
+        usingProjectDir(rootDir).withSearchUpwards().withTasks("do-stuff").run().assertTasksExecuted(":do-stuff", ":child:do-stuff");
+        usingProjectDir(rootDir).withSearchUpwards().withTasks(":do-stuff").run().assertTasksExecuted(":do-stuff");
+
+        usingProjectDir(childDir).withSearchUpwards().withTasks("do-stuff").run().assertTasksExecuted(":child:do-stuff");
+        usingProjectDir(childDir).withSearchUpwards().withTasks(":do-stuff").run().assertTasksExecuted(":do-stuff");
+    }
+
+    @Test
+    public void canDetermineRootProjectAndDefaultProjectBasedOnBuildFile() {
+        testFile("settings.gradle").write("include('child')");
+
+        TestFile rootBuildFile = testFile("build.gradle");
+        rootBuildFile.write("task('do-stuff')");
+
+        TestFile childBuildFile = testFile("child/build.gradle");
+        childBuildFile.write("task('do-stuff')");
+
+        usingBuildFile(rootBuildFile).withSearchUpwards().withTasks("do-stuff").run().assertTasksExecuted(":do-stuff", ":child:do-stuff");
+        usingBuildFile(rootBuildFile).withSearchUpwards().withTasks(":do-stuff").run().assertTasksExecuted(":do-stuff");
+
+        usingBuildFile(childBuildFile).withSearchUpwards().withTasks("do-stuff").run().assertTasksExecuted(":child:do-stuff");
+        usingBuildFile(childBuildFile).withSearchUpwards().withTasks(":do-stuff").run().assertTasksExecuted(":do-stuff");
+    }
+
+    @Test
+    public void buildFailsWhenMultipleProjectsMeetDefaultProjectCriteria() {
+        testFile("settings.gradle").writelns(
+            "include 'child'",
+            "project(':child').projectDir = rootProject.projectDir");
+        testFile("build.gradle").write("// empty");
+
+        ExecutionFailure result = inTestDirectory().withTasks("test").runWithFailure();
+        result.assertThatDescription(startsWith("Could not select the default project for this build. Multiple projects in this build have project directory"));
+
+        result = usingProjectDir(getTestDir()).withTasks("test").runWithFailure();
+        result.assertThatDescription(startsWith("Could not select the default project for this build. Multiple projects in this build have project directory"));
+
+        result = usingBuildFile(testFile("build.gradle")).withTasks("test").runWithFailure();
+        result.assertThatDescription(startsWith("Could not select the default project for this build. Multiple projects in this build have build file"));
+    }
+
+    @Test
+    public void buildFailsWhenSpecifiedBuildFileIsNotAFile() {
+        ExecutionFailure result = usingBuildFile(testFile("unknown build file")).runWithFailure();
+        result.assertThatDescription(startsWith("Build file"));
+        result.assertThatDescription(endsWith("does not exist."));
+    }
+
+    @Test
+    public void buildFailsWhenSpecifiedProjectDirectoryIsNotADirectory() {
+        ExecutionFailure result = usingProjectDir(testFile("unknown dir")).runWithFailure();
+        result.assertThatDescription(startsWith("Project directory"));
+        result.assertThatDescription(endsWith("does not exist."));
+    }
+
+    @Test
+    public void buildFailsWhenSpecifiedSettingsFileIsNotAFile() {
+        ExecutionFailure result = inTestDirectory().usingSettingsFile(testFile("unknown")).runWithFailure();
+        result.assertThatDescription(startsWith("Could not read settings file"));
+        result.assertThatDescription(endsWith("as it does not exist."));
+    }
+
+    @Test
+    public void buildFailsWhenSpecifiedSettingsFileDoesNotContainMatchingProject() {
+        TestFile settingsFile = testFile("settings.gradle");
+        settingsFile.write("// empty");
+
+        TestFile projectdir = testFile("project dir");
+        projectdir.mkdirs();
+
+        ExecutionFailure result = usingProjectDir(projectdir).usingSettingsFile(settingsFile).runWithFailure();
+        result.assertThatDescription(startsWith("Could not select the default project for this build. No projects in this build have project directory"));
+    }
+
+    @Test
+    public void settingsFileTakesPrecedenceOverBuildFileInSameDirectory() {
+        testFile("settings.gradle").write("rootProject.buildFileName = 'root.gradle'");
+        testFile("root.gradle").write("task('do-stuff')");
+        
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.write("throw new RuntimeException()");
+
+        inTestDirectory().withTasks("do-stuff").run();
+        usingProjectDir(getTestDir()).withTasks("do-stuff").run();
+    }
+
+    @Test
+    public void settingsFileInParentDirectoryTakesPrecedenceOverBuildFile() {
+        testFile("settings.gradle").writelns(
+            "include 'child'",
+            "project(':child').buildFileName = 'child.gradle'"
+        );
+
+        TestFile subDirectory = getTestDir().file("child");
+        subDirectory.file("build.gradle").write("throw new RuntimeException()");
+        subDirectory.file("child.gradle").write("task('do-stuff')");
+
+        inDirectory(subDirectory).withSearchUpwards().withTasks("do-stuff").run();
+        usingProjectDir(subDirectory).withSearchUpwards().withTasks("do-stuff").run();
+    }
+
+    @Test
+    public void explicitBuildFileTakesPrecedenceOverSettingsFileInSameDirectory() {
+        testFile("settings.gradle").write("rootProject.buildFileName = 'root.gradle'");
+        testFile("root.gradle").write("throw new RuntimeException()");
+
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.write("task('do-stuff')");
+
+        usingBuildFile(buildFile).withTasks("do-stuff").run();
+    }
+
+    @Test
+    public void ignoresMultiProjectBuildInParentDirectoryWhichDoesNotMeetDefaultProjectCriteria() {
+        testFile("settings.gradle").write("include 'another'");
+        testFile("gradle.properties").writelns("prop=value2", "otherProp=value");
+
+        TestFile subDirectory = getTestDir().file("subdirectory");
+        TestFile buildFile = subDirectory.file("build.gradle");
+        buildFile.writelns("task('do-stuff') << {",
+                "assert prop == 'value'",
+                "assert !project.hasProperty('otherProp')",
+                "}");
+        testFile("subdirectory/gradle.properties").write("prop=value");
+
+        inDirectory(subDirectory).withSearchUpwards().withTasks("do-stuff").run();
+        usingProjectDir(subDirectory).withSearchUpwards().withTasks("do-stuff").run();
+        usingBuildFile(buildFile).withSearchUpwards().withTasks("do-stuff").run();
+    }
+
+    @Test
+    public void multiProjectBuildCanHaveMultipleProjectsWithSameProjectDir() {
+        testFile("settings.gradle").writelns(
+            "include 'child1', 'child2'",
+            "project(':child1').projectDir = new File(settingsDir, 'shared')",
+            "project(':child2').projectDir = new File(settingsDir, 'shared')"
+        );
+        testFile("shared/build.gradle").write("task('do-stuff')");
+
+        inTestDirectory().withTasks("do-stuff").run().assertTasksExecuted(":child1:do-stuff", ":child2:do-stuff");
+    }
+
+    @Test
+    public void multiProjectBuildCanHaveSeveralProjectsWithSameBuildFile() {
+        testFile("settings.gradle").writelns(
+            "include 'child1', 'child2'",
+            "project(':child1').buildFileName = '../child.gradle'",
+            "project(':child2').buildFileName = '../child.gradle'"
+        );
+        testFile("child.gradle").write("task('do-stuff')");
+
+        inTestDirectory().withTasks("do-stuff").run().assertTasksExecuted(":child1:do-stuff", ":child2:do-stuff");
+    }
+
+    @Test
+    public void multiProjectBuildCanHaveSettingsFileAndRootBuildFileInSubDir() {
+        TestFile buildFilesDir = getTestDir().file("root");
+        TestFile settingsFile = buildFilesDir.file("settings.gradle");
+        settingsFile.writelns(
+            "includeFlat 'child'",
+            "rootProject.projectDir = new File(settingsDir, '..')",
+            "rootProject.buildFileName = 'root/build.gradle'"
+        );
+
+        TestFile rootBuildFile = buildFilesDir.file("build.gradle");
+        rootBuildFile.write("task('do-stuff', dependsOn: ':child:task')");
+
+        TestFile childBuildFile = testFile("child/build.gradle");
+        childBuildFile.writelns("task('do-stuff')", "task('task')");
+
+        usingProjectDir(getTestDir()).usingSettingsFile(settingsFile).withTasks("do-stuff").run().assertTasksExecuted(":child:task", ":do-stuff", ":child:do-stuff");
+        usingBuildFile(rootBuildFile).withTasks("do-stuff").run().assertTasksExecuted(":child:task", ":do-stuff", ":child:do-stuff");
+        usingBuildFile(childBuildFile).usingSettingsFile(settingsFile).withTasks("do-stuff").run().assertTasksExecuted(":child:do-stuff");
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesAntlrIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesAntlrIntegrationTest.groovy
new file mode 100644
index 0000000..e046060
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesAntlrIntegrationTest.groovy
@@ -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.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 org.junit.runner.RunWith
+import org.gradle.integtests.fixtures.Sample
+
+ at RunWith (DistributionIntegrationTestRunner.class)
+class SamplesAntlrIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('antlr')
+
+    @Test
+    public void canBuild() {
+        TestFile projectDir = sample.dir
+
+        // Build and test projects
+        executer.inDirectory(projectDir).withTasks('clean', 'build').withArguments("--no-opt").run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(projectDir)
+        result.assertTestClassesExecuted('org.gradle.GrammarTest')
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesCodeQualityIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesCodeQualityIntegrationTest.groovy
new file mode 100644
index 0000000..dc4e1b6
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesCodeQualityIntegrationTest.groovy
@@ -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.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 org.junit.runner.RunWith
+import org.gradle.integtests.fixtures.Sample
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(DistributionIntegrationTestRunner.class)
+class SamplesCodeQualityIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('codeQuality')
+
+    @Test
+    public void checkReportsGenerated() {
+        TestFile projectDir = sample.dir
+        TestFile buildDir = projectDir.file('build')
+
+        executer.inDirectory(projectDir).withTasks('check').run()
+
+        buildDir.file('checkstyle/main.xml').assertIsFile()
+        buildDir.file('reports/codenarc/main.html').assertIsFile()
+        buildDir.file('reports/codenarc/test.html').assertIsFile()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesCustomBuildLanguageIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesCustomBuildLanguageIntegrationTest.groovy
new file mode 100644
index 0000000..db8339b
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesCustomBuildLanguageIntegrationTest.groovy
@@ -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.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 org.junit.runner.RunWith
+import org.gradle.integtests.fixtures.Sample
+
+ at RunWith(DistributionIntegrationTestRunner.class)
+class SamplesCustomBuildLanguageIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('customBuildLanguage')
+
+    @Test
+    public void testBuildProductDistributions() {
+        TestFile rootDir = sample.dir
+        executer.inDirectory(rootDir).withTasks('clean', 'dist').run()
+
+        TestFile expandDir = dist.testFile('expand-basic')
+        rootDir.file('basicEdition/build/distributions/some-company-basic-edition-1.0.zip').unzipTo(expandDir)
+        expandDir.assertHasDescendants(
+                'readme.txt',
+                'end-user-license-agreement.txt',
+                'bin/start.sh',
+                'lib/some-company-identity-management-1.0.jar',
+                'lib/some-company-billing-1.0.jar',
+                'lib/commons-lang-2.4.jar'
+        )
+
+        expandDir = dist.testFile('expand-enterprise')
+        rootDir.file('enterpriseEdition/build/distributions/some-company-enterprise-edition-1.0.zip').unzipTo(expandDir)
+        expandDir.assertHasDescendants(
+                'readme.txt',
+                'end-user-license-agreement.txt',
+                'bin/start.sh',
+                'lib/some-company-identity-management-1.0.jar',
+                'lib/some-company-billing-1.0.jar',
+                'lib/some-company-reporting-1.0.jar',
+                'lib/commons-lang-2.4.jar',
+                'lib/commons-io-1.2.jar'
+        )
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesCustomPluginIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesCustomPluginIntegrationTest.groovy
new file mode 100644
index 0000000..9904cfc
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesCustomPluginIntegrationTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * 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.junit.runner.RunWith
+import org.junit.Rule
+import org.junit.Test
+import org.gradle.util.TestFile
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.Sample
+
+ at RunWith(DistributionIntegrationTestRunner.class)
+class SamplesCustomPluginIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('customPlugin')
+
+    @Test
+    public void canTestPluginAndTaskImplementation() {
+        TestFile projectDir = sample.dir
+
+        executer.inDirectory(projectDir).withTasks('check').run()
+
+        def result = new JUnitTestExecutionResult(projectDir)
+        result.assertTestClassesExecuted('org.gradle.GreetingTaskTest', 'org.gradle.GreetingPluginTest')
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesExcludesAndClassifiersIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesExcludesAndClassifiersIntegrationTest.groovy
new file mode 100644
index 0000000..43d9649
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesExcludesAndClassifiersIntegrationTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * 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
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.gradle.integtests.fixtures.Sample
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(DistributionIntegrationTestRunner.class)
+class SamplesExcludesAndClassifiersIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('userguide/artifacts/excludesAndClassifiers')
+
+    @Test
+    public void checkExcludeAndClassifier() {
+        File projectDir = sample.dir
+        String outputCompile = executer.inDirectory(projectDir).withTasks('clean', 'resolveCompile').run().getOutput()
+        String outputRuntime = executer.inDirectory(projectDir).withTasks('clean', 'resolveRuntime').run().getOutput()
+        assertThat(outputCompile, not(containsString("commons")))
+        assertThat(outputRuntime, not(containsString("commons")))
+        assertThat(outputCompile, not(containsString("reports")))
+        assertThat(outputRuntime, not(containsString("reports")))
+        assertThat(outputCompile, not(containsString("shared")))
+        assertThat(outputRuntime, containsString("shared"))
+
+        assertThat(outputCompile, containsString("service-1.0-jdk15"))
+        assertThat(outputCompile, containsString("service-1.0-jdk14"))
+    }
+
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesGroovyCustomizedLayoutIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesGroovyCustomizedLayoutIntegrationTest.groovy
new file mode 100644
index 0000000..709a1d3
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesGroovyCustomizedLayoutIntegrationTest.groovy
@@ -0,0 +1,50 @@
+/*
+ * 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 org.junit.runner.RunWith
+import org.gradle.integtests.fixtures.Sample
+
+ at RunWith(DistributionIntegrationTestRunner.class)
+class SamplesGroovyCustomizedLayoutIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('groovy/customizedLayout')
+
+    @Test
+    public void groovyProjectQuickstartSample() {
+        TestFile groovyProjectDir = sample.dir
+        executer.inDirectory(groovyProjectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(groovyProjectDir)
+        result.assertTestClassesExecuted('org.gradle.PersonTest')
+
+        // Check contents of jar
+        TestFile tmpDir = dist.testDir.file('jarContents')
+        groovyProjectDir.file('build/libs/customizedLayout.jar').unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/Person.class'
+        )
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesGroovyMultiProjectIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesGroovyMultiProjectIntegrationTest.groovy
new file mode 100644
index 0000000..4e9bc5e
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesGroovyMultiProjectIntegrationTest.groovy
@@ -0,0 +1,81 @@
+/*
+ * 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.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 org.junit.runner.RunWith
+import static org.hamcrest.Matchers.*
+import org.gradle.integtests.fixtures.Sample
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(DistributionIntegrationTestRunner.class)
+class SamplesGroovyMultiProjectIntegrationTest {
+    static final String TEST_PROJECT_NAME = 'testproject'
+
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('groovy/multiproject')
+
+    private List mainFiles = ['JavaPerson', 'GroovyPerson', 'GroovyJavaPerson']
+    private List excludedFiles = ['ExcludeJava', 'ExcludeGroovy', 'ExcludeGroovyJava']
+    private List testFiles = ['JavaPersonTest', 'GroovyPersonTest', 'GroovyJavaPersonTest']
+
+    @Test
+    public void groovyProjectSamples() {
+        String packagePrefix = 'build/classes/main/org/gradle'
+        String testPackagePrefix = 'build/classes/test/org/gradle'
+
+
+        TestFile groovyProjectDir = sample.dir
+        TestFile testProjectDir = groovyProjectDir.file(TEST_PROJECT_NAME)
+
+        executer.inDirectory(groovyProjectDir).withTasks('clean', 'build').run()
+
+        // Check compilation
+        mainFiles.each { testProjectDir.file(packagePrefix, it + ".class").assertIsFile() }
+        excludedFiles.each { testProjectDir.file(packagePrefix, it + ".class").assertDoesNotExist() }
+        testFiles.each { testProjectDir.file(testPackagePrefix, it + ".class").assertIsFile() }
+
+        // The test produce marker files with the name of the test class
+        testFiles.each { testProjectDir.file('build', it).assertIsFile() }
+
+        // Check contents of jar
+        TestFile tmpDir = dist.testDir.file('jarContents')
+        testProjectDir.file("build/libs/$TEST_PROJECT_NAME-1.0.jar").unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'META-INF/myfile',
+                'org/gradle/main.properties',
+                'org/gradle/JavaPerson.class',
+                'org/gradle/GroovyPerson.class',
+                'org/gradle/GroovyJavaPerson.class'
+        )
+        tmpDir.file('META-INF/MANIFEST.MF').assertContents(containsString('myprop: myvalue'))
+
+        // Build docs
+        executer.inDirectory(groovyProjectDir).withTasks('clean', 'javadoc', 'groovydoc').run()
+        testProjectDir.file('build/docs/javadoc/index.html').assertIsFile()
+        testProjectDir.file('build/docs/groovydoc/index.html').assertIsFile()
+        testProjectDir.file('build/docs/groovydoc/org/gradle/GroovyPerson.html').assertIsFile()
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesGroovyOldVersionsIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesGroovyOldVersionsIntegrationTest.groovy
new file mode 100644
index 0000000..d4393f0
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesGroovyOldVersionsIntegrationTest.groovy
@@ -0,0 +1,57 @@
+/*
+ * 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 org.junit.runner.RunWith
+
+ at RunWith(DistributionIntegrationTestRunner.class)
+class SamplesGroovyOldVersionsIntegrationTest {
+
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+
+    @Test
+    public void groovy156() {
+        TestFile groovyProjectDir = dist.samplesDir.file('groovy/groovy-1.5.6')
+        executer.inDirectory(groovyProjectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(groovyProjectDir)
+        result.assertTestClassesExecuted('org.gradle.PersonTest')
+
+        // Check jar exists
+        groovyProjectDir.file("build/libs/groovy-1.5.6.jar").assertIsFile()
+    }
+
+    @Test
+    public void groovy167() {
+        TestFile groovyProjectDir = dist.samplesDir.file('groovy/groovy-1.6.7')
+        executer.inDirectory(groovyProjectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(groovyProjectDir)
+        result.assertTestClassesExecuted('org.gradle.PersonTest')
+
+        // Check jar exists
+        groovyProjectDir.file("build/libs/groovy-1.6.7.jar").assertIsFile()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesGroovyQuickstartIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesGroovyQuickstartIntegrationTest.groovy
new file mode 100644
index 0000000..e633f9d
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesGroovyQuickstartIntegrationTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * 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 org.junit.runner.RunWith
+import org.gradle.integtests.fixtures.Sample
+
+ at RunWith(DistributionIntegrationTestRunner.class)
+class SamplesGroovyQuickstartIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('groovy/quickstart')
+
+    @Test
+    public void groovyProjectQuickstartSample() {
+        TestFile groovyProjectDir = sample.dir
+        executer.inDirectory(groovyProjectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(groovyProjectDir)
+        result.assertTestClassesExecuted('org.gradle.PersonTest')
+
+        // Check contents of jar
+        TestFile tmpDir = dist.testDir.file('jarContents')
+        groovyProjectDir.file('build/libs/quickstart.jar').unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/Person.class',
+                'org/gradle/Person$_closure1.class',
+                'org/gradle/Person$_closure2.class',
+                'resource.txt',
+                'script.groovy'
+        )
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesJavaBaseIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesJavaBaseIntegrationTest.groovy
new file mode 100644
index 0000000..e12cdf5
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesJavaBaseIntegrationTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * 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 org.junit.runner.RunWith
+import org.gradle.integtests.fixtures.Sample
+
+/**
+ * @author Hans Dockter
+ */
+
+ at RunWith (DistributionIntegrationTestRunner.class)
+class SamplesJavaBaseIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('java/base')
+
+    @Test
+    public void canBuildAndUploadJar() {
+        TestFile javaprojectDir = sample.dir
+
+        // Build and test projects
+        executer.inDirectory(javaprojectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(javaprojectDir.file('test'))
+        result.assertTestClassesExecuted('org.gradle.PersonTest')
+
+        // Check jar exists
+        javaprojectDir.file("prod/build/libs/prod-1.0.jar").assertIsFile()
+        
+        // Check contents of Jar
+        TestFile jarContents = dist.testDir.file('jar')
+        javaprojectDir.file('prod/build/libs/prod-1.0.jar').unzipTo(jarContents)
+        jarContents.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/Person.class'
+        )
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesJavaCustomizedLayoutIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesJavaCustomizedLayoutIntegrationTest.groovy
new file mode 100644
index 0000000..dd989ee
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesJavaCustomizedLayoutIntegrationTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * 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 org.junit.runner.RunWith
+import org.gradle.integtests.fixtures.Sample
+
+/**
+ * @author Hans Dockter
+ */
+
+ at RunWith (DistributionIntegrationTestRunner.class)
+class SamplesJavaCustomizedLayoutIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('java/customizedLayout')
+
+    @Test
+    public void canBuildAndUploadJar() {
+        TestFile javaprojectDir = sample.dir
+                                                      
+        // Build and test projects
+        executer.inDirectory(javaprojectDir).withTasks('clean', 'build', 'uploadArchives').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(javaprojectDir)
+        result.assertTestClassesExecuted('org.gradle.PersonTest')
+
+        // Check jar exists
+        javaprojectDir.file('build/libs/customizedLayout.jar').assertIsFile()
+
+        // Check contents of Jar
+        TestFile jarContents = dist.testDir.file('jar')
+        javaprojectDir.file('build/libs/customizedLayout.jar').unzipTo(jarContents)
+        jarContents.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/Person.class'
+        )
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesJavaMultiProjectIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesJavaMultiProjectIntegrationTest.groovy
new file mode 100644
index 0000000..e60ea9a
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesJavaMultiProjectIntegrationTest.groovy
@@ -0,0 +1,228 @@
+/*
+ * 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.integtests.fixtures.Sample
+import org.gradle.util.TestFile
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.hamcrest.Matchers.*
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(DistributionIntegrationTestRunner.class)
+class SamplesJavaMultiProjectIntegrationTest {
+
+    static final String JAVA_PROJECT_NAME = 'java/multiproject'
+    static final String SHARED_NAME = 'shared'
+    static final String API_NAME = 'api'
+    static final String WEBAPP_NAME = 'webservice'
+    static final String SERVICES_NAME = 'services'
+    static final String WEBAPP_PATH = "$SERVICES_NAME/$WEBAPP_NAME" as String
+
+    private TestFile javaprojectDir
+    private List projects;
+
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('java/multiproject')
+
+    @Before
+    void setUp() {
+        javaprojectDir = sample.dir
+        projects = [SHARED_NAME, API_NAME, WEBAPP_PATH].collect {"$JAVA_PROJECT_NAME/$it"} + JAVA_PROJECT_NAME
+    }
+
+    @Test
+    public void multiProjectJavaProjectSample() {
+        // Build and test projects
+        executer.inDirectory(javaprojectDir).withTasks('build').run()
+
+        assertBuildSrcBuilt()
+        assertEverythingBuilt()
+    }
+
+    private void assertBuildSrcBuilt() {
+        TestFile buildSrcDir = javaprojectDir.file('buildSrc')
+
+        buildSrcDir.file('build/libs/buildSrc.jar').assertIsFile()
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(buildSrcDir)
+        result.assertTestClassesExecuted('org.gradle.buildsrc.BuildSrcClassTest')
+    }
+
+    private void assertEverythingBuilt() {
+        String packagePrefix = 'build/classes/main/org/gradle'
+        String testPackagePrefix = 'build/classes/test/org/gradle'
+
+        // Check classes and resources
+        assertExists(javaprojectDir, SHARED_NAME, packagePrefix, SHARED_NAME, 'Person.class')
+        assertExists(javaprojectDir, SHARED_NAME, packagePrefix, SHARED_NAME, 'main.properties')
+
+        // Check test classes and resources
+        assertExists(javaprojectDir, SHARED_NAME, testPackagePrefix, SHARED_NAME, 'PersonTest.class')
+        assertExists(javaprojectDir, SHARED_NAME, testPackagePrefix, SHARED_NAME, 'test.properties')
+        assertExists(javaprojectDir, API_NAME, packagePrefix, API_NAME, 'PersonList.class')
+        assertExists(javaprojectDir, WEBAPP_PATH, packagePrefix, WEBAPP_NAME, 'TestTest.class')
+
+        // Check test results and report
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(javaprojectDir.file(SHARED_NAME))
+        result.assertTestClassesExecuted('org.gradle.shared.PersonTest')
+
+        result = new JUnitTestExecutionResult(javaprojectDir.file(WEBAPP_PATH))
+        result.assertTestClassesExecuted('org.gradle.webservice.TestTestTest')
+
+        // Check contents of shared jar
+        TestFile tmpDir = dist.testDir.file("$SHARED_NAME-1.0.jar")
+        javaprojectDir.file(SHARED_NAME, "build/libs/$SHARED_NAME-1.0.jar").unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/shared/Person.class',
+                'org/gradle/shared/package-info.class',
+                'org/gradle/shared/main.properties'
+        )
+
+        // Check contents of API jar
+        tmpDir = dist.testDir.file("$API_NAME-1.0.jar")
+        javaprojectDir.file(API_NAME, "build/libs/$API_NAME-1.0.jar").unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/api/PersonList.class',
+                'org/gradle/apiImpl/Impl.class')
+
+        // Check contents of API jar
+        tmpDir = dist.testDir.file("$API_NAME-spi-1.0.jar")
+        javaprojectDir.file(API_NAME, "build/libs/$API_NAME-spi-1.0.jar").unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/api/PersonList.class')
+
+        // Check contents of War
+        tmpDir = dist.testDir.file("$WEBAPP_NAME-2.5.war")
+        javaprojectDir.file(WEBAPP_PATH, "build/libs/$WEBAPP_NAME-2.5.war").unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'WEB-INF/classes/org/gradle/webservice/TestTest.class',
+                "WEB-INF/lib/$SHARED_NAME-1.0.jar".toString(),
+                "WEB-INF/lib/$API_NAME-1.0.jar".toString(),
+                "WEB-INF/lib/$API_NAME-spi-1.0.jar".toString(),
+                'WEB-INF/lib/commons-collections-3.2.jar',
+                'WEB-INF/lib/commons-io-1.2.jar',
+                'WEB-INF/lib/commons-lang-2.4.jar'
+        )
+
+        // Check contents of dist zip
+        tmpDir = dist.testDir.file("$API_NAME-1.0.zip")
+        javaprojectDir.file(API_NAME, "build/distributions/$API_NAME-1.0.zip").unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'README.txt',
+                "libs/$API_NAME-spi-1.0.jar".toString(),
+                "libs/$SHARED_NAME-1.0.jar".toString(),
+                'libs/commons-io-1.2.jar',
+                'libs/commons-lang-2.4.jar'
+        )
+    }
+
+    @Test
+    public void multiProjectJavaDoc() {
+        executer.inDirectory(javaprojectDir).withTasks('clean', 'javadoc').run()
+        javaprojectDir.file(SHARED_NAME, 'build/docs/javadoc/index.html').assertIsFile()
+        javaprojectDir.file(SHARED_NAME, 'build/docs/javadoc/index.html').assertContents(containsString("shared 1.0 API"))
+        javaprojectDir.file(SHARED_NAME, 'build/docs/javadoc/org/gradle/shared/Person.html').assertIsFile()
+        javaprojectDir.file(SHARED_NAME, 'build/docs/javadoc/org/gradle/shared/package-summary.html').assertContents(containsString("These are the shared classes."))
+        javaprojectDir.file(API_NAME, 'build/docs/javadoc/index.html').assertIsFile()
+        javaprojectDir.file(API_NAME, 'build/docs/javadoc/org/gradle/api/PersonList.html').assertIsFile()
+        javaprojectDir.file(API_NAME, 'build/docs/javadoc/org/gradle/api/package-summary.html').assertContents(containsString("These are the API classes"))
+        javaprojectDir.file(WEBAPP_PATH, 'build/docs/javadoc/index.html').assertIsFile()
+    }
+
+    @Test
+    public void multiProjectPartialBuild() {
+        String packagePrefix = 'build/classes/main/org/gradle'
+        String testPackagePrefix = 'build/classes/test/org/gradle'
+
+        // Partial build using current directory
+        executer.inDirectory(javaprojectDir.file("$SERVICES_NAME/$WEBAPP_NAME")).withTasks('buildNeeded').run()
+        checkPartialWebAppBuild(packagePrefix, javaprojectDir, testPackagePrefix)
+
+        // Partial build using task path
+        executer.inDirectory(javaprojectDir).withTasks('clean', "$SHARED_NAME:classes".toString()).run()
+        assertExists(javaprojectDir, SHARED_NAME, packagePrefix, SHARED_NAME, 'Person.class')
+        assertDoesNotExist(javaprojectDir, false, API_NAME, packagePrefix, API_NAME, 'PersonList.class')
+    }
+
+
+    @Test
+    public void buildDependents() {
+        executer.inDirectory(javaprojectDir).withTasks('clean', "$SHARED_NAME:buildDependents".toString()).run()
+        assertEverythingBuilt()
+    }
+
+    @Test
+    public void clean() {
+        executer.inDirectory(javaprojectDir).withTasks('classes').run()
+        executer.inDirectory(javaprojectDir).withTasks('clean').run()
+        projects.each { javaprojectDir.file("$it/build").assertDoesNotExist() }
+    }
+
+    @Test
+    public void noRebuildOfProjectDependencies() {
+        TestFile apiDir = javaprojectDir.file(API_NAME)
+        executer.inDirectory(apiDir).withTasks('classes').run()
+        TestFile sharedJar = javaprojectDir.file("shared/build/libs/shared-1.0.jar")
+        TestFile.Snapshot snapshot = sharedJar.snapshot()
+        executer.inDirectory(apiDir).withTasks('clean', 'classes').withArguments("-a").run()
+        sharedJar.assertHasNotChangedSince(snapshot)
+    }
+
+    @Test
+    public void additionalProjectDependenciesTasks() {
+        TestFile apiDir = javaprojectDir.file(API_NAME)
+        executer.inDirectory(apiDir).withTasks('classes').withArguments("-A javadoc").run()
+        assertExists(javaprojectDir, SHARED_NAME, 'build/docs/javadoc/index.html')
+    }
+
+    @Test
+    public void shouldNotUseCacheForProjectDependencies() {
+        TestFile apiDir = javaprojectDir.file(API_NAME)
+        executer.inDirectory(apiDir).withTasks('checkProjectDependency').run()
+    }
+           
+    private static def checkPartialWebAppBuild(String packagePrefix, TestFile javaprojectDir, String testPackagePrefix) {
+        assertExists(javaprojectDir, SHARED_NAME, packagePrefix, SHARED_NAME, 'Person.class')
+        assertExists(javaprojectDir, SHARED_NAME, packagePrefix, SHARED_NAME, 'main.properties')
+        assertExists(javaprojectDir, SHARED_NAME, testPackagePrefix, SHARED_NAME, 'PersonTest.class')
+        assertExists(javaprojectDir, SHARED_NAME, testPackagePrefix, SHARED_NAME, 'test.properties')
+        assertExists(javaprojectDir, "$SERVICES_NAME/$WEBAPP_NAME" as String, packagePrefix, WEBAPP_NAME, 'TestTest.class')
+        assertExists(javaprojectDir, "$SERVICES_NAME/$WEBAPP_NAME" as String, 'build', 'libs', 'webservice-2.5.war')
+        assertDoesNotExist(javaprojectDir, false, "$SERVICES_NAME/$WEBAPP_NAME" as String, 'build', 'libs', 'webservice-2.5.jar')
+    }
+
+    private static void assertExists(TestFile baseDir, Object... path) {
+        baseDir.file(path).assertExists()
+    }
+
+    static void assertDoesNotExist(TestFile baseDir, boolean shouldExists, Object... path) {
+        baseDir.file(path).assertDoesNotExist()
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesJavaOnlyIfIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesJavaOnlyIfIntegrationTest.groovy
new file mode 100644
index 0000000..67d3b9a
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesJavaOnlyIfIntegrationTest.groovy
@@ -0,0 +1,94 @@
+/*
+ * 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 org.junit.runner.RunWith
+import org.gradle.integtests.fixtures.Sample
+
+ at RunWith (DistributionIntegrationTestRunner.class)
+public class SamplesJavaOnlyIfIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('java/onlyif')
+
+    /**
+     * runs a build 3 times.
+     * execute clean dists
+     * check worked correctly
+     *
+     * remove test results
+     * execute dists
+     * check didn't re-run tests
+     *
+     * remove class file
+     * execute dists
+     * check that it re-ran tests 
+     */
+    @Test public void testOptimizedBuild() {
+        TestFile javaprojectDir = sample.dir
+
+        // Build and test projects
+        executer.inDirectory(javaprojectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        assertExists(javaprojectDir, 'build/test-results/TEST-org.gradle.PersonTest.xml')
+        assertExists(javaprojectDir, 'build/test-results/TESTS-TestSuites.xml')
+
+        // Check jar exists
+        assertExists(javaprojectDir, "build/libs/onlyif.jar")
+
+        // remove test results
+        removeFile(javaprojectDir, 'build/test-results/TEST-org.gradle.PersonTest.xml')
+        removeFile(javaprojectDir, 'build/test-results/TESTS-TestSuites.xml')
+
+        executer.inDirectory(javaprojectDir).withTasks('test').run()
+
+        // assert that tests did not run
+        // (since neither compile nor compileTests should have done anything)
+        assertDoesNotExist(javaprojectDir, 'build/test-results/TEST-org.gradle.PersonTest.xml')
+        assertDoesNotExist(javaprojectDir, 'build/test-results/TESTS-TestSuites.xml')
+
+        // remove a compiled class file
+        removeFile(javaprojectDir, 'build/classes/main/org/gradle/Person.class')
+
+        executer.inDirectory(javaprojectDir).withTasks('test').run()
+
+        // Check tests have run
+        assertExists(javaprojectDir, 'build/test-results/TEST-org.gradle.PersonTest.xml')
+        assertExists(javaprojectDir, 'build/test-results/TESTS-TestSuites.xml')
+    }
+
+    private static void assertExists(File baseDir, String path) {
+        new TestFile(baseDir).file(path).assertExists()
+    }
+
+    private static void assertDoesNotExist(File baseDir, String path) {
+        new TestFile(baseDir).file(path).assertDoesNotExist()
+    }
+
+    private static void removeFile(File baseDir, String path) {
+        TestFile file = new TestFile(baseDir).file(path)
+        file.assertExists()
+        file.delete()
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesJavaProjectWithIntTestsIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesJavaProjectWithIntTestsIntegrationTest.groovy
new file mode 100644
index 0000000..a4629d0
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesJavaProjectWithIntTestsIntegrationTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * 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 org.junit.runner.RunWith
+import org.gradle.integtests.fixtures.Sample
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith (DistributionIntegrationTestRunner.class)
+class SamplesJavaProjectWithIntTestsIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('java/withIntegrationTests')
+
+    @Test
+    public void canRunIntegrationTests() {
+        TestFile javaprojectDir = sample.dir
+        
+        // Run int tests
+        executer.inDirectory(javaprojectDir).withTasks('clean', 'integrationTest').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(javaprojectDir)
+        result.assertTestClassesExecuted('org.gradle.PersonIntegrationTest')
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesJavaQuickstartIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesJavaQuickstartIntegrationTest.groovy
new file mode 100644
index 0000000..6c5a5d2
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesJavaQuickstartIntegrationTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * 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 java.util.jar.Manifest
+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 org.junit.runner.RunWith
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.gradle.integtests.fixtures.Sample
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith (DistributionIntegrationTestRunner.class)
+class SamplesJavaQuickstartIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('java/quickstart')
+
+    @Test
+    public void canBuildAndUploadJar() {
+        TestFile javaprojectDir = sample.dir
+
+        // Build and test projects
+        executer.inDirectory(javaprojectDir).withTasks('clean', 'build', 'uploadArchives').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(javaprojectDir)
+        result.assertTestClassesExecuted('org.gradle.PersonTest')
+
+        // Check jar exists
+        javaprojectDir.file("build/libs/quickstart-1.0.jar").assertIsFile()
+
+        // Check jar uploaded
+        javaprojectDir.file('repos/quickstart-1.0.jar').assertIsFile()
+
+        // Check contents of Jar
+        TestFile jarContents = dist.testDir.file('jar')
+        javaprojectDir.file('repos/quickstart-1.0.jar').unzipTo(jarContents)
+        jarContents.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/Person.class'
+        )
+
+        // Check contents of manifest
+        Manifest manifest = new Manifest()
+        jarContents.file('META-INF/MANIFEST.MF').withInputStream { manifest.read(it) }
+        assertThat(manifest.mainAttributes.size(), equalTo(3))
+        assertThat(manifest.mainAttributes.getValue('Manifest-Version'), equalTo('1.0'))
+        assertThat(manifest.mainAttributes.getValue('Implementation-Title'), equalTo('Gradle Quickstart'))
+        assertThat(manifest.mainAttributes.getValue('Implementation-Version'), equalTo('1.0'))
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesMixedJavaAndGroovyIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesMixedJavaAndGroovyIntegrationTest.groovy
new file mode 100644
index 0000000..79a1455
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesMixedJavaAndGroovyIntegrationTest.groovy
@@ -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.integtests
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.util.TestFile
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.hamcrest.Matchers.*
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.Sample
+
+ at RunWith(DistributionIntegrationTestRunner.class)
+class SamplesMixedJavaAndGroovyIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('groovy/mixedJavaAndGroovy')
+
+    @Test
+    public void canBuildJar() {
+        TestFile projectDir = sample.dir
+        executer.inDirectory(projectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(projectDir)
+        result.assertTestClassesExecuted('org.gradle.PersonTest')
+
+        // Check contents of jar
+        TestFile tmpDir = dist.testDir.file('jarContents')
+        projectDir.file('build/libs/mixedJavaAndGroovy-1.0.jar').unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/Person.class',
+                'org/gradle/GroovyPerson.class',
+                'org/gradle/JavaPerson.class',
+                'org/gradle/PersonList.class'
+        )
+    }
+
+    @Test
+    public void canBuildDocs() {
+        TestFile projectDir = sample.dir
+        executer.inDirectory(projectDir).withTasks('clean', 'javadoc', 'groovydoc').run()
+
+        TestFile javadocsDir = projectDir.file("build/docs/javadoc")
+        javadocsDir.file("index.html").assertIsFile()
+        javadocsDir.file("index.html").assertContents(containsString('mixedJavaAndGroovy 1.0 API'))
+        javadocsDir.file("org/gradle/Person.html").assertIsFile()
+        javadocsDir.file("org/gradle/JavaPerson.html").assertIsFile()
+
+        TestFile groovydocsDir = projectDir.file("build/docs/groovydoc")
+        groovydocsDir.file("index.html").assertIsFile()
+        groovydocsDir.file("overview-summary.html").assertContents(containsString('mixedJavaAndGroovy 1.0 API'))
+        groovydocsDir.file("org/gradle/JavaPerson.html").assertIsFile()
+        groovydocsDir.file("org/gradle/GroovyPerson.html").assertIsFile()
+        groovydocsDir.file("org/gradle/PersonList.html").assertIsFile()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesMixedJavaAndScalaIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesMixedJavaAndScalaIntegrationTest.groovy
new file mode 100644
index 0000000..90e0d20
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesMixedJavaAndScalaIntegrationTest.groovy
@@ -0,0 +1,76 @@
+/*
+ * 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 org.junit.runner.RunWith
+import static org.hamcrest.Matchers.*
+import org.gradle.integtests.fixtures.Sample
+
+ at RunWith (DistributionIntegrationTestRunner.class)
+class SamplesMixedJavaAndScalaIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('scala/mixedJavaAndScala')
+
+    @Test
+    public void canBuildJar() {
+        TestFile projectDir = sample.dir
+
+        // Build and test projects
+        executer.inDirectory(projectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(projectDir)
+        result.assertTestClassesExecuted('org.gradle.sample.PersonTest')
+
+        // Check contents of Jar
+        TestFile jarContents = dist.testDir.file('jar')
+        projectDir.file("build/libs/mixedJavaAndScala-1.0.jar").unzipTo(jarContents)
+        jarContents.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/sample/Person.class',
+                'org/gradle/sample/impl/JavaPerson.class',
+                'org/gradle/sample/impl/PersonImpl.class',
+                'org/gradle/sample/impl/PersonList.class'
+        )
+    }
+
+    @Test
+    public void canBuildDocs() {
+        TestFile projectDir = sample.dir
+        executer.inDirectory(projectDir).withTasks('clean', 'javadoc', 'scaladoc').run()
+
+        TestFile javadocsDir = projectDir.file("build/docs/javadoc")
+        javadocsDir.file("index.html").assertIsFile()
+        javadocsDir.file("index.html").assertContents(containsString('mixedJavaAndScala 1.0 API'))
+        javadocsDir.file("org/gradle/sample/Person.html").assertIsFile()
+        javadocsDir.file("org/gradle/sample/impl/JavaPerson.html").assertIsFile()
+
+        TestFile scaladocsDir = projectDir.file("build/docs/scaladoc")
+        scaladocsDir.file("index.html").assertIsFile()
+        scaladocsDir.file("index.html").assertContents(containsString('mixedJavaAndScala 1.0 API'))
+        scaladocsDir.file("org/gradle/sample/impl/PersonImpl.html").assertIsFile()
+        scaladocsDir.file("org/gradle/sample/impl/JavaPerson.html").assertIsFile()
+        scaladocsDir.file("org/gradle/sample/impl/PersonList.html").assertIsFile()
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesRepositoriesIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesRepositoriesIntegrationTest.groovy
new file mode 100644
index 0000000..a17ac6f
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesRepositoriesIntegrationTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * 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
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.gradle.integtests.fixtures.Sample
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(DistributionIntegrationTestRunner.class)
+class SamplesRepositoriesIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('userguide/artifacts/defineRepository')
+
+    @Test
+    public void repositoryNotations() {
+        // This test is not very strong. Its main purpose is to the for the correct syntax as we use many
+        // code snippets from this build script in the user's guide.
+        File projectDir = sample.dir
+        String output = executer.inDirectory(projectDir).withQuietLogging().withTasks('lookup').run().getOutput()
+        assertThat(output, equalTo(String.format("localRepository%nlocalRepository%n")))
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesScalaCustomizedLayoutIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesScalaCustomizedLayoutIntegrationTest.groovy
new file mode 100644
index 0000000..a96bb79
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesScalaCustomizedLayoutIntegrationTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * 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 org.junit.runner.RunWith
+import org.gradle.integtests.fixtures.Sample
+
+ at RunWith (DistributionIntegrationTestRunner.class)
+class SamplesScalaCustomizedLayoutIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('scala/customizedLayout')
+
+    @Test
+    public void canBuildJar() {
+        TestFile projectDir = sample.dir
+
+        // Build and test projects
+        executer.inDirectory(projectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(projectDir)
+        result.assertTestClassesExecuted('org.gradle.sample.impl.PersonImplTest')
+
+        // Check contents of Jar
+        TestFile jarContents = dist.testDir.file('jar')
+        projectDir.file("build/libs/customizedLayout.jar").unzipTo(jarContents)
+        jarContents.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/sample/api/Person.class',
+                'org/gradle/sample/impl/PersonImpl.class'
+        )
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesScalaQuickstartIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesScalaQuickstartIntegrationTest.groovy
new file mode 100644
index 0000000..03cde4f
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesScalaQuickstartIntegrationTest.groovy
@@ -0,0 +1,68 @@
+/*
+ * 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.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.hamcrest.Matchers.*
+import org.gradle.integtests.fixtures.Sample
+
+ at RunWith (DistributionIntegrationTestRunner.class)
+class SamplesScalaQuickstartIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('scala/quickstart')
+
+    private TestFile projectDir
+
+    @Before
+    void setUp() {
+        projectDir = sample.dir
+    }
+
+    @Test
+    public void canBuildJar() {
+        // Build and test projects
+        executer.inDirectory(projectDir).withTasks('clean', 'build').run()
+
+        // Check tests have run
+        JUnitTestExecutionResult result = new JUnitTestExecutionResult(projectDir)
+        result.assertTestClassesExecuted('org.gradle.sample.impl.PersonImplTest')
+
+        // Check contents of Jar
+        TestFile jarContents = dist.testDir.file('jar')
+        projectDir.file("build/libs/quickstart.jar").unzipTo(jarContents)
+        jarContents.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'org/gradle/sample/api/Person.class',
+                'org/gradle/sample/impl/PersonImpl.class'
+        )
+    }
+
+    @Test
+    public void canBuildScalaDoc() {
+        executer.inDirectory(projectDir).withTasks('clean', 'scaladoc').run()
+
+        projectDir.file('build/docs/scaladoc/index.html').assertExists()
+        projectDir.file('build/docs/scaladoc/org/gradle/sample/api/Person.html').assertContents(containsString("Defines the interface for a person."))
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesWebProjectIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesWebProjectIntegrationTest.groovy
new file mode 100644
index 0000000..26e73ce
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesWebProjectIntegrationTest.groovy
@@ -0,0 +1,73 @@
+/*
+ * 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.integtests
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.util.TestFile
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.gradle.integtests.fixtures.Sample
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(DistributionIntegrationTestRunner.class)
+class SamplesWebProjectIntegrationTest {
+    static final String WEB_PROJECT_NAME = 'customised'
+
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('webApplication/customised')
+
+    @Test
+    public void webProjectSamples() {
+        TestFile webProjectDir = sample.dir
+        executer.inDirectory(webProjectDir).withTasks('clean', 'assemble').run()
+        TestFile tmpDir = dist.testDir.file('unjar')
+        webProjectDir.file("build/libs/customised-1.0.war").unzipTo(tmpDir)
+        tmpDir.assertHasDescendants(
+                'root.txt',
+                'META-INF/MANIFEST.MF',
+                'WEB-INF/classes/org/gradle/HelloServlet.class',
+                'WEB-INF/classes/org/gradle/MyClass.class',
+                'WEB-INF/lib/compile-1.0.jar',
+                'WEB-INF/lib/compile-transitive-1.0.jar',
+                'WEB-INF/lib/runtime-1.0.jar',
+                'WEB-INF/lib/additional-1.0.jar',
+                'WEB-INF/lib/otherLib-1.0.jar',
+                'WEB-INF/additional.xml',
+                'WEB-INF/webapp.xml',
+                'WEB-INF/web.xml',
+                'webapp.html')
+    }
+
+    @Test
+    public void checkJettyPlugin() {
+        TestFile webProjectDir = sample.dir
+        executer.inDirectory(webProjectDir).withTasks('clean', 'runTest').run()
+        checkServletOutput(webProjectDir)
+        executer.inDirectory(webProjectDir).withTasks('clean', 'runWarTest').run()
+        checkServletOutput(webProjectDir)
+    }
+
+    static void checkServletOutput(TestFile webProjectDir) {
+        Assert.assertEquals('Hello Gradle', webProjectDir.file("build/servlet-out.txt").text)
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesWebQuickstartIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesWebQuickstartIntegrationTest.groovy
new file mode 100644
index 0000000..63e6fa2
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SamplesWebQuickstartIntegrationTest.groovy
@@ -0,0 +1,67 @@
+/*
+ * 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.integtests
+
+import org.gradle.integtests.fixtures.ExecutionResult
+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 org.junit.runner.RunWith
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.gradle.integtests.fixtures.Sample
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(DistributionIntegrationTestRunner.class)
+class SamplesWebQuickstartIntegrationTest {
+    static final String WEB_PROJECT_NAME = 'web-project'
+
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('webApplication/quickstart')
+
+    @Test
+    public void webProjectSamples() {
+        TestFile webProjectDir = sample.dir
+        executer.inDirectory(webProjectDir).withTasks('clean', 'build').run()
+
+        // Check contents of War
+        TestFile warContents = dist.testDir.file('jar')
+        webProjectDir.file("build/libs/quickstart.war").unzipTo(warContents)
+        warContents.assertHasDescendants(
+                'META-INF/MANIFEST.MF',
+                'index.jsp',
+                'WEB-INF/classes/org/gradle/sample/Greeter.class',
+                'WEB-INF/classes/greeting.txt',
+                'WEB-INF/lib/log4j-1.2.15.jar',
+                'WEB-INF/lib/commons-io-1.4.jar',
+        )
+        
+        ExecutionResult result = executer.inDirectory(webProjectDir).withTasks('clean', 'runTest').run()
+        checkServletOutput(result)
+        result = executer.inDirectory(webProjectDir).withTasks('clean', 'runWarTest').run()
+        checkServletOutput(result)
+    }
+
+    static void checkServletOutput(ExecutionResult result) {
+        assertThat(result.output, containsString('hello Gradle'))
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ScalaProjectIntegrationTest.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ScalaProjectIntegrationTest.java
new file mode 100644
index 0000000..ec5c8a1
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/ScalaProjectIntegrationTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+import org.junit.Test;
+
+public class ScalaProjectIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void handlesEmptyProject() {
+        testFile("build.gradle").writelns(
+                "apply plugin: 'scala'"
+        );
+        inTestDirectory().withTasks("build").run();
+    }
+
+    @Test
+    public void handlesJavaSourceOnly() {
+        testFile("src/main/java/somepackage/SomeClass.java").writelns("public class SomeClass { }");
+        testFile("build.gradle").write("apply plugin: 'scala'");
+        testFile("settings.gradle").write("rootProject.name='javaOnly'");
+        inTestDirectory().withTasks("build").run();
+        testFile("build/libs/javaOnly.jar").assertExists();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SettingsScriptErrorIntegrationTest.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SettingsScriptErrorIntegrationTest.java
new file mode 100644
index 0000000..1868121
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SettingsScriptErrorIntegrationTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+import org.gradle.integtests.fixtures.ExecutionFailure;
+import org.gradle.util.TestFile;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class SettingsScriptErrorIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void reportsSettingsScriptEvaluationFailsWithRuntimeException() throws IOException {
+        TestFile buildFile = testFile("some build.gradle");
+        TestFile settingsFile = testFile("some settings.gradle");
+        settingsFile.writelns("", "", "throw new RuntimeException('<failure message>')");
+
+        ExecutionFailure failure = usingBuildFile(buildFile).usingSettingsFile(settingsFile).withTasks("do-stuff")
+                .runWithFailure();
+
+        failure.assertHasFileName(String.format("Settings file '%s'", settingsFile));
+        failure.assertHasLineNumber(3);
+        failure.assertHasDescription("A problem occurred evaluating settings 'reportsSettingsScriptEvaluationFailsWithRuntimeException");
+        failure.assertHasCause("<failure message>");
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SettingsScriptExecutionIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SettingsScriptExecutionIntegrationTest.groovy
new file mode 100644
index 0000000..7a43d15
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SettingsScriptExecutionIntegrationTest.groovy
@@ -0,0 +1,68 @@
+/*
+ * 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
+
+import org.gradle.integtests.fixtures.ArtifactBuilder
+import org.gradle.integtests.fixtures.ExecutionResult
+import org.gradle.util.TestFile
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+class SettingsScriptExecutionIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void executesSettingsScriptWithCorrectEnvironment() {
+        createExternalJar()
+        createBuildSrc()
+
+        testFile('settings.gradle') << '''
+buildscript {
+    dependencies { classpath files('repo/test-1.3.jar') }
+}
+new org.gradle.test.BuildClass()
+new BuildSrcClass();
+println 'quiet message'
+captureStandardOutput(LogLevel.ERROR)
+println 'error message'
+assert settings != null
+assert buildscript.classLoader == getClass().classLoader.parent
+assert buildscript.classLoader == Thread.currentThread().contextClassLoader
+assert gradle.scriptClassLoader.parent == buildscript.classLoader.parent.parent
+'''
+        testFile('build.gradle') << 'task doStuff'
+
+        ExecutionResult result = inTestDirectory().withTasks('doStuff').run()
+        assertThat(result.output, containsString('quiet message'))
+        assertThat(result.output, not(containsString('error message')))
+        assertThat(result.error, containsString('error message'))
+        assertThat(result.error, not(containsString('quiet message')))
+    }
+
+    private TestFile createBuildSrc() {
+        return testFile('buildSrc/src/main/java/BuildSrcClass.java') << '''
+            public class BuildSrcClass { }
+'''
+    }
+
+    private def createExternalJar() {
+        ArtifactBuilder builder = artifactBuilder();
+        builder.sourceFile('org/gradle/test/BuildClass.java') << '''
+            package org.gradle.test;
+            public class BuildClass { }
+'''
+        builder.buildJar(testFile("repo/test-1.3.jar"))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SyncTaskIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SyncTaskIntegrationTest.groovy
new file mode 100644
index 0000000..06bea7a
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/SyncTaskIntegrationTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * 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
+
+import org.junit.Test
+
+class SyncTaskIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void copiesFilesAndRemovesExtraFilesFromDestDir() {
+        testFile('source').create {
+            dir1 { file 'file1.txt' }
+            dir2 {
+                subdir { file 'file2.txt' }
+                file 'file3.txt'
+            }
+        }
+        testFile('dest').create {
+            file 'extra.txt'
+            extraDir { file 'extra.txt' }
+            dir1 {
+                file 'extra.txt'
+                extraDir { file 'extra.txt' }
+            }
+        }
+
+        testFile('build.gradle') << '''
+            task sync(type: Sync) {
+                into 'dest'
+                from 'source'
+            }
+'''
+
+        inTestDirectory().withTasks('sync').run()
+
+        testFile('dest').assertHasDescendants(
+                'dir1/file1.txt',
+                'dir2/subdir/file2.txt',
+                'dir2/file3.txt'
+        )
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/TaskAutoDependencyIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/TaskAutoDependencyIntegrationTest.groovy
new file mode 100644
index 0000000..475738d
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/TaskAutoDependencyIntegrationTest.groovy
@@ -0,0 +1,71 @@
+/*
+ * 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
+
+import org.junit.Ignore
+import org.junit.Test
+
+class TaskAutoDependencyIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void autoAddsInputFileCollectionAsADependency() {
+        // Include a configuration with transitive dep on a Jar and an unmanaged Jar.
+
+        testFile('settings.gradle') << 'include "a", "b"'
+        testFile('a/build.gradle') << '''
+configurations { compile }
+dependencies { compile project(path: ':b', configuration: 'archives') }
+
+task doStuff(type: InputTask) {
+    src = configurations.compile + fileTree('src/java')
+}
+
+class InputTask extends DefaultTask {
+    @InputFiles
+    def FileCollection src
+}
+'''
+        testFile('b/build.gradle') << '''
+apply plugin: 'base'
+task jar << {
+    file('b.jar').text = 'some jar'
+}
+
+task otherJar(type: Jar) {
+    destinationDir = buildDir
+}
+
+configurations { archives }
+dependencies { archives files('b.jar') { builtBy jar } }
+artifacts { archives otherJar }
+'''
+        inTestDirectory().withTasks('doStuff').run().assertTasksExecuted(':b:jar', ':b:otherJar', ':a:doStuff')
+    }
+
+    @Test @Ignore
+    public void addsDependenciesForInheritedConfiguration() {
+        fail()
+    }
+
+    @Test @Ignore
+    public void addsDependenciesForFileCollectionInSameProject() {
+        fail()
+    }
+    
+    @Test @Ignore
+    public void addsDependenciesForFileCollectionInProjectWithNoArtifacts() {
+        fail()
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/TaskDefinitionIntegrationTest.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/TaskDefinitionIntegrationTest.java
new file mode 100644
index 0000000..e46c6d6
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/TaskDefinitionIntegrationTest.java
@@ -0,0 +1,135 @@
+/*
+ * 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.junit.Test;
+
+public class TaskDefinitionIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void canDefineTasksUsingTaskKeywordAndIdentifier() {
+        testFile("build.gradle").writelns(
+                "task nothing",
+                "task withAction << { }",
+                "task emptyOptions()",
+                "task task",
+                "task withOptions(dependsOn: [nothing, withAction, emptyOptions, task])",
+                "task withOptionsAndAction(dependsOn: withOptions) << { }");
+        inTestDirectory().withTasks("withOptionsAndAction").run().assertTasksExecuted(":emptyOptions", ":nothing",
+                ":task", ":withAction", ":withOptions", ":withOptionsAndAction");
+    }
+
+    @Test
+    public void canDefineTasksUsingTaskKeywordAndGString() {
+        testFile("build.gradle").writelns(
+                "v = 'Task'",
+                "task \"nothing$v\"",
+                "task \"withAction$v\" << { }",
+                "task \"emptyOptions$v\"()",
+                "task \"withOptions$v\"(dependsOn: [nothingTask, withActionTask, emptyOptionsTask])",
+                "task \"withOptionsAndAction$v\"(dependsOn: withOptionsTask) << { }");
+        inTestDirectory().withTasks("withOptionsAndActionTask").run().assertTasksExecuted(":emptyOptionsTask",
+                ":nothingTask", ":withActionTask", ":withOptionsTask", ":withOptionsAndActionTask");
+    }
+
+    @Test
+    public void canDefineTasksUsingTaskKeywordAndString() {
+        testFile("build.gradle").writelns(
+                "task 'nothing'",
+                "task 'withAction' << { }",
+                "task 'emptyOptions'()",
+                "task 'withOptions'(dependsOn: [nothing, withAction, emptyOptions])",
+                "task 'withOptionsAndAction'(dependsOn: withOptions) << { }");
+        inTestDirectory().withTasks("withOptionsAndAction").run().assertTasksExecuted(":emptyOptions", ":nothing",
+                ":withAction", ":withOptions", ":withOptionsAndAction");
+    }
+
+    @Test
+    public void canDefineTasksInNestedBlocks() {
+        testFile("build.gradle").writelns(
+                "2.times { task \"dynamic$it\" << { } }",
+                "if (dynamic0) { task inBlock }",
+                "def task() { task inMethod }",
+                "task()", "def cl = { -> task inClosure }",
+                "cl()",
+                "task all(dependsOn: [dynamic0, dynamic1, inBlock, inMethod, inClosure])");
+        inTestDirectory().withTasks("all").run().assertTasksExecuted(":dynamic0", ":dynamic1", ":inBlock", ":inClosure",
+                ":inMethod", ":all");
+    }
+
+    @Test
+    public void canDefineTasksUsingTaskMethodExpression() {
+        testFile("build.gradle").writelns(
+                "a = 'a' == 'b' ? null: task(withAction) << { }",
+                "a = task(nothing)",
+                "a = task(emptyOptions())", "taskName = 'dynamic'",
+                "a = task(\"$taskName\") << { }",
+                "a = task('string')",
+                "a = task('stringWithAction') << { }",
+                "a = task('stringWithOptions', description: 'description')",
+                "a = task('stringWithOptionsAndAction', description: 'description') << { }",
+                "a = task(withOptions, description: 'description')",
+                "a = task(withOptionsAndAction, description: 'description') << { }",
+                "a = task(anotherWithAction).doFirst\n{}", "task all(dependsOn: tasks.all)");
+        inTestDirectory().withTasks("all").run().assertTasksExecuted(":anotherWithAction", ":dynamic", ":emptyOptions",
+                ":nothing", ":string", ":stringWithAction", ":stringWithOptions", ":stringWithOptionsAndAction",
+                ":withAction", ":withOptions", ":withOptionsAndAction", ":all");
+    }
+
+    @Test
+    public void canConfigureTasksWhenTheyAreDefined() {
+        testFile("build.gradle").writelns(
+                "import org.gradle.integtests.TestTask",
+                "task withDescription { description = 'value' }",
+                "task(asMethod)\n{ description = 'value' }",
+                "task asStatement(type: TestTask) { property = 'value' }",
+                "task \"dynamic\"(type: TestTask) { property = 'value' }",
+                "v = task(asExpression, type: TestTask) { property = 'value' }",
+                "task(postConfigure, type: TestTask).configure { property = 'value' }",
+                "[asStatement, dynamic, asExpression, postConfigure].each { ",
+                "    assert 'value' == it.property",
+                "}",
+                "[withDescription, asMethod].each {",
+                "    assert 'value' == it.description",
+                "}",
+                "task all(dependsOn: tasks.all)");
+        inTestDirectory().withTasks("all").run();
+    }
+
+    @Test
+    public void doesNotHideLocalMethodsAndVariables() {
+        testFile("build.gradle").writelns(
+                "String name = 'a'; task name",
+//                "taskNameVar = 'b'; task taskNameVar",
+                "def taskNameMethod(String name = 'c') { name } ",
+//                "task taskNameMethod",
+                "task taskNameMethod('d')",
+                "def addTaskMethod(String methodParam) { task methodParam }",
+                "addTaskMethod('e')",
+                "def addTaskWithClosure(String methodParam) { task(methodParam) { property = 'value' } }",
+                "addTaskWithClosure('f')",
+                "def addTaskWithMap(String methodParam) { task(methodParam, description: 'description') }",
+                "addTaskWithMap('g')",
+                "cl = { String taskNameParam -> task taskNameParam }",
+                "cl.call('h')",
+                "cl = { String taskNameParam -> task(taskNameParam) { property = 'value' } }",
+                "cl.call('i')",
+                "assert 'value' == f.property",
+                "assert 'value' == i.property",
+                "task all(dependsOn: tasks.all)");
+        inTestDirectory().withTasks("all").run().assertTasksExecuted(":a", ":d", ":e", ":f", ":g", ":h", ":i", ":all");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/TaskExecutionIntegrationTest.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/TaskExecutionIntegrationTest.java
new file mode 100644
index 0000000..d81a0d4
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/TaskExecutionIntegrationTest.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.integtests;
+
+import org.gradle.util.TestFile;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+public class TaskExecutionIntegrationTest extends AbstractIntegrationTest {
+    public static boolean graphListenerNotified;
+
+    @Before
+    public void setUp() {
+        graphListenerNotified = false;
+    }
+
+    @Test
+    public void taskCanAccessTaskGraph() {
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns("import org.gradle.integtests.TaskExecutionIntegrationTest",
+                "task a(dependsOn: 'b') << { task ->",
+                "    assert gradle.taskGraph.hasTask(task)",
+                "    assert gradle.taskGraph.hasTask(':a')",
+                "    assert gradle.taskGraph.hasTask(a)",
+                "    assert gradle.taskGraph.hasTask(':b')",
+                "    assert gradle.taskGraph.hasTask(b)",
+                "    assert gradle.taskGraph.allTasks.contains(task)",
+                "    assert gradle.taskGraph.allTasks.contains(tasks.getByName('b'))",
+                "}",
+                "task b",
+                "gradle.taskGraph.whenReady { graph ->",
+                "    assert graph.hasTask(':a')",
+                "    assert graph.hasTask(a)",
+                "    assert graph.hasTask(':b')",
+                "    assert graph.hasTask(b)",
+                "    assert graph.allTasks.contains(a)",
+                "    assert graph.allTasks.contains(b)",
+                "    TaskExecutionIntegrationTest.graphListenerNotified = true", "}");
+        usingBuildFile(buildFile).withTasks("a").run().assertTasksExecuted(":b", ":a");
+
+        assertTrue(graphListenerNotified);
+    }
+
+    @Test
+    public void executesAllTasksInASingleBuildAndEachTaskAtMostOnce() {
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns(
+                "gradle.taskGraph.whenReady { assert !project.hasProperty('graphReady'); graphReady = true }",
+                "task a << { task -> project.executedA = task }",
+                "task b << { ",
+                "    assert a == project.executedA",
+                "    assert gradle.taskGraph.hasTask(':a')",
+                "}",
+                "task c(dependsOn: a)",
+                "task d(dependsOn: a)",
+                "task e(dependsOn: [a, d])");
+        usingBuildFile(buildFile).withTasks("a", "b").run().assertTasksExecuted(":a", ":b");
+        usingBuildFile(buildFile).withTasks("a", "a").run().assertTasksExecuted(":a");
+        usingBuildFile(buildFile).withTasks("c", "a").run().assertTasksExecuted(":a", ":c");
+        usingBuildFile(buildFile).withTasks("c", "e").run().assertTasksExecuted(":a", ":c", ":d", ":e");
+    }
+
+    @Test
+    public void executesMultiProjectsTasksInASingleBuildAndEachTaskAtMostOnce() {
+        testFile("settings.gradle").writelns("include 'child1', 'child2'");
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns(
+                "task a",
+                "allprojects {",
+                "    task b",
+                "    task c(dependsOn: ['b', ':a'])",
+                "}");
+        usingBuildFile(buildFile).withTasks("a", "c").run().assertTasksExecuted(":a", ":b", ":c", ":child1:b",
+                ":child1:c", ":child2:b", ":child2:c");
+        usingBuildFile(buildFile).withTasks("b", ":child2:c").run().assertTasksExecuted(":b", ":child1:b", ":child2:b",
+                ":a", ":child2:c");
+    }
+
+    @Test
+    public void executesMultiProjectDefaultTasksInASingleBuildAndEachTaskAtMostOnce() {
+        testFile("settings.gradle").writelns("include 'child1', 'child2'");
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns("defaultTasks 'a', 'b'", "task a", "subprojects {", "    task a(dependsOn: ':a')",
+                "    task b(dependsOn: ':a')", "}");
+        usingBuildFile(buildFile).run().assertTasksExecuted(":a", ":child1:a", ":child2:a", ":child1:b", ":child2:b");
+    }
+
+    @Test
+    public void executesProjectDefaultTasksWhenNoneSpecified() {
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns(
+                "task a",
+                "task b(dependsOn: a)",
+                "defaultTasks 'b'"
+        );
+        usingBuildFile(buildFile).withTasks().run().assertTasksExecuted(":a", ":b");
+    }
+    
+    @Test
+    public void doesNotExecuteTaskActionsWhenDryRunSpecified() {
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns(
+                "task a << { fail() }",
+                "task b(dependsOn: a) << { fail() }",
+                "defaultTasks 'b'"
+        );
+
+        // project defaults
+        usingBuildFile(buildFile).withArguments("-m").run().assertTasksExecuted(":a", ":b");
+        // named tasks
+        usingBuildFile(buildFile).withArguments("-m").withTasks("b").run().assertTasksExecuted(":a", ":b");
+    }
+
+    @Test
+    public void excludesTasksWhenExcludePatternSpecified() {
+        testFile("settings.gradle").write("include 'sub'");
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns(
+                "task a",
+                "task b(dependsOn: a)",
+                "task c(dependsOn: [a, b])",
+                "task d(dependsOn: c)",
+                "defaultTasks 'd'"
+        );
+        testFile("sub/build.gradle").writelns(
+                "task c",
+                "task d(dependsOn: c)"
+        );
+
+        // Exclude entire branch
+        usingBuildFile(buildFile).withTasks(":d").withArguments("-x", "c").run().assertTasksExecuted(":d");
+        // Exclude direct dependency
+        usingBuildFile(buildFile).withTasks(":d").withArguments("-x", "b").run().assertTasksExecuted(":a", ":c", ":d");
+        // Exclude using paths and multi-project
+        usingBuildFile(buildFile).withTasks("d").withArguments("-x", "c").run().assertTasksExecuted(":d", ":sub:d");
+        usingBuildFile(buildFile).withTasks("d").withArguments("-x", "sub:c").run().assertTasksExecuted(":a", ":b", ":c", ":d", ":sub:d");
+        usingBuildFile(buildFile).withTasks("d").withArguments("-x", ":sub:c").run().assertTasksExecuted(":a", ":b", ":c", ":d", ":sub:d");
+        usingBuildFile(buildFile).withTasks("d").withArguments("-x", "d").run().assertTasksExecuted();
+        // Project defaults
+        usingBuildFile(buildFile).withArguments("-x", "b").run().assertTasksExecuted(":a", ":c", ":d", ":sub:c", ":sub:d");
+        // Unknown task
+        usingBuildFile(buildFile).withTasks("d").withArguments("-x", "unknown").runWithFailure().assertThatDescription(startsWith("Task 'unknown' not found in root project"));
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/TestTask.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/TestTask.java
new file mode 100644
index 0000000..84b180f
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/TestTask.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+import org.gradle.api.DefaultTask;
+
+public class TestTask extends DefaultTask {
+    private String property;
+    
+    public String getProperty() {
+        return property;
+    }
+
+    public void setProperty(String property) {
+        this.property = property;
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/TransformerTask.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/TransformerTask.java
new file mode 100644
index 0000000..44a4028
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/TransformerTask.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.integtests;
+
+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;
+import org.gradle.util.TestFile;
+
+import java.io.File;
+
+public class TransformerTask extends DefaultTask {
+    private File inputFile;
+    private File outputFile;
+    private String format = "[%s]";
+
+    @InputFile
+    public File getInputFile() {
+        return inputFile;
+    }
+
+    public void setInputFile(File inputFile) {
+        this.inputFile = inputFile;
+    }
+
+    @OutputFile
+    public File getOutputFile() {
+        return outputFile;
+    }
+
+    public void setOutputFile(File outputFile) {
+        this.outputFile = outputFile;
+    }
+
+    @Input
+    public String getFormat() {
+        return format;
+    }
+
+    public void setFormat(String format) {
+        this.format = format;
+    }
+
+    @TaskAction
+    public void transform() {
+        TestFile inputFile = new TestFile(this.inputFile);
+        TestFile outputFile = new TestFile(this.outputFile);
+        outputFile.write(String.format(format, inputFile.getText()));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/UserGuideSamplesIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/UserGuideSamplesIntegrationTest.groovy
new file mode 100644
index 0000000..9b5b764
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/UserGuideSamplesIntegrationTest.groovy
@@ -0,0 +1,23 @@
+/*
+ * 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.junit.runner.RunWith
+
+ at RunWith(UserGuideSamplesRunner.class)
+class UserGuideSamplesIntegrationTest {
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/UserGuideSamplesRunner.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/UserGuideSamplesRunner.groovy
new file mode 100644
index 0000000..939ab69
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/UserGuideSamplesRunner.groovy
@@ -0,0 +1,260 @@
+/*
+ * 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 groovy.io.PlatformLineWriter
+import junit.framework.AssertionFailedError
+import org.apache.tools.ant.taskdefs.Delete
+import org.gradle.StartParameter
+import org.gradle.integtests.fixtures.ExecutionResult
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.GradleDistributionExecuter.StartParameterModifier
+import org.gradle.util.AntUtil
+import org.junit.Assert
+import org.junit.runner.Description
+import org.junit.runner.Runner
+import org.junit.runner.notification.Failure
+import org.junit.runner.notification.RunNotifier
+
+class UserGuideSamplesRunner extends Runner {
+    static final String NL = System.properties['line.separator']
+    Class<?> testClass
+    Description description
+    Map<Description, SampleRun> samples;
+    GradleDistribution dist = new GradleDistribution()
+    GradleDistributionExecuter executer = new GradleDistributionExecuter(dist)
+
+    def UserGuideSamplesRunner(Class<?> testClass) {
+        this.testClass = testClass
+        this.description = Description.createSuiteDescription(testClass)
+        samples = new LinkedHashMap()
+        for (sample in getScriptsForSamples(dist.userGuideInfoDir)) {
+            Description childDescription = Description.createTestDescription(testClass, sample.id)
+            description.addChild(childDescription)
+            samples.put(childDescription, sample)
+
+            println "Sample $sample.id dir: $sample.subDir"
+            sample.runs.each { println "    args: $it.args expect: $it.outputFile" }
+        }
+    }
+
+    Description getDescription() {
+        return description
+    }
+
+    void run(RunNotifier notifier) {
+        for (childDescription in description.children) {
+            notifier.fireTestStarted(childDescription)
+            SampleRun sampleRun = samples.get(childDescription)
+            try {
+                cleanup(sampleRun)
+                for (run in sampleRun.runs) {
+                    runSample(run)
+                }
+            } catch (Throwable t) {
+                notifier.fireTestFailure(new Failure(childDescription, t))
+            }
+            notifier.fireTestFinished(childDescription)
+        }
+    }
+
+    private def cleanup(SampleRun run) {
+        // Clean up previous runs
+        File rootProjectDir = dist.samplesDir.file(run.subDir)
+        if (rootProjectDir.exists()) {
+            def delete = new Delete()
+            delete.dir = rootProjectDir
+            delete.includes = "**/.gradle/** **/build/**"
+            AntUtil.execute(delete)
+        }
+    }
+
+    private def runSample(GradleRun run) {
+        try {
+            println("Test Id: $run.id, dir: $run.subDir, args: $run.args")
+            File rootProjectDir = dist.samplesDir.file(run.subDir)
+            executer.inDirectory(rootProjectDir).withArguments(run.args as String[]).withEnvironmentVars(run.envs)
+            if (executer instanceof GradleDistributionExecuter) {
+                ((GradleDistributionExecuter) executer).setInProcessStartParameterModifier(createModifier(rootProjectDir))
+            }
+            
+            ExecutionResult result = run.expectFailure ? executer.runWithFailure() : executer.run()
+            if (run.outputFile) {
+                String expectedResult = replaceWithPlatformNewLines(dist.userGuideOutputDir.file(run.outputFile).text)
+                try {
+                    compareStrings(expectedResult, result.output, run.ignoreExtraLines)
+                } catch (AssertionFailedError e) {
+                    println 'Expected Result:'
+                    println expectedResult
+                    println 'Actual Result:'
+                    println result.output
+                    println '---'
+                    throw e
+                }
+            }
+        } catch (Throwable e) {
+            throw new AssertionError("Integration test for sample '$run.id' in dir '$run.subDir' with args $run.args failed:${NL}$e.message").initCause(e)
+        }
+    }
+
+    private def compareStrings(String expected, String actual, boolean ignoreExtraLines) {
+        List actualLines = normaliseOutput(actual.readLines())
+        List expectedLines = expected.readLines()
+        int pos = 0
+        for (; pos < actualLines.size() && pos < expectedLines.size(); pos++) {
+            String expectedLine = expectedLines[pos]
+            String actualLine = actualLines[pos]
+            boolean matches = actualLine == expectedLine
+            if (!matches) {
+                if (expectedLine.contains(actualLine)) {
+                    Assert.fail("Missing text at line ${pos + 1}.${NL}Expected: ${expectedLine}${NL}Actual: ${actualLine}${NL}---${NL}Actual output:${NL}$actual${NL}---")
+                }
+                if (actualLine.contains(expectedLine)) {
+                    Assert.fail("Extra text at line ${pos + 1}.${NL}Expected: ${expectedLine}${NL}Actual: ${actualLine}${NL}---${NL}Actual output:${NL}$actual${NL}---")
+                }
+                Assert.fail("Unexpected value at line ${pos + 1}.${NL}Expected: ${expectedLine}${NL}Actual: ${actualLine}${NL}---${NL}Actual output:${NL}$actual${NL}---")
+            }
+        }
+        if (pos == actualLines.size() && pos < expectedLines.size()) {
+            Assert.fail("Lines missing from actual result, starting at line ${pos + 1}.${NL}Expected: ${expectedLines[pos]}${NL}Actual output:${NL}$actual${NL}---")
+        }
+        if (!ignoreExtraLines && pos < actualLines.size() && pos == expectedLines.size()) {
+            Assert.fail("Extra lines in actual result, starting at line ${pos + 1}.${NL}Actual: ${actualLines[pos]}${NL}Actual output:${NL}$actual${NL}---")
+        }
+    }
+
+    static String replaceWithPlatformNewLines(String text) {
+        StringWriter stringWriter = new StringWriter()
+        new PlatformLineWriter(stringWriter).withWriter { it.write(text) }
+        stringWriter.toString()
+    }
+
+    List<String> normaliseOutput(List<String> lines) {
+        lines.inject(new ArrayList<String>()) { List values, String line ->
+            if (line.matches('Total time: .+ secs')) {
+                values << 'Total time: 1 secs'
+            } else if (line.matches('Download .+')) {
+                // ignore
+            } else {
+                // Normalise default object toString() values
+                line = line.replaceAll('(\\w+(\\.\\w+)*)@\\p{XDigit}+', '$1 at 12345')
+                // Normalise $samplesDir
+                line = line.replaceAll(java.util.regex.Pattern.quote(dist.samplesDir.absolutePath), '/home/user/gradle/samples')
+                // Normalise file separators
+                line = line.replaceAll(java.util.regex.Pattern.quote(File.separator), '/')
+                values << line
+            }
+            values
+        }
+    }
+
+    static GradleDistributionExecuter.StartParameterModifier createModifier(File rootProjectDir) {
+        {StartParameter parameter ->
+            if (parameter.getCurrentDir() != null) {
+                parameter.setCurrentDir(normalizedPath(parameter.getCurrentDir(), rootProjectDir));
+            }
+            if (parameter.getBuildFile() != null) {
+                parameter.setBuildFile(normalizedPath(parameter.getBuildFile(), rootProjectDir));
+            }
+            List<File> initScripts = new ArrayList<File>();
+            for (File initScript: parameter.getInitScripts()) {
+                initScripts.add(normalizedPath(initScript, rootProjectDir));
+            }
+            parameter.setInitScripts(initScripts);
+        } as StartParameterModifier
+    }
+
+    static File normalizedPath(File path, File rootProjectDir) {
+        String pathName = path.getAbsolutePath();
+        if (!pathName.startsWith(rootProjectDir.getAbsolutePath())) {
+            String currentDirName = new File("").getAbsolutePath();
+            if (!pathName.startsWith(currentDirName)) {
+                throw new RuntimeException("Path " + path + " is neither subdir of Gradle home nor of the root project!.")
+            }
+            pathName = new File(rootProjectDir, pathName.substring(currentDirName.length() + 1)).getAbsolutePath();
+        }
+        return new File(pathName);
+    }
+
+    static Collection<SampleRun> getScriptsForSamples(File userguideInfoDir) {
+        Node samples = new XmlParser().parse(new File(userguideInfoDir, 'samples.xml'))
+        Map<String, List<GradleRun>> samplesByDir = new LinkedHashMap<String, List<GradleRun>>()
+
+        samples.children().each {Node sample ->
+            String id = sample.'@id'
+            String dir = sample.'@dir'
+            String args = sample.'@args'
+            String outputFile = sample.'@outputFile'
+            boolean ignoreExtraLines = Boolean.valueOf(sample.'@ignoreExtraLines')
+            if (!samplesByDir[dir]) {
+                samplesByDir[dir] = []
+            }
+            GradleRun run = new GradleRun(id: id, subDir: dir, args: args ? args.split('\\s+') as List : null, envs: [:], expectFailure: false, outputFile: outputFile)
+            run.ignoreExtraLines = ignoreExtraLines as boolean
+            samplesByDir[dir] << run
+        }
+
+        // Some custom values
+        samplesByDir['userguide/tutorial/properties'].each { it.envs['ORG_GRADLE_PROJECT_envProjectProp'] = 'envPropertyValue' }
+        samplesByDir['userguide/buildlifecycle/taskExecutionEvents']*.expectFailure = true
+        samplesByDir['userguide/buildlifecycle/buildProjectEvaluateEvents']*.expectFailure = true
+
+        Map<String, SampleRun> samplesById = new TreeMap<String, SampleRun>()
+
+        // Remove duplicates for a given directory.
+        samplesByDir.values().collect {List<GradleRun> dirSamples ->
+            Collection<GradleRun> runs = dirSamples.findAll {it.args}
+            if (!runs) {
+                // No samples in this dir have any args, so just run gradle -t in the dir
+                def sample = dirSamples[0]
+                sample.args = ['-t']
+                sample
+            } else {
+                return runs
+            }
+        }.flatten().each { GradleRun run ->
+            // Collect up into sample runs
+            SampleRun sampleRun = samplesById[run.id]
+            if (!sampleRun) {
+                sampleRun = new SampleRun(id: run.id, subDir: run.subDir)
+                samplesById[run.id] = sampleRun
+            }
+            sampleRun.runs << run
+        }
+
+        return samplesById.values()
+    }
+}
+
+class SampleRun {
+    String id
+    String subDir
+    List<GradleRun> runs = []
+}
+
+class GradleRun {
+    String id
+    List args = []
+    String subDir
+    Map envs = [:]
+    String outputFile
+    boolean expectFailure
+    boolean ignoreExtraLines
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/UserguideIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/UserguideIntegrationTest.groovy
new file mode 100644
index 0000000..70c23a1
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/UserguideIntegrationTest.groovy
@@ -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.integtests
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.util.TestFile
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith (DistributionIntegrationTestRunner.class)
+class UserguideIntegrationTest {
+
+    private static Logger logger = LoggerFactory.getLogger(UserguideIntegrationTest)
+
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+
+    @Test
+    public void apiLinks() {
+        TestFile gradleHome = dist.gradleHomeDir         
+        TestFile userguideInfoDir = dist.userGuideInfoDir
+        Node links = new XmlParser().parse(new File(userguideInfoDir, 'links.xml'))
+        links.children().each {Node link ->
+            String classname = link.'@className'
+            String lang = link.'@lang'
+            File classDocFile = new File(gradleHome, "docs/${lang}doc/${classname.replace('.', '/')}.html")
+            Assert.assertTrue("Could not find javadoc for class '$classname' referenced in userguide: $classDocFile does not exist.", classDocFile.isFile())
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/WaterProjectIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/WaterProjectIntegrationTest.groovy
new file mode 100644
index 0000000..d0008b9
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/WaterProjectIntegrationTest.groovy
@@ -0,0 +1,79 @@
+/*
+ * 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.integtests
+
+import org.gradle.integtests.fixtures.ExecutionResult
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.junit.Assert.*
+import org.gradle.integtests.fixtures.Sample
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(DistributionIntegrationTestRunner.class)
+class WaterProjectIntegrationTest {
+    final static String NL = System.properties['line.separator']
+
+    final static String HELLO_CLAUSE = "Hello, I'm "
+    final static String CHILDREN_TEXT = 'I love water.'
+    final static String WATER_INFO = 'As you all know, I cover three quarters of this planet!'
+    final static String BLUE_WHALE_INFO = "I'm the largets animal which has ever lived on this planet!"
+    final static String KRILL_INFO = "The weight of my species in summer is twice as heavy as all human beings!"
+    final static String PHYTOPLANKTON_INFO = "I produce as much oxygen as all the other plants on earth together!"
+
+    final static String WATER_NAME = 'water'
+    final static String BLUE_WHALE_NAME = 'bluewhale'
+    final static String KRILL_NAME = 'krill'
+    final static String PHYTOPLANKTON_NAME = 'phytoplankton'
+
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample(WATER_NAME)
+
+    @Test
+    public void waterProject() {
+        File waterDir = sample.dir
+        ExecutionResult result = executer.inDirectory(waterDir).withTasks('hello').withQuietLogging().run()
+        assertEquals(result.output, list2text([intro(WATER_NAME), WATER_INFO,
+                intro(PHYTOPLANKTON_NAME), CHILDREN_TEXT, PHYTOPLANKTON_INFO,
+                intro(KRILL_NAME), CHILDREN_TEXT, KRILL_INFO,
+                intro(BLUE_WHALE_NAME), CHILDREN_TEXT, BLUE_WHALE_INFO]))
+
+        result = executer.inDirectory(new File(waterDir, BLUE_WHALE_NAME)).withTasks('hello').withQuietLogging().run()
+        assertEquals(result.output, list2text([intro(WATER_NAME), WATER_INFO,
+                intro(PHYTOPLANKTON_NAME), CHILDREN_TEXT, PHYTOPLANKTON_INFO,
+                intro(KRILL_NAME), CHILDREN_TEXT, KRILL_INFO,
+                intro(BLUE_WHALE_NAME), CHILDREN_TEXT, BLUE_WHALE_INFO]))
+
+        result = executer.inDirectory(new File(waterDir, PHYTOPLANKTON_NAME)).withTasks('hello').withQuietLogging().run()
+        assertEquals(result.output, list2text([intro(WATER_NAME), WATER_INFO,
+                intro(PHYTOPLANKTON_NAME), CHILDREN_TEXT, PHYTOPLANKTON_INFO]))
+    }
+
+    static String intro(String projectName) {
+        HELLO_CLAUSE + projectName
+    }
+
+    static String list2text(List list) {
+        list.join(NL) + NL
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/WebProjectIntegrationTest.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/WebProjectIntegrationTest.java
new file mode 100644
index 0000000..ede24e1
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/WebProjectIntegrationTest.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.integtests;
+
+import org.gradle.util.TestFile;
+import org.junit.Test;
+
+public class WebProjectIntegrationTest extends AbstractIntegrationTest {
+    @Test
+    public void handlesEmptyProject() {
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns(
+                "apply plugin: 'war'"
+        );
+
+        usingBuildFile(buildFile).withTasks("build").run();
+    }
+
+    @Test
+    public void createsAWar() {
+        testFile("settings.gradle").writelns("rootProject.name = 'test'");
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns(
+                "apply plugin: 'war'"
+        );
+        testFile("src/main/webapp/index.jsp").write("<p>hi</p>");
+
+        usingBuildFile(buildFile).withTasks("assemble").run();
+        testFile("build/libs/test.war").assertIsFile();
+    }
+
+    @Test
+    public void canCustomiseArchiveNamesUsingConventionProperties() {
+        testFile("settings.gradle").writelns("rootProject.name = 'test'");
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns(
+                "apply plugin: 'war'",
+                "jar.enabled = true",
+                "buildDirName = 'output'",
+                "libsDirName = 'archives'",
+                "archivesBaseName = 'test'",
+                "version = '0.5-RC2'"
+        );
+        testFile("src/main/resources/org/gradle/resource.file").write("some resource");
+
+        usingBuildFile(buildFile).withTasks("assemble").run();
+        testFile("output/archives/test-0.5-RC2.jar").assertIsFile();
+        testFile("output/archives/test-0.5-RC2.war").assertIsFile();
+    }
+
+    @Test
+    public void generatesArtifactsWhenVersionIsEmpty() {
+        testFile("settings.gradle").write("rootProject.name = 'empty'");
+        TestFile buildFile = testFile("build.gradle");
+        buildFile.writelns(
+                "apply plugin: 'war'",
+                "jar.enabled = true",
+                "version = ''"
+        );
+        testFile("src/main/resources/org/gradle/resource.file").write("some resource");
+
+        usingBuildFile(buildFile).withTasks("assemble").run();
+        testFile("build/libs/empty.jar").assertIsFile();
+        testFile("build/libs/empty.war").assertIsFile();
+    }
+
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/WorkerProcessIntegrationTest.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/WorkerProcessIntegrationTest.java
new file mode 100644
index 0000000..59a7629
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/WorkerProcessIntegrationTest.java
@@ -0,0 +1,372 @@
+/*
+ * 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.apache.tools.ant.Project;
+import org.gradle.CacheUsage;
+import org.gradle.api.Action;
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.DefaultClassPathRegistry;
+import org.gradle.api.internal.file.IdentityFileResolver;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.cache.DefaultCacheFactory;
+import org.gradle.cache.DefaultCacheRepository;
+import org.gradle.listener.ListenerBroadcast;
+import org.gradle.messaging.remote.ObjectConnection;
+import org.gradle.messaging.remote.internal.TcpMessagingServer;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+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;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class WorkerProcessIntegrationTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final TestListenerInterface listenerMock = context.mock(TestListenerInterface.class);
+    private final TcpMessagingServer server = new TcpMessagingServer(getClass().getClassLoader());
+    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder();
+    private final ClassPathRegistry classPathRegistry = new DefaultClassPathRegistry(new WorkerProcessClassPathProvider(new DefaultCacheRepository(tmpDir.getDir(), CacheUsage.ON, new DefaultCacheFactory())));
+    private final DefaultWorkerProcessFactory workerFactory = new DefaultWorkerProcessFactory(LogLevel.INFO, server, classPathRegistry, new IdentityFileResolver(), new LongIdGenerator());
+    private final ListenerBroadcast<TestListenerInterface> broadcast = new ListenerBroadcast<TestListenerInterface>(
+            TestListenerInterface.class);
+    private final RemoteExceptionListener exceptionListener = new RemoteExceptionListener(broadcast);
+
+    @Before
+    public void setUp() {
+        broadcast.add(listenerMock);
+    }
+
+    @Test
+    public void workerProcessCanSendMessagesToThisProcess() throws Throwable {
+        context.checking(new Expectations() {{
+            Sequence sequence = context.sequence("sequence");
+            one(listenerMock).send("message 1", 1);
+            inSequence(sequence);
+            one(listenerMock).send("message 2", 2);
+            inSequence(sequence);
+        }});
+
+        execute(worker(new RemoteProcess()));
+    }
+
+    @Test
+    public void thisProcessCanSendEventsToWorkerProcess() throws Throwable {
+        execute(worker(new PingRemoteProcess()).onServer(new Action<ObjectConnection>() {
+            public void execute(ObjectConnection objectConnection) {
+                TestListenerInterface listener = objectConnection.addOutgoing(TestListenerInterface.class);
+                listener.send("1", 0);
+                listener.send("1", 1);
+                listener.send("1", 2);
+                listener.send("stop", 3);
+            }
+        }));
+    }
+
+    @Test
+    public void multipleWorkerProcessesCanSendMessagesToThisProcess() throws Throwable {
+        context.checking(new Expectations() {{
+            Sequence process1 = context.sequence("sequence1");
+            one(listenerMock).send("message 1", 1);
+            inSequence(process1);
+            one(listenerMock).send("message 2", 2);
+            inSequence(process1);
+            Sequence process2 = context.sequence("sequence2");
+            one(listenerMock).send("other 1", 1);
+            inSequence(process2);
+            one(listenerMock).send("other 2", 2);
+            inSequence(process2);
+        }});
+
+        execute(worker(new RemoteProcess()), worker(new OtherRemoteProcess()));
+    }
+
+    @Test
+    public void handlesWorkerProcessWhichCrashes() throws Throwable {
+        context.checking(new Expectations() {{
+            atMost(1).of(listenerMock).send("message 1", 1);
+            atMost(1).of(listenerMock).send("message 2", 2);
+        }});
+
+        execute(worker(new CrashingRemoteProcess()).expectStopFailure());
+    }
+
+    @Test
+    public void handlesWorkerActionWhichThrowsException() throws Throwable {
+        execute(worker(new BrokenRemoteProcess()).expectStopFailure());
+    }
+
+    @Test
+    public void handlesWorkerActionThatLeavesThreadsRunning() throws Throwable {
+        context.checking(new Expectations() {{
+            one(listenerMock).send("message 1", 1);
+            one(listenerMock).send("message 2", 2);
+        }});
+
+        execute(worker(new NoCleanUpRemoteProcess()));
+    }
+
+    @Test
+    public void handlesWorkerProcessWhichNeverConnects() throws Throwable {
+        execute(worker(new NoConnectRemoteProcess()).expectStartFailure());
+    }
+
+    @Test
+    public void handlesWorkerProcessWhenJvmFailsToStart() throws Throwable {
+        execute(mainClass("no-such-class").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();
+        }
+        for (ChildProcess process : processes) {
+            process.waitForStop();
+        }
+        server.stop();
+        exceptionListener.rethrow();
+    }
+
+    private class ChildProcess {
+        private boolean stopFails;
+        private boolean startFails;
+        private WorkerProcess proc;
+        private Action<WorkerProcessContext> action;
+        private String mainClass;
+        private Action<ObjectConnection> serverAction;
+
+        public ChildProcess(Action<WorkerProcessContext> action) {
+            this.action = action;
+        }
+
+        ChildProcess expectStopFailure() {
+            stopFails = true;
+            return this;
+        }
+
+        ChildProcess expectStartFailure() {
+            startFails = true;
+            return this;
+        }
+
+        public void start() {
+            WorkerProcessBuilder builder = workerFactory.newProcess();
+            builder.applicationClasspath(classPathRegistry.getClassPathFiles("ANT"));
+            builder.sharedPackages("org.apache.tools.ant");
+            builder.getJavaCommand().systemProperty("test.system.property", "value");
+            builder.getJavaCommand().environment("TEST_ENV_VAR", "value");
+            builder.worker(action);
+
+            if (mainClass != null) {
+                builder.getJavaCommand().setMain(mainClass);
+            }
+
+            proc = builder.build();
+            try {
+                proc.start();
+                assertFalse(startFails);
+            } catch (ExecException e) {
+                assertTrue(startFails);
+                return;
+            }
+            proc.getConnection().addIncoming(TestListenerInterface.class, exceptionListener);
+            if (serverAction != null) {
+                serverAction.execute(proc.getConnection());
+            }
+        }
+
+        public void waitForStop() {
+            if (startFails) {
+                return;
+            }
+            try {
+                proc.waitForStop();
+                assertFalse("Expected process to fail", stopFails);
+            } catch (ExecException e) {
+                assertTrue("Unexpected failure in worker process", stopFails);
+            }
+        }
+
+        public ChildProcess mainClass(String mainClass) {
+            this.mainClass = mainClass;
+            return this;
+        }
+
+        public ChildProcess onServer(Action<ObjectConnection> action) {
+            this.serverAction = action;
+            return this;
+        }
+    }
+
+    public static class RemoteExceptionListener implements Dispatch<MethodInvocation> {
+        Throwable ex;
+        final Dispatch<MethodInvocation> dispatch;
+
+        public RemoteExceptionListener(Dispatch<MethodInvocation> dispatch) {
+            this.dispatch = dispatch;
+        }
+
+        public void dispatch(MethodInvocation message) {
+            try {
+                dispatch.dispatch(message);
+            } catch (Throwable e) {
+                ex = e;
+            }
+        }
+
+        public void rethrow() throws Throwable {
+            if (ex != null) {
+                throw ex;
+            }
+        }
+    }
+
+    public static class RemoteProcess implements Action<WorkerProcessContext>, Serializable {
+        public void execute(WorkerProcessContext workerProcessContext) {
+            // Check environment
+            assertThat(System.getProperty("test.system.property"), equalTo("value"));
+            assertThat(System.getenv().get("TEST_ENV_VAR"), equalTo("value"));
+
+            // Check ClassLoaders
+            ClassLoader antClassLoader = Project.class.getClassLoader();
+            ClassLoader thisClassLoader = getClass().getClassLoader();
+            ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
+
+            assertThat(antClassLoader, not(sameInstance(systemClassLoader)));
+            assertThat(thisClassLoader, not(sameInstance(systemClassLoader)));
+            assertThat(antClassLoader.getParent(), equalTo(systemClassLoader.getParent()));
+            assertThat(thisClassLoader.getParent().getParent().getParent(), sameInstance(antClassLoader));
+
+            // Send some messages
+            TestListenerInterface sender = workerProcessContext.getServerConnection().addOutgoing(
+                    TestListenerInterface.class);
+            sender.send("message 1", 1);
+            sender.send("message 2", 2);
+        }
+    }
+
+    public static class OtherRemoteProcess implements Action<WorkerProcessContext>, Serializable {
+        public void execute(WorkerProcessContext workerProcessContext) {
+            TestListenerInterface sender = workerProcessContext.getServerConnection().addOutgoing(TestListenerInterface.class);
+            sender.send("other 1", 1);
+            sender.send("other 2", 2);
+        }
+    }
+
+    public static class NoCleanUpRemoteProcess implements Action<WorkerProcessContext>, Serializable {
+        public void execute(WorkerProcessContext workerProcessContext) {
+            final Lock lock = new ReentrantLock();
+            lock.lock();
+            new Thread(new Runnable() {
+                public void run() {
+                    lock.lock();
+                }
+            }).start();
+
+            TestListenerInterface sender = workerProcessContext.getServerConnection().addOutgoing(
+                    TestListenerInterface.class);
+            sender.send("message 1", 1);
+            sender.send("message 2", 2);
+        }
+    }
+
+    public static class PingRemoteProcess implements Action<WorkerProcessContext>, Serializable, TestListenerInterface {
+        CountDownLatch stopReceived;
+        int count;
+
+        public void send(String message, int count) {
+            assertEquals(this.count, count);
+            this.count++;
+            if (message.equals("stop")) {
+                assertEquals(4, this.count);
+                stopReceived.countDown();
+            }
+        }
+
+        public void execute(WorkerProcessContext workerProcessContext) {
+            stopReceived = new CountDownLatch(1);
+            workerProcessContext.getServerConnection().addIncoming(TestListenerInterface.class, this);
+            try {
+                stopReceived.await();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    public static class CrashingRemoteProcess implements Action<WorkerProcessContext>, Serializable {
+        public void execute(WorkerProcessContext workerProcessContext) {
+            TestListenerInterface sender = workerProcessContext.getServerConnection().addOutgoing(TestListenerInterface.class);
+            sender.send("message 1", 1);
+            sender.send("message 2", 2);
+            // crash
+            Runtime.getRuntime().halt(1);
+        }
+    }
+
+    public static class BrokenRemoteProcess implements Action<WorkerProcessContext>, Serializable {
+        public void execute(WorkerProcessContext workerProcessContext) {
+            throw new RuntimeException("broken");
+        }
+    }
+
+    public static class NoOpAction implements Action<WorkerProcessContext>, Serializable {
+        public void execute(WorkerProcessContext workerProcessContext) {
+        }
+    }
+
+    public static class NoConnectRemoteProcess implements Action<WorkerProcessContext>, Serializable {
+        private void readObject(ObjectInputStream instr) {
+            System.exit(0);
+        }
+        
+        public void execute(WorkerProcessContext workerProcessContext) {
+            throw new UnsupportedOperationException();
+        }
+    }
+    
+    public interface TestListenerInterface {
+        public void send(String message, int count);
+    }
+}
+
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/WrapperProjectIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/WrapperProjectIntegrationTest.groovy
new file mode 100644
index 0000000..4ba13af
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/WrapperProjectIntegrationTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * 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.integtests
+
+import org.gradle.integtests.fixtures.ExecutionResult
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.gradle.integtests.fixtures.Sample
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(DistributionIntegrationTestRunner.class)
+class WrapperProjectIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('wrapper-project')
+
+    @Test
+    public void wrapperSample() {
+        File wrapperSampleDir = sample.dir
+
+        executer.inDirectory(wrapperSampleDir).withTasks('wrapper').run()
+
+        ExecutionResult result = executer.usingExecutable('gradlew').inDirectory(wrapperSampleDir).withTasks('hello').run()
+        assertThat(result.output, containsString('hello'))
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/AbstractExecutionResult.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/AbstractExecutionResult.java
new file mode 100644
index 0000000..fef20d5
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/AbstractExecutionResult.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.integtests.fixtures;
+
+import org.gradle.util.Matchers;
+
+public abstract class AbstractExecutionResult implements ExecutionResult {
+    public void assertOutputHasNoStackTraces() {
+        assertNoStackTraces(getOutput(), "Standard output");
+    }
+
+    public void assertErrorHasNoStackTraces() {
+        assertNoStackTraces(getError(), "Standard error");
+    }
+
+    private void assertNoStackTraces(String output, String displayName) {
+        if (Matchers.containsLine(Matchers.matchesRegexp("\\s+at [\\w.$_]+\\([\\w._]+:\\d+\\)")).matches(output)) {
+            throw new RuntimeException(String.format("%s contains an unexpected stack trace:%n=====%n%s%n=====%n", displayName, output));
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/AbstractGradleExecuter.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/AbstractGradleExecuter.java
new file mode 100644
index 0000000..811ed62
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/AbstractGradleExecuter.java
@@ -0,0 +1,191 @@
+/*
+ * 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.fixtures;
+
+import java.io.File;
+import java.util.*;
+
+public abstract class AbstractGradleExecuter implements GradleExecuter {
+    private final List<String> args = new ArrayList<String>();
+    private final List<String> tasks = new ArrayList<String>();
+    private File workingDir;
+    private boolean quiet;
+    private boolean taskList;
+    private boolean searchUpwards;
+    private Map<String, String> environmentVars = new HashMap<String, String>();
+    private String executable;
+    private File userHomeDir;
+
+    public GradleExecuter reset() {
+        args.clear();
+        tasks.clear();
+        workingDir = null;
+        quiet = false;
+        taskList = false;
+        searchUpwards = false;
+        executable = null;
+        userHomeDir = null;
+        environmentVars.clear();
+        return this;
+    }
+
+    public GradleExecuter inDirectory(File directory) {
+        workingDir = directory;
+        return this;
+    }
+
+    public File getWorkingDir() {
+        return workingDir;
+    }
+
+    protected void copyTo(GradleExecuter executer) {
+        if (workingDir != null) {
+            executer.inDirectory(workingDir);
+        }
+        executer.withTasks(tasks);
+        executer.withArguments(args);
+        executer.withEnvironmentVars(environmentVars);
+        executer.usingExecutable(executable);
+        if (quiet) {
+            executer.withQuietLogging();
+        }
+        if (taskList) {
+            executer.withTaskList();
+        }
+        executer.withUserHomeDir(userHomeDir);
+    }
+
+    public GradleExecuter usingBuildScript(String script) {
+        throw new UnsupportedOperationException();
+    }
+
+    public GradleExecuter usingSettingsFile(File settingsFile) {
+        throw new UnsupportedOperationException();
+    }
+
+    public GradleExecuter usingInitScript(File initScript) {
+        throw new UnsupportedOperationException();
+    }
+
+    public GradleExecuter withUserHomeDir(File userHomeDir) {
+        this.userHomeDir = userHomeDir;
+        return this;
+    }
+
+    public GradleExecuter usingExecutable(String script) {
+        this.executable = script;
+        return this;
+    }
+
+    public String getExecutable() {
+        return executable;
+    }
+
+    public GradleExecuter withSearchUpwards() {
+        searchUpwards = true;
+        return this;
+    }
+
+    public boolean isQuiet() {
+        return quiet;
+    }
+
+    public GradleExecuter withQuietLogging() {
+        quiet = true;
+        return this;
+    }
+
+    public GradleExecuter withTaskList() {
+        taskList = true;
+        return this;
+    }
+
+    public GradleExecuter withDependencyList() {
+        throw new UnsupportedOperationException();
+    }
+
+    public GradleExecuter withArguments(String... args) {
+        return withArguments(Arrays.asList(args));
+    }
+
+    public GradleExecuter withArguments(List<String> args) {
+        this.args.clear();
+        this.args.addAll(args);
+        return this;
+    }
+
+    public GradleExecuter withEnvironmentVars(Map<String, ?> environment) {
+        environmentVars.clear();
+        for (Map.Entry<String, ?> entry : environment.entrySet()) {
+            environmentVars.put(entry.getKey(), entry.getValue().toString());
+        }
+        return this;
+    }
+
+    public Map<String, String> getEnvironmentVars() {
+        return environmentVars;
+    }
+
+    public GradleExecuter withTasks(String... names) {
+        return withTasks(Arrays.asList(names));
+    }
+
+    public GradleExecuter withTasks(List<String> names) {
+        tasks.clear();
+        tasks.addAll(names);
+        return this;
+    }
+
+    protected List<String> getAllArgs() {
+        List<String> allArgs = new ArrayList<String>();
+        if (quiet) {
+            allArgs.add("--quiet");
+        }
+        if (taskList) {
+            allArgs.add("--tasks");
+        }
+        if (!searchUpwards) {
+            allArgs.add("--no-search-upward");
+        }
+        if (userHomeDir != null) {
+            args.add("--gradle-user-home");
+            args.add(userHomeDir.getAbsolutePath());
+        }
+        allArgs.addAll(args);
+        allArgs.addAll(tasks);
+        return allArgs;
+    }
+
+    public final ExecutionResult run() {
+        try {
+            return doRun();
+        } finally {
+            reset();
+        }
+    }
+
+    public final ExecutionFailure runWithFailure() {
+        try {
+            return doRunWithFailure();
+        } finally {
+            reset();
+        }
+    }
+
+    protected abstract ExecutionResult doRun();
+
+    protected abstract ExecutionFailure doRunWithFailure();
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/ArtifactBuilder.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/ArtifactBuilder.java
new file mode 100644
index 0000000..0c44b87
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/ArtifactBuilder.java
@@ -0,0 +1,28 @@
+/*
+ * 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.fixtures;
+
+import org.gradle.util.TestFile;
+
+import java.io.File;
+
+public interface ArtifactBuilder {
+    TestFile sourceFile(String path);
+
+    TestFile resourceFile(String path);
+
+    void buildJar(File jarFile);
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/ExecutionFailure.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/ExecutionFailure.java
new file mode 100644
index 0000000..9ed774a
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/ExecutionFailure.java
@@ -0,0 +1,32 @@
+/*
+ * 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.fixtures;
+
+import org.hamcrest.Matcher;
+
+public interface ExecutionFailure extends ExecutionResult {
+    void assertHasLineNumber(int lineNumber);
+
+    void assertHasFileName(String filename);
+
+    void assertHasCause(String description);
+
+    void assertThatCause(Matcher<String> matcher);
+
+    void assertHasDescription(String context);
+
+    void assertThatDescription(Matcher<String> matcher);
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/ExecutionResult.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/ExecutionResult.java
new file mode 100644
index 0000000..219dd38
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/ExecutionResult.java
@@ -0,0 +1,41 @@
+/*
+ * 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.fixtures;
+
+public interface ExecutionResult {
+    String getOutput();
+
+    String getError();
+
+    void assertOutputHasNoStackTraces();
+
+    void assertErrorHasNoStackTraces();
+
+    /**
+     * Asserts that exactly the given set of tasks have been executed
+     */
+    ExecutionResult assertTasksExecuted(String... taskPaths);
+
+    /**
+     * Asserts that exactly the given set of tasks have been skipped
+     */
+    ExecutionResult assertTasksSkipped(String... taskPaths);
+
+    /**
+     * Asserts that exactly the given set of tasks have not been skipped 
+     */
+    ExecutionResult assertTasksNotSkipped(String... taskPaths);
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/ForkingGradleExecuter.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/ForkingGradleExecuter.java
new file mode 100644
index 0000000..d586400
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/ForkingGradleExecuter.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.integtests.fixtures;
+
+import org.gradle.process.internal.ExecHandle;
+import org.gradle.process.internal.ExecHandleBuilder;
+import org.gradle.util.GUtil;
+import org.gradle.util.OperatingSystem;
+import org.gradle.util.TestFile;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import static org.gradle.util.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+public class ForkingGradleExecuter extends AbstractGradleExecuter {
+    private static final Logger LOG = LoggerFactory.getLogger(ForkingGradleExecuter.class);
+    private final TestFile gradleHomeDir;
+
+    public ForkingGradleExecuter(TestFile gradleHomeDir) {
+        this.gradleHomeDir = gradleHomeDir;
+    }
+
+    @Override
+    protected ExecutionResult doRun() {
+        Map result = doRun(false);
+        return new ForkedExecutionResult(result);
+    }
+
+    @Override
+    protected ExecutionFailure doRunWithFailure() {
+        Map result = doRun(true);
+        return new ForkedExecutionFailure(result);
+    }
+
+    private Map doRun(boolean expectFailure) {
+        gradleHomeDir.assertIsDir();
+
+        CommandBuilder commandBuilder = OperatingSystem.current().isWindows() ? new WindowsCommandBuilder()
+                : new UnixCommandBuilder();
+
+        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+        ByteArrayOutputStream errStream = new ByteArrayOutputStream();
+
+        ExecHandleBuilder builder = new ExecHandleBuilder() {
+            @Override
+            public File getWorkingDir() {
+                // Override this, so that the working directory is not canonicalised. Some int tests require that
+                // the working directory is not canonicalised
+                return ForkingGradleExecuter.this.getWorkingDir();
+            }
+        };
+        builder.setStandardOutput(outStream);
+        builder.setErrorOutput(errStream);
+        builder.environment("GRADLE_HOME", "");
+        builder.environment("JAVA_HOME", System.getProperty("java.home"));
+        builder.environment("GRADLE_OPTS", "-ea");
+        builder.environment(getEnvironmentVars());
+        builder.workingDir(getWorkingDir());
+
+        commandBuilder.build(builder);
+
+        builder.args(getAllArgs());
+
+        LOG.info(String.format("Execute in %s with: %s %s", builder.getWorkingDir(), builder.getExecutable(),
+                builder.getArgs()));
+
+        ExecHandle proc = builder.build();
+        int exitValue = proc.start().waitForFinish().getExitValue();
+
+        String output = outStream.toString();
+        String error = errStream.toString();
+        boolean failed = exitValue != 0;
+
+        LOG.info("OUTPUT: " + output);
+        LOG.info("ERROR: " + error);
+
+        if (failed != expectFailure) {
+            String message = String.format("Gradle execution %s in %s with: %s %s%nOutput:%n%s%nError:%n%s%n-----%n",
+                    expectFailure ? "did not fail" : "failed", builder.getWorkingDir(), builder.getExecutable(),
+                    builder.getArgs(), output, error);
+            System.out.println(message);
+            throw new RuntimeException(message);
+        }
+        return GUtil.map("output", output, "error", error);
+    }
+
+    private interface CommandBuilder {
+        void build(ExecHandleBuilder builder);
+    }
+
+    private class WindowsCommandBuilder implements CommandBuilder {
+        public void build(ExecHandleBuilder builder) {
+            String cmd;
+            if (getExecutable() != null) {
+                cmd = getExecutable().replace('/', File.separatorChar);
+            } else {
+                cmd = "gradle";
+            }
+            builder.executable("cmd");
+            builder.args("/c", cmd);
+            String gradleHome = gradleHomeDir.getAbsolutePath();
+            builder.environment("Path", String.format("%s\\bin;%s", gradleHome, System.getenv("Path")));
+            builder.environment("GRADLE_EXIT_CONSOLE", "true");
+        }
+    }
+
+    private class UnixCommandBuilder implements CommandBuilder {
+        public void build(ExecHandleBuilder builder) {
+            if (getExecutable() != null) {
+                builder.executable(String.format("%s/%s", getWorkingDir().getAbsolutePath(), getExecutable()));
+            } else {
+                builder.executable(String.format("%s/bin/gradle", gradleHomeDir.getAbsolutePath()));
+            }
+        }
+    }
+
+    private static class ForkedExecutionResult extends AbstractExecutionResult {
+        private final Map result;
+        private final Pattern skippedTaskPattern = Pattern.compile("(:\\w+(:\\w+)*)\\s+((SKIPPED)|(UP-TO-DATE))");
+        private final Pattern notSkippedTaskPattern = Pattern.compile("(:\\w+(:\\w+)*)");
+
+        public ForkedExecutionResult(Map result) {
+            this.result = result;
+        }
+
+        public String getOutput() {
+            return result.get("output").toString();
+        }
+
+        public String getError() {
+            return result.get("error").toString();
+        }
+
+        public ExecutionResult assertTasksExecuted(String... taskPaths) {
+            throw new UnsupportedOperationException();
+        }
+
+        public ExecutionResult assertTasksSkipped(String... taskPaths) {
+            List<String> tasks = findTasks(skippedTaskPattern);
+            assertThat(tasks, equalTo(Arrays.asList(taskPaths)));
+            return this;
+        }
+
+        public ExecutionResult assertTasksNotSkipped(String... taskPaths) {
+            List<String> tasks = findTasks(notSkippedTaskPattern);
+            assertThat(tasks, equalTo(Arrays.asList(taskPaths)));
+            return this;
+        }
+
+        private List<String> findTasks(Pattern pattern) {
+            List<String> tasks = new ArrayList<String>();
+            BufferedReader reader = new BufferedReader(new StringReader(getOutput()));
+            String line;
+            try {
+                while ((line = reader.readLine()) != null) {
+                    java.util.regex.Matcher matcher = pattern.matcher(line);
+                    if (matcher.matches()) {
+                        tasks.add(matcher.group(1));
+                    }
+                }
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+            return tasks;
+        }
+    }
+
+    private static class ForkedExecutionFailure extends ForkedExecutionResult implements ExecutionFailure {
+        public ForkedExecutionFailure(Map result) {
+            super(result);
+        }
+
+        public void assertHasLineNumber(int lineNumber) {
+            assertThat(getError(), containsString(String.format(" line: %d", lineNumber)));
+        }
+
+        public void assertHasFileName(String filename) {
+            assertThat(getError(), containsLine(startsWith(filename)));
+        }
+
+        public void assertHasCause(String description) {
+            assertThatCause(startsWith(description));
+        }
+
+        public void assertThatCause(final Matcher<String> matcher) {
+            assertThat(getError(), containsLine(new BaseMatcher<String>() {
+                public boolean matches(Object o) {
+                    String str = (String) o;
+                    String prefix = "Cause: ";
+                    return str.startsWith(prefix) && matcher.matches(str.substring(prefix.length()));
+                }
+
+                public void describeTo(Description description) {
+                    matcher.describeTo(description);
+                }
+            }));
+        }
+
+        public void assertHasDescription(String context) {
+            assertThatDescription(startsWith(context));
+        }
+
+        public void assertThatDescription(Matcher<String> matcher) {
+            assertThat(getError(), containsLine(matcher));
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleBackedArtifactBuilder.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleBackedArtifactBuilder.java
new file mode 100644
index 0000000..c022564
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleBackedArtifactBuilder.java
@@ -0,0 +1,50 @@
+/*
+ * 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.fixtures;
+
+import org.apache.commons.io.FilenameUtils;
+import org.gradle.util.TestFile;
+
+import java.io.File;
+
+public class GradleBackedArtifactBuilder implements ArtifactBuilder {
+    private final GradleExecuter executer;
+    private final TestFile rootDir;
+
+    public GradleBackedArtifactBuilder(GradleExecuter executer, File rootDir) {
+        this.executer = executer;
+        this.rootDir = new TestFile(rootDir);
+    }
+
+    public TestFile sourceFile(String path) {
+        return rootDir.file("src/main/java", path);
+    }
+
+    public TestFile resourceFile(String path) {
+        return rootDir.file("src/main/resources", path);
+    }
+
+    public void buildJar(File jarFile) {
+        rootDir.file("build.gradle").writelns(
+                "apply plugin: 'java'",
+                "dependencies { compile gradleApi() }",
+                String.format("jar.destinationDir = file('%s')", FilenameUtils.separatorsToUnix(jarFile.getParent())),
+                String.format("jar.archiveName = '%s'", jarFile.getName())
+        );
+        executer.inDirectory(rootDir).withTasks("clean", "jar").run();
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleDistribution.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleDistribution.java
new file mode 100644
index 0000000..a08b95d
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleDistribution.java
@@ -0,0 +1,144 @@
+/*
+ * 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.fixtures;
+
+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;
+
+import java.io.File;
+
+/**
+ * Provides access to a Gradle distribution for integration testing.
+ */
+public class GradleDistribution implements MethodRule, TestFileContext {
+    private static final TestFile USER_HOME_DIR;
+    private static final TestFile GRADLE_HOME_DIR;
+    private static final TestFile SAMPLES_DIR;
+    private static final TestFile USER_GUIDE_OUTPUT_DIR;
+    private static final TestFile USER_GUIDE_INFO_DIR;
+    private static final TestFile DISTS_DIR;
+    private final TemporaryFolder temporaryFolder = new TemporaryFolder();
+    private TestFile userHome;
+
+    static {
+        String workerId = System.getProperty("org.gradle.test.worker", "1");
+        USER_HOME_DIR = file("integTest.gradleUserHomeDir", "intTestHomeDir").file(String.format("worker-%s", workerId));
+        GRADLE_HOME_DIR = file("integTest.gradleHomeDir", null);
+        SAMPLES_DIR = file("integTest.samplesdir", new File(GRADLE_HOME_DIR, "samples").getAbsolutePath());
+        USER_GUIDE_OUTPUT_DIR = file("integTest.userGuideOutputDir",
+                "subprojects/gradle-docs/src/samples/userguideOutput");
+        USER_GUIDE_INFO_DIR = file("integTest.userGuideInfoDir", "subprojects/gradle-docs/build/src/docbook");
+        DISTS_DIR = file("integTest.distsDir", "build/distributions");
+    }
+
+    public GradleDistribution() {
+        this.userHome = USER_HOME_DIR;
+    }
+
+    public void requireOwnUserHomeDir() {
+        userHome = getTestDir().file("user-home");
+    }
+
+    public Statement apply(Statement base, FrameworkMethod method, Object target) {
+        return temporaryFolder.apply(base, method, target);
+    }
+
+    private static TestFile file(String propertyName, String defaultFile) {
+        String path = System.getProperty(propertyName, defaultFile);
+        if (path == null) {
+            throw new RuntimeException(String.format("You must set the '%s' property to run the integration tests.",
+                    propertyName));
+        }
+        return new TestFile(new File(path));
+    }
+
+    /**
+     * The user home dir used for the current test. This is usually shared with other tests unless
+     * {@link #requireOwnUserHomeDir()} is called.
+     */
+    public TestFile getUserHomeDir() {
+        return userHome;
+    }
+
+    /**
+     * The distribution for the current test. This is usually shared with other tests.
+     */
+    public TestFile getGradleHomeDir() {
+        return GRADLE_HOME_DIR;
+    }
+
+    /**
+     * The samples from the distribution. These are usually shared with other tests.
+     */
+    public TestFile getSamplesDir() {
+        return SAMPLES_DIR;
+    }
+
+    public TestFile getUserGuideInfoDir() {
+        return USER_GUIDE_INFO_DIR;
+    }
+
+    public TestFile getUserGuideOutputDir() {
+        return USER_GUIDE_OUTPUT_DIR;
+    }
+
+    /**
+     * The directory containing the distribution Zips
+     */
+    public TestFile getDistributionsDir() {
+        return DISTS_DIR;
+    }
+
+    /**
+     * Returns true if the given file is either part of the distributions, samples, or test files.
+     */
+    public boolean isFileUnderTest(File file) {
+        return GRADLE_HOME_DIR.isSelfOrDescendent(file)
+                || SAMPLES_DIR.isSelfOrDescendent(file)
+                || getTestDir().isSelfOrDescendent(file)
+                || getUserHomeDir().isSelfOrDescendent(file);
+    }
+
+    /**
+     * Returns a scratch-pad directory for the current test. This directory is not shared with any other tests.
+     */
+    public TestFile getTestDir() {
+        return temporaryFolder.getDir();
+    }
+
+    public TemporaryFolder getTemporaryFolder() {
+        return temporaryFolder;
+    }
+
+    /**
+     * Returns a scratch-pad file for the current test. Equivalent to getTestDir().file(path)
+     */
+    public TestFile file(Object... path) {
+        return getTestDir().file(path);
+    }
+
+    /**
+     * Returns a scratch-pad file for the current test. Equivalent to getTestDir().file(path)
+     */
+    public TestFile testFile(Object... path) {
+        return getTestDir().file(path);
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleDistributionExecuter.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleDistributionExecuter.java
new file mode 100644
index 0000000..74842fa
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleDistributionExecuter.java
@@ -0,0 +1,154 @@
+/*
+ * 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.fixtures;
+
+import org.gradle.StartParameter;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.util.TestFile;
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import java.io.File;
+
+/**
+ * A Junit rule which provides a {@link GradleExecuter} implementation that executes Gradle using a given {@link
+ * GradleDistribution}. If not supplied in the constructor, this rule locates a field on the test object with type
+ * {@link GradleDistribution}.
+ *
+ * By default, this executer will execute Gradle in a forked process. There is a system property which enables
+ * executing Gradle in the current process.
+ */
+public class GradleDistributionExecuter extends AbstractGradleExecuter implements MethodRule {
+    private static final String NOFORK_SYS_PROP = "org.gradle.integtest.nofork";
+    private static final boolean FORK;
+    private GradleDistribution dist;
+    private StartParameterModifier inProcessStartParameterModifier;
+    private boolean workingDirSet;
+    private boolean userHomeSet;
+
+    static {
+        FORK = System.getProperty(NOFORK_SYS_PROP, "false").equalsIgnoreCase("false");
+    }
+
+    public GradleDistributionExecuter(GradleDistribution dist) {
+        this.dist = dist;
+        reset();
+    }
+
+    public GradleDistributionExecuter() {
+    }
+
+    public Statement apply(Statement base, FrameworkMethod method, Object target) {
+        if (dist == null) {
+            dist = RuleHelper.getField(target, GradleDistribution.class);
+        }
+        reset();
+        return base;
+    }
+
+    @Override
+    public GradleExecuter reset() {
+        super.reset();
+        workingDirSet = false;
+        userHomeSet = false;
+        return this;
+    }
+
+    @Override
+    public GradleExecuter inDirectory(File directory) {
+        super.inDirectory(directory);
+        workingDirSet = true;
+        return this;
+    }
+
+    @Override
+    public GradleExecuter withUserHomeDir(File userHomeDir) {
+        super.withUserHomeDir(userHomeDir);
+        userHomeSet = true;
+        return this;
+    }
+
+    @Override
+    protected ExecutionResult doRun() {
+        return checkResult(configureExecuter().run());
+    }
+
+    @Override
+    protected ExecutionFailure doRunWithFailure() {
+        return checkResult(configureExecuter().runWithFailure());
+    }
+
+    private <T extends ExecutionResult> T checkResult(T result) {
+        result.assertOutputHasNoStackTraces();
+        result.assertErrorHasNoStackTraces();
+        return result;
+    }
+
+    public void setInProcessStartParameterModifier(StartParameterModifier inProcessStartParameterModifier) {
+        this.inProcessStartParameterModifier = inProcessStartParameterModifier;
+    }
+
+    private GradleExecuter configureExecuter() {
+        if (!workingDirSet) {
+            inDirectory(dist.getTestDir());
+        }
+        if (!userHomeSet) {
+            withUserHomeDir(dist.getUserHomeDir());
+        }
+
+        if (!getClass().desiredAssertionStatus()) {
+            throw new RuntimeException("Assertions must be enabled when running integration tests.");
+        }
+        
+        StartParameter parameter = new StartParameter();
+        parameter.setLogLevel(LogLevel.INFO);
+        parameter.setSearchUpwards(false);
+
+        InProcessGradleExecuter inProcessGradleExecuter = new InProcessGradleExecuter(parameter);
+        copyTo(inProcessGradleExecuter);
+
+        GradleExecuter returnedExecuter = inProcessGradleExecuter;
+
+        if (FORK || !inProcessGradleExecuter.canExecute()) {
+            ForkingGradleExecuter forkingGradleExecuter = new ForkingGradleExecuter(dist.getGradleHomeDir());
+            copyTo(forkingGradleExecuter);
+            returnedExecuter = forkingGradleExecuter;
+        } else {
+            if (inProcessStartParameterModifier != null) {
+                inProcessStartParameterModifier.modify(inProcessGradleExecuter.getParameter());
+            }
+        }
+
+        boolean settingsFound = false;
+        for (
+                File dir = new TestFile(getWorkingDir()); dir != null && dist.isFileUnderTest(dir) && !settingsFound;
+                dir = dir.getParentFile()) {
+            if (new File(dir, "settings.gradle").isFile()) {
+                settingsFound = true;
+            }
+        }
+        if (settingsFound) {
+            returnedExecuter.withSearchUpwards();
+        }
+
+        return returnedExecuter;
+    }
+
+    public static interface StartParameterModifier {
+        void modify(StartParameter startParameter);
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleExecuter.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleExecuter.java
new file mode 100644
index 0000000..8d32e88
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/GradleExecuter.java
@@ -0,0 +1,87 @@
+/*
+ * 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.fixtures;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+public interface GradleExecuter {
+    GradleExecuter inDirectory(File directory);
+
+    /**
+     * Enables search upwards. Defaults to false.
+     */
+    GradleExecuter withSearchUpwards();
+
+    /**
+     * Sets the task names to execute. Defaults to an empty list.
+     */
+    GradleExecuter withTasks(String... names);
+
+    /**
+     * Sets the task names to execute. Defaults to an empty list.
+     */
+    GradleExecuter withTasks(List<String> names);
+
+    GradleExecuter withTaskList();
+
+    GradleExecuter withDependencyList();
+
+    GradleExecuter withQuietLogging();
+
+    /**
+     * Sets the additional command-line arguments to use when executing the build. Defaults to an empty list.
+     */
+    GradleExecuter withArguments(String... args);
+
+    /**
+     * Sets the additional command-line arguments to use when executing the build. Defaults to an empty list.
+     */
+    GradleExecuter withArguments(List<String> args);
+
+    GradleExecuter withEnvironmentVars(Map<String, ?> environment);
+
+    GradleExecuter usingSettingsFile(File settingsFile);
+
+    GradleExecuter usingInitScript(File initScript);
+
+    GradleExecuter usingBuildScript(String script);
+
+    /**
+     * Sets the user home dir. Set to null to use the default user home dir.
+     */
+    GradleExecuter withUserHomeDir(File userHomeDir);
+
+    /**
+     * Sets the executable to use. Set to null to use the default executable (if any)
+     */
+    GradleExecuter usingExecutable(String script);
+
+    /**
+     * Executes the requested build, asserting that the build succeeds. Resets the configuration of this executer.
+     *
+     * @return The result.
+     */
+    ExecutionResult run();
+
+    /**
+     * Executes the requested build, asserting that the build fails. Resets the configuration of this executer.
+     *
+     * @return The result.
+     */
+    ExecutionFailure runWithFailure();
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/HttpServer.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/HttpServer.groovy
new file mode 100644
index 0000000..8dc4149
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/HttpServer.groovy
@@ -0,0 +1,60 @@
+/*
+ * 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.fixtures
+
+import org.apache.commons.lang.StringUtils
+import org.mortbay.jetty.Server
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.mortbay.jetty.handler.*
+
+class HttpServer {
+    private Logger logger = LoggerFactory.getLogger(HttpServer.class)
+    private final Server server = new Server(0)
+    private final ContextHandlerCollection collection = new ContextHandlerCollection()
+
+    def HttpServer() {
+        HandlerCollection handlers = new HandlerCollection()
+        handlers.addHandler(collection)
+        handlers.addHandler(new DefaultHandler())
+        server.setHandler(handlers)
+    }
+
+    def start() {
+        server.start()
+    }
+
+    def stop() {
+        server.stop()
+    }
+
+    /**
+     * Adds a given file at the given URL.
+     */
+    def add(String path, File srcFile) {
+        assert path.startsWith('/')
+        ContextHandler context = new ContextHandler()
+        String contextPath = StringUtils.substringBeforeLast(path, '/')
+        context.contextPath = contextPath ?: '/'
+        context.resourceBase = srcFile.parentFile.path
+        context.addHandler(new ResourceHandler())
+        collection.addHandler(context)
+    }
+
+    def int getPort() {
+        return server.connectors[0].localPort
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/InProcessGradleExecuter.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/InProcessGradleExecuter.java
new file mode 100644
index 0000000..9304f06
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/InProcessGradleExecuter.java
@@ -0,0 +1,322 @@
+/*
+ * 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.fixtures;
+
+import junit.framework.AssertionFailedError;
+import org.gradle.BuildLogger;
+import org.gradle.BuildResult;
+import org.gradle.GradleLauncher;
+import org.gradle.StartParameter;
+import org.gradle.api.GradleException;
+import org.gradle.api.LocationAwareException;
+import org.gradle.api.Task;
+import org.gradle.api.execution.TaskExecutionGraph;
+import org.gradle.api.execution.TaskExecutionGraphListener;
+import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.logging.StandardOutputListener;
+import org.gradle.api.tasks.TaskState;
+import org.gradle.execution.BuiltInTaskBuildExecuter;
+import org.gradle.execution.DependencyReportBuildExecuter;
+import org.gradle.execution.TaskReportBuildExecuter;
+import org.gradle.initialization.DefaultCommandLine2StartParameterConverter;
+import org.gradle.util.Clock;
+import org.hamcrest.Matcher;
+
+import java.io.File;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.gradle.util.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+public class InProcessGradleExecuter extends AbstractGradleExecuter {
+    private StartParameter parameter;
+
+    public InProcessGradleExecuter(StartParameter parameter) {
+        this.parameter = parameter;
+    }
+
+    @Override
+    public GradleExecuter reset() {
+        super.reset();
+        parameter = new StartParameter();
+        return this;
+    }
+
+    public StartParameter getParameter() {
+        return parameter;
+    }
+
+    @Override
+    public GradleExecuter inDirectory(File directory) {
+        parameter.setCurrentDir(directory);
+        return this;
+    }
+
+    @Override
+    public InProcessGradleExecuter withSearchUpwards() {
+        parameter.setSearchUpwards(true);
+        return this;
+    }
+
+    @Override
+    public GradleExecuter withTasks(List<String> names) {
+        parameter.setTaskNames(names);
+        return this;
+    }
+
+    @Override
+    public InProcessGradleExecuter withTaskList() {
+        parameter.setBuildExecuter(new TaskReportBuildExecuter(BuiltInTaskBuildExecuter.ALL_PROJECTS_WILDCARD, true));
+        return this;
+    }
+
+    @Override
+    public InProcessGradleExecuter withDependencyList() {
+        parameter.setBuildExecuter(new DependencyReportBuildExecuter(BuiltInTaskBuildExecuter.ALL_PROJECTS_WILDCARD));
+        return this;
+    }
+
+    @Override
+    public InProcessGradleExecuter usingSettingsFile(File settingsFile) {
+        parameter.setSettingsFile(settingsFile);
+        return this;
+    }
+
+    @Override
+    public GradleExecuter usingInitScript(File initScript) {
+        parameter.addInitScript(initScript);
+        return this;
+    }
+
+    @Override
+    public GradleExecuter usingBuildScript(String script) {
+        parameter.useEmbeddedBuildFile(script);
+        return this;
+    }
+
+    @Override
+    public GradleExecuter withArguments(List<String> args) {
+        new DefaultCommandLine2StartParameterConverter().convert(args.toArray(new String[args.size()]), parameter);
+        return this;
+    }
+
+    @Override
+    public GradleExecuter withUserHomeDir(File userHomeDir) {
+        parameter.setGradleUserHomeDir(userHomeDir);
+        return this;
+    }
+
+    @Override
+    protected ExecutionResult doRun() {
+        OutputListenerImpl outputListener = new OutputListenerImpl();
+        OutputListenerImpl errorListener = new OutputListenerImpl();
+        BuildListenerImpl buildListener = new BuildListenerImpl();
+        BuildResult result = doRun(outputListener, errorListener, buildListener);
+        result.rethrowFailure();
+        return new InProcessExecutionResult(buildListener.executedTasks, buildListener.skippedTasks,
+                outputListener.toString(), errorListener.toString());
+    }
+
+    @Override
+    protected ExecutionFailure doRunWithFailure() {
+        OutputListenerImpl outputListener = new OutputListenerImpl();
+        OutputListenerImpl errorListener = new OutputListenerImpl();
+        BuildListenerImpl buildListener = new BuildListenerImpl();
+        try {
+            doRun(outputListener, errorListener, buildListener).rethrowFailure();
+            throw new AssertionFailedError("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);
+        }
+    }
+
+    private BuildResult doRun(OutputListenerImpl outputListener, OutputListenerImpl errorListener,
+                              BuildListenerImpl listener) {
+        assertCanExecute();
+        if (isQuiet()) {
+            parameter.setLogLevel(LogLevel.QUIET);
+        }
+        GradleLauncher gradleLauncher = GradleLauncher.newInstance(parameter);
+        gradleLauncher.addListener(listener);
+        gradleLauncher.useLogger(new BuildLogger(Logging.getLogger(InProcessGradleExecuter.class), new Clock(),
+                parameter));
+        gradleLauncher.addStandardOutputListener(outputListener);
+        gradleLauncher.addStandardErrorListener(errorListener);
+        try {
+            return gradleLauncher.run();
+        } finally {
+            System.clearProperty("test.single");
+        }
+    }
+
+    public void assertCanExecute() {
+        assertNull(getExecutable());
+        assertTrue(getEnvironmentVars().isEmpty());
+        assertFalse(parameter.isShowHelp());
+        assertFalse(parameter.isShowVersion());
+        assertFalse(parameter.isLaunchGUI());
+    }
+
+    public boolean canExecute() {
+        try {
+            assertCanExecute();
+        } catch (AssertionError e) {
+            return false;
+        }
+        return true;
+    }
+    private class BuildListenerImpl implements TaskExecutionGraphListener {
+        private final List<String> executedTasks = new ArrayList<String>();
+        private final List<String> skippedTasks = new ArrayList<String>();
+
+        public void graphPopulated(TaskExecutionGraph graph) {
+            List<Task> planned = new ArrayList<Task>(graph.getAllTasks());
+            graph.addTaskExecutionListener(new TaskListenerImpl(planned, executedTasks, skippedTasks));
+        }
+    }
+
+    private class OutputListenerImpl implements StandardOutputListener {
+        private StringWriter writer = new StringWriter();
+
+        @Override
+        public String toString() {
+            return writer.toString();
+        }
+
+        public void onOutput(CharSequence output) {
+            writer.append(output);
+        }
+    }
+
+    private class TaskListenerImpl implements TaskExecutionListener {
+        private final List<Task> planned;
+        private final List<String> executedTasks;
+        private final List<String> skippedTasks;
+        private Task current;
+
+        public TaskListenerImpl(List<Task> planned, List<String> executedTasks, List<String> skippedTasks) {
+            this.planned = planned;
+            this.executedTasks = executedTasks;
+            this.skippedTasks = skippedTasks;
+        }
+
+        public void beforeExecute(Task task) {
+            assertThat(current, nullValue());
+            assertTrue(planned.contains(task));
+            current = task;
+        }
+
+        public void afterExecute(Task task, TaskState state) {
+            assertThat(task, sameInstance(current));
+            current = null;
+            executedTasks.add(task.getPath());
+            if (state.getSkipped()) {
+                skippedTasks.add(task.getPath());
+            }
+        }
+    }
+
+    public static class InProcessExecutionResult extends AbstractExecutionResult {
+        private final List<String> plannedTasks;
+        private final List<String> skippedTasks;
+        private final String output;
+        private final String error;
+
+        public InProcessExecutionResult(List<String> plannedTasks, List<String> skippedTasks, String output,
+                                        String error) {
+            this.plannedTasks = plannedTasks;
+            this.skippedTasks = skippedTasks;
+            this.output = output;
+            this.error = error;
+        }
+
+        public String getOutput() {
+            return output;
+        }
+
+        public String getError() {
+            return error;
+        }
+
+        public ExecutionResult assertTasksExecuted(String... taskPaths) {
+            List<String> expected = Arrays.asList(taskPaths);
+            assertThat(plannedTasks, equalTo(expected));
+            return this;
+        }
+
+        public ExecutionResult assertTasksSkipped(String... taskPaths) {
+            List<String> expected = Arrays.asList(taskPaths);
+            assertThat(skippedTasks, equalTo(expected));
+            return this;
+        }
+
+        public ExecutionResult assertTasksNotSkipped(String... taskPaths) {
+            List<String> expected = Arrays.asList(taskPaths);
+            List<String> notSkipped = new ArrayList<String>(plannedTasks);
+            notSkipped.removeAll(skippedTasks);
+            assertThat(notSkipped, equalTo(expected));
+            return this;
+        }
+    }
+
+    private static class InProcessExecutionFailure extends InProcessExecutionResult implements ExecutionFailure {
+        private final GradleException failure;
+
+        public InProcessExecutionFailure(List<String> tasks, List<String> skippedTasks, String output, String error,
+                                         GradleException failure) {
+            super(tasks, skippedTasks, output, error);
+            this.failure = failure;
+        }
+
+        public void assertHasLineNumber(int lineNumber) {
+            assertThat(failure.getMessage(), containsString(String.format(" line: %d", lineNumber)));
+        }
+
+        public void assertHasFileName(String filename) {
+            assertThat(failure.getMessage(), startsWith(String.format("%s", filename)));
+        }
+
+        public void assertHasCause(String description) {
+            assertThatCause(startsWith(description));
+        }
+
+        public void assertThatCause(final Matcher<String> matcher) {
+            if (failure instanceof LocationAwareException) {
+                LocationAwareException exception = (LocationAwareException) failure;
+                assertThat(exception.getReportableCauses(), hasItem(hasMessage(matcher)));
+            } else {
+                assertThat(failure.getCause(), notNullValue());
+                assertThat(failure.getCause().getMessage(), matcher);
+            }
+        }
+
+        public void assertHasDescription(String context) {
+            assertThatDescription(startsWith(context));
+        }
+
+        public void assertThatDescription(Matcher<String> matcher) {
+            assertThat(failure.getMessage(), containsLine(matcher));
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/RuleHelper.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/RuleHelper.java
new file mode 100644
index 0000000..2e51654
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/RuleHelper.java
@@ -0,0 +1,56 @@
+/*
+ * 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.fixtures;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+public class RuleHelper {
+    public static <T> T getField(Object target, Class<T> type) {
+        T value = findField(target, type);
+        if (value != null) {
+            return value;
+        }
+        throw new RuntimeException(String.format("Cannot find a field of type %s for test class %s.",
+                type.getSimpleName(), target.getClass().getSimpleName()));
+    }
+
+    public static <T> T findField(Object target, Class<T> type) {
+        List<T> matches = new ArrayList<T>();
+        for (Class<?> cl = target.getClass(); cl != Object.class; cl = cl.getSuperclass()) {
+            for (Field field : cl.getDeclaredFields()) {
+                if (type.isAssignableFrom(field.getType())) {
+                    field.setAccessible(true);
+                    try {
+                        matches.add(type.cast(field.get(target)));
+                    } catch (IllegalAccessException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        }
+        if (matches.isEmpty()) {
+            return null;
+        }
+        if (matches.size() > 1) {
+            throw new RuntimeException(String.format("Multiple %s fields found for test class %s.",
+                    type.getSimpleName(), target.getClass().getSimpleName()));
+        }
+        return matches.get(0);
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/Sample.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/Sample.java
new file mode 100644
index 0000000..f7cbf79
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/Sample.java
@@ -0,0 +1,49 @@
+/*
+ * 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.fixtures;
+
+import org.gradle.util.TestFile;
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+public class Sample implements MethodRule {
+    private final String name;
+    private GradleDistribution dist;
+    private TestFile sampleDir;
+
+    public Sample(String name) {
+        this.name = name;
+    }
+
+    public Statement apply(final Statement base, FrameworkMethod method, Object target) {
+        dist = RuleHelper.getField(target, GradleDistribution.class);
+        sampleDir = dist.getTestDir().file(name);
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                TestFile srcDir = dist.getSamplesDir().file(name).assertIsDir();
+                srcDir.copyTo(sampleDir);
+                base.evaluate();
+            }
+        };
+    }
+
+    public TestFile getDir() {
+        return sampleDir;
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/TestClassExecutionResult.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/TestClassExecutionResult.java
new file mode 100644
index 0000000..d916261
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/TestClassExecutionResult.java
@@ -0,0 +1,50 @@
+/*
+ * 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.fixtures;
+
+import org.hamcrest.Matcher;
+
+public interface TestClassExecutionResult {
+    /**
+     * Asserts that the given tests (and only the given tests) were executed for the given test class.
+     */
+    TestClassExecutionResult assertTestsExecuted(String... testNames);
+
+    /**
+     * Asserts that the given test passed.
+     */
+    TestClassExecutionResult assertTestPassed(String name);
+
+    /**
+     * Asserts that the given test failed.
+     */
+    TestClassExecutionResult assertTestFailed(String name, Matcher<? super String> messageMatcher);
+
+    /**
+     * Asserts that the given config method passed.
+     */
+    TestClassExecutionResult assertConfigMethodPassed(String name);
+
+    /**
+     * Asserts that the given config method failed.
+     */
+    TestClassExecutionResult assertConfigMethodFailed(String name);
+
+    TestClassExecutionResult assertStdout(Matcher<? super String> matcher);
+
+    TestClassExecutionResult assertStderr(Matcher<? super String> matcher);
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/TestExecutionResult.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/TestExecutionResult.java
new file mode 100644
index 0000000..74e89c3
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/TestExecutionResult.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.integtests.fixtures;
+
+public interface TestExecutionResult {
+    /**
+     * Asserts that the given test classes (and only the given test classes) were executed.
+     */
+    TestExecutionResult assertTestClassesExecuted(String... testClasses);
+
+    /**
+     * Returns the result for the given test class.
+     */
+    TestClassExecutionResult testClass(String testClass);
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/TestResources.java b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/TestResources.java
new file mode 100644
index 0000000..750b83c
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/fixtures/TestResources.java
@@ -0,0 +1,85 @@
+/*
+ * 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.fixtures;
+
+import org.gradle.util.Resources;
+import org.gradle.util.TemporaryFolder;
+import org.gradle.util.TestFile;
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * Provides access to test resources for integration testing. Looks for the following directory in the test classpath:
+ * <ul> <li>${testClass}/shared</li> <li>${testClass}/${testName}</li> </ul>
+ *
+ * Copies the contents of each such directory into a temporary directory for the test to use.
+ */
+public class TestResources implements MethodRule {
+    private TemporaryFolder temporaryFolder;
+    private final Collection<String> extraResources;
+    private final Resources resources = new Resources();
+
+    public TestResources(String... extraResources) {
+        this.extraResources = Arrays.asList(extraResources);
+    }
+
+    public TestFile getDir() {
+        return temporaryFolder.getDir();
+    }
+
+    public Statement apply(Statement base, final FrameworkMethod method, Object target) {
+        final Statement statement = resources.apply(base, method, target);
+        temporaryFolder = findTempDir(target);
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                String className = method.getMethod().getDeclaringClass().getSimpleName();
+                maybeCopy(String.format("%s/shared", className));
+                maybeCopy(String.format("%s/%s", className, method.getName()));
+                for (String extraResource : extraResources) {
+                    maybeCopy(extraResource);
+                }
+                statement.evaluate();
+            }
+        };
+    }
+
+    private TemporaryFolder findTempDir(Object target) {
+        GradleDistribution dist = RuleHelper.findField(target, GradleDistribution.class);
+        if (dist != null) {
+            return dist.getTemporaryFolder();
+        }
+        TemporaryFolder folder = RuleHelper.findField(target, TemporaryFolder.class);
+        if (folder != null) {
+            return folder;
+        }
+        throw new RuntimeException(String.format(
+                "Could not find a GradleDistribution or TemporaryFolder field for test class %s.",
+                target.getClass().getSimpleName()));
+    }
+
+    private void maybeCopy(String resource) {
+        TestFile dir = resources.findResource(resource);
+        if (dir != null) {
+            dir.copyTo(getDir());
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/MavenProjectIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/MavenProjectIntegrationTest.groovy
new file mode 100644
index 0000000..30e1722
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/MavenProjectIntegrationTest.groovy
@@ -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.integtests.maven
+
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.gradle.integtests.DistributionIntegrationTestRunner
+
+ at RunWith(DistributionIntegrationTestRunner.class)
+class MavenProjectIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+
+    @Test
+    public void handlesSubProjectsWithoutTheMavenPluginApplied() {
+        dist.testFile("settings.gradle").write("include 'subProject'");
+        dist.testFile("build.gradle") << '''
+            apply plugin: 'java'
+            apply plugin: 'maven'
+        '''
+        executer.withTaskList().run();
+    }
+
+    @Test
+    public void canDeployAProjectWithDependencyInMappedAndUnMappedConfiguration() {
+        dist.testFile("build.gradle") << '''
+            apply plugin: 'java'
+            apply plugin: 'maven'
+            group = 'root'
+            repositories { mavenCentral() }
+            configurations { custom }
+            dependencies {
+                custom 'commons-collections:commons-collections:3.2'
+                runtime 'commons-collections:commons-collections:3.2'
+            }
+            uploadArchives {
+                repositories {
+                    mavenDeployer {
+                        repository(url: "file://localhost/$projectDir/pomRepo/")
+                    }
+                }
+            }
+        '''
+        executer.withTasks('uploadArchives').run()
+    }
+    
+    @Test
+    public void canDeployAProjectWithNoMainArtifact() {
+        def file = dist.testFile("build.gradle") << '''
+            apply plugin: 'java'
+            apply plugin: 'maven'
+            group = 'root'
+            jar.enabled = false
+            task sourceJar(type: Jar) {
+                classifier = 'source'
+            }
+            artifacts {
+                archives sourceJar
+            }
+            uploadArchives {
+                repositories {
+                    mavenDeployer {
+                        repository(url: "file://localhost/$projectDir/pomRepo/")
+                    }
+                }
+            }
+        '''
+        println file.absolutePath
+        executer.withTasks('uploadArchives').run()
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/MavenRepoIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/MavenRepoIntegrationTest.groovy
new file mode 100644
index 0000000..3d5c900
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/MavenRepoIntegrationTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * 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.integtests.maven
+
+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 org.junit.runner.RunWith
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.integtests.DistributionIntegrationTestRunner
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(DistributionIntegrationTestRunner.class)
+class MavenRepoIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('mavenRepo')
+
+    @Test
+    public void mavenRepoSample() {
+        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 = sample.dir
+        executer.inDirectory(projectDir).withTasks('retrieve').run()
+        expectedFiles.each { new TestFile(projectDir, 'build', it).assertExists() }
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/MavenSnapshotIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/MavenSnapshotIntegrationTest.groovy
new file mode 100644
index 0000000..f802328
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/MavenSnapshotIntegrationTest.groovy
@@ -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.integtests.maven
+
+import org.junit.Test
+import org.gradle.integtests.fixtures.TestResources
+import org.junit.Rule
+import org.gradle.integtests.AbstractIntegrationTest
+
+/**
+ * @author Hans Dockter
+ */
+class MavenSnapshotIntegrationTest extends AbstractIntegrationTest {
+    @Rule
+    public final TestResources testResources = new TestResources()
+
+    @Test
+    public void retrievesAndCacheSnapshot() {
+        File buildFile = testFile("projectWithMavenSnapshots.gradle");
+        usingBuildFile(buildFile).withTasks('clean', 'retrieveWithTimedOutSnapshotInCache').run();
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/SamplesMavenPomGenerationIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/SamplesMavenPomGenerationIntegrationTest.groovy
new file mode 100644
index 0000000..cddacb3
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/SamplesMavenPomGenerationIntegrationTest.groovy
@@ -0,0 +1,155 @@
+/*
+ * 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.maven
+
+import groovy.text.SimpleTemplateEngine
+import org.custommonkey.xmlunit.Diff
+import org.custommonkey.xmlunit.XMLAssert
+import org.custommonkey.xmlunit.examples.RecursiveElementNameAndTextQualifier
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.util.Resources
+import org.gradle.util.TestFile
+import org.hamcrest.Matchers
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.junit.Assert.*
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.integtests.DistributionIntegrationTestRunner
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(DistributionIntegrationTestRunner.class)
+class SamplesMavenPomGenerationIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+
+    private TestFile pomProjectDir
+    private TestFile repoDir
+    private TestFile snapshotRepoDir
+
+    @Rule public Resources resources = new Resources();
+    @Rule public final Sample sample = new Sample('maven/pomGeneration')
+
+    @Before
+    public void setUp() {
+        pomProjectDir = sample.dir
+        repoDir = pomProjectDir.file('pomRepo');
+        snapshotRepoDir = pomProjectDir.file('snapshotRepo');
+    }
+    
+    @Test
+    public void checkWithNoCustomVersion() {
+        String version = '1.0'
+        String groupId = "gradle"
+        long start = System.currentTimeMillis();
+        executer.inDirectory(pomProjectDir).withTasks('clean', 'uploadArchives', 'install').run()
+        String repoPath = repoPath(groupId, version)
+        compareXmlWithIgnoringOrder(expectedPom(version, groupId),
+                pomFile(repoDir, repoPath, version).text)
+        repoDir.file("$repoPath/mywar-${version}.war").assertIsFile()
+        pomProjectDir.file('build').assertDoesNotExist()
+        pomProjectDir.file('target').assertIsDir()
+        checkInstall(start, pomProjectDir, version, groupId)
+    }
+
+    @Test
+    public void checkWithCustomVersion() {
+        long start = System.currentTimeMillis();
+        String version = "1.0MVN"
+        String groupId = "deployGroup"
+        executer.inDirectory(pomProjectDir).withArguments("-PcustomVersion=${version}").withTasks('clean', 'uploadArchives', 'install').run()
+        String repoPath = repoPath(groupId, version)
+        compareXmlWithIgnoringOrder(expectedPom(version, groupId),
+                pomFile(repoDir, repoPath, version).text)
+        repoDir.file("$repoPath/mywar-${version}.war").assertIsFile()
+        repoDir.file("$repoPath/mywar-${version}-javadoc.zip").assertIsFile()
+        pomProjectDir.file('build').assertDoesNotExist()
+        pomProjectDir.file('target').assertIsDir()
+        checkInstall(start, pomProjectDir, version, 'installGroup')
+    }
+
+    @Test
+    public void checkWithSnapshotVersion() {
+        String version = '1.0-SNAPSHOT'
+        String groupId = "deployGroup"
+        long start = System.currentTimeMillis();
+        executer.inDirectory(pomProjectDir).withArguments("-PcustomVersion=${version}").withTasks('clean', 'uploadArchives', 'install').run()
+        String repoPath = repoPath(groupId, version)
+        File pomFile = pomFile(snapshotRepoDir, repoPath, version)
+        compareXmlWithIgnoringOrder(expectedPom(version, groupId), pomFile.text)
+        new TestFile(new File(pomFile.absolutePath.replace(".pom", ".war"))).assertIsFile()
+        pomProjectDir.file('build').assertDoesNotExist()
+        pomProjectDir.file('target').assertIsDir()
+        checkInstall(start, pomProjectDir, version, 'installGroup')
+    }
+
+    @Test
+    public void writeNewPom() {
+        executer.inDirectory(pomProjectDir).withTasks('clean', 'writeNewPom').run()
+        compareXmlWithIgnoringOrder(expectedPom(null, null, 'pomGeneration/expectedNewPom.txt'),
+                pomProjectDir.file("target/newpom.xml").text)
+    }
+
+    @Test
+    public void writeDeployerPom() {
+        String version = '1.0'
+        String groupId = "gradle"
+        executer.inDirectory(pomProjectDir).withTasks('clean', 'writeDeployerPom').run()
+        compareXmlWithIgnoringOrder(expectedPom(version, groupId), pomProjectDir.file("target/deployerpom.xml").text)
+    }
+    
+    static String repoPath(String group, String version) {
+        "$group/mywar/$version"
+    }
+
+    static File pomFile(TestFile repoDir, String repoPath, String version) {
+        TestFile versionDir = repoDir.file(repoPath)
+        List matches = versionDir.listFiles().findAll { it.name.endsWith('.pom') }
+        assertEquals(1, matches.size())
+        matches[0]
+    }
+
+    void checkInstall(long start, TestFile pomProjectDir, String version, String groupId) {
+        TestFile localMavenRepo = new TestFile(pomProjectDir.file("target/localRepoPath.txt").text as File)
+        TestFile installedFile = localMavenRepo.file("$groupId/mywar/$version/mywar-${version}.war")
+        TestFile installedJavadocFile = localMavenRepo.file("$groupId/mywar/$version/mywar-${version}-javadoc.zip")
+        TestFile installedPom = localMavenRepo.file("$groupId/mywar/$version/mywar-${version}.pom")
+        installedFile.assertIsFile()
+        installedJavadocFile.assertIsFile()
+        installedPom.assertIsFile()
+        Assert.assertTrue(start <= installedFile.lastModified());
+        Assert.assertTrue(start <= installedJavadocFile.lastModified());
+        compareXmlWithIgnoringOrder(expectedPom(version, groupId), installedPom.text)
+    }
+    
+    private String expectedPom(String version, String groupId, String path = 'pomGeneration/expectedPom.txt') {
+        SimpleTemplateEngine templateEngine = new SimpleTemplateEngine();
+        String text = resources.getResource(path).text
+        return templateEngine.createTemplate(text).make(version: version, groupId: groupId)
+    }
+
+    private static void compareXmlWithIgnoringOrder(String expectedXml, String actualXml) {
+        Diff diff = new Diff(expectedXml, actualXml)
+        diff.overrideElementQualifier(new RecursiveElementNameAndTextQualifier())
+        XMLAssert.assertXMLEqual(diff, true);
+        Assert.assertThat(actualXml, Matchers.startsWith(String.format('<?xml version="1.0" encoding="UTF-8"?>')))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/SamplesMavenQuickstartIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/SamplesMavenQuickstartIntegrationTest.groovy
new file mode 100644
index 0000000..9d3f2b4
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/maven/SamplesMavenQuickstartIntegrationTest.groovy
@@ -0,0 +1,100 @@
+/*
+ * 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.maven
+
+import groovy.text.SimpleTemplateEngine
+import org.custommonkey.xmlunit.Diff
+import org.custommonkey.xmlunit.XMLAssert
+import org.custommonkey.xmlunit.examples.RecursiveElementNameAndTextQualifier
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.util.Resources
+import org.gradle.util.TestFile
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.junit.Assert.*
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.integtests.DistributionIntegrationTestRunner
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(DistributionIntegrationTestRunner.class)
+class SamplesMavenQuickstartIntegrationTest {
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+
+    private TestFile pomProjectDir
+    private TestFile repoDir
+
+    @Rule public Resources resources = new Resources();
+    @Rule public final Sample sample = new Sample('maven/quickstart')
+
+    @Before
+    public void setUp() {
+        pomProjectDir = sample.dir
+        repoDir = pomProjectDir.file('pomRepo');
+    }
+
+    @Test
+    public void checkDeployAndInstall() {
+        String version = '1.0'
+        String groupId = "gradle"
+        long start = System.currentTimeMillis();
+        executer.inDirectory(pomProjectDir).withTasks('clean', 'uploadArchives', 'install').run()
+        String repoPath = repoPath(groupId, version)
+        compareXmlWithIgnoringOrder(expectedPom(version, groupId),
+                pomFile(repoDir, repoPath, version).text)
+        repoDir.file("$repoPath/quickstart-${version}.jar").assertIsFile()
+        checkInstall(start, pomProjectDir, version, groupId)
+    }
+
+    static String repoPath(String group, String version) {
+        "$group/quickstart/$version"
+    }
+
+    static File pomFile(TestFile repoDir, String repoPath, String version) {
+        TestFile versionDir = repoDir.file(repoPath)
+        List matches = versionDir.listFiles().findAll { it.name.endsWith('.pom') }
+        assertEquals(1, matches.size())
+        matches[0]
+    }
+
+    void checkInstall(long start, TestFile pomProjectDir, String version, String groupId) {
+        TestFile localMavenRepo = new TestFile(pomProjectDir.file("build/localRepoPath.txt").text as File)
+        TestFile installedFile = localMavenRepo.file("$groupId/quickstart/$version/quickstart-${version}.jar")
+        TestFile installedPom = localMavenRepo.file("$groupId/quickstart/$version/quickstart-${version}.pom")
+        installedFile.assertIsFile()
+        installedPom.assertIsFile()
+        Assert.assertTrue(start <= installedFile.lastModified());
+        compareXmlWithIgnoringOrder(expectedPom(version, groupId), installedPom.text)
+    }
+
+    private String expectedPom(String version, String groupId) {
+        SimpleTemplateEngine templateEngine = new SimpleTemplateEngine();
+        String text = resources.getResource('pomGeneration/expectedQuickstartPom.txt').text
+        return templateEngine.createTemplate(text).make(version: version, groupId: groupId)
+    }
+
+    private static void compareXmlWithIgnoringOrder(String expectedXml, String actualXml) {
+        Diff diff = new Diff(expectedXml, actualXml)
+        diff.overrideElementQualifier(new RecursiveElementNameAndTextQualifier())
+        XMLAssert.assertXMLEqual(diff, true);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/testng/TestNGIntegrationProject.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/testng/TestNGIntegrationProject.groovy
new file mode 100644
index 0000000..dbdf762
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/testng/TestNGIntegrationProject.groovy
@@ -0,0 +1,67 @@
+/*
+ * 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.testng
+
+/**
+ * @author Tom Eyckmans
+ */
+
+public class TestNGIntegrationProject {
+    String name
+    boolean expectFailure
+    Closure assertClosure
+
+    static TestNGIntegrationProject failingIntegrationProject(String language, String jdk, assertClosure)
+    {
+        new TestNGIntegrationProject(language + "-" + jdk + "-failing", true, null, assertClosure)
+    }
+
+    static TestNGIntegrationProject failingIntegrationProject(String language, String jdk, String nameSuffix, assertClosure)
+    {
+        new TestNGIntegrationProject(language + "-" + jdk + "-failing", true, nameSuffix, assertClosure)
+    }
+
+    static TestNGIntegrationProject passingIntegrationProject(String language, String jdk, assertClosure)
+    {
+        new TestNGIntegrationProject(language + "-" + jdk + "-passing", false, null, assertClosure)
+    }
+
+    static TestNGIntegrationProject passingIntegrationProject(String language, String jdk, String nameSuffix, assertClosure)
+    {
+        new TestNGIntegrationProject(language + "-" + jdk + "-passing", false, nameSuffix, assertClosure)
+    }
+
+    public TestNGIntegrationProject(String name, boolean expectFailure, String nameSuffix, assertClosure)
+    {
+        if ( nameSuffix == null ) {
+            this.name = name
+        } else {
+            this.name = name + nameSuffix
+        }
+        this.expectFailure = expectFailure
+        this.assertClosure = assertClosure
+    }
+
+    void doAssert(projectDir, result) {
+        if (assertClosure.maximumNumberOfParameters == 3) {
+            assertClosure(name, projectDir, new TestNgExecutionResult(projectDir))
+        } else {
+            assertClosure(name, projectDir, new TestNgExecutionResult(projectDir), result)
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/testng/TestNGIntegrationTest.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/testng/TestNGIntegrationTest.groovy
new file mode 100644
index 0000000..bf32f54
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/testng/TestNGIntegrationTest.groovy
@@ -0,0 +1,239 @@
+/*
+ * 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.testng
+
+import org.gradle.api.Project
+import org.gradle.integtests.DistributionIntegrationTestRunner
+import org.gradle.integtests.fixtures.ExecutionResult
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.TestExecutionResult
+import org.gradle.util.TestFile
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.gradle.integtests.testng.TestNGIntegrationProject.*
+import static org.gradle.util.Matchers.*
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+/**
+ * @author Tom Eyckmans
+ */
+ at RunWith(DistributionIntegrationTestRunner.class)
+public class TestNGIntegrationTest {
+    static final String GROOVY = "groovy"
+    static final String JAVA = "java"
+    static final String JDK14 = "jdk14"
+    static final String JDK15 = "jdk15"
+
+    static final GROOVY_JDK15_FAILING = failingIntegrationProject(GROOVY, JDK15, { name, projectDir, TestExecutionResult result ->
+        result.assertTestClassesExecuted('org.gradle.BadTest')
+        result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('broken'))
+    })
+    static final GROOVY_JDK15_PASSING = passingIntegrationProject(GROOVY, JDK15, { name, TestFile projectDir, TestExecutionResult result ->
+        result.assertTestClassesExecuted('org.gradle.OkTest')
+        result.testClass('org.gradle.OkTest').assertTestPassed('passingTest')
+    })
+    static final JAVA_JDK14_FAILING = failingIntegrationProject(JAVA, JDK14, { name, projectDir, TestExecutionResult result ->
+        result.assertTestClassesExecuted('org.gradle.BadTest')
+        result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('broken'))
+    })
+    static final JAVA_JDK14_PASSING = passingIntegrationProject(JAVA, JDK14, { name, projectDir, TestExecutionResult result ->
+        result.assertTestClassesExecuted('org.gradle.OkTest')
+        result.testClass('org.gradle.OkTest').assertTestPassed('passingTest')
+    })
+    static final JAVA_JDK15_FAILING = failingIntegrationProject(JAVA, JDK15, { name, projectDir, TestExecutionResult result, ExecutionResult execution ->
+        result.assertTestClassesExecuted('org.gradle.BadTest', 'org.gradle.TestWithBrokenSetup', 'org.gradle.BrokenAfterSuite')
+        result.testClass('org.gradle.BadTest').assertTestFailed('failingTest', equalTo('broken'))
+        result.testClass('org.gradle.TestWithBrokenSetup').assertConfigMethodFailed('setup')
+        result.testClass('org.gradle.BrokenAfterSuite').assertConfigMethodFailed('cleanup')
+        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'))
+    })
+    static final JAVA_JDK15_PASSING = passingIntegrationProject(JAVA, JDK15, { name, projectDir, TestExecutionResult result ->
+        result.assertTestClassesExecuted('org.gradle.OkTest', 'org.gradle.ConcreteTest', 'org.gradle.SuiteSetup', 'org.gradle.SuiteCleanup', 'org.gradle.TestSetup', 'org.gradle.TestCleanup')
+        result.testClass('org.gradle.OkTest').assertTestsExecuted('passingTest', 'expectedFailTest')
+        result.testClass('org.gradle.OkTest').assertTestPassed('passingTest')
+        result.testClass('org.gradle.OkTest').assertTestPassed('expectedFailTest')
+        result.testClass('org.gradle.ConcreteTest').assertTestsExecuted('ok', 'alsoOk')
+        result.testClass('org.gradle.ConcreteTest').assertTestPassed('ok')
+        result.testClass('org.gradle.ConcreteTest').assertTestPassed('alsoOk')
+        result.testClass('org.gradle.SuiteSetup').assertConfigMethodPassed('setupSuite')
+        result.testClass('org.gradle.SuiteCleanup').assertConfigMethodPassed('cleanupSuite')
+        result.testClass('org.gradle.TestSetup').assertConfigMethodPassed('setupTest')
+        result.testClass('org.gradle.TestCleanup').assertConfigMethodPassed('cleanupTest')
+    })
+    static final JAVA_JDK15_PASSING_NO_REPORT = passingIntegrationProject(JAVA, JDK15, "-no-report", { name, TestFile projectDir, TestExecutionResult result ->
+        result.assertTestClassesExecuted('org.gradle.OkTest')
+        result.testClass('org.gradle.OkTest').assertTestPassed('passingTest')
+        projectDir.file('build/reports/tests/index.html').assertDoesNotExist()
+    })
+    static final SUITE_XML_BUILDER = new TestNGIntegrationProject("suitexmlbuilder", false, null, { name, projectDir, TestExecutionResult result ->
+        result.assertTestClassesExecuted('org.gradle.testng.UserImplTest')
+        result.testClass('org.gradle.testng.UserImplTest').assertTestsExecuted('testOkFirstName')
+        result.testClass('org.gradle.testng.UserImplTest').assertTestPassed('testOkFirstName')
+    })
+
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+
+    @Test
+    public void executesTestsInCorrectEnvironment() {
+        TestFile testDir = dist.testDir;
+        TestFile buildFile = testDir.file('build.gradle');
+        buildFile << '''
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'org.testng:testng:5.8:jdk15' }
+            test {
+                useTestNG()
+                systemProperties.testSysProperty = 'value'
+                environment.TEST_ENV_VAR = 'value'
+            }
+        '''
+        testDir.file("src/test/java/org/gradle/OkTest.java") << """
+            package org.gradle;
+            import java.util.logging.Logger;
+            import static org.testng.Assert.*;
+            public class OkTest {
+                @org.testng.annotations.Test public void ok() throws Exception {
+                    // check working dir
+                    assertEquals("${testDir.absolutePath.replaceAll('\\\\', '\\\\\\\\')}", System.getProperty("user.dir"));
+                    // check Gradle classes not visible
+                    try { getClass().getClassLoader().loadClass("${Project.class.getName()}"); fail(); } catch(ClassNotFoundException e) { }
+                    // check context classloader
+                    assertSame(getClass().getClassLoader(), Thread.currentThread().getContextClassLoader());
+                    // check sys properties
+                    assertEquals("value", System.getProperty("testSysProperty"));
+                    // check env vars
+                    assertEquals("value", System.getenv("TEST_ENV_VAR"));
+                    // check logging
+                    System.out.println("stdout");
+                    System.err.println("stderr");
+                    Logger.getLogger("test").warning("a warning");
+                }
+            }
+        """
+
+        ExecutionResult result = executer.withTasks('build').withArguments('-s').run();
+        assertThat(result.output, not(containsString('stdout')))
+        assertThat(result.error, not(containsString('stderr')))
+        assertThat(result.error, not(containsString('a warning')))
+
+        new TestNgExecutionResult(testDir).testClass('org.gradle.OkTest').assertTestPassed('ok')
+    }
+
+    @Test
+    public void canListenerForTestResults() {
+        TestFile testDir = dist.getTestDir();
+        testDir.file('src/main/java/AppException.java').writelns(
+                "public class AppException extends Exception { }"
+        );
+
+        testDir.file('src/test/java/SomeTest.java').writelns(
+                "public class SomeTest {",
+                "@org.testng.annotations.Test public void pass() { }",
+                "@org.testng.annotations.Test public void fail() { assert false; }",
+                "@org.testng.annotations.Test public void knownError() { throw new RuntimeException(\"message\"); }",
+                "@org.testng.annotations.Test public void unknownError() throws AppException { throw new AppException(); }",
+                "}"
+        );
+
+        testDir.file('build.gradle') << '''
+            apply plugin: 'java'
+            repositories { mavenCentral() }
+            dependencies { testCompile 'org.testng:testng:5.8:jdk15' }
+            def listener = new TestListenerImpl()
+            test {
+                useTestNG()
+                addTestListener(listener)
+                ignoreFailures = true
+            }
+            class TestListenerImpl implements TestListener {
+                void beforeSuite(TestDescriptor suite) { println "START [$suite] [$suite.name]" }
+                void afterSuite(TestDescriptor suite, TestResult result) { println "FINISH [$suite] [$suite.name]" }
+                void beforeTest(TestDescriptor test) { println "START [$test] [$test.name]" }
+                void afterTest(TestDescriptor test, TestResult result) { println "FINISH [$test] [$test.name] [$result.error]" }
+            }
+        '''
+
+        ExecutionResult result = executer.withTasks("test").run();
+        assertThat(result.getOutput(), containsLine("START [tests] []"));
+        assertThat(result.getOutput(), containsLine("FINISH [tests] []"));
+        assertThat(result.getOutput(), containsLine("START [test process 'Gradle Worker 1'] [Gradle Worker 1]"));
+        assertThat(result.getOutput(), containsLine("FINISH [test process 'Gradle Worker 1'] [Gradle Worker 1]"));
+        assertThat(result.getOutput(), containsLine("START [test 'Gradle test'] [Gradle test]"));
+        assertThat(result.getOutput(), containsLine("FINISH [test 'Gradle test'] [Gradle test]"));
+        assertThat(result.getOutput(), containsLine("START [test method pass(SomeTest)] [pass]"));
+        assertThat(result.getOutput(), containsLine("FINISH [test method pass(SomeTest)] [pass] [null]"));
+        assertThat(result.getOutput(), containsLine("START [test method fail(SomeTest)] [fail]"));
+        assertThat(result.getOutput(), containsLine("FINISH [test method fail(SomeTest)] [fail] [java.lang.AssertionError]"));
+        assertThat(result.getOutput(), containsLine("START [test method knownError(SomeTest)] [knownError]"));
+        assertThat(result.getOutput(), containsLine("FINISH [test method knownError(SomeTest)] [knownError] [java.lang.RuntimeException: message]"));
+        assertThat(result.getOutput(), containsLine("START [test method unknownError(SomeTest)] [unknownError]"));
+        assertThat(result.getOutput(), containsLine("FINISH [test method unknownError(SomeTest)] [unknownError] [org.gradle.messaging.remote.internal.PlaceholderException: AppException: null]"));
+    }
+
+    @Test
+    public void suiteXmlBuilder() {
+        checkProject(SUITE_XML_BUILDER)
+    }
+
+    @Test
+    public void groovyJdk15() {
+        checkProject(GROOVY_JDK15_FAILING)
+        checkProject(GROOVY_JDK15_PASSING)
+    }
+
+    @Test
+    public void javaJdk14() {
+        checkProject(JAVA_JDK14_PASSING)
+        checkProject(JAVA_JDK14_FAILING)
+    }
+
+    @Test
+    public void javaJdk15() {
+        checkProject(JAVA_JDK15_PASSING)
+        checkProject(JAVA_JDK15_FAILING)
+    }
+
+    @Ignore
+    public void javaJdk15WithNoReports() {
+        // TODO currently reports are always generated because the antTestNGExecute task uses the
+        // default listeners and these generate reports by default. Enable the test when this has changed.
+        checkProject(JAVA_JDK15_PASSING_NO_REPORT)
+    }
+
+    private def checkProject(TestNGIntegrationProject project) {
+        final File projectDir = dist.samplesDir.file("testng", project.name)
+
+        def result
+        executer.inDirectory(projectDir).withTasks('clean', 'test')
+        if (project.expectFailure) {
+            result = executer.runWithFailure()
+        } else {
+            result = executer.run()
+        }
+
+        // output: output, error: error, command: actualCommand, unixCommand: unixCommand, windowsCommand: windowsCommand
+        project.doAssert(projectDir, result)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/testng/TestNgExecutionResult.groovy b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/testng/TestNgExecutionResult.groovy
new file mode 100644
index 0000000..734c8ec
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/groovy/org/gradle/integtests/testng/TestNgExecutionResult.groovy
@@ -0,0 +1,145 @@
+/*
+ * 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.testng
+
+import groovy.util.slurpersupport.GPathResult
+import org.gradle.integtests.fixtures.TestClassExecutionResult
+import org.gradle.integtests.fixtures.TestExecutionResult
+import org.gradle.util.TestFile
+import org.hamcrest.Matcher
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+class TestNgExecutionResult implements TestExecutionResult {
+    private final TestFile projectDir
+    private final GPathResult resultsXml
+
+    def TestNgExecutionResult(projectDir) {
+        this.projectDir = projectDir;
+        resultsXml = new XmlSlurper().parse(projectDir.file('build/reports/tests/testng-results.xml').assertIsFile())
+    }
+
+    TestExecutionResult assertTestClassesExecuted(String... testClasses) {
+        projectDir.file('build/reports/tests/index.html').assertIsFile()
+        def actualTestClasses = findTestClasses().keySet()
+        assertThat(actualTestClasses, equalTo(testClasses as Set))
+        this
+    }
+
+    TestClassExecutionResult testClass(String testClass) {
+        return new TestNgTestClassExecutionResult(testClass, findTestClass(testClass))
+    }
+
+    private def findTestClass(String testClass) {
+        def testClasses = findTestClasses()
+        if (!testClasses.containsKey(testClass)) {
+            fail("Could not find test class ${testClass}. Found ${testClasses.keySet()}")
+        }
+        testClasses[testClass]
+    }
+
+    private def findTestClasses() {
+        Map testClasses = [:]
+        resultsXml.suite.test.'class'.each {
+            testClasses.put(it. at name as String, it)
+        }
+        testClasses
+    }
+}
+
+private class TestNgTestClassExecutionResult implements TestClassExecutionResult {
+    def String testClass
+    def GPathResult testClassNode
+
+    def TestNgTestClassExecutionResult(String testClass, GPathResult resultXml) {
+        this.testClass = testClass
+        this.testClassNode = resultXml
+    }
+
+    TestClassExecutionResult assertTestsExecuted(String... testNames) {
+        def actualTestMethods = findTestMethods().keySet()
+        assertThat(actualTestMethods, equalTo(testNames as Set))
+        this
+    }
+
+    TestClassExecutionResult assertTestPassed(String name) {
+        def testMethodNode = findTestMethod(name)
+        assertEquals('PASS', testMethodNode. at status as String)
+        this
+    }
+
+    TestClassExecutionResult assertTestFailed(String name, Matcher<? super String> messageMatcher) {
+        def testMethodNode = findTestMethod(name)
+        assertEquals('FAIL', testMethodNode. at status as String)
+        assertThat(testMethodNode.exception[0].message[0].text().trim(), messageMatcher)
+        this
+    }
+
+    TestClassExecutionResult assertStdout(Matcher<? super String> matcher) {
+        throw new UnsupportedOperationException();
+    }
+
+    TestClassExecutionResult assertStderr(Matcher<? super String> matcher) {
+        throw new UnsupportedOperationException();
+    }
+
+    TestClassExecutionResult assertConfigMethodPassed(String name) {
+        def testMethodNode = findConfigMethod(name)
+        assertEquals('PASS', testMethodNode. at status as String)
+        this
+    }
+
+    TestClassExecutionResult assertConfigMethodFailed(String name) {
+        def testMethodNode = findConfigMethod(name)
+        assertEquals('FAIL', testMethodNode. at status as String)
+        this
+    }
+
+    private def findConfigMethod(String testName) {
+        def testMethods = findConfigMethods()
+        if (!testMethods.containsKey(testName)) {
+            fail("Could not find configuration method ${testClass}.${testName}. Found ${testMethods.keySet()}")
+        }
+        testMethods[testName]
+    }
+
+    private def findConfigMethods() {
+        Map testMethods = [:]
+        testClassNode.'test-method'.findAll { it.'@is-config' == 'true' }.each {
+            testMethods.put(it. at name as String, it)
+        }
+        testMethods
+    }
+
+    private def findTestMethod(String testName) {
+        def testMethods = findTestMethods()
+        if (!testMethods.containsKey(testName)) {
+            fail("Could not find test ${testClass}.${testName}. Found ${testMethods.keySet()}")
+        }
+        testMethods[testName]
+    }
+
+    private def findTestMethods() {
+        Map testMethods = [:]
+        testClassNode.'test-method'.findAll { it.'@is-config' != 'true' }.each {
+            testMethods.put(it. at name as String, it)
+        }
+        testMethods
+    }
+
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectA-1.2-ivy.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectA-1.2-ivy.xml
new file mode 100644
index 0000000..679fb1a
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectA-1.2-ivy.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="1.0">
+	<info organisation="test"
+		module="projectA"
+		revision="1.2"
+	/>
+	<configurations>
+		<conf name="runtime" visibility="public"/>
+		<conf name="default" visibility="public" extends="runtime"/>
+		<conf name="api" visibility="public"/>
+	</configurations>
+	<publications>
+		<artifact name="projectA" type="jar" ext="jar" conf="*"/>
+	</publications>
+    <dependencies>
+        <dependency org="test" name="projectB" rev="1.5" conf="runtime->default"/>
+        <dependency org="test" name="projectB" rev="1.5" conf="api->compileTime"/>
+    </dependencies>
+</ivy-module>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectB-1.5-ivy.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectB-1.5-ivy.xml
new file mode 100644
index 0000000..18fce4a
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectB-1.5-ivy.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="1.0">
+	<info organisation="test"
+		module="projectB"
+		revision="1.5"
+	/>
+	<configurations>
+		<conf name="runtime" visibility="public"/>
+		<conf name="default" visibility="public" extends="runtime"/>
+		<conf name="compileTime" visibility="public"/>
+		<conf name="extraRuntime" visibility="public" extends="runtime"/>
+	</configurations>
+	<publications>
+		<artifact name="projectB" type="jar" ext="jar" conf="runtime"/>
+		<artifact name="projectB-api" type="jar" ext="jar" conf="compileTime"/>
+		<artifact name="projectB-extraRuntime" type="jar" ext="jar" conf="extraRuntime"/>
+	</publications>
+</ivy-module>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectWithConfigurationHierarchy.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectWithConfigurationHierarchy.gradle
new file mode 100644
index 0000000..8626e19
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveConfigurationHierarchy/projectWithConfigurationHierarchy.gradle
@@ -0,0 +1,59 @@
+import static org.junit.Assert.*
+
+configurations {
+    compile
+    runtime { extendsFrom compile }
+}
+dependencies {
+    repositories {
+        add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
+            name = 'resolver'
+            addArtifactPattern(projectDir.absolutePath + '/[artifact]-[revision].jar')
+            addIvyPattern(projectDir.absolutePath + '/[module]-[revision]-ivy.xml')
+        }
+    }
+    compile group: 'test', name: 'projectA', version: '1.2', configuration: 'api'
+    runtime group: 'test', name: 'projectA', version: '1.2'
+    runtime group: 'test', name: 'projectB', version: '1.5', configuration: 'extraRuntime'
+}
+
+file("projectA-1.2.jar").text = ''
+file("projectB-1.5.jar").text = ''
+file("projectB-api-1.5.jar").text = ''
+file("projectB-extraRuntime-1.5.jar").text = ''
+
+defaultTasks 'listJars'
+
+task listJars << {
+    def compile = configurations.compile
+
+    Set jars = compile.collect { it.name } as Set
+    assertEquals(['projectA-1.2.jar', 'projectB-api-1.5.jar'] as Set, jars)
+
+    def projectA = compile.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectA' }
+    def root = (projectA.parents as List)[0]
+    def artifacts = projectA.getAllArtifacts(root).collect { it.name } as Set
+    assertEquals(['projectA', 'projectB-api'] as Set, artifacts)
+
+    def projectB = projectA.children.find { it.moduleName == 'projectB' }
+    artifacts = projectB.getAllArtifacts(projectA).collect { it.name } as Set
+    assertEquals(['projectB-api'] as Set, artifacts)
+
+    def runtime = configurations.runtime
+
+    jars = runtime.collect { it.name } as Set
+    assertEquals(['projectA-1.2.jar', 'projectB-api-1.5.jar', 'projectB-1.5.jar', 'projectB-extraRuntime-1.5.jar'] as Set, jars)
+
+    projectA = runtime.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectA' && it.configuration == 'api' }
+    root = (projectA.parents as List)[0]
+    artifacts = projectA.getAllArtifacts(root).collect { it.name } as Set
+    // TODO - this is not right
+//    assertEquals(['projectA', 'projectB-api', 'projectB'] as Set, artifacts)
+    assertEquals(['projectA', 'projectB-api', 'projectB', 'projectB-extraRuntime'] as Set, artifacts)
+
+    projectB = runtime.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectB' && it.configuration == 'extraRuntime' }
+    artifacts = projectB.getAllArtifacts(root).collect { it.name } as Set
+    // TODO - this is not right
+//    assertEquals(['projectB', 'projectB-extraRuntime'] as Set, artifacts)
+    assertEquals(['projectB-api', 'projectB', 'projectB-extraRuntime'] as Set, artifacts)
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectA-1.2-ivy.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectA-1.2-ivy.xml
new file mode 100644
index 0000000..24f9b7b
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectA-1.2-ivy.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="1.0">
+	<info organisation="test"
+		module="projectA"
+		revision="1.2"
+	/>
+	<configurations>
+		<conf name="runtime" visibility="public"/>
+		<conf name="default" visibility="public" extends="runtime"/>
+	</configurations>
+	<publications>
+		<artifact name="projectA" type="jar" ext="jar" conf="runtime"/>
+	</publications>
+    <dependencies>
+        <dependency org="test" name="projectB" rev="1.5" conf="runtime->default"/>
+    </dependencies>
+</ivy-module>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectB-1.5-ivy.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectB-1.5-ivy.xml
new file mode 100644
index 0000000..9829213
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectB-1.5-ivy.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<ivy-module version="1.0">
+	<info organisation="test"
+		module="projectB"
+		revision="1.5"
+	/>
+	<configurations>
+		<conf name="runtime" visibility="public"/>
+		<conf name="default" visibility="public" extends="runtime"/>
+	</configurations>
+	<publications>
+		<artifact name="projectB" type="jar" ext="jar" conf="runtime"/>
+	</publications>
+    <dependencies>
+        <dependency org="test" name="projectA" rev="1.2" conf="runtime->default"/>
+    </dependencies>
+</ivy-module>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectWithCyclesInDependencyGraph.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectWithCyclesInDependencyGraph.gradle
new file mode 100644
index 0000000..1ecaba2
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInDependencyGraph/projectWithCyclesInDependencyGraph.gradle
@@ -0,0 +1,38 @@
+import static org.junit.Assert.*
+
+configurations {
+    compile
+}
+dependencies {
+    repositories {
+        add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
+            name = 'resolver'
+            addArtifactPattern(projectDir.absolutePath + '/[module]-[revision].jar')
+            addIvyPattern(projectDir.absolutePath + '/[module]-[revision]-ivy.xml')
+        }
+    }
+    compile 'test:projectA:1.2'
+}
+
+file("projectA-1.2.jar").text = ''
+file("projectB-1.5.jar").text = ''
+
+defaultTasks 'listJars'
+
+task listJars << {
+    def compile = configurations.compile
+
+    Set jars = compile.collect { it.name } as Set
+    assertEquals(['projectA-1.2.jar', 'projectB-1.5.jar'] as Set, jars)
+
+    Set artifacts = compile.resolvedConfiguration.resolvedArtifacts.collect {
+        "${it.name}-${it.type}-${it.extension}" as String
+    } as Set
+    assertEquals(['projectA-jar-jar', 'projectB-jar-jar'] as Set, artifacts)
+
+    Set modules = compile.resolvedConfiguration.resolvedArtifacts.collect {
+        def dep = it.resolvedDependency
+        "${dep.moduleGroup}-${dep.moduleName}-${dep.moduleVersion}" as String
+    } as Set
+    assertEquals(['test-projectA-1.2', 'test-projectB-1.5'] as Set, modules)
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInProjectDependencies/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInProjectDependencies/build.gradle
new file mode 100644
index 0000000..7cd52a9
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInProjectDependencies/build.gradle
@@ -0,0 +1,37 @@
+import org.junit.Assert
+
+subprojects {
+    apply plugin: 'base'
+    configurations {
+        'default'
+    }
+    task jar(type: Jar)
+    artifacts {
+        'default' jar
+    }
+}
+
+project('a') {
+    dependencies {
+        'default' project(':b')
+    }
+    task listJars {
+        dependsOn configurations['default']
+        doFirst {
+            def jars = configurations['default'].collect { it.name } as Set
+            Assert.assertEquals(['b.jar', 'c.jar'] as Set, jars)
+        }
+    }
+}
+
+project('b') {
+    dependencies {
+        'default' project(':c')
+    }
+}
+
+project('c') {
+    dependencies {
+        'default' project(':b')
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInProjectDependencies/settings.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInProjectDependencies/settings.gradle
new file mode 100644
index 0000000..ebaf9bc
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canHaveCycleInProjectDependencies/settings.gradle
@@ -0,0 +1 @@
+include 'a', 'b', 'c'
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canNestModules/projectWithNestedModules.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canNestModules/projectWithNestedModules.gradle
new file mode 100644
index 0000000..e405a51
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canNestModules/projectWithNestedModules.gradle
@@ -0,0 +1,31 @@
+import org.junit.Assert
+
+configurations {
+    compile
+}
+dependencies {
+    repositories {
+        add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
+            name = 'resolver'
+            addArtifactPattern(projectDir.absolutePath + '/[module]-[revision].jar')
+        }
+    }
+    compile module('test:projectA:1.2') {
+        module('test:projectB:1.5') {
+            dependencies('test:projectC:2.0')
+        }
+    }
+}
+
+defaultTasks 'listJars'
+
+file("projectA-1.2.jar").text = ''
+file("projectB-1.5.jar").text = ''
+file("projectC-2.0.jar").text = ''
+
+task listJars << {
+    List jars = configurations.compile.collect { it.name }
+    Assert.assertTrue(jars.contains('projectA-1.2.jar'))
+    Assert.assertTrue(jars.contains('projectB-1.5.jar'))
+    Assert.assertTrue(jars.contains('projectC-2.0.jar'))
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectA-1.2-ivy.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectA-1.2-ivy.xml
new file mode 100644
index 0000000..216ab82
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectA-1.2-ivy.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="1.0">
+	<info organisation="test"
+		module="projectA"
+		revision="1.2"
+	/>
+	<configurations>
+		<conf name="runtime" visibility="public"/>
+		<conf name="default" visibility="public" extends="runtime"/>
+	</configurations>
+	<publications>
+		<artifact name="projectA" type="jar" ext="jar" conf="*"/>
+	</publications>
+    <dependencies>
+        <dependency org="test" name="projectB" rev="latest.release" conf="runtime->default"/>
+    </dependencies>
+</ivy-module>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectB-1.5-ivy.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectB-1.5-ivy.xml
new file mode 100644
index 0000000..4003cbb
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectB-1.5-ivy.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="1.0">
+    <info organisation="test" module="projectB" revision="1.5" status="release"/>
+    <configurations>
+        <conf name="runtime" visibility="public"/>
+        <conf name="default" visibility="public" extends="runtime"/>
+    </configurations>
+    <publications>
+        <artifact name="projectB" type="jar" ext="jar" conf="runtime"/>
+    </publications>
+</ivy-module>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectWithDynamicVersions.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectWithDynamicVersions.gradle
new file mode 100644
index 0000000..285f98f
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/canUseDynamicVersions/projectWithDynamicVersions.gradle
@@ -0,0 +1,33 @@
+import static org.junit.Assert.*
+
+configurations {
+    compile
+}
+dependencies {
+    repositories {
+        add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
+            name = 'resolver'
+            addArtifactPattern(projectDir.absolutePath + '/[artifact]-[revision].jar')
+            addIvyPattern(projectDir.absolutePath + '/[module]-[revision]-ivy.xml')
+        }
+    }
+    compile group: 'test', name: 'projectA', version: '1.+'
+}
+
+file("projectA-1.2.jar").text = ''
+file("projectB-1.5.jar").text = ''
+
+defaultTasks 'listJars'
+
+task listJars << {
+    def compile = configurations.compile
+
+    Set jars = compile.collect { it.name } as Set
+    assertEquals(['projectA-1.2.jar', 'projectB-1.5.jar'] as Set, jars)
+
+    def projectA = compile.resolvedConfiguration.firstLevelModuleDependencies.find { it.moduleName == 'projectA' }
+    assertEquals('1.2', projectA.moduleVersion)
+
+    def projectB = projectA.children.find { it.moduleName == 'projectB' }
+    assertEquals('1.5', projectB.moduleVersion)
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-1.2-ivy.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-1.2-ivy.xml
new file mode 100644
index 0000000..24f9b7b
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-1.2-ivy.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="1.0">
+	<info organisation="test"
+		module="projectA"
+		revision="1.2"
+	/>
+	<configurations>
+		<conf name="runtime" visibility="public"/>
+		<conf name="default" visibility="public" extends="runtime"/>
+	</configurations>
+	<publications>
+		<artifact name="projectA" type="jar" ext="jar" conf="runtime"/>
+	</publications>
+    <dependencies>
+        <dependency org="test" name="projectB" rev="1.5" conf="runtime->default"/>
+    </dependencies>
+</ivy-module>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-2.0-ivy.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-2.0-ivy.xml
new file mode 100644
index 0000000..51e36e6
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectA-2.0-ivy.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="1.0">
+	<info organisation="test"
+		module="projectA"
+		revision="2.0"
+	/>
+	<configurations>
+		<conf name="runtime" visibility="public"/>
+		<conf name="default" visibility="public" extends="runtime"/>
+	</configurations>
+	<publications>
+		<artifact name="projectA" type="jar" ext="jar" conf="runtime"/>
+	</publications>
+    <dependencies>
+        <dependency org="test" name="projectB" rev="2.1.5" conf="runtime->default"/>
+    </dependencies>
+</ivy-module>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-1.5-ivy.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-1.5-ivy.xml
new file mode 100644
index 0000000..bd90d33
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-1.5-ivy.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="1.0">
+	<info organisation="test"
+		module="projectB"
+		revision="1.5"
+	/>
+	<configurations>
+		<conf name="runtime" visibility="public"/>
+		<conf name="default" visibility="public" extends="runtime"/>
+	</configurations>
+	<publications>
+		<artifact name="projectB" type="jar" ext="jar" conf="runtime"/>
+	</publications>
+</ivy-module>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-2.1.5-ivy.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-2.1.5-ivy.xml
new file mode 100644
index 0000000..a713cf0
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectB-2.1.5-ivy.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ivy-module version="1.0">
+	<info organisation="test"
+		module="projectB"
+		revision="2.1.5"
+	/>
+	<configurations>
+		<conf name="runtime" visibility="public"/>
+		<conf name="default" visibility="public" extends="runtime"/>
+	</configurations>
+	<publications>
+		<artifact name="projectB" type="jar" ext="jar" conf="runtime"/>
+	</publications>
+</ivy-module>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectWithConflicts.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectWithConflicts.gradle
new file mode 100644
index 0000000..a027959
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/projectWithConflicts.gradle
@@ -0,0 +1,57 @@
+import static org.junit.Assert.*
+
+allprojects {
+    configurations {
+        evictedTransitive
+        evictedDirect
+        multiProject
+        add('default')
+    }
+    dependencies {
+        repositories {
+            add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
+                name = 'resolver'
+                addArtifactPattern(projectDir.absolutePath + '/[module]-[revision].jar')
+                addIvyPattern(projectDir.absolutePath + '/[module]-[revision]-ivy.xml')
+            }
+        }
+    }
+}
+
+dependencies {
+    // projectA-1.2 depends on projectB-1.5, which should be evicted by projectB-2.1.5
+    evictedTransitive 'test:projectA:1.2'
+    evictedTransitive 'test:projectB:2.1.5'
+
+    // projectA-2.0 depends on projectB-2.1.5, which should evict projectB-1.5
+    evictedDirect 'test:projectA:2.0'
+    evictedDirect 'test:projectB:1.5'
+
+    // subproject depends on projectA-2.0, which should evict projectA-1.2
+    multiProject 'test:projectA:1.2'
+    multiProject project(':subproject')
+}
+
+project(':subproject') {
+    dependencies {
+        add('default', 'test:projectA:2.0')
+    }
+}
+
+file("projectA-1.2.jar").text = ''
+file("projectA-2.0.jar").text = ''
+file("projectB-1.5.jar").text = ''
+file("projectB-2.1.5.jar").text = ''
+
+defaultTasks 'check'
+
+task check << {
+    def jars = configurations.evictedTransitive.collect { it.name }
+    assertEquals(['projectA-1.2.jar', 'projectB-2.1.5.jar'] as Set, jars as Set)
+
+    jars = configurations.evictedDirect.collect { it.name }
+    assertEquals(['projectA-2.0.jar', 'projectB-2.1.5.jar'] as Set, jars as Set)
+
+    jars = configurations.multiProject.collect { it.name }
+    assertEquals(['projectA-2.0.jar', 'projectB-2.1.5.jar'] as Set, jars as Set)
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/settings.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/settings.gradle
new file mode 100644
index 0000000..e7bc057
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/dependencyReportWithConflicts/settings.gradle
@@ -0,0 +1,3 @@
+include 'subproject'
+
+rootProject.buildFileName = 'projectWithConflicts.gradle'
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/reportsUnknownDependencyError/projectWithUnknownDependency.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/reportsUnknownDependencyError/projectWithUnknownDependency.gradle
new file mode 100644
index 0000000..bbb94dc
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ArtifactDependenciesIntegrationTest/reportsUnknownDependencyError/projectWithUnknownDependency.gradle
@@ -0,0 +1,16 @@
+import org.junit.Assert
+
+configurations {
+    compile
+}
+dependencies {
+    compile 'test:unknownProjectA:1.2'
+    compile 'test:unknownProjectB:2.1.5'
+}
+
+defaultTasks 'listJars'
+
+task listJars << {
+    configurations.compile.resolve()
+    Assert.fail()
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/BackwardsCompatibilityIntegrationTest/canBuildJavaProject/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/BackwardsCompatibilityIntegrationTest/canBuildJavaProject/build.gradle
new file mode 100644
index 0000000..1aa6152
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/BackwardsCompatibilityIntegrationTest/canBuildJavaProject/build.gradle
@@ -0,0 +1,3 @@
+usePlugin('java')
+
+task custom(type: org.gradle.CustomTask)
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/BackwardsCompatibilityIntegrationTest/canBuildJavaProject/buildSrc/src/main/groovy/org/gradle/CustomTask.groovy b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/BackwardsCompatibilityIntegrationTest/canBuildJavaProject/buildSrc/src/main/groovy/org/gradle/CustomTask.groovy
new file mode 100644
index 0000000..549422b
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/BackwardsCompatibilityIntegrationTest/canBuildJavaProject/buildSrc/src/main/groovy/org/gradle/CustomTask.groovy
@@ -0,0 +1,10 @@
+package org.gradle
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.TaskAction
+
+class CustomTask extends DefaultTask {
+    @TaskAction
+    def go() {
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/BackwardsCompatibilityIntegrationTest/canBuildJavaProject/src/main/java/org/gradle/Person.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/BackwardsCompatibilityIntegrationTest/canBuildJavaProject/src/main/java/org/gradle/Person.java
new file mode 100644
index 0000000..8df4e4e
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/BackwardsCompatibilityIntegrationTest/canBuildJavaProject/src/main/java/org/gradle/Person.java
@@ -0,0 +1,5 @@
+package org.gradle;
+
+public interface Person {
+    String getName();
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/build.gradle
new file mode 100644
index 0000000..f3208d8
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/build.gradle
@@ -0,0 +1,9 @@
+dependencies {
+    runtime 'commons-collections:commons-collections:3.2 at jar'
+}
+
+sourceSets {
+    integTest {
+        java.srcDirs = ['src/integTest/java']
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/src/integTest/java/org/gradle/SomeClass.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/src/integTest/java/org/gradle/SomeClass.java
new file mode 100644
index 0000000..ad3b8c3
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/src/integTest/java/org/gradle/SomeClass.java
@@ -0,0 +1,3 @@
+package org.gradle;
+
+public class SomeClass {}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/src/main/java/org/gradle/api/PersonList.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/src/main/java/org/gradle/api/PersonList.java
new file mode 100644
index 0000000..7c10e39
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/src/main/java/org/gradle/api/PersonList.java
@@ -0,0 +1,5 @@
+package org.gradle.integtests.IdeaIntegrationTest.api.src.main.java.org.gradle.api;
+
+public class PersonList {
+   
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/src/main/resources/someprops.properties b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/src/main/resources/someprops.properties
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/src/test/java/org/gradle/shared/PersonTest.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/src/test/java/org/gradle/shared/PersonTest.java
new file mode 100644
index 0000000..02cee2c
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/src/test/java/org/gradle/shared/PersonTest.java
@@ -0,0 +1,12 @@
+package org.gradle.shared;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.util.Properties;
+
+public class PersonTest extends TestCase {
+    public void testTest() {
+        assertTrue(true);
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/src/test/resources/someprops.properties b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/api/src/test/resources/someprops.properties
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiClasspath.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiClasspath.xml
new file mode 100644
index 0000000..4ff1df4
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiClasspath.xml
@@ -0,0 +1,11 @@
+<classpath>
+  <classpathentry kind="output" path="bin"/>
+  <classpathentry output="build/classes/integTest" kind="src" path="src/integTest/java"/>
+  <classpathentry output="build/classes/main" kind="src" path="src/main/resources"/>
+  <classpathentry output="build/classes/main" kind="src" path="src/main/java"/>
+  <classpathentry output="build/classes/test" kind="src" path="src/test/resources"/>
+  <classpathentry output="build/classes/test" kind="src" path="src/test/java"/>
+  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
+  <classpathentry sourcepath="GRADLE_CACHE/commons-collections/commons-collections/sources/commons-collections-3.2-sources.jar" kind="var" path="GRADLE_CACHE/commons-collections/commons-collections/jars/commons-collections-3.2.jar" exported="true"/>
+  <classpathentry sourcepath="GRADLE_CACHE/junit/junit/sources/junit-4.7-sources.jar" kind="var" path="GRADLE_CACHE/junit/junit/jars/junit-4.7.jar" exported="true"/>
+</classpath>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiProject.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiProject.xml
new file mode 100644
index 0000000..dc51005
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/apiProject.xml
@@ -0,0 +1,21 @@
+<projectDescription>
+  <name>
+    api
+  </name>
+  <comment/>
+  <projects/>
+  <natures>
+    <nature>
+      org.eclipse.jdt.core.javanature
+    </nature>
+  </natures>
+  <buildSpec>
+    <buildCommand>
+      <name>
+        org.eclipse.jdt.core.javabuilder
+      </name>
+      <arguments/>
+    </buildCommand>
+  </buildSpec>
+  <links/>
+</projectDescription>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectClasspath.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectClasspath.xml
new file mode 100644
index 0000000..d857ce5
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectClasspath.xml
@@ -0,0 +1,11 @@
+<classpath>
+  <classpathentry kind="output" path="bin"/>
+  <classpathentry output="build/classes/main" kind="src" path="src/main/resources"/>
+  <classpathentry output="build/classes/main" kind="src" path="src/main/java"/>
+  <classpathentry output="build/classes/main" kind="src" path="src/main/groovy"/>
+  <classpathentry output="build/classes/test" kind="src" path="src/test/resources"/>
+  <classpathentry output="build/classes/test" kind="src" path="src/test/java"/>
+  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
+  <classpathentry sourcepath="GRADLE_CACHE/commons-collections/commons-collections/sources/commons-collections-3.2-sources.jar" kind="var" path="GRADLE_CACHE/commons-collections/commons-collections/jars/commons-collections-3.2.jar" exported="true"/>
+  <classpathentry sourcepath="GRADLE_CACHE/junit/junit/sources/junit-4.7-sources.jar" kind="var" path="GRADLE_CACHE/junit/junit/jars/junit-4.7.jar" exported="true"/>
+</classpath>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectProject.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectProject.xml
new file mode 100644
index 0000000..536a6ba
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/groovyprojectProject.xml
@@ -0,0 +1,24 @@
+<projectDescription>
+  <name>
+    groovyproject
+  </name>
+  <comment/>
+  <projects/>
+  <natures>
+    <nature>
+      org.eclipse.jdt.groovy.core.groovyNature
+    </nature>
+    <nature>
+      org.eclipse.jdt.core.javanature
+    </nature>
+  </natures>
+  <buildSpec>
+    <buildCommand>
+      <name>
+        org.eclipse.jdt.core.javabuilder
+      </name>
+      <arguments/>
+    </buildCommand>
+  </buildSpec>
+  <links/>
+</projectDescription>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/masterProject.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/masterProject.xml
new file mode 100644
index 0000000..a072ffa
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/masterProject.xml
@@ -0,0 +1,10 @@
+<projectDescription>
+  <name>
+    master
+  </name>
+  <comment/>
+  <projects/>
+  <natures/>
+  <buildSpec/>
+  <links/>
+</projectDescription>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceClasspath.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceClasspath.xml
new file mode 100644
index 0000000..a1a77fb
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceClasspath.xml
@@ -0,0 +1,18 @@
+<classpath>
+  <classpathentry kind="output" path="bin"/>
+  <classpathentry output="build/classes/main" kind="src" path="src/main/java"/>
+  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" exported="true"/>
+  <classpathentry kind="src" path="/api" exported="true"/>
+  <classpathentry sourcepath="GRADLE_CACHE/commons-lang/commons-lang/sources/commons-lang-2.5-sources.jar" kind="var" path="GRADLE_CACHE/commons-lang/commons-lang/jars/commons-lang-2.5.jar" exported="true">
+    <attributes>
+      <attribute name="javadoc_location" value="GRADLE_CACHE/commons-lang/commons-lang/javadocs/commons-lang-2.5-javadoc.jar"/>
+    </attributes>
+  </classpathentry>
+  <classpathentry sourcepath="GRADLE_CACHE/commons-io/commons-io/sources/commons-io-1.2-sources.jar" kind="var" path="GRADLE_CACHE/commons-io/commons-io/jars/commons-io-1.2.jar" exported="true">
+    <attributes>
+      <attribute name="javadoc_location" value="GRADLE_CACHE/commons-io/commons-io/javadocs/commons-io-1.2-javadoc.jar"/>
+    </attributes>
+  </classpathentry>
+  <classpathentry sourcepath="GRADLE_CACHE/junit/junit/sources/junit-4.7-sources.jar" kind="var" path="GRADLE_CACHE/junit/junit/jars/junit-4.7.jar" exported="true"/>
+  <classpathentry sourcepath="GRADLE_CACHE/org.slf4j/slf4j-api/sources/slf4j-api-1.5.8-sources.jar" kind="var" path="GRADLE_CACHE/org.slf4j/slf4j-api/jars/slf4j-api-1.5.8.jar" exported="true"/>
+</classpath>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceProject.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceProject.xml
new file mode 100644
index 0000000..ce3f506
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceProject.xml
@@ -0,0 +1,39 @@
+<projectDescription>
+  <name>
+    webservice
+  </name>
+  <comment/>
+  <projects/>
+  <natures>
+    <nature>
+      org.eclipse.jdt.core.javanature
+    </nature>
+    <nature>
+      org.eclipse.wst.common.project.facet.core.nature
+    </nature>
+    <nature>
+      org.eclipse.wst.common.modulecore.ModuleCoreNature
+    </nature>
+  </natures>
+  <buildSpec>
+    <buildCommand>
+      <name>
+        org.eclipse.jdt.core.javabuilder
+      </name>
+      <arguments/>
+    </buildCommand>
+    <buildCommand>
+      <name>
+        org.eclipse.wst.common.project.facet.core.builder
+      </name>
+      <arguments/>
+    </buildCommand>
+    <buildCommand>
+      <name>
+        org.eclipse.wst.validation.validationbuilder
+      </name>
+      <arguments/>
+    </buildCommand>
+  </buildSpec>
+  <links/>
+</projectDescription>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpComponent.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpComponent.xml
new file mode 100644
index 0000000..77ad972
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpComponent.xml
@@ -0,0 +1,17 @@
+<project-modules id="moduleCoreId" project-version="2.0">
+  <wb-module deploy-name="webservice">
+    <property name="java-output-path" value="build/classes/main"/>
+    <wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/java"/>
+    <wb-resource deploy-path="/" source-path="src/main/webapp"/>
+    <dependent-module deploy-path="/WEB-INF/lib" handle="module:/resource/api/api">
+      <dependency-type>
+        uses
+      </dependency-type>
+    </dependent-module>
+    <dependent-module deploy-path="/WEB-INF/lib" handle="module:/classpath/var/GRADLE_CACHE//commons-lang/commons-lang/jars/commons-lang-2.5.jar">
+      <dependency-type>
+        uses
+      </dependency-type>
+    </dependent-module>
+  </wb-module>
+</project-modules>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpFacet.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpFacet.xml
new file mode 100644
index 0000000..b5d8ac9
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/webserviceWtpFacet.xml
@@ -0,0 +1,6 @@
+<faceted-project>
+  <fixed facet="jst.java"/>
+  <fixed facet="jst.web"/>
+  <installed facet="jst.web" version="2.4"/>
+  <installed facet="jst.java" version="1.4"/>
+</faceted-project>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/build.gradle
new file mode 100644
index 0000000..2e45a1c
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/build.gradle
@@ -0,0 +1,5 @@
+apply plugin: 'groovy'
+
+dependencies {
+    runtime 'commons-collections:commons-collections:3.2 at jar'
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/src/main/groovy/script.groovy b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/src/main/groovy/script.groovy
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/src/main/java/org/gradle/api/PersonList.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/src/main/java/org/gradle/api/PersonList.java
new file mode 100644
index 0000000..7c10e39
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/src/main/java/org/gradle/api/PersonList.java
@@ -0,0 +1,5 @@
+package org.gradle.integtests.IdeaIntegrationTest.api.src.main.java.org.gradle.api;
+
+public class PersonList {
+   
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/src/main/resources/someprops.properties b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/src/main/resources/someprops.properties
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/src/test/java/org/gradle/shared/PersonTest.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/src/test/java/org/gradle/shared/PersonTest.java
new file mode 100644
index 0000000..02cee2c
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/src/test/java/org/gradle/shared/PersonTest.java
@@ -0,0 +1,12 @@
+package org.gradle.shared;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.util.Properties;
+
+public class PersonTest extends TestCase {
+    public void testTest() {
+        assertTrue(true);
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/src/test/resources/someprops.properties b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/groovyproject/src/test/resources/someprops.properties
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/build.gradle
new file mode 100644
index 0000000..4ca1c5c
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/build.gradle
@@ -0,0 +1,65 @@
+import org.custommonkey.xmlunit.Diff
+import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier
+import org.custommonkey.xmlunit.XMLAssert
+
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath 'xmlunit:xmlunit:1.3'
+    }
+}
+
+defaultTasks 'eclipse', 'cleanEclipse'
+
+allprojects {
+    apply plugin: 'eclipse'
+}
+
+subprojects {
+    apply plugin: 'java'
+
+    repositories {
+        mavenCentral()
+    }
+
+    dependencies {
+        testCompile 'junit:junit:4.7'
+    }
+
+    group = 'org.gradle'
+    version = '1.0'
+
+    eclipseClasspath {
+        downloadJavadoc = true
+        doLast {
+            compareXmlWithIgnoringOrder(file("$rootDir/../expectedFiles/${project.name}Classpath.xml").text,
+                    file(".classpath").text)
+        }
+    }
+}
+
+allprojects {
+    eclipseProject.doLast {
+        compareXmlWithIgnoringOrder(file("$rootDir/../expectedFiles/${project.name}Project.xml").text,
+                file(".project").text)
+    }
+    cleanEclipse.doLast {
+        assert !file(".classpath").isFile()
+        assert !file(".project").isFile()
+    }
+}
+
+void compareXmlWithIgnoringOrder(String expectedXml, String actualXml) {
+    Diff diff = new Diff(expectedXml, actualXml)
+    diff.overrideElementQualifier(new ElementNameAndAttributeQualifier())
+    XMLAssert.assertXMLEqual(diff, true);
+}
+
+String getExpectedXml(Project subProject, String filename) {
+    def cache = new File(gradle.gradleUserHomeDir, "/cache")
+    def path = org.gradle.plugins.idea.model.Path.getRelativePath(subProject.projectDir, '$MODULE_DIR$', cache)
+    return rootProject.file("expectedFiles/$filename").text.replace('@CACHE_DIR@', path)
+}
+
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/settings.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/settings.gradle
new file mode 100644
index 0000000..e08e661
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/master/settings.gradle
@@ -0,0 +1 @@
+includeFlat "api", "webservice", "groovyproject"
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webservice/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webservice/build.gradle
new file mode 100644
index 0000000..c263edc
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webservice/build.gradle
@@ -0,0 +1,25 @@
+apply plugin: 'war'
+
+version = '2.5'
+
+dependencies {
+    providedCompile 'org.slf4j:slf4j-api:1.5.8'
+    providedRuntime "commons-io:commons-io:1.2"
+    compile project(':api')
+    runtime "commons-lang:commons-lang:2.5"
+}
+
+eclipseWtp {
+    doLast {
+        compareXmlWithIgnoringOrder(file("$rootDir/../expectedFiles/webserviceWtpComponent.xml").text,
+                file(".settings/org.eclipse.wst.common.component.xml").text)
+        compareXmlWithIgnoringOrder(file("$rootDir/../expectedFiles/webserviceWtpFacet.xml").text,
+                file(".settings/org.eclipse.wst.common.project.facet.core.xml").text)
+    }
+}
+
+cleanEclipse {
+    doLast {
+        assert !file(".settings").isFile()
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webservice/src/main/java/org/gradle/webservice/TestTest.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webservice/src/main/java/org/gradle/webservice/TestTest.java
new file mode 100644
index 0000000..f7ce8c2
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/EclipseIntegrationTest/canCreateAndDeleteMetaData/webservice/src/main/java/org/gradle/webservice/TestTest.java
@@ -0,0 +1,5 @@
+package org.gradle.integtests.IdeaIntegrationTest.webservice.src.main.java.org.gradle.webservice;
+
+public class TestTest {
+    
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteCommands/canExecuteCommands.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteCommands/canExecuteCommands.gradle
new file mode 100644
index 0000000..f5c6f01
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteCommands/canExecuteCommands.gradle
@@ -0,0 +1,33 @@
+apply plugin: 'java'
+
+defaultTasks 'execTask', 'execByMethod'
+
+task execTask(type: Exec) {
+    dependsOn sourceSets.main.runtimeClasspath
+    testFile = file("$buildDir/$name")
+    executable = Jvm.current().getJavaExecutable()
+    workingDir = buildDir
+    args '-cp', sourceSets.main.runtimeClasspath.asPath, 'org.gradle.TestMain', testFile.name
+    doFirst {
+        mkdir buildDir
+    }
+    doLast {
+        assert testFile.exists()
+    }
+}
+
+task execByMethod {
+    dependsOn sourceSets.main.runtimeClasspath
+    testFile = file("$buildDir/$name")
+    doFirst {
+        mkdir buildDir
+        exec {
+            executable = Jvm.current().getJavaExecutable()
+            workingDir = buildDir
+            args '-cp', sourceSets.main.runtimeClasspath.asPath, 'org.gradle.TestMain', testFile.name
+        }
+    }
+    doLast {
+        assert testFile.exists()
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteCommands/src/main/java/org/gradle/TestMain.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteCommands/src/main/java/org/gradle/TestMain.java
new file mode 100644
index 0000000..40778ca
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteCommands/src/main/java/org/gradle/TestMain.java
@@ -0,0 +1,9 @@
+package org.gradle;
+
+import java.io.File;
+
+public class TestMain {
+    public static void main(String[] args) throws Exception {
+        new File(args[0]).createNewFile();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteJava/canExecuteJava.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteJava/canExecuteJava.gradle
new file mode 100644
index 0000000..04ab680
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteJava/canExecuteJava.gradle
@@ -0,0 +1,27 @@
+apply plugin: 'java'
+
+defaultTasks 'javaexecTask', 'javaexecByMethod'
+
+task javaexecTask(type: JavaExec, dependsOn: classes) {
+    testFile = file("$buildDir/$name")
+    classpath(sourceSets.main.classesDir)
+    main = 'org.gradle.TestMain'
+    args testFile.absolutePath
+    doLast {
+        assert testFile.exists()
+    }
+}
+
+task javaexecByMethod(dependsOn: classes) {
+    testFile = file("$buildDir/$name")
+    doFirst {
+        javaexec {
+            classpath(sourceSets.main.classesDir)
+            main = 'org.gradle.TestMain'
+            args testFile.absolutePath
+        }
+    }
+    doLast {
+        assert testFile.exists()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteJava/src/main/java/org/gradle/TestMain.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteJava/src/main/java/org/gradle/TestMain.java
new file mode 100644
index 0000000..40778ca
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ExecIntegrationTest/canExecuteJava/src/main/java/org/gradle/TestMain.java
@@ -0,0 +1,9 @@
+package org.gradle;
+
+import java.io.File;
+
+public class TestMain {
+    public static void main(String[] args) throws Exception {
+        new File(args[0]).createNewFile();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/api/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/api/build.gradle
new file mode 100644
index 0000000..dd769ba
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/api/build.gradle
@@ -0,0 +1,11 @@
+dependencies {
+    runtime 'commons-collections:commons-collections:3.2 at jar'
+}
+
+ideaModule.doLast {
+    compareXmlWithIgnoringOrder(getExpectedXml(project, 'expectedApiModule.xml'), file("api.iml").text)
+}
+
+cleanIdea.doLast {
+    assert !file("api/api.iml").isFile()
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/api/src/main/java/org/gradle/api/PersonList.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/api/src/main/java/org/gradle/api/PersonList.java
new file mode 100644
index 0000000..7c10e39
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/api/src/main/java/org/gradle/api/PersonList.java
@@ -0,0 +1,5 @@
+package org.gradle.integtests.IdeaIntegrationTest.api.src.main.java.org.gradle.api;
+
+public class PersonList {
+   
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/api/src/test/java/org/gradle/shared/PersonTest.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/api/src/test/java/org/gradle/shared/PersonTest.java
new file mode 100644
index 0000000..02cee2c
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/api/src/test/java/org/gradle/shared/PersonTest.java
@@ -0,0 +1,12 @@
+package org.gradle.shared;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.util.Properties;
+
+public class PersonTest extends TestCase {
+    public void testTest() {
+        assertTrue(true);
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/build.gradle
new file mode 100644
index 0000000..42b0cee
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/build.gradle
@@ -0,0 +1,71 @@
+import org.custommonkey.xmlunit.Diff
+import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier
+import org.custommonkey.xmlunit.XMLAssert
+
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath 'xmlunit:xmlunit:1.3'
+    }
+}
+
+defaultTasks 'idea', 'cleanIdea'
+
+allprojects {
+    apply plugin: 'idea'
+}
+
+subprojects {
+    apply plugin: 'java'
+
+    ideaModule {
+        downloadJavadoc = true
+    }
+
+    repositories {
+        mavenCentral()
+    }
+
+    dependencies {
+        testCompile 'junit:junit:4.7'
+    }
+
+    group = 'org.gradle'
+    version = '1.0'
+}
+
+ideaModule.doLast {
+    compareXmlWithIgnoringOrder(file('expectedFiles/expectedRootModule.xml').text,
+            file("${project.name}.iml").text)
+}
+
+ideaProject.doLast {
+    compareXmlWithIgnoringOrder(file('expectedFiles/expectedProjectFile.xml').text,
+            file("${project.name}.ipr").text)
+}
+
+ideaWorkspace.doLast {
+    compareXmlWithIgnoringOrder(file('expectedFiles/expectedIwsFile.xml').text,
+            file("${project.name}.iws").text )
+}
+
+cleanIdea.doLast {
+    assert !file("${project.name}.iml").isFile()
+    assert !file("${project.name}.ipr").isFile()
+    assert !file("${project.name}.iws").isFile()
+}
+
+void compareXmlWithIgnoringOrder(String expectedXml, String actualXml) {
+    Diff diff = new Diff(expectedXml, actualXml)
+    diff.overrideElementQualifier(new ElementNameAndAttributeQualifier())
+    XMLAssert.assertXMLEqual(diff, true);
+}
+
+String getExpectedXml(Project subProject, String filename) {
+    def cache = new File(gradle.gradleUserHomeDir, "/cache")
+    def path = org.gradle.plugins.idea.model.Path.getRelativePath(subProject.projectDir, '$MODULE_DIR$', cache)
+    return rootProject.file("expectedFiles/$filename").text.replace('@CACHE_DIR@', path)
+}
+
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/expectedApiModule.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/expectedApiModule.xml
new file mode 100644
index 0000000..a5fe291
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/expectedApiModule.xml
@@ -0,0 +1,40 @@
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <exclude-output/>
+    <orderEntry type="inheritedJdk"/>
+    <content url="file://$MODULE_DIR$/">
+      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false"/>
+      <sourceFolder url="file://$MODULE_DIR$/src/main/resources" isTestSource="false"/>
+      <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true"/>
+      <sourceFolder url="file://$MODULE_DIR$/src/test/resources" isTestSource="true"/>
+      <excludeFolder url="file://$MODULE_DIR$/.gradle"/>
+      <excludeFolder url="file://$MODULE_DIR$/build"/>
+    </content>
+    <orderEntry type="sourceFolder" forTests="false"/>
+    <output url="file://$MODULE_DIR$/build/classes/main"/>
+    <output-test url="file://$MODULE_DIR$/build/classes/test"/>
+    <orderEntry type="module-library" exported="" scope="RUNTIME">
+      <library>
+        <CLASSES>
+          <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/jars/commons-collections-3.2.jar!/"/>
+        </CLASSES>
+        <JAVADOC/>
+        <SOURCES>
+          <root url="jar://@CACHE_DIR@/commons-collections/commons-collections/sources/commons-collections-3.2-sources.jar!/"/>
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library>
+        <CLASSES>
+          <root url="jar://@CACHE_DIR@/junit/junit/jars/junit-4.7.jar!/"/>
+        </CLASSES>
+        <JAVADOC/>
+        <SOURCES>
+          <root url="jar://@CACHE_DIR@/junit/junit/sources/junit-4.7-sources.jar!/"/>
+        </SOURCES>
+      </library>
+    </orderEntry>
+  </component>
+  <component name="ModuleRootManager"/>
+</module>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/expectedIwsFile.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/expectedIwsFile.xml
new file mode 100644
index 0000000..6e4b035
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/expectedIwsFile.xml
@@ -0,0 +1,212 @@
+<project version="4">
+  <component name="ChangeListManager">
+    <option name="TRACKING_ENABLED" value="true"/>
+    <option name="SHOW_DIALOG" value="false"/>
+    <option name="HIGHLIGHT_CONFLICTS" value="true"/>
+    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false"/>
+    <option name="LAST_RESOLUTION" value="IGNORE"/>
+  </component>
+  <component name="ChangesViewManager" flattened_view="true" show_ignored="false"/>
+  <component name="CreatePatchCommitExecutor">
+    <option name="PATCH_PATH" value=""/>
+    <option name="REVERSE_PATCH" value="false"/>
+  </component>
+  <component name="DaemonCodeAnalyzer">
+    <disable_hints/>
+  </component>
+  <component name="DebuggerManager">
+    <breakpoint_any>
+      <breakpoint>
+        <option name="NOTIFY_CAUGHT" value="true"/>
+        <option name="NOTIFY_UNCAUGHT" value="true"/>
+        <option name="ENABLED" value="false"/>
+        <option name="LOG_ENABLED" value="false"/>
+        <option name="LOG_EXPRESSION_ENABLED" value="false"/>
+        <option name="SUSPEND_POLICY" value="SuspendAll"/>
+        <option name="COUNT_FILTER_ENABLED" value="false"/>
+        <option name="COUNT_FILTER" value="0"/>
+        <option name="CONDITION_ENABLED" value="false"/>
+        <option name="CLASS_FILTERS_ENABLED" value="false"/>
+        <option name="INSTANCE_FILTERS_ENABLED" value="false"/>
+        <option name="CONDITION" value=""/>
+        <option name="LOG_MESSAGE" value=""/>
+      </breakpoint>
+      <breakpoint>
+        <option name="NOTIFY_CAUGHT" value="true"/>
+        <option name="NOTIFY_UNCAUGHT" value="true"/>
+        <option name="ENABLED" value="false"/>
+        <option name="LOG_ENABLED" value="false"/>
+        <option name="LOG_EXPRESSION_ENABLED" value="false"/>
+        <option name="SUSPEND_POLICY" value="SuspendAll"/>
+        <option name="COUNT_FILTER_ENABLED" value="false"/>
+        <option name="COUNT_FILTER" value="0"/>
+        <option name="CONDITION_ENABLED" value="false"/>
+        <option name="CLASS_FILTERS_ENABLED" value="false"/>
+        <option name="INSTANCE_FILTERS_ENABLED" value="false"/>
+        <option name="CONDITION" value=""/>
+        <option name="LOG_MESSAGE" value=""/>
+      </breakpoint>
+    </breakpoint_any>
+    <breakpoint_rules/>
+    <ui_properties/>
+  </component>
+  <component name="ModuleEditorState">
+    <option name="LAST_EDITED_MODULE_NAME"/>
+    <option name="LAST_EDITED_TAB_NAME"/>
+  </component>
+  <component name="ProjectInspectionProfilesVisibleTreeState">
+    <entry key="Project Default">
+      <profile-state/>
+    </entry>
+  </component>
+  <component name="ProjectLevelVcsManager">
+    <OptionsSetting value="true" id="Add"/>
+    <OptionsSetting value="true" id="Remove"/>
+    <OptionsSetting value="true" id="Checkout"/>
+    <OptionsSetting value="true" id="Update"/>
+    <OptionsSetting value="true" id="Status"/>
+    <OptionsSetting value="true" id="Edit"/>
+    <ConfirmationsSetting value="0" id="Add"/>
+    <ConfirmationsSetting value="0" id="Remove"/>
+  </component>
+  <component name="ProjectReloadState">
+    <option name="STATE" value="0"/>
+  </component>
+  <component name="PropertiesComponent">
+    <property name="GoToFile.includeJavaFiles" value="false"/>
+    <property name="GoToClass.toSaveIncludeLibraries" value="false"/>
+    <property name="MemberChooser.sorted" value="false"/>
+    <property name="MemberChooser.showClasses" value="true"/>
+    <property name="GoToClass.includeLibraries" value="false"/>
+    <property name="MemberChooser.copyJavadoc" value="false"/>
+  </component>
+  <component name="RunManager">
+    <configuration default="true" type="Remote" factoryName="Remote">
+      <option name="USE_SOCKET_TRANSPORT" value="true"/>
+      <option name="SERVER_MODE" value="false"/>
+      <option name="SHMEM_ADDRESS" value="javadebug"/>
+      <option name="HOST" value="localhost"/>
+      <option name="PORT" value="5005"/>
+      <method>
+        <option name="BuildArtifacts" enabled="false"/>
+      </method>
+    </configuration>
+    <configuration default="true" type="Applet" factoryName="Applet">
+      <module name=""/>
+      <option name="MAIN_CLASS_NAME"/>
+      <option name="HTML_FILE_NAME"/>
+      <option name="HTML_USED" value="false"/>
+      <option name="WIDTH" value="400"/>
+      <option name="HEIGHT" value="300"/>
+      <option name="POLICY_FILE" value="$APPLICATION_HOME_DIR$/bin/appletviewer.policy"/>
+      <option name="VM_PARAMETERS"/>
+      <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false"/>
+      <option name="ALTERNATIVE_JRE_PATH"/>
+      <method>
+        <option name="BuildArtifacts" enabled="false"/>
+        <option name="Make" enabled="true"/>
+      </method>
+    </configuration>
+    <configuration default="true" type="Application" factoryName="Application">
+      <extension name="coverage" enabled="false" merge="false"/>
+      <option name="MAIN_CLASS_NAME"/>
+      <option name="VM_PARAMETERS"/>
+      <option name="PROGRAM_PARAMETERS"/>
+      <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$"/>
+      <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false"/>
+      <option name="ALTERNATIVE_JRE_PATH"/>
+      <option name="ENABLE_SWING_INSPECTOR" value="false"/>
+      <option name="ENV_VARIABLES"/>
+      <option name="PASS_PARENT_ENVS" value="true"/>
+      <module name=""/>
+      <envs/>
+      <method>
+        <option name="BuildArtifacts" enabled="false"/>
+        <option name="Make" enabled="true"/>
+      </method>
+    </configuration>
+    <configuration default="true" type="JUnit" factoryName="JUnit">
+      <extension name="coverage" enabled="false" merge="false"/>
+      <module name=""/>
+      <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false"/>
+      <option name="ALTERNATIVE_JRE_PATH"/>
+      <option name="PACKAGE_NAME"/>
+      <option name="MAIN_CLASS_NAME"/>
+      <option name="METHOD_NAME"/>
+      <option name="TEST_OBJECT" value="class"/>
+      <option name="VM_PARAMETERS"/>
+      <option name="PARAMETERS"/>
+      <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$"/>
+      <option name="ENV_VARIABLES"/>
+      <option name="PASS_PARENT_ENVS" value="true"/>
+      <option name="TEST_SEARCH_SCOPE">
+        <value defaultName="moduleWithDependencies"/>
+      </option>
+      <envs/>
+      <method>
+        <option name="BuildArtifacts" enabled="false"/>
+        <option name="Make" enabled="true"/>
+      </method>
+    </configuration>
+    <list size="0"/>
+    <configuration name="<template>" type="WebApp" default="true" selected="false">
+      <Host>
+        localhost
+      </Host>
+      <Port>
+        5050
+      </Port>
+    </configuration>
+  </component>
+  <component name="ShelveChangesManager" show_recycled="false"/>
+  <component name="SvnConfiguration" maxAnnotateRevisions="500">
+    <option name="USER" value=""/>
+    <option name="PASSWORD" value=""/>
+    <option name="LAST_MERGED_REVISION"/>
+    <option name="UPDATE_RUN_STATUS" value="false"/>
+    <option name="MERGE_DRY_RUN" value="false"/>
+    <option name="MERGE_DIFF_USE_ANCESTRY" value="true"/>
+    <option name="UPDATE_LOCK_ON_DEMAND" value="false"/>
+    <option name="IGNORE_SPACES_IN_MERGE" value="false"/>
+    <option name="DETECT_NESTED_COPIES" value="true"/>
+    <option name="IGNORE_SPACES_IN_ANNOTATE" value="true"/>
+    <option name="SHOW_MERGE_SOURCES_IN_ANNOTATE" value="true"/>
+    <myIsUseDefaultProxy>
+      false
+    </myIsUseDefaultProxy>
+  </component>
+  <component name="TaskManager">
+    <task active="true" id="Default" summary="Default task"/>
+    <servers/>
+  </component>
+  <component name="VcsManagerConfiguration">
+    <option name="OFFER_MOVE_TO_ANOTHER_CHANGELIST_ON_PARTIAL_COMMIT" value="true"/>
+    <option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="true"/>
+    <option name="PERFORM_UPDATE_IN_BACKGROUND" value="true"/>
+    <option name="PERFORM_COMMIT_IN_BACKGROUND" value="true"/>
+    <option name="PERFORM_EDIT_IN_BACKGROUND" value="true"/>
+    <option name="PERFORM_CHECKOUT_IN_BACKGROUND" value="true"/>
+    <option name="PERFORM_ADD_REMOVE_IN_BACKGROUND" value="true"/>
+    <option name="PERFORM_ROLLBACK_IN_BACKGROUND" value="false"/>
+    <option name="CHECK_LOCALLY_CHANGED_CONFLICTS_IN_BACKGROUND" value="false"/>
+    <option name="ENABLE_BACKGROUND_PROCESSES" value="false"/>
+    <option name="CHANGED_ON_SERVER_INTERVAL" value="60"/>
+    <option name="FORCE_NON_EMPTY_COMMENT" value="false"/>
+    <option name="LAST_COMMIT_MESSAGE"/>
+    <option name="MAKE_NEW_CHANGELIST_ACTIVE" value="true"/>
+    <option name="OPTIMIZE_IMPORTS_BEFORE_PROJECT_COMMIT" value="false"/>
+    <option name="CHECK_FILES_UP_TO_DATE_BEFORE_COMMIT" value="false"/>
+    <option name="REFORMAT_BEFORE_PROJECT_COMMIT" value="false"/>
+    <option name="REFORMAT_BEFORE_FILE_COMMIT" value="false"/>
+    <option name="FILE_HISTORY_DIALOG_COMMENTS_SPLITTER_PROPORTION" value="0.8"/>
+    <option name="FILE_HISTORY_DIALOG_SPLITTER_PROPORTION" value="0.5"/>
+    <option name="ACTIVE_VCS_NAME"/>
+    <option name="UPDATE_GROUP_BY_PACKAGES" value="false"/>
+    <option name="UPDATE_GROUP_BY_CHANGELIST" value="false"/>
+    <option name="SHOW_FILE_HISTORY_AS_TREE" value="false"/>
+    <option name="FILE_HISTORY_SPLITTER_PROPORTION" value="0.6"/>
+  </component>
+  <component name="XDebuggerManager">
+    <breakpoint-manager/>
+  </component>
+</project>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/expectedProjectFile.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/expectedProjectFile.xml
new file mode 100644
index 0000000..5de2103
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/expectedProjectFile.xml
@@ -0,0 +1,104 @@
+<project version="4">
+  <component name="CompilerConfiguration">
+    <option name="DEFAULT_COMPILER" value="Javac"/>
+    <resourceExtensions>
+      <entry name=".+\.(properties|xml|html|dtd|tld)"/>
+      <entry name=".+\.(gif|png|jpeg|jpg)"/>
+    </resourceExtensions>
+    <annotationProcessing enabled="false" useClasspath="true"/>
+    <wildcardResourcePatterns>
+      <entry name="!?*.groovy"/>
+      <entry name="!?*.java"/>
+    </wildcardResourcePatterns>
+  </component>
+  <component name="CopyrightManager" default="">
+    <module2copyright/>
+  </component>
+  <component name="DependencyValidationManager">
+    <option name="SKIP_IMPORT_STATEMENTS" value="false"/>
+  </component>
+  <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false"/>
+  <component name="GradleUISettings">
+    <setting name="root"/>
+  </component>
+  <component name="GradleUISettings2">
+    <setting name="root"/>
+  </component>
+  <component name="IdProvider" IDEtalkID="11DA1DB66DD62DDA1ED602B7079FE97C"/>
+  <component name="JavadocGenerationManager">
+    <option name="OUTPUT_DIRECTORY"/>
+    <option name="OPTION_SCOPE" value="protected"/>
+    <option name="OPTION_HIERARCHY" value="true"/>
+    <option name="OPTION_NAVIGATOR" value="true"/>
+    <option name="OPTION_INDEX" value="true"/>
+    <option name="OPTION_SEPARATE_INDEX" value="true"/>
+    <option name="OPTION_DOCUMENT_TAG_USE" value="false"/>
+    <option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false"/>
+    <option name="OPTION_DOCUMENT_TAG_VERSION" value="false"/>
+    <option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true"/>
+    <option name="OPTION_DEPRECATED_LIST" value="true"/>
+    <option name="OTHER_OPTIONS" value=""/>
+    <option name="HEAP_SIZE"/>
+    <option name="LOCALE"/>
+    <option name="OPEN_IN_BROWSER" value="true"/>
+  </component>
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/root.iml" filepath="$PROJECT_DIR$/root.iml"/>
+      <module fileurl="file://$PROJECT_DIR$/api/api.iml" filepath="$PROJECT_DIR$/api/api.iml"/>
+      <module fileurl="file://$PROJECT_DIR$/webservice/webservice.iml" filepath="$PROJECT_DIR$/webservice/webservice.iml"/>
+    </modules>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-type="JavaSDK" assert-jdk-15="true" project-jdk-name="1.6">
+    <output url="file://$PROJECT_DIR$/out"/>
+  </component>
+  <component name="SvnBranchConfigurationManager">
+    <option name="mySupportsUserInfoFilter" value="true"/>
+  </component>
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs=""/>
+  </component>
+  <component name="masterDetails">
+    <states>
+      <state key="ArtifactsStructureConfigurable.UI">
+        <UIState>
+          <splitter-proportions>
+            <SplitterProportionsDataImpl/>
+          </splitter-proportions>
+          <settings/>
+        </UIState>
+      </state>
+      <state key="Copyright.UI">
+        <UIState>
+          <splitter-proportions>
+            <SplitterProportionsDataImpl/>
+          </splitter-proportions>
+        </UIState>
+      </state>
+      <state key="ProjectJDKs.UI">
+        <UIState>
+          <splitter-proportions>
+            <SplitterProportionsDataImpl>
+              <option name="proportions">
+                <list>
+                  <option value="0.2"/>
+                </list>
+              </option>
+            </SplitterProportionsDataImpl>
+          </splitter-proportions>
+          <last-edited>
+            1.6
+          </last-edited>
+        </UIState>
+      </state>
+      <state key="ScopeChooserConfigurable.UI">
+        <UIState>
+          <splitter-proportions>
+            <SplitterProportionsDataImpl/>
+          </splitter-proportions>
+          <settings/>
+        </UIState>
+      </state>
+    </states>
+  </component>
+</project>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/expectedRootModule.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/expectedRootModule.xml
new file mode 100644
index 0000000..bd2993e
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/expectedRootModule.xml
@@ -0,0 +1,12 @@
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <exclude-output/>
+    <orderEntry type="inheritedJdk"/>
+    <content url="file://$MODULE_DIR$/">
+      <excludeFolder url="file://$MODULE_DIR$/.gradle"/>
+      <excludeFolder url="file://$MODULE_DIR$/build"/>
+    </content>
+    <orderEntry type="sourceFolder" forTests="false"/>
+  </component>
+  <component name="ModuleRootManager"/>
+</module>
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/expectedWebserviceModule.xml b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/expectedWebserviceModule.xml
new file mode 100644
index 0000000..6932e66
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/expectedWebserviceModule.xml
@@ -0,0 +1,76 @@
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <exclude-output/>
+    <orderEntry type="inheritedJdk"/>
+    <content url="file://$MODULE_DIR$/">
+      <sourceFolder url="file://$MODULE_DIR$/src/main/resources" isTestSource="false"/>
+      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false"/>
+      <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true"/>
+      <sourceFolder url="file://$MODULE_DIR$/src/test/resources" isTestSource="true"/>
+      <excludeFolder url="file://$MODULE_DIR$/.gradle"/>
+      <excludeFolder url="file://$MODULE_DIR$/build"/>
+    </content>
+    <orderEntry type="sourceFolder" forTests="false"/>
+    <output url="file://$MODULE_DIR$/build/classes/main"/>
+    <output-test url="file://$MODULE_DIR$/build/classes/test"/>
+    <orderEntry type="module-library" exported="">
+      <library>
+        <CLASSES>
+          <root url="jar://@CACHE_DIR@/org.slf4j/slf4j-api/jars/slf4j-api-1.5.8.jar!/"/>
+        </CLASSES>
+        <JAVADOC/>
+        <SOURCES>
+          <root url="jar://@CACHE_DIR@/org.slf4j/slf4j-api/sources/slf4j-api-1.5.8-sources.jar!/"/>
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" exported="">
+      <library>
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/lib/compile-1.0.jar!/"/>
+        </CLASSES>
+        <JAVADOC/>
+        <SOURCES/>
+      </library>
+    </orderEntry>
+    <orderEntry type="module" module-name="api" exported=""/>
+    <orderEntry type="module-library" exported="" scope="RUNTIME">
+      <library>
+        <CLASSES>
+          <root url="jar://@CACHE_DIR@/commons-lang/commons-lang/jars/commons-lang-2.4.jar!/"/>
+        </CLASSES>
+        <JAVADOC>
+          <root url="jar://@CACHE_DIR@/commons-lang/commons-lang/javadocs/commons-lang-2.4-javadoc.jar!/"/>
+        </JAVADOC>
+        <SOURCES>
+          <root url="jar://@CACHE_DIR@/commons-lang/commons-lang/sources/commons-lang-2.4-sources.jar!/"/>
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" exported="" scope="RUNTIME">
+      <library>
+        <CLASSES>
+          <root url="jar://@CACHE_DIR@/commons-io/commons-io/jars/commons-io-1.2.jar!/"/>
+        </CLASSES>
+        <JAVADOC>
+          <root url="jar://@CACHE_DIR@/commons-io/commons-io/javadocs/commons-io-1.2-javadoc.jar!/"/>
+        </JAVADOC>
+        <SOURCES>
+          <root url="jar://@CACHE_DIR@/commons-io/commons-io/sources/commons-io-1.2-sources.jar!/"/>
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library>
+        <CLASSES>
+          <root url="jar://@CACHE_DIR@/junit/junit/jars/junit-4.7.jar!/"/>
+        </CLASSES>
+        <JAVADOC/>
+        <SOURCES>
+          <root url="jar://@CACHE_DIR@/junit/junit/sources/junit-4.7-sources.jar!/"/>
+        </SOURCES>
+      </library>
+    </orderEntry>
+  </component>
+  <component name="ModuleRootManager"/>
+</module>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/settings.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/settings.gradle
new file mode 100644
index 0000000..f6c5fd8
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/settings.gradle
@@ -0,0 +1,4 @@
+include "api", "webservice"
+
+rootProject.name = 'root'
+
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/webservice/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/webservice/build.gradle
new file mode 100644
index 0000000..0477c28
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/webservice/build.gradle
@@ -0,0 +1,19 @@
+apply plugin: 'war'
+
+version = '2.5'
+
+dependencies {
+    providedCompile 'org.slf4j:slf4j-api:1.5.8 at jar'
+    compile project(':api'), files("$projectDir/lib/compile-1.0.jar")
+    runtime module("commons-lang:commons-lang:2.4") {
+        dependency("commons-io:commons-io:1.2")
+    }
+}
+
+ideaModule.doLast {
+    compareXmlWithIgnoringOrder(getExpectedXml(project, 'expectedWebserviceModule.xml'), file("webservice.iml").text)
+}
+
+cleanIdea.doLast {
+    assert !file("webservice/webservice.iml").isFile()
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/webservice/src/main/java/org/gradle/webservice/TestTest.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/webservice/src/main/java/org/gradle/webservice/TestTest.java
new file mode 100644
index 0000000..f7ce8c2
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IdeaIntegrationTest/canCreateAndDeleteMetaData/webservice/src/main/java/org/gradle/webservice/TestTest.java
@@ -0,0 +1,5 @@
+package org.gradle.integtests.IdeaIntegrationTest.webservice.src.main.java.org.gradle.webservice;
+
+public class TestTest {
+    
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/NewIPerson.groovy b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/NewIPerson.groovy
new file mode 100644
index 0000000..e57e80b
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/NewIPerson.groovy
@@ -0,0 +1,4 @@
+interface IPerson {
+    String getName()
+    String getAddress()
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/build.gradle
new file mode 100644
index 0000000..69928e4
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/build.gradle
@@ -0,0 +1,5 @@
+apply plugin: 'groovy'
+
+dependencies {
+    groovy localGroovy()
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/src/main/groovy/IPerson.groovy b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/src/main/groovy/IPerson.groovy
new file mode 100644
index 0000000..7900cbf
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/src/main/groovy/IPerson.groovy
@@ -0,0 +1,3 @@
+interface IPerson {
+    String getName()
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/src/main/groovy/Person.groovy b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/src/main/groovy/Person.groovy
new file mode 100644
index 0000000..cb9c12a
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesDependentClasses/src/main/groovy/Person.groovy
@@ -0,0 +1,3 @@
+class Person implements IPerson {
+    def String name
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
new file mode 100644
index 0000000..58fd49e
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: 'groovy'
+
+dependencies {
+    groovy localGroovy()
+}
+
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/groovy/Person.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/groovy/Person.java
new file mode 100644
index 0000000..6ef4192
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/groovy/Person.java
@@ -0,0 +1,4 @@
+
+interface Person {
+    String getName();
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/groovy/PersonImpl.Groovy b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/groovy/PersonImpl.Groovy
new file mode 100644
index 0000000..f5d5b6e
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalGroovyCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/groovy/PersonImpl.Groovy
@@ -0,0 +1,4 @@
+
+class PersonImpl implements Person {
+    def String name
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/NewIPerson.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/NewIPerson.java
new file mode 100644
index 0000000..995be43
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/NewIPerson.java
@@ -0,0 +1,4 @@
+interface IPerson {
+    String getName();
+    String getAddress();
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/build.gradle
new file mode 100644
index 0000000..f3eca85
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/build.gradle
@@ -0,0 +1 @@
+apply plugin: 'java'
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/src/main/java/IPerson.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/src/main/java/IPerson.java
new file mode 100644
index 0000000..d117756
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/src/main/java/IPerson.java
@@ -0,0 +1,3 @@
+interface IPerson {
+    String getName();
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/src/main/java/Person.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/src/main/java/Person.java
new file mode 100644
index 0000000..59bda04
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClasses/src/main/java/Person.java
@@ -0,0 +1,3 @@
+class Person implements IPerson {
+    public String getName() { return "name"; }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/NewIPerson.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/NewIPerson.java
new file mode 100644
index 0000000..995be43
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/NewIPerson.java
@@ -0,0 +1,4 @@
+interface IPerson {
+    String getName();
+    String getAddress();
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/app/src/main/java/Person.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/app/src/main/java/Person.java
new file mode 100644
index 0000000..59bda04
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/app/src/main/java/Person.java
@@ -0,0 +1,3 @@
+class Person implements IPerson {
+    public String getName() { return "name"; }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/build.gradle
new file mode 100644
index 0000000..36aa097
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/build.gradle
@@ -0,0 +1,9 @@
+subprojects {
+    apply plugin: 'java'
+}
+
+project(':app') {
+    dependencies {
+        compile project(':lib')
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/lib/src/main/java/IPerson.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/lib/src/main/java/IPerson.java
new file mode 100644
index 0000000..d117756
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/lib/src/main/java/IPerson.java
@@ -0,0 +1,3 @@
+interface IPerson {
+    String getName();
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/settings.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/settings.gradle
new file mode 100644
index 0000000..421e7f7
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesDependentClassesAcrossProjectBoundaries/settings.gradle
@@ -0,0 +1 @@
+include 'lib', 'app'
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
new file mode 100644
index 0000000..adf21b6
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
@@ -0,0 +1,4 @@
+apply plugin: 'java'
+
+sourceCompatibility = 1.5
+compileJava.debug = true
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/java/Test.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/java/Test.java
new file mode 100644
index 0000000..fa2fc0d
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalJavaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/java/Test.java
@@ -0,0 +1,3 @@
+public class Test {
+    
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/NewIPerson.scala b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/NewIPerson.scala
new file mode 100644
index 0000000..45a7ee7
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/NewIPerson.scala
@@ -0,0 +1,4 @@
+trait IPerson {
+    def getName(): String
+    def getAddress(): String
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/build.gradle
new file mode 100644
index 0000000..765f414
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/build.gradle
@@ -0,0 +1,11 @@
+apply plugin: 'scala'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    scalaTools 'org.scala-lang:scala-compiler:2.7.7'
+    scalaTools 'org.scala-lang:scala-library:2.7.7'
+    compile 'org.scala-lang:scala-library:2.7.7'
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/src/main/scala/IPerson.scala b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/src/main/scala/IPerson.scala
new file mode 100644
index 0000000..90343c6
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/src/main/scala/IPerson.scala
@@ -0,0 +1,3 @@
+trait IPerson {
+    def getName(): String
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/src/main/scala/Person.scala b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/src/main/scala/Person.scala
new file mode 100644
index 0000000..965f7de
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesDependentClasses/src/main/scala/Person.scala
@@ -0,0 +1,3 @@
+class Person(name: String) extends IPerson {
+    def getName(): String = name
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
new file mode 100644
index 0000000..2ba99a6
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'scala'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    scalaTools 'org.scala-lang:scala-compiler:2.7.7'
+    scalaTools 'org.scala-lang:scala-library:2.7.7'
+
+    compile 'org.scala-lang:scala-library:2.7.7'
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/scala/Person.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/scala/Person.java
new file mode 100644
index 0000000..f86b6d1
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/scala/Person.java
@@ -0,0 +1,3 @@
+interface Person {
+    String getName();
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/scala/PersonImpl.scala b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/scala/PersonImpl.scala
new file mode 100644
index 0000000..d1d9f52
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalScalaCompileIntegrationTest/recompilesSourceWhenPropertiesChange/src/main/scala/PersonImpl.scala
@@ -0,0 +1,3 @@
+class PersonImpl(val name: String) extends Person {
+    def getName(): String = name
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/doesNotRunStaleTests/src/test/java/Broken.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/doesNotRunStaleTests/src/test/java/Broken.java
new file mode 100644
index 0000000..6ab9964
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/doesNotRunStaleTests/src/test/java/Broken.java
@@ -0,0 +1,8 @@
+import org.junit.Test;
+
+public class Broken {
+    @Test
+    public void broken() {
+        throw new RuntimeException("broken");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/build.gradle
new file mode 100644
index 0000000..31b3cc5
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'java'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile 'junit:junit:4.7'
+    testCompile 'org.testng:testng:5.12.1'
+}
+
+test {
+    include '**/*Test.*'
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/src/test/java/JUnitExtra.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/src/test/java/JUnitExtra.java
new file mode 100644
index 0000000..0b3206b
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/src/test/java/JUnitExtra.java
@@ -0,0 +1,7 @@
+import org.junit.Test;
+
+public class JUnitExtra {
+    @Test
+    public void ok() {
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/src/test/java/JUnitTest.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/src/test/java/JUnitTest.java
new file mode 100644
index 0000000..533526f
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/src/test/java/JUnitTest.java
@@ -0,0 +1,7 @@
+import org.junit.Test;
+
+public class JUnitTest {
+    @Test
+    public void ok() {
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/src/test/java/TestNGTest.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/src/test/java/TestNGTest.java
new file mode 100644
index 0000000..76f13e0
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSelectedTestsChange/src/test/java/TestNGTest.java
@@ -0,0 +1,6 @@
+import org.testng.annotations.Test;
+
+public class TestNGTest {
+    @Test
+    public void ok() {}
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/NewMainClass.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/NewMainClass.java
new file mode 100644
index 0000000..981627b
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/NewMainClass.java
@@ -0,0 +1,6 @@
+public class MainClass {
+    @Override
+    public String toString() {
+        return "new main class";
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/NewOk.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/NewOk.java
new file mode 100644
index 0000000..8d98813
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/NewOk.java
@@ -0,0 +1,5 @@
+public class Ok {
+    @org.junit.Test
+    public void someTest() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/src/main/java/MainClass.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/src/main/java/MainClass.java
new file mode 100644
index 0000000..47dc26b
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/executesTestsWhenSourceChanges/src/main/java/MainClass.java
@@ -0,0 +1,2 @@
+class MainClass {
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/shared/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/shared/build.gradle
new file mode 100644
index 0000000..145edfd
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/shared/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: 'java'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile 'junit:junit:4.7'
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/shared/src/test/java/Ok.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/shared/src/test/java/Ok.java
new file mode 100644
index 0000000..f6fe7c4
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/IncrementalTestIntegrationTest/shared/src/test/java/Ok.java
@@ -0,0 +1,7 @@
+import org.junit.Test;
+
+public class Ok {
+    @Test
+    public void ok() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canRunSingleTests/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canRunSingleTests/build.gradle
new file mode 100644
index 0000000..de01156
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canRunSingleTests/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: 'java'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile 'junit:junit:4.8.1'
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canRunSingleTests/src/test/java/NotATest.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canRunSingleTests/src/test/java/NotATest.java
new file mode 100644
index 0000000..2e10657
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canRunSingleTests/src/test/java/NotATest.java
@@ -0,0 +1,2 @@
+public class NotATest {
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canRunSingleTests/src/test/java/Ok.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canRunSingleTests/src/test/java/Ok.java
new file mode 100644
index 0000000..f6fe7c4
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canRunSingleTests/src/test/java/Ok.java
@@ -0,0 +1,7 @@
+import org.junit.Test;
+
+public class Ok {
+    @Test
+    public void ok() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canRunSingleTests/src/test/java/Ok2.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canRunSingleTests/src/test/java/Ok2.java
new file mode 100644
index 0000000..c47e685
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/canRunSingleTests/src/test/java/Ok2.java
@@ -0,0 +1,7 @@
+import org.junit.Test;
+
+public class Ok2 {
+    @Test
+    public void ok() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/build.gradle
new file mode 100644
index 0000000..9e3599e
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/build.gradle
@@ -0,0 +1,8 @@
+apply plugin: 'java'
+
+repositories {
+    mavenCentral()
+}
+dependencies {
+    compile 'junit:junit:4.7'
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/AbstractHasRunWith.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/AbstractHasRunWith.java
new file mode 100644
index 0000000..206f20a
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/AbstractHasRunWith.java
@@ -0,0 +1,7 @@
+package org.gradle;
+
+import org.junit.runner.RunWith;
+
+ at RunWith(CustomRunner.class)
+public abstract class AbstractHasRunWith {
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/CustomRunner.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/CustomRunner.java
new file mode 100644
index 0000000..5ee597c
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/CustomRunner.java
@@ -0,0 +1,26 @@
+package org.gradle;
+
+import org.junit.runner.*;
+import org.junit.runner.notification.RunNotifier;
+
+public class CustomRunner extends Runner {
+    private final Description description;
+
+    public CustomRunner(Class type) throws Exception {
+        description = Description.createSuiteDescription(type);
+        description.addChild(Description.createTestDescription(type, "ok"));
+    }
+
+    @Override
+    public Description getDescription() {
+        return description;
+    }
+
+    @Override
+    public void run(RunNotifier notifier) {
+        for (Description child : description.getChildren()) {
+            notifier.fireTestStarted(child);
+            notifier.fireTestFinished(child);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/EmptyRunWithSubclass.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/EmptyRunWithSubclass.java
new file mode 100644
index 0000000..6455710
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/EmptyRunWithSubclass.java
@@ -0,0 +1,5 @@
+package org.gradle;
+
+public class EmptyRunWithSubclass extends AbstractHasRunWith {
+
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/TestsOnInner.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/TestsOnInner.java
new file mode 100644
index 0000000..695e2ec
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/detectsTestClasses/src/test/java/org/gradle/TestsOnInner.java
@@ -0,0 +1,7 @@
+package org.gradle;
+
+public class TestsOnInner {
+    public static class SomeInner {
+        @org.junit.Test public void ok() { }
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/executesTestsInCorrectEnvironment/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/executesTestsInCorrectEnvironment/build.gradle
new file mode 100644
index 0000000..fad6253
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/executesTestsInCorrectEnvironment/build.gradle
@@ -0,0 +1,8 @@
+apply plugin: 'java'
+repositories { mavenCentral() }
+dependencies { testCompile 'junit:junit:4.4', 'ant:ant:1.6.1', 'ant:ant-launcher:1.6.1' }
+test {
+    systemProperties.testSysProperty = 'value'
+    systemProperties.projectDir = projectDir
+    environment.TEST_ENV_VAR = 'value'
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java
new file mode 100644
index 0000000..5370bea
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OkTest.java
@@ -0,0 +1,72 @@
+package org.gradle;
+
+import static org.junit.Assert.*;
+
+import java.io.PrintStream;
+import java.util.logging.Logger;
+
+public class OkTest {
+    static {
+        System.out.println("class loaded");
+    }
+
+    public OkTest() {
+        System.out.println("test constructed");
+    }
+
+    @org.junit.Test
+    public void ok() throws Exception {
+        // check JUnit version
+        assertEquals("4.4", new org.junit.runner.JUnitCore().getVersion());
+        // check Ant version
+        assertTrue(org.apache.tools.ant.Main.getAntVersion().contains("1.6.1"));
+        // check working dir
+        assertEquals(System.getProperty("projectDir"), System.getProperty("user.dir"));
+        // check classloader
+        assertSame(ClassLoader.getSystemClassLoader(), getClass().getClassLoader());
+        assertSame(getClass().getClassLoader(), Thread.currentThread().getContextClassLoader());
+        // check Gradle and impl classes not visible
+        try {
+            getClass().getClassLoader().loadClass("org.gradle.api.Project");
+            fail();
+        } catch (ClassNotFoundException e) {
+        }
+        try {
+            getClass().getClassLoader().loadClass("org.slf4j.Logger");
+            fail();
+        } catch (ClassNotFoundException e) {
+        }
+        // check sys properties
+        assertEquals("value", System.getProperty("testSysProperty"));
+        // check env vars
+        assertEquals("value", System.getenv("TEST_ENV_VAR"));
+
+        // check stdout and stderr and logging
+        System.out.println("This is test stdout");
+        System.out.print("no EOL");
+        System.out.println();
+        System.err.println("This is test stderr");
+        Logger.getLogger("test-logger").warning("this is a warning");
+
+        final PrintStream out = System.out;
+        // logging from a shutdown hook
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            @Override
+            public void run() {
+                out.println("stdout from a shutdown hook.");
+                Logger.getLogger("test-logger").info("info from a shutdown hook.");
+            }
+        });
+
+        // logging from another thread
+        Thread thread = new Thread() {
+            @Override
+            public void run() {
+                System.out.println("stdout from another thread");
+                Logger.getLogger("test-logger").info("info from another thread.");
+            }
+        };
+        thread.start();
+        thread.join();
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OtherTest.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OtherTest.java
new file mode 100644
index 0000000..36cbf50
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/executesTestsInCorrectEnvironment/src/test/java/org/gradle/OtherTest.java
@@ -0,0 +1,22 @@
+package org.gradle;
+
+import java.util.logging.Logger;
+
+public class OtherTest {
+    static {
+        System.out.println("other class loaded");
+    }
+
+    public OtherTest() {
+        System.out.println("other test constructed");
+    }
+
+    @org.junit.Test
+    public void ok() throws Exception {
+        // check stdout and stderr
+        System.out.println("This is other stdout");
+        System.err.println("This is other stderr");
+        // check logging
+        Logger.getLogger("test-logger").warning("this is another warning");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/build.gradle
new file mode 100644
index 0000000..8ea4a04
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/build.gradle
@@ -0,0 +1,3 @@
+apply plugin: 'java'
+repositories { mavenCentral() }
+dependencies { testCompile 'junit:junit:4.7' }
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenAfter.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenAfter.java
new file mode 100644
index 0000000..0e0c901
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenAfter.java
@@ -0,0 +1,17 @@
+package org.gradle;
+
+import org.junit.After;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class BrokenAfter {
+    @After
+    public void broken() {
+        fail("failed");
+    }
+
+    @Test
+    public void ok() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenAfterClass.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenAfterClass.java
new file mode 100644
index 0000000..8452a92
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenAfterClass.java
@@ -0,0 +1,17 @@
+package org.gradle;
+
+import org.junit.AfterClass;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class BrokenAfterClass {
+    @AfterClass
+    public static void broken() {
+        fail("failed");
+    }
+
+    @Test
+    public void ok() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenBefore.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenBefore.java
new file mode 100644
index 0000000..dccf416
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenBefore.java
@@ -0,0 +1,17 @@
+package org.gradle;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class BrokenBefore {
+    @Before
+    public void broken() {
+        fail("failed");
+    }
+
+    @Test
+    public void ok() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenBeforeClass.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenBeforeClass.java
new file mode 100644
index 0000000..b69ef80
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenBeforeClass.java
@@ -0,0 +1,17 @@
+package org.gradle;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class BrokenBeforeClass {
+    @BeforeClass
+    public static void broken() {
+        fail("failed");
+    }
+
+    @Test
+    public void ok() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenConstructor.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenConstructor.java
new file mode 100644
index 0000000..4e86b14
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenConstructor.java
@@ -0,0 +1,15 @@
+package org.gradle;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class BrokenConstructor {
+    public BrokenConstructor() {
+        fail("failed");
+    }
+
+    @Test
+    public void ok() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenException.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenException.java
new file mode 100644
index 0000000..969897d
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenException.java
@@ -0,0 +1,18 @@
+package org.gradle;
+
+import org.junit.Test;
+
+public class BrokenException {
+    @Test
+    public void broken() {
+        // Exception's toString() throws an exception
+        throw new BrokenRuntimeException();
+    }
+
+    private static class BrokenRuntimeException extends RuntimeException {
+        @Override
+        public String toString() {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenTest.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenTest.java
new file mode 100644
index 0000000..3abad56
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/BrokenTest.java
@@ -0,0 +1,17 @@
+package org.gradle;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class BrokenTest {
+    @Test
+    public void failure() {
+        fail("failed");
+    }
+
+    @Test
+    public void broken() {
+        throw new IllegalStateException();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/Unloadable.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/Unloadable.java
new file mode 100644
index 0000000..3019224
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/JUnitIntegrationTest/reportsAndBreaksBuildWhenTestFails/src/test/java/org/gradle/Unloadable.java
@@ -0,0 +1,15 @@
+package org.gradle;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class Unloadable {
+    static {
+        fail("failed");
+    }
+
+    @Test
+    public void ok() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ProjectLayoutIntegrationTest/canUseANonStandardBuildDir/build.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ProjectLayoutIntegrationTest/canUseANonStandardBuildDir/build.gradle
new file mode 100644
index 0000000..d6b40bb
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ProjectLayoutIntegrationTest/canUseANonStandardBuildDir/build.gradle
@@ -0,0 +1,13 @@
+apply plugin: 'java'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile 'junit:junit:4.7'
+}
+
+buildDirName = 'target'
+sourceSets.main.classesDir = new File(buildDir, 'main-classes')
+sourceSets.test.classesDir = new File(buildDir, 'test-classes')
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ProjectLayoutIntegrationTest/canUseANonStandardBuildDir/src/main/java/Person.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ProjectLayoutIntegrationTest/canUseANonStandardBuildDir/src/main/java/Person.java
new file mode 100644
index 0000000..425d40e
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ProjectLayoutIntegrationTest/canUseANonStandardBuildDir/src/main/java/Person.java
@@ -0,0 +1,2 @@
+public class Person {
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ProjectLayoutIntegrationTest/canUseANonStandardBuildDir/src/test/java/PersonTest.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ProjectLayoutIntegrationTest/canUseANonStandardBuildDir/src/test/java/PersonTest.java
new file mode 100644
index 0000000..bdf3346
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/ProjectLayoutIntegrationTest/canUseANonStandardBuildDir/src/test/java/PersonTest.java
@@ -0,0 +1,8 @@
+import org.junit.Test;
+
+public class PersonTest {
+    @Test
+    public void ok() {
+        Person person = new Person();
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/ignore/bad.file b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/ignore/bad.file
new file mode 100644
index 0000000..6aa6bf7
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/ignore/bad.file
@@ -0,0 +1 @@
+this should not be copied
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/one.a b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/one.a
new file mode 100644
index 0000000..a182e25
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/one.a
@@ -0,0 +1 @@
+Something to copy
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/one.b b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/one.b
new file mode 100644
index 0000000..a182e25
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/one.b
@@ -0,0 +1 @@
+Something to copy
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/sub/ignore/bad.file b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/sub/ignore/bad.file
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/sub/onesub.a b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/sub/onesub.a
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/sub/onesub.b b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/one/sub/onesub.b
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/root.a b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/root.a
new file mode 100644
index 0000000..0839846
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/root.a
@@ -0,0 +1,2 @@
+line 1
+line 2
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/root.b b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/root.b
new file mode 100644
index 0000000..7090d7f
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/root.b
@@ -0,0 +1 @@
+root.b
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/two/ignore/bad.file b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/two/ignore/bad.file
new file mode 100644
index 0000000..6aa6bf7
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/two/ignore/bad.file
@@ -0,0 +1 @@
+this should not be copied
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/two/two.a b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/two/two.a
new file mode 100644
index 0000000..9c2bb0f
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/two/two.a
@@ -0,0 +1,3 @@
+${one}
+${one+1}
+${one+1+1}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/two/two.b b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/two/two.b
new file mode 100644
index 0000000..a182e25
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src/two/two.b
@@ -0,0 +1 @@
+Something to copy
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src2/three/three.a b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src2/three/three.a
new file mode 100644
index 0000000..a182e25
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src2/three/three.a
@@ -0,0 +1 @@
+Something to copy
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src2/three/three.b b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src2/three/three.b
new file mode 100644
index 0000000..a182e25
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/copyTestResources/src2/three/three.b
@@ -0,0 +1 @@
+Something to copy
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/groovy/expectedClasspathFile.txt b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/groovy/expectedClasspathFile.txt
new file mode 100644
index 0000000..0888a2f
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/groovy/expectedClasspathFile.txt
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<classpath>
+  <classpathentry kind="output" path="build/classes/main"/>
+  <classpathentry kind="src" path="src/main/groovy"/>
+  <classpathentry kind="src" path="src/main/resources"/>
+  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+  <classpathentry kind="src" path="src/test/groovy" output="build/classes/test"/>
+  <classpathentry kind="src" path="src/test/resources" output="build/classes/test"/>
+  <classpathentry kind="lib" path="$cachePath/antlr/antlr/jars/antlr-2.7.7.jar"/>
+  <classpathentry kind="lib" path="$cachePath/asm/asm-analysis/jars/asm-analysis-3.2.jar"/>
+  <classpathentry kind="lib" path="$cachePath/asm/asm-commons/jars/asm-commons-3.2.jar"/>
+  <classpathentry kind="lib" path="$cachePath/asm/asm-tree/jars/asm-tree-3.2.jar"/>
+  <classpathentry kind="lib" path="$cachePath/asm/asm-util/jars/asm-util-3.2.jar"/>
+  <classpathentry kind="lib" path="$cachePath/asm/asm/jars/asm-3.2.jar"/>
+  <classpathentry kind="lib" path="$cachePath/jline/jline/jars/jline-0.9.94.jar"/>
+  <classpathentry kind="lib" path="$cachePath/junit/junit/jars/junit-4.7.jar"/>
+  <classpathentry kind="lib" path="$cachePath/org.codehaus.groovy/groovy/jars/groovy-1.7.0.jar"/>
+</classpath>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/groovy/expectedProjectFile.txt b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/groovy/expectedProjectFile.txt
new file mode 100644
index 0000000..83b53b1
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/groovy/expectedProjectFile.txt
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<projectDescription>
+  <name>quickstart</name>
+  <comment/>
+  <projects/>
+  <natures>
+    <nature>org.eclipse.jdt.groovy.core.groovyNature</nature>
+    <nature>org.eclipse.jdt.core.javanature</nature>
+  </natures>
+  <buildSpec>
+    <buildCommand>
+      <name>org.eclipse.jdt.core.javabuilder</name>
+      <arguments/>
+    </buildCommand>
+  </buildSpec>
+</projectDescription>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/java/expectedApiClasspathFile.txt b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/java/expectedApiClasspathFile.txt
new file mode 100644
index 0000000..c06f356
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/java/expectedApiClasspathFile.txt
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<classpath>
+  <classpathentry kind="output" path="build/classes/main"/>
+  <classpathentry kind="src" path="src/main/java"/>
+  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+  <classpathentry kind="src" path="src/test/java" output="build/classes/test"/>
+  <classpathentry kind="src" path="src/test/resources" output="build/classes/test"/>
+  <classpathentry kind="lib" path="$cachePath/commons-collections/commons-collections/jars/commons-collections-3.2.jar"/>
+  <classpathentry kind="lib" path="$cachePath/junit/junit/jars/junit-4.7.jar"/>
+</classpath>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/java/expectedApiProjectFile.txt b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/java/expectedApiProjectFile.txt
new file mode 100644
index 0000000..44ca408
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/java/expectedApiProjectFile.txt
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<projectDescription>
+  <name>api</name>
+  <comment/>
+  <projects/>
+  <natures>
+    <nature>org.eclipse.jdt.core.javanature</nature>
+  </natures>
+  <buildSpec>
+    <buildCommand>
+      <name>org.eclipse.jdt.core.javabuilder</name>
+      <arguments/>
+    </buildCommand>
+  </buildSpec>
+</projectDescription>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/java/expectedWebserviceClasspathFile.txt b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/java/expectedWebserviceClasspathFile.txt
new file mode 100644
index 0000000..b349096
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/java/expectedWebserviceClasspathFile.txt
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<classpath>
+  <classpathentry kind="src" path="src/main/java"/>
+  <classpathentry kind="output" path="build/classes/main"/>
+  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+  <classpathentry kind="src" path="/api" combineaccessrules="false"/>
+  <classpathentry kind="lib" path="$projectDir/lib/compile-1.0.jar"/>
+  <classpathentry kind="lib" path="$cachePath/commons-io/commons-io/jars/commons-io-1.2.jar"/>
+  <classpathentry kind="lib" path="$cachePath/commons-lang/commons-lang/jars/commons-lang-2.4.jar"/>
+  <classpathentry kind="lib" path="$cachePath/junit/junit/jars/junit-4.7.jar"/>
+  <classpathentry kind="lib" path="$cachePath/org.slf4j/slf4j-api/jars/slf4j-api-1.5.8.jar"/>
+</classpath>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/java/expectedWebserviceProjectFile.txt b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/java/expectedWebserviceProjectFile.txt
new file mode 100644
index 0000000..1220c82
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/java/expectedWebserviceProjectFile.txt
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<projectDescription>
+  <name>webservice</name>
+  <comment/>
+  <projects/>
+  <natures>
+    <nature>org.eclipse.jdt.core.javanature</nature>
+  </natures>
+  <buildSpec>
+    <buildCommand>
+      <name>org.eclipse.jdt.core.javabuilder</name>
+      <arguments/>
+    </buildCommand>
+  </buildSpec>
+</projectDescription>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/java/expectedWebserviceWtpFile.txt b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/java/expectedWebserviceWtpFile.txt
new file mode 100644
index 0000000..89a99af
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/java/expectedWebserviceWtpFile.txt
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project-modules id="moduleCoreId" project-version="1.5.0">
+  <wb-module deploy-name="webservice">
+    <property name="context-root" value="webservice"/>
+    <wb-resource deploy-path="/" source-path="src/main/webapp"/>
+    <wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/java"/>
+    <wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/resources"/>
+    <property name="java-output-path" value="build/classes/main"/>
+    <dependent-module deploy-path="/WEB-INF/lib" handle="module:/classpath/lib//$projectDir/lib/compile-1.0.jar">
+      <dependency-type>uses</dependency-type>
+    </dependent-module>
+    <dependent-module deploy-path="/WEB-INF/lib" handle="module:/classpath/lib//$cachePath/commons-io/commons-io/jars/commons-io-1.2.jar">
+      <dependency-type>uses</dependency-type>
+    </dependent-module>
+    <dependent-module deploy-path="/WEB-INF/lib" handle="module:/classpath/lib//$cachePath/commons-lang/commons-lang/jars/commons-lang-2.4.jar">
+      <dependency-type>uses</dependency-type>
+    </dependent-module>
+    <dependent-module deploy-path="/WEB-INF/lib" handle="module:/resource/api/api">
+      <dependency-type>uses</dependency-type>
+    </dependent-module>
+  </wb-module>
+</project-modules>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/scala/expectedClasspathFile.txt b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/scala/expectedClasspathFile.txt
new file mode 100644
index 0000000..17f5d40
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/scala/expectedClasspathFile.txt
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<classpath>
+  <classpathentry kind="output" path="build/classes/main"/>
+  <classpathentry kind="src" path="src/main/scala"/>
+  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+  <classpathentry kind="src" path="src/test/scala" output="build/classes/test"/>
+  <classpathentry kind="lib" path="$cachePath/commons-collections/commons-collections/jars/commons-collections-3.2.jar"/>
+  <classpathentry kind="lib" path="$cachePath/junit/junit/jars/junit-4.7.jar"/>
+  <classpathentry kind="lib" path="$cachePath/org.scala-lang/scala-library/jars/scala-library-2.7.7.jar"/>
+</classpath>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/scala/expectedProjectFile.txt b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/scala/expectedProjectFile.txt
new file mode 100644
index 0000000..fd5ca0a
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/eclipseproject/scala/expectedProjectFile.txt
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<projectDescription>
+  <name>quickstart</name>
+  <comment/>
+  <projects/>
+  <natures>
+    <nature>org.eclipse.jdt.core.javanature</nature>
+    <nature>ch.epfl.lamp.sdt.core.scalanature</nature>
+  </natures>
+  <buildSpec>
+    <buildCommand>
+      <name>ch.epfl.lamp.sdt.core.scalabuilder</name>
+      <arguments/>
+    </buildCommand>
+  </buildSpec>
+</projectDescription>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenSnapshotIntegrationTest/retrievesAndCacheSnapshot/projectWithMavenSnapshots.gradle b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenSnapshotIntegrationTest/retrievesAndCacheSnapshot/projectWithMavenSnapshots.gradle
new file mode 100644
index 0000000..490cae9
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenSnapshotIntegrationTest/retrievesAndCacheSnapshot/projectWithMavenSnapshots.gradle
@@ -0,0 +1,78 @@
+apply plugin: 'java'
+apply plugin: 'maven'
+
+group = 'org.gradle'
+version = '1.0-SNAPSHOT'
+
+def repoUrl = "file://localhost/$buildDir/pomRepo/"
+
+repositories {
+    project.repo = mavenRepo(urls: repoUrl)
+}
+
+configurations {
+    snapshot1
+    snapshot2
+    snapshot3
+    archives2
+}
+
+jar {
+    baseName = 'testproject'
+}
+
+task jar2(type: Jar) {
+    baseName = 'testproject'
+    from sourceSets.main.allJava
+}
+
+configure([jar, jar2]) {
+    doLast { task ->
+        ant.checksum(file: archivePath, property: "checksum_$task.name")
+        task.checksum = ant.properties."checksum_$task.name"
+    }
+}
+
+artifacts {
+    archives2 jar2
+}
+
+dependencies {
+    snapshot1 "org.gradle:testproject:1.0-SNAPSHOT"
+    snapshot2 "org.gradle:testproject:1.0-SNAPSHOT"
+    snapshot3 "org.gradle:testproject:1.0-SNAPSHOT"
+}
+
+uploadArchives2.doFirst {
+    // Fix for https://issues.apache.org/jira/browse/IVY-1209
+    Thread.sleep 1100
+}
+
+delete {
+    delete "$gradle.gradleUserHomeDir/cache/${group}/testproject"
+}
+
+task retrieveWithEmptyCache(dependsOn: uploadArchives) << {
+    ant.checksum(file: configurations.snapshot1.singleFile, property: 'checksum1')
+    assert jar.checksum == ant.properties.checksum1
+}
+
+task retrieveWithSnapshotInCache(dependsOn: [retrieveWithEmptyCache, uploadArchives2]) << {
+    repo.setSnapshotTimeout(Long.MAX_VALUE)
+    ant.checksum(file: configurations.snapshot2.singleFile, property: 'checksum2')
+    assert jar.checksum == ant.properties.checksum2
+}
+
+task retrieveWithTimedOutSnapshotInCache(dependsOn: retrieveWithSnapshotInCache) << {
+    repo.setSnapshotTimeout(0)
+    ant.checksum(file: configurations.snapshot3.singleFile, property: 'checksum3')
+    assert jar2.checksum == ant.properties.checksum3
+}
+
+configure([uploadArchives, uploadArchives2]) {
+    repositories {
+        mavenDeployer {
+            repository(url: repoUrl)
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenSnapshotIntegrationTest/retrievesAndCacheSnapshot/src/main/java/org/gradle/Test.java b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenSnapshotIntegrationTest/retrievesAndCacheSnapshot/src/main/java/org/gradle/Test.java
new file mode 100644
index 0000000..9561bd6
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/MavenSnapshotIntegrationTest/retrievesAndCacheSnapshot/src/main/java/org/gradle/Test.java
@@ -0,0 +1,4 @@
+package org.gradle;
+
+public class Test {
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedNewPom.txt b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedNewPom.txt
new file mode 100644
index 0000000..a50b717
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedNewPom.txt
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>gradle</groupId>
+  <artifactId>pomGeneration</artifactId>
+  <version>1.0</version>
+  <inceptionYear>2008</inceptionYear>
+  <licenses>
+    <license>
+      <name>The Apache Software License, Version 2.0</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+      <distribution>repo</distribution>
+    </license>
+  </licenses>
+  <dependencies>
+    <dependency>
+      <groupId>group4</groupId>
+      <artifactId>providedRuntime</artifactId>
+      <version>1.0</version>
+      <type>zip</type>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>group4</groupId>
+      <artifactId>providedRuntime-util</artifactId>
+      <version>1.0</version>
+      <type>war</type>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>group6</groupId>
+      <artifactId>testRuntime</artifactId>
+      <version>1.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>group2</groupId>
+      <artifactId>providedCompile</artifactId>
+      <version>1.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>group1</groupId>
+      <artifactId>compile</artifactId>
+      <version>1.0</version>
+      <scope>compile</scope>
+      <exclusions>
+        <exclusion>
+          <artifactId>excludeArtifact</artifactId>
+          <groupId>excludeGroup</groupId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>group3</groupId>
+      <artifactId>runtime</artifactId>
+      <version>1.0</version>
+      <scope>runtime</scope>
+    </dependency>
+    <dependency>
+      <groupId>group5</groupId>
+      <artifactId>testCompile</artifactId>
+      <version>1.0</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedPom.txt b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedPom.txt
new file mode 100644
index 0000000..666c294
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedPom.txt
@@ -0,0 +1,61 @@
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>${groupId}</groupId>
+  <artifactId>mywar</artifactId>
+  <version>${version}</version>
+  <packaging>war</packaging>
+  <dependencies>
+    <dependency>
+      <groupId>group4</groupId>
+      <artifactId>providedRuntime</artifactId>
+      <version>1.0</version>
+      <type>zip</type>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>group4</groupId>
+      <artifactId>providedRuntime-util</artifactId>
+      <version>1.0</version>
+      <type>war</type>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>group6</groupId>
+      <artifactId>testRuntime</artifactId>
+      <version>1.0</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>group2</groupId>
+      <artifactId>providedCompile</artifactId>
+      <version>1.0</version>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>group1</groupId>
+      <artifactId>compile</artifactId>
+      <version>1.0</version>
+      <scope>compile</scope>
+      <exclusions>
+        <exclusion>
+          <artifactId>excludeArtifact</artifactId>
+          <groupId>excludeGroup</groupId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>group3</groupId>
+      <artifactId>runtime</artifactId>
+      <version>1.0</version>
+      <scope>runtime</scope>
+      <optional>true</optional>
+    </dependency>
+    <dependency>
+      <groupId>group5</groupId>
+      <artifactId>testCompile</artifactId>
+      <version>1.0</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+</project>
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedQuickstartPom.txt b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedQuickstartPom.txt
new file mode 100644
index 0000000..0868d06
--- /dev/null
+++ b/subprojects/gradle-core/src/integTest/resources/org/gradle/integtests/maven/pomGeneration/expectedQuickstartPom.txt
@@ -0,0 +1,7 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>${groupId}</groupId>
+  <artifactId>quickstart</artifactId>
+  <version>${version}</version>
+</project>
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/BuildAdapter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/BuildAdapter.java
new file mode 100644
index 0000000..b06e4d6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/BuildAdapter.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+import org.gradle.api.initialization.Settings;
+import org.gradle.api.invocation.Gradle;
+
+/**
+ * A {@link BuildListener} adapter class for receiving build events. The methods in this class are empty.
+ * This class exists as convenience for creating listener objects.
+ */
+public class BuildAdapter implements BuildListener
+{
+    public void buildStarted(Gradle gradle) {
+    }
+
+    public void settingsEvaluated(Settings settings) {
+    }
+
+    public void projectsLoaded(Gradle gradle) {
+    }
+
+    public void projectsEvaluated(Gradle gradle) {
+    }
+
+    public void buildFinished(BuildResult result) {
+    }
+}
+
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/BuildExceptionReporter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/BuildExceptionReporter.java
new file mode 100644
index 0000000..8041fab
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/BuildExceptionReporter.java
@@ -0,0 +1,176 @@
+/*
+ * 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;
+
+import org.codehaus.groovy.runtime.StackTraceUtils;
+import org.gradle.api.GradleException;
+import org.gradle.api.LocationAwareException;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.initialization.DefaultCommandLine2StartParameterConverter;
+import org.gradle.util.GUtil;
+import org.gradle.execution.TaskSelectionException;
+import org.slf4j.Logger;
+
+import java.util.Formatter;
+
+/**
+ * A {@link BuildListener} which reports the build exception, if any.
+ */
+public class BuildExceptionReporter extends BuildAdapter {
+    private enum ExceptionStyle {
+        None, Sanitized, Full
+    }
+
+    private final Logger logger;
+    private final StartParameter startParameter;
+
+    public BuildExceptionReporter(Logger logger, StartParameter startParameter) {
+        this.logger = logger;
+        this.startParameter = startParameter;
+    }
+
+    public void buildFinished(BuildResult result) {
+        Throwable failure = result.getFailure();
+        if (failure == null) {
+            return;
+        }
+
+        FailureDetails details = new FailureDetails(failure);
+        if (failure instanceof GradleException) {
+            reportBuildFailure((GradleException) failure, details);
+        } else {
+            reportInternalError(details);
+        }
+
+        write(details);
+    }
+
+    protected void write(FailureDetails details) {
+        Formatter formatter = new Formatter();
+        formatter.format("%nFAILURE: %s", details.summary.toString().trim());
+
+        String location = details.location.toString().trim();
+        if (location.length() > 0) {
+            formatter.format("%n%n* Where:%n%s", location);
+        }
+
+        String failureDetails = details.details.toString().trim();
+        if (failureDetails.length() > 0) {
+            formatter.format("%n%n* What went wrong:%n%s", failureDetails);
+        }
+
+        String resolution = details.resolution.toString().trim();
+        if (resolution.length() > 0) {
+            formatter.format("%n%n* Try:%n%s", resolution);
+        }
+        switch (details.exception) {
+            case None:
+                logger.error(formatter.toString());
+                break;
+            case Sanitized:
+                formatter.format("%n%n* Exception is:");
+                logger.error(formatter.toString(), StackTraceUtils.deepSanitize(details.failure));
+                break;
+            case Full:
+                formatter.format("%n%n* Exception is:");
+                logger.error(formatter.toString(), details.failure);
+                break;
+        }
+    }
+
+    public void reportInternalError(FailureDetails details) {
+        details.summary.format("Build aborted because of an internal error.");
+        details.details.format("Build aborted because of an unexpected internal error. Please file an issue at: www.gradle.org.");
+        details.resolution.format("Run with -%s option to get additional debug info.",
+                DefaultCommandLine2StartParameterConverter.DEBUG);
+        details.exception = ExceptionStyle.Full;
+    }
+
+    private void reportBuildFailure(GradleException failure, FailureDetails details) {
+        boolean stacktrace = startParameter != null
+                && (startParameter.getShowStacktrace() != StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS
+                        || startParameter.getLogLevel() == LogLevel.DEBUG);
+        if (stacktrace) {
+            details.exception = ExceptionStyle.Sanitized;
+        }
+        boolean fullStacktrace = startParameter != null
+                && (startParameter.getShowStacktrace() == StartParameter.ShowStacktrace.ALWAYS_FULL);
+        if (fullStacktrace) {
+            details.exception = ExceptionStyle.Full;
+        }
+
+        if (failure instanceof TaskSelectionException) {
+            formatTaskSelectionFailure((TaskSelectionException) failure, details);
+        } else {
+            formatGenericFailure(failure, stacktrace, fullStacktrace, details);
+        }
+    }
+
+    private void formatTaskSelectionFailure(TaskSelectionException failure, FailureDetails details) {
+        assert failure.getCause() == null;
+        details.summary.format("Could not determine which tasks to execute.");
+        details.details.format("%s", getMessage(failure));
+        details.resolution.format("Run with -%s to get a list of available tasks.", DefaultCommandLine2StartParameterConverter.TASKS);
+    }
+
+    private void formatGenericFailure(GradleException failure, boolean stacktrace, boolean fullStacktrace,
+                                      FailureDetails details) {
+        details.summary.format("Build failed with an exception.");
+        if (!fullStacktrace) {
+            if (!stacktrace) {
+                details.resolution.format("Run with -%s or -%s option to get more details. ",
+                        DefaultCommandLine2StartParameterConverter.STACKTRACE,
+                        DefaultCommandLine2StartParameterConverter.DEBUG);
+            }
+            details.resolution.format("Run with -%s option to get the full (very verbose) stacktrace.",
+                    DefaultCommandLine2StartParameterConverter.FULL_STACKTRACE);
+        }
+
+        if (failure instanceof LocationAwareException) {
+            LocationAwareException scriptException = (LocationAwareException) failure;
+            if (scriptException.getLocation() != null) {
+                details.location.format("%s", scriptException.getLocation());
+            }
+            details.details.format("%s", scriptException.getOriginalMessage());
+            for (Throwable cause : scriptException.getReportableCauses()) {
+                details.details.format("%nCause: %s", getMessage(cause));
+            }
+        } else {
+            details.details.format("%s", getMessage(failure));
+        }
+    }
+
+    private String getMessage(Throwable throwable) {
+        String message = throwable.getMessage();
+        if (GUtil.isTrue(message)) {
+            return message;
+        }
+        return String.format("%s (no error message)", throwable.getClass().getName());
+    }
+
+    private static class FailureDetails {
+        private ExceptionStyle exception = ExceptionStyle.None;
+        private final Formatter summary = new Formatter();
+        private final Formatter details = new Formatter();
+        private final Formatter location = new Formatter();
+        private final Formatter resolution = new Formatter();
+        private final Throwable failure;
+
+        public FailureDetails(Throwable failure) {
+            this.failure = failure;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/BuildListener.java b/subprojects/gradle-core/src/main/groovy/org/gradle/BuildListener.java
new file mode 100644
index 0000000..a27aaad
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/BuildListener.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+import org.gradle.api.initialization.Settings;
+import org.gradle.api.invocation.Gradle;
+
+/**
+ * <p>A {@code BuildListener} is notified of the major lifecycle events as a {@link GradleLauncher} instance executes a
+ * build.</p>
+ *
+ * @author Hans Dockter
+ * @see org.gradle.api.invocation.Gradle#addListener(Object)
+ */
+public interface BuildListener {
+    /**
+     * <p>Called when the build is started.</p>
+     *
+     * @param gradle The build which is being started. Never null.
+     */
+    void buildStarted(Gradle gradle);
+
+    /**
+     * <p>Called when the build settings have been loaded and evaluated. The settings object is fully configured and is
+     * ready to use to load the build projects.</p>
+     *
+     * @param settings The settings. Never null.
+     */
+    void settingsEvaluated(Settings settings);
+
+    /**
+     * <p>Called when the projects for the build have been created from the settings. None of the projects have been
+     * evaluated.</p>
+     *
+     * @param gradle The build which has been loaded. Never null.
+     */
+    void projectsLoaded(Gradle gradle);
+
+    /**
+     * <p>Called when all projects for the build have been evaluated. The project objects are fully configured and are
+     * ready to use to populate the task graph.</p>
+     *
+     * @param gradle The build which has been evaluated. Never null.
+     */
+    void projectsEvaluated(Gradle gradle);
+
+    /**
+     * <p>Called when the build is completed. All selected tasks have been executed.</p>
+     *
+     * @param result The result of the build. Never null.
+     */
+    void buildFinished(BuildResult result);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/BuildLogger.java b/subprojects/gradle-core/src/main/groovy/org/gradle/BuildLogger.java
new file mode 100644
index 0000000..0f5c290
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/BuildLogger.java
@@ -0,0 +1,79 @@
+/*
+ * 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;
+
+import org.gradle.api.execution.TaskExecutionGraph;
+import org.gradle.api.execution.TaskExecutionGraphListener;
+import org.gradle.api.initialization.Settings;
+import org.gradle.api.internal.SettingsInternal;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.api.logging.Logger;
+import org.gradle.util.Clock;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * A {@link org.gradle.BuildListener} which logs the build progress.
+ */
+public class BuildLogger implements BuildListener, TaskExecutionGraphListener {
+    private final Logger logger;
+    private final List<BuildListener> resultLoggers = new ArrayList<BuildListener>();
+
+    public BuildLogger(Logger logger, Clock buildTimeClock, StartParameter startParameter) {
+        this.logger = logger;
+        resultLoggers.add(new BuildExceptionReporter(logger, startParameter));
+        resultLoggers.add(new BuildResultLogger(logger, buildTimeClock));
+    }
+
+    public void buildStarted(Gradle gradle) {
+        StartParameter startParameter = gradle.getStartParameter();
+        logger.info("Starting Build");
+        logger.debug("Gradle user home: " + startParameter.getGradleUserHomeDir());
+        logger.debug("Current dir: " + startParameter.getCurrentDir());
+        logger.debug("Settings file: " + startParameter.getSettingsScriptSource());
+        logger.debug("Build file: " + startParameter.getBuildFile());
+        logger.debug("Select default project: " + startParameter.getDefaultProjectSelector().getDisplayName());
+    }
+
+    public void settingsEvaluated(Settings settings) {
+        SettingsInternal settingsInternal = (SettingsInternal) settings;
+        logger.info(String.format("Settings evaluated using %s.",
+                settingsInternal.getSettingsScript().getDisplayName()));
+    }
+
+    public void projectsLoaded(Gradle gradle) {
+        ProjectInternal projectInternal = (ProjectInternal) gradle.getRootProject();
+        logger.info(String.format("Projects loaded. Root project using %s.",
+                projectInternal.getBuildScriptSource().getDisplayName()));
+        logger.info(String.format("Included projects: %s", projectInternal.getAllprojects()));
+    }
+
+    public void projectsEvaluated(Gradle gradle) {
+        logger.info("All projects evaluated.");
+    }
+
+    public void graphPopulated(TaskExecutionGraph graph) {
+        logger.info(String.format("Tasks to be executed: %s", graph.getAllTasks()));
+    }
+
+    public void buildFinished(BuildResult result) {
+        for (BuildListener logger : resultLoggers) {
+            logger.buildFinished(result);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/BuildResult.java b/subprojects/gradle-core/src/main/groovy/org/gradle/BuildResult.java
new file mode 100644
index 0000000..303b1df
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/BuildResult.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.invocation.Gradle;
+
+/**
+ * <p>A {@code BuildResult} packages up the results of a build executed by a {@link GradleLauncher} instance.</p>
+ */
+public class BuildResult {
+    private final Throwable failure;
+    private final Gradle gradle;
+
+    public BuildResult(Gradle gradle, Throwable failure) {
+        this.gradle = gradle;
+        this.failure = failure;
+    }
+
+    public Gradle getGradle() {
+        return gradle;
+    }
+
+    public Throwable getFailure() {
+        return failure;
+    }
+
+    /**
+     * <p>Rethrows the build failure. Does nothing if there was no build failure.</p>
+     */
+    public BuildResult rethrowFailure() {
+        if (failure instanceof GradleException) {
+            throw (GradleException) failure;
+        }
+        if (failure != null) {
+            throw new GradleException("Build aborted because of an internal error.", failure);
+        }
+        return this;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/BuildResultLogger.java b/subprojects/gradle-core/src/main/groovy/org/gradle/BuildResultLogger.java
new file mode 100644
index 0000000..c6fe485
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/BuildResultLogger.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.util.Clock;
+
+/**
+ * A {@link BuildListener} which logs the final result of the build.
+ */
+public class BuildResultLogger extends BuildAdapter {
+    private final Logger logger;
+    private final Clock buildTimeClock;
+
+    public BuildResultLogger(Logger logger, Clock buildTimeClock) {
+        this.logger = logger;
+        this.buildTimeClock = buildTimeClock;
+    }
+
+    public void buildFinished(BuildResult result) {
+        if (result.getFailure() == null) {
+            logger.lifecycle(String.format("%nBUILD SUCCESSFUL%n"));
+        } else {
+            logger.error(String.format("%nBUILD FAILED%n"));
+        }
+        logger.lifecycle(String.format("Total time: %s", buildTimeClock.getTime()));
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/CacheUsage.java b/subprojects/gradle-core/src/main/groovy/org/gradle/CacheUsage.java
new file mode 100644
index 0000000..bcde614
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/CacheUsage.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+import org.gradle.api.InvalidUserDataException;
+
+/**
+ * <p>{@code CacheUsage} specifies how compiled scripts should be cached.</p>
+ * 
+ * @author Hans Dockter
+ */
+public enum CacheUsage {
+    ON, REBUILD;
+
+    public static CacheUsage fromString(String usagestr) {
+        try {
+            return valueOf(usagestr.toUpperCase());
+        } catch (IllegalArgumentException e) {
+            throw new InvalidUserDataException(String.format("Unknown cache usage '%s' specified.", usagestr));
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/CommandLineArgumentException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/CommandLineArgumentException.java
new file mode 100644
index 0000000..94eb752
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/CommandLineArgumentException.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+import org.gradle.api.GradleException;
+
+/**
+ * @author Hans Dockter
+ */
+public class CommandLineArgumentException extends GradleException {
+    public CommandLineArgumentException(String message) {
+        super(message);
+    }
+
+    public CommandLineArgumentException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/GradleLauncher.java b/subprojects/gradle-core/src/main/groovy/org/gradle/GradleLauncher.java
new file mode 100644
index 0000000..f88c285
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/GradleLauncher.java
@@ -0,0 +1,142 @@
+/*
+ * 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;
+
+import org.gradle.api.logging.StandardOutputListener;
+import org.gradle.initialization.DefaultGradleLauncherFactory;
+
+/**
+ * <p>{@code GradleLauncher} is the main entry point for embedding Gradle. You use this class to manage a Gradle build,
+ * as follows:</p>
+ *
+ * <ol>
+ *
+ * <li>Optionally create a {@link StartParameter} instance and configure it with the desired properties. The properties
+ * of {@code StartParameter} generally correspond to the command-line options of Gradle. You can use {@link
+ * #createStartParameter(String...)} to create a {@link StartParameter} from a set of command-line options.</li>
+ *
+ * <li>Obtain a {@code GradleLauncher} instance by calling {@link #newInstance}, passing in the {@code StartParameter},
+ * or an array of Strings that will be treated as command line arguments.</li>
+ *
+ * <li>Optionally add one or more listeners to the {@code GradleLauncher}.</li>
+ *
+ * <li>Call {@link #run} to execute the build. This will return a {@link BuildResult}. Note that if the build fails, the
+ * resulting exception will be contained in the {@code BuildResult}.</li>
+ *
+ * <li>Query the build result. You might want to call {@link BuildResult#rethrowFailure()} to rethrow any build
+ * failure.</li>
+ *
+ * </ol>
+ *
+ * @author Hans Dockter
+ */
+public abstract class GradleLauncher {
+
+    private static GradleLauncherFactory factory = new DefaultGradleLauncherFactory();
+
+    /**
+     * <p>Executes the build for this GradleLauncher instance and returns the result. Note that when the build fails,
+     * the exception is available using {@link org.gradle.BuildResult#getFailure()}.</p>
+     *
+     * @return The result. Never returns null.
+     */
+    public abstract BuildResult run();
+
+    /**
+     * Evaluates the settings and all the projects. The information about available tasks and projects is accessible via
+     * the {@link org.gradle.api.invocation.Gradle#getRootProject()} object.
+     *
+     * @return A BuildResult object. Never returns null.
+     */
+    public abstract BuildResult getBuildAnalysis();
+
+    /**
+     * Evaluates the settings and all the projects. The information about available tasks and projects is accessible via
+     * the {@link org.gradle.api.invocation.Gradle#getRootProject()} object. Fills the execution plan without running
+     * the build. The tasks to be executed tasks are available via {@link org.gradle.api.invocation.Gradle#getTaskGraph()}.
+     *
+     * @return A BuildResult object. Never returns null.
+     */
+    public abstract BuildResult getBuildAndRunAnalysis();
+
+    /**
+     * Returns a GradleLauncher instance based on the passed start parameter.
+     *
+     * @param startParameter The start parameter object the GradleLauncher instance is initialized with
+     * @return The GradleLauncher. Never returns null.
+     */
+    public static GradleLauncher newInstance(final StartParameter startParameter) {
+        return factory.newInstance(startParameter);
+    }
+
+    /**
+     * Returns a GradleLauncher instance based on the passed command line syntax arguments. Certain command line
+     * arguments won't have any effect if you choose this method (e.g. -v, -h). If you want to act upon, you better use
+     * {@link #createStartParameter(String...)} in conjunction with {@link #newInstance(String...)}.
+     *
+     * @param commandLineArgs A String array where each element denotes an entry of the Gradle command line syntax
+     * @return The GradleLauncher. Never returns null.
+     */
+    public static GradleLauncher newInstance(final String... commandLineArgs) {
+        return factory.newInstance(commandLineArgs);
+    }
+
+    /**
+     * Returns a StartParameter object out of command line syntax arguments. Every possible command line option has it
+     * associated field in the StartParameter object.
+     *
+     * @param commandLineArgs A String array where each element denotes an entry of the Gradle command line syntax
+     * @return The GradleLauncher. Never returns null.
+     */
+    public static StartParameter createStartParameter(final String... commandLineArgs) {
+        return factory.createStartParameter(commandLineArgs);
+    }
+
+    public static void injectCustomFactory(GradleLauncherFactory gradleLauncherFactory) {
+        factory = gradleLauncherFactory == null ? new DefaultGradleLauncherFactory() : gradleLauncherFactory;
+    }
+
+    /**
+     * <p>Adds a listener to this build instance. The listener is notified of events which occur during the execution of
+     * the build. See {@link org.gradle.api.invocation.Gradle#addListener(Object)} for supported listener types.</p>
+     *
+     * @param listener The listener to add. Has no effect if the listener has already been added.
+     */
+    public abstract void addListener(Object listener);
+
+    /**
+     * Use the given listener. See {@link org.gradle.api.invocation.Gradle#useLogger(Object)} for details.
+     *
+     * @param logger The logger to use.
+     */
+    public abstract void useLogger(Object logger);
+
+    /**
+     * <p>Adds a {@link StandardOutputListener} to this build instance. The listener is notified of any text written to
+     * standard output by Gradle's logging system
+     *
+     * @param listener The listener to add. Has no effect if the listener has already been added.
+     */
+    public abstract void addStandardOutputListener(StandardOutputListener listener);
+
+    /**
+     * <p>Adds a {@link StandardOutputListener} to this build instance. The listener is notified of any text written to
+     * standard error by Gradle's logging system
+     *
+     * @param listener The listener to add. Has no effect if the listener has already been added.
+     */
+    public abstract void addStandardErrorListener(StandardOutputListener listener);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/GradleLauncherFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/GradleLauncherFactory.java
new file mode 100644
index 0000000..be98f25
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/GradleLauncherFactory.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * <p>A {@code GradleFactory} is responsible for creating a {@link GradleLauncher} instance for a build, from a {@link
+ * StartParameter}.</p>
+ *
+ * @author Hans Dockter
+ */
+public interface GradleLauncherFactory {
+    /**
+     * Creates a new {@link GradleLauncher} instance for the given parameters.
+     *
+     * @param startParameter The parameters to use for the build.
+     * @return The new instance.
+     */
+    GradleLauncher newInstance(StartParameter startParameter);
+
+    GradleLauncher newInstance(final String[] commandLineArgs);
+
+    StartParameter createStartParameter(String[] commandLineArgs);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/StartParameter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/StartParameter.java
new file mode 100644
index 0000000..e9d69db
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/StartParameter.java
@@ -0,0 +1,533 @@
+/*
+ * 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;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.gradle.api.artifacts.ProjectDependenciesBuildInstruction;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.execution.*;
+import org.gradle.groovy.scripts.UriScriptSource;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.groovy.scripts.StringScriptSource;
+import org.gradle.initialization.*;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * <p>{@code StartParameter} defines the configuration used by a {@link GradleLauncher} instance to execute a build. The
+ * properties of {@code StartParameter} generally correspond to the command-line options of Gradle. You pass a {@code
+ * StartParameter} instance to {@link GradleLauncher#newInstance(StartParameter)} when you create a new {@code Gradle}
+ * instance.</p>
+ *
+ * <p>You can obtain an instance of a {@code StartParameter} by either creating a new one, or duplicating an existing
+ * one using {@link #newInstance} or {@link #newBuild}.</p>
+ *
+ * @author Hans Dockter
+ * @see GradleLauncher
+ */
+public class StartParameter {
+    public enum ShowStacktrace {
+        INTERNAL_EXCEPTIONS, ALWAYS, ALWAYS_FULL
+    }
+
+    private List<String> taskNames = new ArrayList<String>();
+    private Set<String> excludedTaskNames = new HashSet<String>();
+    private ProjectDependenciesBuildInstruction projectDependenciesBuildInstruction
+            = new ProjectDependenciesBuildInstruction(Collections.<String>emptyList());
+    private File currentDir;
+    private boolean searchUpwards = true;
+    private Map<String, String> projectProperties = new HashMap<String, String>();
+    private Map<String, String> systemPropertiesArgs = new HashMap<String, String>();
+    public static final String GRADLE_USER_HOME_PROPERTY_KEY = "gradle.user.home";
+    /**
+     * The default user home directory.
+     */
+    public static final File DEFAULT_GRADLE_USER_HOME = new File(System.getProperty("user.home") + "/.gradle");
+    private File gradleUserHomeDir = new File(GUtil.elvis(System.getProperty(GRADLE_USER_HOME_PROPERTY_KEY), DEFAULT_GRADLE_USER_HOME.getAbsolutePath()));
+    private CacheUsage cacheUsage = CacheUsage.ON;
+    private ScriptSource buildScriptSource;
+    private ScriptSource settingsScriptSource;
+    private BuildExecuter buildExecuter;
+    private ProjectSpec defaultProjectSelector;
+    private LogLevel logLevel = LogLevel.LIFECYCLE;
+    private ShowStacktrace showStacktrace = ShowStacktrace.INTERNAL_EXCEPTIONS;
+    private File buildFile;
+    private List<File> initScripts = new ArrayList<File>();
+    private boolean showHelp;
+    private boolean showVersion;
+    private boolean launchGUI;
+    private boolean dryRun;
+    private boolean noOpt;
+
+    /**
+     * Creates a {@code StartParameter} with default values. This is roughly equivalent to running Gradle on the
+     * command-line with no arguments.
+     */
+    public StartParameter() {
+        setCurrentDir(null);
+    }
+
+    /**
+     * Duplicates this {@code StartParameter} instance.
+     *
+     * @return the new parameters.
+     */
+    public StartParameter newInstance() {
+        StartParameter startParameter = new StartParameter();
+        startParameter.buildFile = buildFile;
+        startParameter.taskNames = taskNames;
+        startParameter.projectDependenciesBuildInstruction = projectDependenciesBuildInstruction;
+        startParameter.currentDir = currentDir;
+        startParameter.searchUpwards = searchUpwards;
+        startParameter.projectProperties = projectProperties;
+        startParameter.systemPropertiesArgs = systemPropertiesArgs;
+        startParameter.gradleUserHomeDir = gradleUserHomeDir;
+        startParameter.cacheUsage = cacheUsage;
+        startParameter.buildScriptSource = buildScriptSource;
+        startParameter.settingsScriptSource = settingsScriptSource;
+        startParameter.initScripts = new ArrayList<File>(initScripts); 
+        startParameter.buildExecuter = buildExecuter;
+        startParameter.defaultProjectSelector = defaultProjectSelector;
+        startParameter.logLevel = logLevel;
+        startParameter.showStacktrace = showStacktrace;
+        startParameter.showHelp = showHelp;
+        startParameter.showVersion = showVersion;
+        startParameter.dryRun = dryRun;
+        startParameter.noOpt = noOpt;
+        return startParameter;
+    }
+
+    /**
+     * <p>Creates the parameters for a new build, using these parameters as a template. Copies the environmental
+     * properties from this parameter (eg gradle user home dir, etc), but does not copy the build specific properties
+     * (eg task names).</p>
+     *
+     * @return The new parameters.
+     */
+    public StartParameter newBuild() {
+        StartParameter startParameter = new StartParameter();
+        startParameter.gradleUserHomeDir = gradleUserHomeDir;
+        startParameter.cacheUsage = cacheUsage;
+        startParameter.logLevel = logLevel;
+        return startParameter;
+    }
+
+    public boolean equals(Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj);
+    }
+
+    public int hashCode() {
+        return HashCodeBuilder.reflectionHashCode(this);
+    }
+
+    /**
+     * Returns the build file to use to select the default project. Returns null when the build file is not used to
+     * select the default project.
+     *
+     * @return The build file. May be null.
+     */
+    public File getBuildFile() {
+        return buildFile;
+    }
+
+    /**
+     * Sets the build file to use to select the default project. Use null to disable selecting the default project using
+     * the build file.
+     *
+     * @param buildFile The build file. May be null.
+     */
+    public void setBuildFile(File buildFile) {
+        if (buildFile == null) {
+            this.buildFile = null;
+            setCurrentDir(null);
+        } else {
+            this.buildFile = GFileUtils.canonicalise(buildFile);
+            currentDir = this.buildFile.getParentFile();
+            defaultProjectSelector = new BuildFileProjectSpec(this.buildFile);
+        }
+    }
+
+    /**
+     * <p>Returns the {@link ScriptSource} to use for the build file for this build. Returns null when the default build
+     * file(s) are to be used. This source is used for <em>all</em> projects included in the build.</p>
+     *
+     * @return The build file source, or null to use the defaults.
+     */
+    public ScriptSource getBuildScriptSource() {
+        return buildScriptSource;
+    }
+
+    /**
+     * <p>Returns the {@link ScriptSource} to use for the settings script for this build. Returns null when the default
+     * settings script is to be used.</p>
+     *
+     * @return The settings script source, or null to use the default.
+     */
+    public ScriptSource getSettingsScriptSource() {
+        return settingsScriptSource;
+    }
+
+    /**
+     * <p>Sets the {@link ScriptSource} to use for the settings script. Set to null to use the default settings
+     * script.</p>
+     *
+     * @param settingsScriptSource The settings script source.
+     */
+    public void setSettingsScriptSource(ScriptSource settingsScriptSource) {
+        this.settingsScriptSource = settingsScriptSource;
+    }
+
+    /**
+     * <p>Specifies that the given script should be used as the build file for this build. Uses an empty settings file.
+     * </p>
+     *
+     * @param buildScriptText The script to use as the build file.
+     * @return this
+     */
+    public StartParameter useEmbeddedBuildFile(String buildScriptText) {
+        return setBuildScriptSource(new StringScriptSource("embedded build file", buildScriptText));
+    }
+    
+    /**
+     * <p>Specifies that the given script should be used as the build file for this build. Uses an empty settings file.
+     * </p>
+     *
+     * @param buildScript The script to use as the build file.
+     * @return this
+     */
+    public StartParameter setBuildScriptSource(ScriptSource buildScript) {
+        buildScriptSource = buildScript;
+        settingsScriptSource = new StringScriptSource("empty settings file", "");
+        searchUpwards = false;
+        return this;
+    }
+
+    /**
+     * <p>Returns the {@link BuildExecuter} to use for the build.</p>
+     *
+     * @return The {@link BuildExecuter}. Never returns null.
+     */
+    public BuildExecuter getBuildExecuter() {
+        BuildExecuter executer = buildExecuter;
+        if (executer == null) {
+            executer = new DefaultBuildExecuter(taskNames, excludedTaskNames);
+        }
+        if (dryRun) {
+            executer = new DryRunBuildExecuter(executer);
+        }
+        return executer;
+    }
+
+    /**
+     * <p>Sets the {@link BuildExecuter} to use for the build. You can use the method to change the algorithm used to
+     * execute the build, by providing your own {@code BuildExecuter} implementation.</p>
+     *
+     * <p> Set to null to use the default executer. When this property is set to a non-null value, the taskNames and
+     * mergedBuild properties are ignored.</p>
+     *
+     * @param buildExecuter The executer to use, or null to use the default executer.
+     */
+    public void setBuildExecuter(BuildExecuter buildExecuter) {
+        this.buildExecuter = buildExecuter;
+    }
+
+    /**
+     * Returns the names of the tasks to execute in this build. When empty, the default tasks for the project will be
+     * executed.
+     *
+     * @return the names of the tasks to execute in this build. Never returns null.
+     */
+    public List<String> getTaskNames() {
+        return taskNames;
+    }
+
+    /**
+     * <p>Sets the tasks to execute in this build. Set to an empty list, or null, to execute the default tasks for the
+     * project. The tasks are executed in the order provided, subject to dependency between the tasks.</p>
+     *
+     * @param taskNames the names of the tasks to execute in this build.
+     */
+    public void setTaskNames(Collection<String> taskNames) {
+        this.taskNames = !GUtil.isTrue(taskNames) ? new ArrayList<String>() : new ArrayList<String>(taskNames);
+        buildExecuter = null;
+    }
+
+    /**
+     * Returns the names of the tasks to be excluded from this build. When empty, no tasks are excluded from the build.
+     *
+     * @return The names of the excluded tasks. Returns an empty set if there are no such tasks.
+     */
+    public Set<String> getExcludedTaskNames() {
+        return excludedTaskNames;
+    }
+
+    /**
+     * Sets the tasks to exclude from this build.
+     *
+     * @param excludedTaskNames The task names. Can be null.
+     */
+    public void setExcludedTaskNames(Collection<String> excludedTaskNames) {
+        this.excludedTaskNames = !GUtil.isTrue(excludedTaskNames) ? new HashSet<String>() : new HashSet<String>(excludedTaskNames);
+    }
+
+    /**
+     * Returns the directory to use to select the default project, and to search for the settings file.
+     *
+     * @return The current directory. Never returns null.
+     */
+    public File getCurrentDir() {
+        return currentDir;
+    }
+
+    /**
+     * Sets the directory to use to select the default project, and to search for the settings file. Set to null to use
+     * the default current directory.
+     *
+     * @param currentDir The directory. Should not be null.
+     */
+    public void setCurrentDir(File currentDir) {
+        if (currentDir != null) {
+            this.currentDir = GFileUtils.canonicalise(currentDir);
+        } else {
+            this.currentDir = GFileUtils.canonicalise(new File(System.getProperty("user.dir")));
+        }
+        defaultProjectSelector = null;
+    }
+
+    public boolean isSearchUpwards() {
+        return searchUpwards;
+    }
+
+    public void setSearchUpwards(boolean searchUpwards) {
+        this.searchUpwards = searchUpwards;
+    }
+
+    public Map<String, String> getProjectProperties() {
+        return projectProperties;
+    }
+
+    public void setProjectProperties(Map<String, String> projectProperties) {
+        this.projectProperties = projectProperties;
+    }
+
+    public Map<String, String> getSystemPropertiesArgs() {
+        return systemPropertiesArgs;
+    }
+
+    public void setSystemPropertiesArgs(Map<String, String> systemPropertiesArgs) {
+        this.systemPropertiesArgs = systemPropertiesArgs;
+    }
+
+    /**
+     * Returns the directory to use as the user home directory.
+     *
+     * @return The home directory.
+     */
+    public File getGradleUserHomeDir() {
+        return gradleUserHomeDir;
+    }
+
+    /**
+     * Sets the directory to use as the user home directory. Set to null to use the default directory.
+     *
+     * @param gradleUserHomeDir The home directory. May be null.
+     */
+    public void setGradleUserHomeDir(File gradleUserHomeDir) {
+        this.gradleUserHomeDir = gradleUserHomeDir == null ? DEFAULT_GRADLE_USER_HOME : GFileUtils.canonicalise(gradleUserHomeDir);
+    }
+
+    public ProjectDependenciesBuildInstruction getProjectDependenciesBuildInstruction() {
+        return projectDependenciesBuildInstruction;
+    }
+
+    public void setProjectDependenciesBuildInstruction(
+            ProjectDependenciesBuildInstruction projectDependenciesBuildInstruction) {
+        this.projectDependenciesBuildInstruction = projectDependenciesBuildInstruction;
+    }
+
+    public CacheUsage getCacheUsage() {
+        return cacheUsage;
+    }
+
+    public void setCacheUsage(CacheUsage cacheUsage) {
+        this.cacheUsage = cacheUsage;
+    }
+
+    public boolean isShowHelp() {
+        return showHelp;
+    }
+
+    public void setShowHelp(boolean showHelp) {
+        this.showHelp = showHelp;
+    }
+
+    public boolean isShowVersion() {
+        return showVersion;
+    }
+
+    public void setShowVersion(boolean showVersion) {
+        this.showVersion = showVersion;
+    }
+
+    public boolean isDryRun() {
+        return dryRun;
+    }
+
+    public void setDryRun(boolean dryRun) {
+        this.dryRun = dryRun;
+    }
+
+    public boolean isNoOpt() {
+        return noOpt;
+    }
+
+    public void setNoOpt(boolean noOpt) {
+        this.noOpt = noOpt;
+    }
+
+    /**
+     * Sets the settings file to use for the build. Use null to use the default settings file.
+     *
+     * @param settingsFile The settings file to use. May be null.
+     */
+    public void setSettingsFile(File settingsFile) {
+        if (settingsFile == null) {
+            settingsScriptSource = null;
+        } else {
+            File canonicalFile = GFileUtils.canonicalise(settingsFile);
+            currentDir = canonicalFile.getParentFile();
+            settingsScriptSource = new UriScriptSource("settings file", canonicalFile);
+        }
+    }
+
+    /**
+     * Adds the given file to the list of init scripts that are run before the build starts.  This list is in
+     * addition to the user init script located in ${user.home}/.gradle/init.gradle.
+     * @param initScriptFile The init script to be run during the Gradle invocation.
+     */
+    public void addInitScript(File initScriptFile)
+    {
+        initScripts.add(initScriptFile);
+    }
+
+    public void setInitScripts(List<File> initScripts) {
+        this.initScripts = initScripts;
+    }
+
+    /**
+     * Returns all explicitly added init scripts that will be run before the build starts.  This list does not
+     * contain the user init script located in ${user.home}/.gradle/init.gradle, even though that init script
+     * will also be run.
+     * @return list of all explicitly added init scripts.
+     */
+    public List<File> getInitScripts() {
+        return Collections.unmodifiableList(initScripts);
+    }
+
+    public LogLevel getLogLevel() {
+        return logLevel;
+    }
+
+    public void setLogLevel(LogLevel logLevel) {
+        this.logLevel = logLevel;
+    }
+
+    public ShowStacktrace getShowStacktrace() {
+        return showStacktrace;
+    }
+
+    public void setShowStacktrace(ShowStacktrace showStacktrace) {
+        this.showStacktrace = showStacktrace;
+    }
+
+    /**
+     * Returns the selector used to choose the default project of the build. This is the project used as the starting
+     * point for resolving task names, and for determining the default tasks.
+     *
+     * @return The default project. Never returns null.
+     */
+    public ProjectSpec getDefaultProjectSelector() {
+        return defaultProjectSelector != null ? defaultProjectSelector : new DefaultProjectSpec(currentDir);
+    }
+
+    /**
+     * Sets the selector used to choose the default project of the build.
+     *
+     * @param defaultProjectSelector The selector. Should not be null.
+     */
+    public void setDefaultProjectSelector(ProjectSpec defaultProjectSelector) {
+        this.defaultProjectSelector = defaultProjectSelector;
+    }
+
+    /**
+     * Sets the project directory to use to select the default project. Use null to use the default criteria for
+     * selecting the default project.
+     *
+     * @param projectDir The project directory. May be null.
+     */
+    public void setProjectDir(File projectDir) {
+        if (projectDir == null) {
+            setCurrentDir(null);
+        } else {
+            File canonicalFile = GFileUtils.canonicalise(projectDir);
+            currentDir = canonicalFile;
+            defaultProjectSelector = new ProjectDirectoryProjectSpec(canonicalFile);
+        }
+    }
+
+    /**
+       Determines whether or not the GUI was requested to be launched.
+    */
+    public boolean isLaunchGUI() {
+        return launchGUI;
+    }
+
+    public void setLaunchGUI(boolean launchGUI) {
+        this.launchGUI = launchGUI;
+    }
+
+    @Override
+    public String toString() {
+        return "StartParameter{" +
+                "taskNames=" + taskNames +
+                ", excludedTaskNames=" + excludedTaskNames +
+                ", currentDir=" + currentDir +
+                ", searchUpwards=" + searchUpwards +
+                ", projectProperties=" + projectProperties +
+                ", systemPropertiesArgs=" + systemPropertiesArgs +
+                ", gradleUserHomeDir=" + gradleUserHomeDir +
+                ", cacheUsage=" + cacheUsage +
+                ", buildScriptSource=" + buildScriptSource +
+                ", settingsScriptSource=" + settingsScriptSource +
+                ", buildExecuter=" + buildExecuter +
+                ", defaultProjectSelector=" + defaultProjectSelector +
+                ", logLevel=" + logLevel +
+                ", showStacktrace=" + showStacktrace +
+                ", buildFile=" + buildFile +
+                ", initScripts=" + initScripts +
+                ", showHelp=" + showHelp +
+                ", showVersion=" + showVersion +
+                ", launchGUI=" + launchGUI +
+                ", dryRun=" + dryRun +
+                ", noOpt=" + noOpt +
+                '}';
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/TaskExecutionLogger.java b/subprojects/gradle-core/src/main/groovy/org/gradle/TaskExecutionLogger.java
new file mode 100644
index 0000000..ced9f87
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/TaskExecutionLogger.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;
+
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.logging.ProgressLogger;
+import org.gradle.api.tasks.TaskState;
+import org.gradle.logging.ProgressLoggerFactory;
+
+/**
+ * A listener which logs the execution of tasks.
+ */
+public class TaskExecutionLogger implements TaskExecutionListener {
+    private ProgressLogger currentTask;
+    private final ProgressLoggerFactory progressLoggerFactory;
+
+    public TaskExecutionLogger(ProgressLoggerFactory progressLoggerFactory) {
+        this.progressLoggerFactory = progressLoggerFactory;
+    }
+
+    public void beforeExecute(Task task) {
+        assert currentTask == null;
+        currentTask = progressLoggerFactory.start(getDisplayName(task));
+        currentTask.progress(getDisplayName(task));
+    }
+
+    public void afterExecute(Task task, TaskState state) {
+        if (state.getSkipMessage() != null) {
+            currentTask.completed(state.getSkipMessage());
+        } else {
+            currentTask.completed();
+        }
+        currentTask = null;
+    }
+
+    private String getDisplayName(Task task) {
+        Gradle build = task.getProject().getGradle();
+        if (build.getParent() == null) {
+            // The main build, use the task path
+            return task.getPath();
+        }
+        // A nested build, use a discriminator
+        return Project.PATH_SEPARATOR + build.getRootProject().getName() + task.getPath();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/Action.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/Action.java
new file mode 100644
index 0000000..bd6a20c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/Action.java
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+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/gradle-core/src/main/groovy/org/gradle/api/AntBuilder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/AntBuilder.java
new file mode 100644
index 0000000..deac473
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/AntBuilder.java
@@ -0,0 +1,46 @@
+/*
+ * 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 java.util.Map;
+
+/**
+ * <p>An {@code AntBuilder} allows you to use Ant from your build script.</p>
+ */
+public abstract class AntBuilder extends groovy.util.AntBuilder {
+    /**
+     * Returns the properties of the Ant project. This is a live map, you that you can make changes to the map and these
+     * changes are reflected in the Ant project.
+     *
+     * @return The properties. Never returns null.
+     */
+    public abstract Map<String, Object> getProperties();
+
+    /**
+     * Returns the references of the Ant project. This is a live map, you that you can make changes to the map and these
+     * changes are reflected in the Ant project.
+     *
+     * @return The references. Never returns null.
+     */
+    public abstract Map<String, Object> getReferences();
+
+    /**
+     * Imports an Ant build into the associated Gradle project.
+     *
+     * @param antBuildFile The build file. This is resolved as per {@link Project#file(Object)}.
+     */
+    public abstract void importBuild(Object antBuildFile);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/Buildable.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/Buildable.java
new file mode 100644
index 0000000..2fbb93a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/Buildable.java
@@ -0,0 +1,34 @@
+/*
+ * 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.api.tasks.TaskDependency;
+
+/**
+ * A {@code Buildable} represents an artifact or set of artifacts which are built by one or more {@link Task}
+ * instances.
+ */
+public interface Buildable {
+    /**
+     * Returns a dependency which contains the tasks which build this artifact. All {@code Buildable} implementations
+     * must ensure that the returned dependency object is live, so that it tracks changes to the dependencies of this
+     * buildable.
+     *
+     * @return The dependency. Never returns null. Returns an empty dependency when this artifact is not built by any
+     *         tasks.
+     */
+    TaskDependency getBuildDependencies();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/CircularReferenceException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/CircularReferenceException.java
new file mode 100644
index 0000000..34b7d5c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/CircularReferenceException.java
@@ -0,0 +1,33 @@
+/*
+ * 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.api;
+
+/**
+ * <p>A <code>CircularReferenceException</code> is thrown if circular references exists between tasks, the project
+ * evaluation order or the project dependsOn order.</p>
+ *
+ * @author Hans Dockter
+ */
+public class CircularReferenceException extends GradleException {
+    public CircularReferenceException(String message) {
+        super(message);
+    }
+
+    public CircularReferenceException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/DefaultTask.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/DefaultTask.java
new file mode 100644
index 0000000..041a87f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/DefaultTask.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+import groovy.lang.GroovyObject;
+import org.codehaus.groovy.runtime.InvokerHelper;
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.internal.NoConventionMapping;
+
+/**
+ * {@code DefaultTask} is the standard {@link Task} implementation. You can extend this to implement your own tasks.
+ *
+ * @author Hans Dockter
+ */
+ at NoConventionMapping
+public class DefaultTask extends AbstractTask implements Task {
+    public DefaultTask() {
+        if (this instanceof GroovyObject) {
+            GroovyObject groovyObject = (GroovyObject) this;
+            if (groovyObject.getMetaClass() == null) {
+                groovyObject.setMetaClass(InvokerHelper.getMetaClass(getClass()));
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/DomainObjectCollection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/DomainObjectCollection.java
new file mode 100644
index 0000000..75e5da3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/DomainObjectCollection.java
@@ -0,0 +1,118 @@
+/*
+ * 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 groovy.lang.Closure;
+import org.gradle.api.specs.Spec;
+
+import java.util.Set;
+
+/**
+ * <p>A {@code DomainObjectCollection} represents a read-only set of domain objects of type {@code T}.</p>
+ *
+ * <p>You can use the methods of this interface to query the elements of the collection. You can also add actions
+ * which are executed as elements are added to this collection.</p>
+ *
+ * @param <T> The type of domain objects in this collection.
+ */
+public interface DomainObjectCollection<T> extends Iterable<T> {
+    /**
+     * Returns the objects in this collection.
+     *
+     * @return The objects. Returns an empty set if this collection is empty.
+     */
+    Set<T> getAll();
+
+    /**
+     * Returns the objects in this collection which meet the given specification.
+     *
+     * @param spec The specification to use.
+     * @return The matching objects. Returns an empty set if there are no such objects in this collection.
+     */
+    Set<T> findAll(Spec<? super T> spec);
+
+    /**
+     * Returns a collection containing the objects in this collection of the given type.  The returned collection is
+     * live, so that when matching objects are later added to this collection, they are also visible in the filtered
+     * collection.
+     *
+     * @param type The type of objects to find.
+     * @return The matching objects. Returns an empty set if there are no such objects in this collection.
+     */
+    <S extends T> DomainObjectCollection<S> withType(Class<S> type);
+
+    /**
+     * Returns a collection which contains the objects in this collection which meet the given specification. The
+     * returned collection is live, so that when matching objects are added to this collection, they are also visible in
+     * the filtered collection.
+     *
+     * @param spec The specification to use.
+     * @return The collection of matching objects. Returns an empty collection if there are no such objects in this
+     *         collection.
+     */
+    DomainObjectCollection<T> matching(Spec<? super T> spec);
+
+    /**
+     * Returns a collection which contains the objects in this collection which meet the given closure specification. The
+     * returned collection is live, so that when matching objects are added to this collection, they are also visible in
+     * the filtered collection.
+     *
+     * @param spec The specification to use. The closure gets a collection element as an argument.
+     * @return The collection of matching objects. Returns an empty collection if there are no such objects in this
+     *         collection.
+     */
+    DomainObjectCollection<T> matching(Closure spec);
+
+    /**
+     * Adds an {@code Action} to be executed when an object is added to this collection.
+     *
+     * @param action The action to be executed
+     * @return the supplied action
+     */
+    Action<? super T> whenObjectAdded(Action<? super T> action);
+
+    /**
+     * Adds a closure to be called when an object is added to this collection. The newly added object is passed to the
+     * closure as the parameter.
+     *
+     * @param action The closure to be called
+     */
+    void whenObjectAdded(Closure action);
+
+    /**
+     * Adds an {@code Action} to be executed when an object is removed from this collection.
+     *
+     * @param action The action to be executed
+     * @return the supplied action
+     */
+    Action<? super T> whenObjectRemoved(Action<? super T> action);
+
+    /**
+     * Executes the given action against all objects in this collection, and any objects subsequently added to this
+     * collection.
+     *
+     * @param action The action to be executed
+     */
+    void allObjects(Action<? super T> action);
+
+    /**
+     * Executes the given closure against all objects in this collection, and any objects subsequently added to this
+     * collection.
+     *
+     * @param action The closure to be called
+     */
+    void allObjects(Closure action);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/GradleException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/GradleException.java
new file mode 100644
index 0000000..788305f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/GradleException.java
@@ -0,0 +1,36 @@
+/*
+ * 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.api;
+
+/**
+ * <p><code>GradleException</code> is the base class of all exceptions thrown by Gradle.</p>
+ *
+ * @author Hans Dockter
+ */
+public class GradleException extends RuntimeException {
+    public GradleException() {
+        super();
+    }
+
+    public GradleException(String message) {
+        super(message);
+    }
+
+    public GradleException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/GradleScriptException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/GradleScriptException.java
new file mode 100644
index 0000000..6e7c89a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/GradleScriptException.java
@@ -0,0 +1,36 @@
+/*
+ * 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.api.internal.Contextual;
+
+/**
+ * <p>A <code>GradleScriptException</code> is thrown when an exception occurs in the compilation or execution of a
+ * script.</p>
+ *
+ * @author Hans Dockter
+ */
+ at Contextual
+public class GradleScriptException extends GradleException {
+    // Required for @Contextual
+    public GradleScriptException() {
+        super();
+    }
+
+    public GradleScriptException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/IllegalDependencyNotation.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/IllegalDependencyNotation.java
new file mode 100644
index 0000000..481bd52
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/IllegalDependencyNotation.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * This exceptions is thrown, if a dependency is declared with a ilegal notation.
+ * 
+ * @author Hans Dockter
+ */
+public class IllegalDependencyNotation extends GradleException {
+    public IllegalDependencyNotation() {
+    }
+
+    public IllegalDependencyNotation(String message) {
+        super(message);
+    }
+
+    public IllegalDependencyNotation(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/IllegalOperationAtExecutionTimeException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/IllegalOperationAtExecutionTimeException.java
new file mode 100644
index 0000000..412c520
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/IllegalOperationAtExecutionTimeException.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+/**
+ * A <code>IllegalOperationAtExecutionTimeException</code> is thrown if you try to trigger an operation at runtime,
+ * which is only allowed at configuration time.
+ *
+ * @author Hans Dockter
+ */
+public class IllegalOperationAtExecutionTimeException extends InvalidUserDataException {
+    public IllegalOperationAtExecutionTimeException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/InvalidUserDataException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/InvalidUserDataException.java
new file mode 100644
index 0000000..f166d61
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/InvalidUserDataException.java
@@ -0,0 +1,35 @@
+/*
+ * 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.api;
+
+/**
+ * A <code>InvalidUserDataException</code> is thrown, if a user is providing illegal data for the build.
+ *
+ * @author Hans Dockter
+ */
+public class InvalidUserDataException extends GradleException {
+    public InvalidUserDataException() {
+    }
+
+    public InvalidUserDataException(String message) {
+        super(message);
+    }
+
+    public InvalidUserDataException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/JavaVersion.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/JavaVersion.java
new file mode 100644
index 0000000..392412e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/JavaVersion.java
@@ -0,0 +1,67 @@
+/*
+ * 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 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);
+
+    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));
+    }
+
+    @Override
+    public String toString() {
+        return name().substring("VERSION_".length()).replace('_', '.');
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/LocationAwareException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/LocationAwareException.java
new file mode 100644
index 0000000..6b80c6c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/LocationAwareException.java
@@ -0,0 +1,67 @@
+/*
+ * 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;
+
+import org.gradle.groovy.scripts.ScriptSource;
+
+import java.util.List;
+
+/**
+ * A {@code LocationAwareException} is an exception which can be annotated with a location in a script.
+ */
+public interface LocationAwareException {
+    /**
+     * <p>Returns the undecorated message of this exception.</p>
+     *
+     * @return The undecorated message. May return null.
+     */
+    public String getOriginalMessage();
+
+    /**
+     * <p>Returns the source of the script where this exception occurred.</p>
+     *
+     * @return The source. May return null.
+     */
+    public ScriptSource getScriptSource();
+
+    /**
+     * <p>Returns a description of the location of where this exception occurred.</p>
+     *
+     * @return The location description. May return null.
+     */
+    public String getLocation();
+
+    /**
+     * Returns the line in the script where this exception occurred, if known.
+     *
+     * @return The line number, or null if not known.
+     */
+    public Integer getLineNumber();
+
+    /**
+     * Returns the fully formatted error message, including the location.
+     *
+     * @return the message. May return null.
+     */
+    public String getMessage();
+
+    /**
+     * Returns the reportable causes for this failure.
+     *
+     * @return The causes. Never returns null, returns an empty list if this exception has no reportable causes.
+     */
+    public List<Throwable> getReportableCauses();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/NamedDomainObjectCollection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/NamedDomainObjectCollection.java
new file mode 100644
index 0000000..4ee53cc
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/NamedDomainObjectCollection.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 groovy.lang.Closure;
+import org.gradle.api.specs.Spec;
+
+import java.util.Map;
+
+/**
+ * <p>A {@code NamedDomainObjectCollection} represents a read-only set of domain objects of type {@code T}. Each domain
+ * object in this collection has a name, which uniquely identifies the object in this collection.</p>
+ *
+ * <p>Each object in a collection are accessible as read-only properties of the collection, using the name of the object
+ * as the property name. For example:</p>
+ *
+ * <pre>
+ * tasks.add('myTask')
+ * tasks.myTask.dependsOn someOtherTask
+ * </pre>
+ *
+ * <p>A dynamic method is added for each object which takes a configuration closure. This is equivalent to calling
+ * {@link #getByName(String, groovy.lang.Closure)}. For example:</p>
+ *
+ * <pre>
+ * tasks.add('myTask')
+ * tasks.myTask {
+ *     dependsOn someOtherTask
+ * }
+ * </pre>
+ *
+ * <p>You can also use the {@code []} operator to access the objects of a collection by name. For example:</p>
+ *
+ * <pre>
+ * tasks.add('myTask')
+ * tasks['myTask'].dependsOn someOtherTask
+ * </pre>
+ *
+ * @param <T> The type of domain objects in this collection.
+ */
+public interface NamedDomainObjectCollection<T> extends DomainObjectCollection<T> {
+    /**
+     * Returns the objects in this collection, as a map from object name to object instance.
+     *
+     * @return The objects. Returns an empty map if this collection is empty.
+     */
+    Map<String, T> getAsMap();
+
+    /**
+     * Locates an object by name, returning null if there is no such object.
+     *
+     * @param name The object name
+     * @return The object with the given name, or null if there is no such object in this collection.
+     */
+    T findByName(String name);
+
+    /**
+     * Locates an object by name, failing if there is no such object.
+     *
+     * @param name The object name
+     * @return The object with the given name. Never returns null.
+     * @throws UnknownDomainObjectException when there is no such object in this collection.
+     */
+    T getByName(String name) throws UnknownDomainObjectException;
+
+    /**
+     * Locates an object by name, failing if there is no such object. The given configure closure is executed against
+     * the object before it is returned from this method. The object is passed to the closure as it's delegate.
+     *
+     * @param name The object name
+     * @param configureClosure The closure to use to configure the object.
+     * @return The object with the given name, after the configure closure has been applied to it. Never returns null.
+     * @throws UnknownDomainObjectException when there is no such object in this collection.
+     */
+    T getByName(String name, Closure configureClosure) throws UnknownDomainObjectException;
+
+    /**
+     * Locates an object by name, failing if there is no such task. This method is identical to {@link
+     * #getByName(String)}. You can call this method in your build script by using the groovy {@code []} operator.
+     *
+     * @param name The object name
+     * @return The object with the given name. Never returns null.
+     * @throws UnknownDomainObjectException when there is no such object in this collection.
+     */
+    T getAt(String name) throws UnknownDomainObjectException;
+
+    /**
+     * {@inheritDoc}
+     */
+    <S extends T> NamedDomainObjectCollection<S> withType(Class<S> type);
+
+    /**
+     * {@inheritDoc}
+     */
+    NamedDomainObjectCollection<T> matching(Spec<? super T> spec);
+
+    /**
+     * {@inheritDoc}
+     */
+    NamedDomainObjectCollection<T> matching(Closure spec);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/NamedDomainObjectContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/NamedDomainObjectContainer.java
new file mode 100644
index 0000000..885155c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/NamedDomainObjectContainer.java
@@ -0,0 +1,53 @@
+/*
+ * 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 groovy.lang.Closure;
+
+import java.util.List;
+
+/**
+ * A {@code DomainObjectContainer} represents a mutable collection of domain objects of type {@code T}.
+ *
+ * @param <T> The type of domain objects in this container.
+ */
+public interface NamedDomainObjectContainer<T> extends NamedDomainObjectCollection<T> {
+
+    /**
+     * Adds a rule to this container. The given rule is invoked when an unknown object is requested by name.
+     *
+     * @param rule The rule to add.
+     * @return The added rule.
+     */
+    Rule addRule(Rule rule);
+
+    /**
+     * Adds a rule to this container. The given closure is executed when an unknown object is requested by name. The
+     * requested name is passed to the closure as a parameter.
+     *
+     * @param description The description of the rule.
+     * @param ruleAction The closure to execute to apply the rule.
+     * @return The added rule.
+     */
+    Rule addRule(String description, Closure ruleAction);
+
+    /**
+     * Returns the rules used by this container.
+     *
+     * @return The rules, in the order they will be applied.
+     */
+    List<Rule> getRules();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/PathValidation.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/PathValidation.java
new file mode 100644
index 0000000..9b918de
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/PathValidation.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+/**
+ * An enumeration for describing validation policies for file paths.
+ */
+public enum PathValidation {
+    NONE(), EXISTS(), FILE(), DIRECTORY()
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/Plugin.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/Plugin.java
new file mode 100644
index 0000000..878ed7a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/Plugin.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * <p>A <code>Plugin</code> represents an extension to Gradle. A plugin applies some configuration to a target object.
+ * Usually, this target object is a {@link org.gradle.api.Project}, but plugins can be applied to any type of
+ * objects.</p>
+ *
+ * @author Hans Dockter
+ */
+public interface Plugin<T> {
+    /**
+     * Apply this plugin to the given target object.
+     *
+     * @param target The target object
+     */
+    void apply(T target);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/Project.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/Project.java
new file mode 100644
index 0000000..9342e29
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/Project.java
@@ -0,0 +1,1343 @@
+/*
+ * 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;
+
+import groovy.lang.Closure;
+import groovy.lang.MissingPropertyException;
+import org.gradle.api.file.ConfigurableFileTree;
+import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.file.CopySpec;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.artifacts.dsl.*;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.initialization.dsl.ScriptHandler;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.LoggingManager;
+import org.gradle.api.plugins.Convention;
+import org.gradle.api.plugins.PluginContainer;
+import org.gradle.api.tasks.TaskContainer;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.process.ExecResult;
+
+import java.io.File;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * <p>This interface is the main API you use to interact with Gradle from your build file. From a <code>Project</code>,
+ * you have programmatic access to all of Gradle's features.</p>
+ *
+ * <h3>Lifecycle</h3>
+ *
+ * <p>There is a one-to-one relationship between a <code>Project</code> and a <code>{@value #DEFAULT_BUILD_FILE}</code>
+ * file. During build initialisation, Gradle assembles a <code>Project</code> object for each project which is to
+ * participate in the build, as follows:</p>
+ *
+ * <ul>
+ *
+ * <li>Create a {@link org.gradle.api.initialization.Settings} instance for the build.</li>
+ *
+ * <li>Evaluate the <code>{@value org.gradle.api.initialization.Settings#DEFAULT_SETTINGS_FILE}</code> script, if
+ * present, against the {@link org.gradle.api.initialization.Settings} object to configure it.</li>
+ *
+ * <li>Use the configured {@link org.gradle.api.initialization.Settings} object to create the hierarchy of
+ * <code>Project</code> instances.</li>
+ *
+ * <li>Finally, evaluate each <code>Project</code> by executing its <code>{@value #DEFAULT_BUILD_FILE}</code> file, if
+ * present, against the project. The project are evaulated in breadth-wise order, such that a project is evaulated
+ * before its child projects. This order can be overridden by adding an evaluation dependency.</li>
+ *
+ * </ul>
+ *
+ * <h3>Tasks</h3>
+ *
+ * <p>A project is essentially a collection of {@link Task} objects. Each task performs some basic piece of work, such
+ * as compiling classes, or running unit tests, or zipping up a WAR file. You add tasks to a project using one of the
+ * {@code add()} methods on {@link TaskContainer}, such as {@link TaskContainer#add(String)}.  You can locate existing
+ * tasks using one of the lookup methods on {@link TaskContainer}, such as {@link TaskContainer#getByName(String)}.</p>
+ *
+ * <h3>Dependencies</h3>
+ *
+ * <p>A project generally has a number of dependencies it needs in order to do its work.  Also, a project generally
+ * produces a number of artifacts, which other projects can use. Those dependencies are grouped in configurations, and
+ * can be retrieved and uploaded from repositories. You use the {@link org.gradle.api.artifacts.ConfigurationContainer}
+ * returned by {@link #getConfigurations()} ()} method to manage the configurations. The {@link
+ * org.gradle.api.artifacts.dsl.DependencyHandler} returned by {@link #getDependencies()} method to manage the
+ * dependencies. The {@link org.gradle.api.artifacts.dsl.ArtifactHandler} returned by {@link #getArtifacts()} ()} method
+ * to manage the artifacts. The {@link org.gradle.api.artifacts.dsl.RepositoryHandler} returned by {@link
+ * #getRepositories()} ()} method to manage the repositories.</p>
+ *
+ * <h3>Multi-project Builds</h3>
+ *
+ * <p>Projects are arranged into a hierarchy of projects. A project has a name, and a fully qualified path which
+ * uniquely identifies it in the hierarchy.</p>
+ *
+ * <h3>Using a Project in a Build File</h3>
+ *
+ * <p>Gradle executes the project's build file against the <code>Project</code> instance to configure the project. Any
+ * property or method which your script uses which is not defined in the script is delegated through to the associated
+ * <code>Project</code> object.  This means, that you can use any of the methods and properties on the
+ * <code>Project</code> interface directly in your script.</p><p>For example:
+ * <pre>
+ * defaultTasks('some-task')  // Delegates to Project.defaultTasks()
+ * reportDir = file('reports') // Delegates to Project.file() and Project.setProperty()
+ * </pre>
+ * <p>You can also access the <code>Project</code> instance using the <code>project</code> property. This can make the
+ * script clearer in some cases. For example, you could use <code>project.name</code> rather than <code>name</code> to
+ * access the project's name.</p>
+ *
+ * <a name="properties"/> <h4>Dynamic Properties</h4>
+ *
+ * <p>A project has 5 property 'scopes', which it searches for properties. You can access these properties by name in
+ * your build file, or by calling the project's {@link #property(String)} method. The scopes are:</p>
+ *
+ * <ul>
+ *
+ * <li>The <code>Project</code> object itself. This scope includes any property getters and setters declared by the
+ * <code>Project</code> implementation class.  For example, {@link #getRootProject()} is accessible as the
+ * <code>rootProject</code> property.  The properties of this scope are readable or writable depending on the presence
+ * of the corresponding getter or setter method.</li>
+ *
+ * <li>The <em>additional</em> properties of the project.  Each project maintains a map of additional properties, which
+ * can contain any arbitrary name -> value pair.  The properties of this scope are readable and writable.</li>
+ *
+ * <li>The <em>convention</em> properties added to the project by each {@link Plugin} applied to the project. A {@link
+ * Plugin} can add properties and methods to a project through the project's {@link Convention} object.  The properties
+ * of this scope may be readable or writable, depending on the convention objects.</li>
+ *
+ * <li>The tasks of the project.  A task is accessible by using its name as a property name.  The properties of this
+ * scope are read-only. For example, a task called <code>compile</code> is accessible as the <code>compile</code>
+ * property.</li>
+ *
+ * <li>The additional properties and convention properties of the project's parent project, recursively up to the root
+ * project. The properties of this scope are read-only.</li>
+ *
+ * </ul>
+ *
+ * <p>When reading a property, the project searches the above scopes in order, and returns the value from the first
+ * scope it finds the property in.  See {@link #property(String)} for more details.</p>
+ *
+ * <p>When writing a property, the project searches the above scopes in order, and sets the property in the first scope
+ * it finds the property in. If not found, the project adds the property to its map of additional properties. See {@link
+ * #setProperty(String, Object)} for more details.</p>
+ *
+ * <h4>Dynamic Methods</h4>
+ *
+ * <p>A project has 5 method 'scopes', which it searches for methods:</p>
+ *
+ * <ul>
+ *
+ * <li>The <code>Project</code> object itself.</li>
+ *
+ * <li>The build file. The project searches for a matching method declared in the build file.</li>
+ *
+ * <li>The <em>convention</em> methods added to the project by each {@link Plugin} applied to the project. A {@link
+ * Plugin} can add properties and method to a project through the project's {@link Convention} object.</li>
+ *
+ * <li>The tasks of the project. A method is added for each task, using the name of the task as the method name and
+ * taking a single closure parameter. The method calls the {@link Task#configure(groovy.lang.Closure)} method for the
+ * associated task with the provided closure. For example, if the project has a task called <code>compile</code>, then a
+ * method is added with the following signature: <code>void compile(Closure configureClosure)</code>.</li>
+ *
+ * <li>The parent project, recursively up to the root project.</li>
+ *
+ * </ul>
+ *
+ * @author Hans Dockter
+ */
+public interface Project extends Comparable<Project> {
+    /**
+     * The default project build file name.
+     */
+    public static final String DEFAULT_BUILD_FILE = "build.gradle";
+
+    /**
+     * The hierarchy separator for project and task path names
+     */
+    public static final String PATH_SEPARATOR = ":";
+
+    /**
+     * The default build directory name.
+     */
+    public static final String DEFAULT_BUILD_DIR_NAME = "build";
+
+    public static final String GRADLE_PROPERTIES = "gradle.properties";
+
+    public static final String SYSTEM_PROP_PREFIX = "systemProp";
+
+    public static final String TMP_DIR_NAME = ".gradle";
+
+    public static final String DEFAULT_VERSION = "unspecified";
+
+    public static final String DEFAULT_STATUS = "release";
+
+    /**
+     * <p>Returns the root project for the hierarchy that this project belongs to.  In the case of a single-project
+     * build, this method returns this project.</p> <p/> <p>You can access this property in your build file using
+     * <code>rootProject</code></p>
+     *
+     * @return The root project. Never returns null.
+     */
+    Project getRootProject();
+
+    /**
+     * <p>Returns the root directory of this project. The root directory is the project directory of the root
+     * project.</p> <p/> <p>You can access this property in your build file using <code>rootDir</code></p>
+     *
+     * @return The root directory. Never returns null.
+     */
+    File getRootDir();
+
+    /**
+     * <p>Returns the build directory of this project.  The build directory is the directory which all artifacts are
+     * generated into.  The default value for the build directory is <code><i>projectDir</i>/build</code></p> <p/>
+     * <p>You can access this property in your build file using <code>buildDir</code></p>
+     *
+     * @return The build directory. Never returns null.
+     */
+    File getBuildDir();
+
+    /**
+     * <p>Sets the build directory of this project. The build directory is the directory which all artifacts are
+     * generated into. The path parameter is evaluated as described for {@link #file(Object)}. This mean you can use,
+     * amongst other things, a relative or absolute path or File object to specify the build directory.
+     * </p>
+     *
+     * @param path The build directory. This is evaluated as for {@link #file(Object)}
+     */
+    void setBuildDir(Object path);
+
+    /**
+     * <p>Returns the name of the build directory of this project. It is resolved relative to the project directory of
+     * this project to determine the build directory. The default value is {@value #DEFAULT_BUILD_DIR_NAME}.</p> <p/>
+     * <p>You can access this property in your build file using <code>buildDirName</code></p>
+     *
+     * @return The build dir name. Never returns null.
+     */
+    @Deprecated
+    String getBuildDirName();
+
+    /**
+     * <p>Sets the build directory name of this project.</p>
+     *
+     * @param buildDirName The build dir name. Should not be null.
+     * @deprecated Use {@link #setBuildDir(Object)} instead.
+     */
+    @Deprecated
+    void setBuildDirName(String buildDirName);
+
+    /**
+     * <p>Returns the build file Gradle will evaluate against this project object. The default is <code> {@value
+     * #DEFAULT_BUILD_FILE}</code>. If an embedded script is provided the build file will be null. <p/> <p>You can
+     * access this property in your build file using <code>buildFile</code></p>
+     *
+     * @return Current build file. May return null.
+     */
+    File getBuildFile();
+
+    /**
+     * <p>Returns the parent project of this project, if any.</p> <p/> <p>You can access this property in your build
+     * file using <code>parent</code></p>
+     *
+     * @return The parent project, or null if this is the root project.
+     */
+    Project getParent();
+
+    /**
+     * <p>Returns the name of this project. The project's name is not necessarily unique within a project hierarchy. You
+     * should use the {@link #getPath()} method for a unique identifier for the project.</p> <p/> <p>You can access this
+     * property in your build file using <code>name</code></p>
+     *
+     * @return The name of this project. Never return null.
+     */
+    String getName();
+
+    /**
+     * <p>Returns the group of this project. Gradle always uses the toString() value of a group. The group defaults to
+     * the path with dots a separators.</p> <p/> <p>You can access this property in your build file using
+     * <code>group</code></p>
+     *
+     * @return The group of this project. Never returns null.
+     */
+    Object getGroup();
+
+    /**
+     * <p>Returns the version of this project. Gradle always uses the toString() value of a version. The version
+     * defaults to {@value #DEFAULT_VERSION}.</p> <p/> <p>You can access this property in your build file using
+     * <code>version</code></p>
+     *
+     * @return The version of this project. Never returns null.
+     */
+    Object getVersion();
+
+    /**
+     * <p>Returns the status of this project. Gradle always uses the toString() value of a version. The status defaults
+     * to {@value #DEFAULT_STATUS}.</p> <p/> <p>You can access this property in your build file using
+     * <code>status</code></p> <p/> The status of the project is only relevant, if you upload libraries together with a
+     * module descriptor. The status specified here, will be part of this module descriptor.
+     *
+     * @return The status of this project. Never returns null.
+     */
+    Object getStatus();
+
+    /**
+     * <p>Returns the direct children of this project.</p> <p/> <p>You can access this property in your build file using
+     * <code>childProjects</code></p>
+     *
+     * @return A map from child project name to child project. Returns an empty map if this this project does not have
+     *         any children.
+     */
+    Map<String, Project> getChildProjects();
+
+    /**
+     * <p>Returns the set of projects which this project depends on.</p> <p/> <p>You can access this property in your
+     * build file using <code>dependsOnProjects</code></p>
+     *
+     * @return The set of projects. Returns an empty set if this project depends on no projects.
+     */
+    Set<Project> getDependsOnProjects();
+
+    /**
+     * <p>Sets a property of this project.  This method searches for a property with the given name in the following
+     * locations, and sets the property on the first location where it finds the property.</p> <p/> <ol> <p/> <li>The
+     * project object itself.  For example, the <code>rootDir</code> project property.</li> <p/> <li>The project's
+     * {@link Convention} object.  For example, the <code>srcRootName</code> java plugin property.</li> <p/> <li>The
+     * project's additional properties.</li> <p/> </ol> <p/> <p>If the property is not found in any of these locations,
+     * it is added to the project's additional properties.</p>
+     *
+     * @param name The name of the property
+     * @param value The value of the property
+     */
+    void setProperty(String name, Object value);
+
+    /**
+     * <p>Returns this project. This method is useful in build files to explicitly access project properties and
+     * methods. For example, using <code>project.name</code> can express your intent better than using <code>name</code>.
+     * This method also allows you to access project properties from a scope where the property may be hidden,
+     * such as, for example, from a method or closure.
+     * </p>
+     *
+     * <p>You can access this property in your build file using <code>project</code></p>
+     *
+     * @return This project. Never returns null.
+     */
+    Project getProject();
+
+    /**
+     * <p>Returns the set containing this project and its subprojects.</p> <p/> <p>You can access this property in your
+     * build file using <code>allprojects</code></p>
+     *
+     * @return The set of projects.
+     */
+    Set<Project> getAllprojects();
+
+    /**
+     * <p>Returns the set containing the subprojects of this project.</p> <p/> <p>You can access this property in your
+     * build file using <code>subprojects</code></p>
+     *
+     * @return The set of projects.  Returns an empty set if this project has no subprojects.
+     */
+    Set<Project> getSubprojects();
+
+    /**
+     * <p>Applies a {@link Plugin} to this project.</p>
+     *
+     * @param pluginId The id of the plugin.
+     * @return This project.
+     * @deprecated You should use the {@link #apply(java.util.Map)} or {@link #apply(groovy.lang.Closure)} method
+     *             instead.
+     */
+    @Deprecated
+    Project usePlugin(String pluginId);
+
+    /**
+     * <p>Applies a {@link Plugin} to this project.</p>
+     *
+     * @param pluginClass The class of the plugin.  This class must implement the {@link Plugin} interface.
+     * @return This project.
+     * @deprecated You should use the {@link #apply(java.util.Map)} or {@link #apply(groovy.lang.Closure)} method
+     *             instead.
+     */
+    @Deprecated
+    Project usePlugin(Class<? extends Plugin> pluginClass);
+
+    /**
+     * <p>Creates a {@link Task} with the given name and adds it to this project. Calling this method is equivalent to
+     * calling {@link #task(java.util.Map, String)} with an empty options map.</p> <p/> <p>After the task is added to
+     * the project, it is made available as a property of the project, so that you can reference the task by name in
+     * your build file.  See <a href="#properties">here</a> for more details</p> <p/> <p>If a task with the given name
+     * already exists in this project, an exception is thrown.</p>
+     *
+     * @param name The name of the task to be created
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
+     */
+    Task task(String name) throws InvalidUserDataException;
+
+    /**
+     * <p>Creates a {@link Task} with the given name and adds it to this project. A map of creation options can be
+     * passed to this method to control how the task is created. The following options are available:</p> <p/> <table>
+     * <p/> <tr><th>Option</th><th>Description</th><th>Default Value</th></tr> <p/> <tr><td><code>{@value
+     * org.gradle.api.Task#TASK_TYPE}</code></td><td>The class of the task to create.</td><td>{@link
+     * org.gradle.api.DefaultTask}</td></tr> <p/> <tr><td><code>{@value org.gradle.api.Task#TASK_OVERWRITE}</code></td><td>Replace
+     * an existing task?</td><td><code>false</code></td></tr> <p/> <tr><td><code>{@value
+     * org.gradle.api.Task#TASK_DEPENDS_ON}</code></td><td>A task name or set of task names which this task depends
+     * on</td><td><code>[]</code></td></tr> <p/> <tr><td><code>{@value org.gradle.api.Task#TASK_ACTION}</code></td><td>A
+     * closure or {@link Action} to add to the task.</td><td><code>null</code></td></tr> <p/> </table> <p/> <p>After the
+     * task is added to the project, it is made available as a property of the project, so that you can reference the
+     * task by name in your build file.  See <a href="#properties">here</a> for more details</p> <p/> <p>If a task with
+     * the given name already exists in this project and the <code>override</code> option is not set to true, an
+     * exception is thrown.</p>
+     *
+     * @param args The task creation options.
+     * @param name The name of the task to be created
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
+     */
+    Task task(Map<String, ?> args, String name) throws InvalidUserDataException;
+
+    /**
+     * <p>Creates a {@link Task} with the given name and adds it to this project. Before the task is returned, the given
+     * closure is executed to configure the task. A map of creation options can be passed to this method to control how
+     * the task is created. See {@link #task(java.util.Map, String)} for the available options.</p> <p/> <p>After the
+     * task is added to the project, it is made available as a property of the project, so that you can reference the
+     * task by name in your build file.  See <a href="#properties">here</a> for more details</p> <p/> <p>If a task with
+     * the given name already exists in this project and the <code>override</code> option is not set to true, an
+     * exception is thrown.</p>
+     *
+     * @param args The task creation options.
+     * @param name The name of the task to be created
+     * @param configureClosure The closure to use to configure the created task.
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
+     */
+    Task task(Map<String, ?> args, String name, Closure configureClosure);
+
+    /**
+     * <p>Creates a {@link Task} with the given name and adds it to this project. Before the task is returned, the given
+     * closure is executed to configure the task.</p> <p/> <p>After the task is added to the project, it is made
+     * available as a property of the project, so that you can reference the task by name in your build file.  See <a
+     * href="#properties">here</a> for more details</p>
+     *
+     * @param name The name of the task to be created
+     * @param configureClosure The closure to use to configure the created task.
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
+     */
+    Task task(String name, Closure configureClosure);
+
+    /**
+     * <p>Creates a {@link Task} with the given name and adds it to this project. Calling this method is equivalent to
+     * calling {@link #createTask(java.util.Map, String)} with an empty options map.</p> <p/> <p>After the task is added
+     * to the project, it is made available as a property of the project, so that you can reference the task by name in
+     * your build file.  See <a href="#properties">here</a> for more details</p> <p/> <p>If a task with the given name
+     * already exists in this project, an exception is thrown.</p>
+     *
+     * @param name The name of the task to be created
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
+     * @deprecated You should use {@link #task(String)} instead.
+     */
+    @Deprecated
+    Task createTask(String name) throws InvalidUserDataException;
+
+    /**
+     * <p>Creates a {@link Task} with the given name and adds it to this project. Before the task is returned, the given
+     * action is passed to the task's {@link Task#doFirst(Action)} method. Calling this method is equivalent to calling
+     * {@link #createTask(java.util.Map, String, Action)} with an empty options map.</p> <p/> <p>After the task is added
+     * to the project, it is made available as a property of the project, so that you can reference the task by name in
+     * your build file.  See <a href="#properties">here</a> for more details</p> <p/> <p>If a task with the given name
+     * already exists in this project, an exception is thrown.</p>
+     *
+     * @param name The name of the task to be created
+     * @param action The action to be passed to the {@link Task#doFirst(Action)} method of the created task.
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
+     * @deprecated You should use {@link #task(java.util.Map, String)} instead.
+     */
+    @Deprecated
+    Task createTask(String name, Action<? super Task> action) throws InvalidUserDataException;
+
+    /**
+     * <p>Creates a {@link Task} with the given name and adds it to this project. A map of creation options can be
+     * passed to this method to control how the task is created. The following options are available:</p> <p/> <table>
+     * <p/> <tr><th>Option</th><th>Description</th><th>Default Value</th></tr> <p/> <tr><td><code>{@value
+     * org.gradle.api.Task#TASK_TYPE}</code></td><td>The class of the task to create.</td><td>{@link
+     * org.gradle.api.DefaultTask}</td></tr> <p/> <tr><td><code>{@value org.gradle.api.Task#TASK_OVERWRITE}</code></td><td>Replace
+     * an existing task?</td><td><code>false</code></td></tr> <p/> <tr><td><code>{@value
+     * org.gradle.api.Task#TASK_DEPENDS_ON}</code></td><td>A task name or set of task names which this task depends
+     * on</td><td><code>[]</code></td></tr> <p/> </table> <p/> <p>After the task is added to the project, it is made
+     * available as a property of the project, so that you can reference the task by name in your build file.  See <a
+     * href="#properties">here</a> for more details</p> <p/> <p>If a task with the given name already exists in this
+     * project and the <code>override</code> option is not set to true, an exception is thrown.</p>
+     *
+     * @param args The task creation options.
+     * @param name The name of the task to be created
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
+     * @deprecated You should use {@link #task(java.util.Map, String)} instead.
+     */
+    @Deprecated
+    Task createTask(Map<String, ?> args, String name) throws InvalidUserDataException;
+
+    /**
+     * <p>Creates a {@link Task} with the given name and adds it to this project. Before the task is returned, the given
+     * action is passed to the task's {@link Task#doFirst(Action)} method. A map of creation options can be passed to
+     * this method to control how the task is created. See {@link #createTask(java.util.Map, String)} for the available
+     * options.</p> <p/> <p>After the task is added to the project, it is made available as a property of the project,
+     * so that you can reference the task by name in your build file.  See <a href="#properties">here</a> for more
+     * details</p> <p/> <p>If a task with the given name already exists in this project and the <code>override</code>
+     * option is not set to true, an exception is thrown.</p>
+     *
+     * @param args The task creation options.
+     * @param name The name of the task to be created
+     * @param action The action to be passed to the {@link Task#doFirst(Action)} method of the created task.
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
+     * @deprecated You should use {@link #task(java.util.Map, String)} instead.
+     */
+    @Deprecated
+    Task createTask(Map<String, ?> args, String name, Action<? super Task> action) throws InvalidUserDataException;
+
+    /**
+     * <p>Creates a {@link Task} with the given name and adds it to this project. Before the task is returned, the given
+     * action closure is passed to the task's {@link Task#doFirst(Closure)} method. Calling this method is equivalent to
+     * calling {@link #createTask(java.util.Map, String, Closure)} with an empty options map.</p> <p/> <p>After the task
+     * is added to the project, it is made available as a property of the project, so that you can reference the task by
+     * name in your build file.  See <a href="#properties">here</a> for more details</p> <p/> <p>If a task with the
+     * given name already exists in this project, an exception is thrown.</p>
+     *
+     * @param name The name of the task to be created
+     * @param action The closure to be passed to the {@link Task#doFirst(Closure)} method of the created task.
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
+     * @deprecated You should use {@link #task(java.util.Map, String)} instead.
+     */
+    @Deprecated
+    Task createTask(String name, Closure action);
+
+    /**
+     * <p>Creates a {@link Task} with the given name and adds it to this project. Before the task is returned, the given
+     * action closure is passed to the task's {@link Task#doFirst(Closure)} method. A map of creation options can be
+     * passed to this method to control how the task is created. See {@link #createTask(java.util.Map, String)} for the
+     * available options.</p> <p/> <p>After the task is added to the project, it is made available as a property of the
+     * project, so that you can reference the task by name in your build file.  See <a href="#properties">here</a> for
+     * more details</p> <p/> <p>If a task with the given name already exists in this project and the
+     * <code>override</code> option is not set to true, an exception is thrown.</p>
+     *
+     * @param args The task creation options.
+     * @param name The name of the task to be created
+     * @param action The closure to be passed to the {@link Task#doFirst(Closure)} method of the created task.
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exists in this project.
+     * @deprecated You should use {@link #task(java.util.Map, String)} instead.
+     */
+    @Deprecated
+    Task createTask(Map<String, ?> args, String name, Closure action);
+
+    /**
+     * <p>Returns the path of this project.  The path is the fully qualified name of the project.</p>
+     *
+     * @return The path. Never returns null.
+     */
+    String getPath();
+
+    /**
+     * <p>Returns the names of the default tasks of this project. These are used when no tasks names are provided when
+     * starting the build.</p>
+     *
+     * @return The default task names. Returns an empty list if this project has no default tasks.
+     */
+    List<String> getDefaultTasks();
+
+    /**
+     * <p>Sets the names of the default tasks of this project. These are used when no tasks names are provided when
+     * starting the build.</p>
+     *
+     * @param defaultTasks The default task names.
+     */
+    void setDefaultTasks(List<String> defaultTasks);
+
+    /**
+     * <p>Sets the names of the default tasks of this project. These are used when no tasks names are provided when
+     * starting the build.</p>
+     *
+     * @param defaultTasks The default task names.
+     */
+    void defaultTasks(String... defaultTasks);
+
+    /**
+     * <p>Declares that this project has an execution dependency on the project with the given path.</p>
+     *
+     * @param path The path of the project which this project depends on.
+     * @throws UnknownProjectException If no project with the given path exists.
+     */
+    void dependsOn(String path) throws UnknownProjectException;
+
+    /**
+     * <p>Declares that this project has an execution dependency on the project with the given path.</p>
+     *
+     * @param path The path of the project which this project depends on.
+     * @param evaluateDependsOnProject If true, adds an evaluation dependency.
+     * @throws UnknownProjectException If no project with the given path exists.
+     */
+    void dependsOn(String path, boolean evaluateDependsOnProject) throws UnknownProjectException;
+
+    /**
+     * <p>Declares that this project has an evaulation dependency on the project with the given path.</p>
+     *
+     * @param path The path of the project which this project depends on.
+     * @return The project which this project depends on.
+     * @throws UnknownProjectException If no project with the given path exists.
+     */
+    Project evaluationDependsOn(String path) throws UnknownProjectException;
+
+    /**
+     * <p>Declares that all child projects of this project have an execution dependency on this project.</p>
+     *
+     * @return this project.
+     */
+    Project childrenDependOnMe();
+
+    /**
+     * <p>Declares that this project have an execution dependency on each of its child projects.</p>
+     *
+     * @return this project.
+     */
+    Project dependsOnChildren();
+
+    /**
+     * <p>Declares that this project have an execution dependency on each of its child projects.</p>
+     *
+     * @param evaluateDependsOnProject If true, adds an evaluation dependency.
+     * @return this project.
+     */
+    Project dependsOnChildren(boolean evaluateDependsOnProject);
+
+    /**
+     * <p>Locates a project by path. If the path is relative, it is interpreted relative to this project.</p>
+     *
+     * @param path The path.
+     * @return The project with the given path. Returns null if no such project exists.
+     */
+    Project findProject(String path);
+
+    /**
+     * <p>Locates a project by path. If the path is relative, it is interpreted relative to this project.</p>
+     *
+     * @param path The path.
+     * @return The project with the given path. Never returns null.
+     * @throws UnknownProjectException If no project with the given path exists.
+     */
+    Project project(String path) throws UnknownProjectException;
+
+    /**
+     * <p>Locates a project by path and configures it using the given closure. If the path is relative, it is
+     * interpreted relative to this project. The target project is passed to the closure as the closure's delegate.</p>
+     *
+     * @param path The path.
+     * @param configureClosure The closure to use to configure the project.
+     * @return The project with the given path. Never returns null.
+     * @throws UnknownProjectException If no project with the given path exists.
+     */
+    Project project(String path, Closure configureClosure);
+
+    /**
+     * <p>Returns a map of the tasks contained in this project, and optionally its subprojects.</p>
+     *
+     * @param recursive If true, returns the tasks of this project and its subprojects.  If false, returns the tasks of
+     * just this project.
+     * @return A map from project to a set of tasks.
+     */
+    Map<Project, Set<Task>> getAllTasks(boolean recursive);
+
+    /**
+     * Returns the set of tasks with the given name contained in this project, and optionally its subprojects.</p>
+     *
+     * @param name The name of the task to locate.
+     * @param recursive If true, returns the tasks of this project and its subprojects. If false, returns the tasks of
+     * just this project.
+     * @return The set of tasks. Returns an empty set if no such tasks exist in this project.
+     */
+    Set<Task> getTasksByName(String name, boolean recursive);
+
+    /**
+     * <p>The directory containing the project build file.</p> <p/> <p>You can access this property in your build file
+     * using <code>projectDir</code></p>
+     *
+     * @return The project directory. Never returns null.
+     */
+    File getProjectDir();
+
+    /**
+     * <p>Resolves a file path relative to the project directory of this project. This method converts the supplied path
+     * based on its type:</p>
+     *
+     * <ul>
+     *
+     * <li>{@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
+     * {@code file:} URLs are supported.
+     *
+     * <li>{@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>{@link Object}. The object's {@code toString()} value is interpreted as a path. If the path is a relative
+     * path, the project directory will be used as a base directory. A String starting with {@code file:} is treated as
+     * a file URL.</li>
+     *
+     * </ul>
+     *
+     * @param path The object to resolve as a File.
+     * @return The resolved file. Never returns null.
+     */
+    File file(Object path);
+
+    /**
+     * <p>Resolves a file path relative to the project directory of this project and validates it using the given
+     * scheme. See {@link PathValidation} for the list of possible validations.</p>
+     *
+     * @param path An object which toString method value is interpreted as a relative path to the project directory.
+     * @param validation The validation to perform on the file.
+     * @return The resolved file. Never returns null.
+     * @throws InvalidUserDataException When the file does not meet the given validation constraint.
+     */
+    File file(Object path, PathValidation validation) throws InvalidUserDataException;
+
+    /**
+     * <p>Resolves a file path to a URI, relative to the project directory of this project. Evaluates the provided path
+     * object as described for {@link #file(Object)}, with the exception that any URI scheme is supported, not just
+     * 'file:' URIs.</p>
+     *
+     * @param path The object to resolve as a URI.
+     * @return The resolved URI. Never returns null.
+     */
+    URI uri(Object path);
+
+    /**
+     * <p>Returns the relative path from the project directory to the given path. The given path object is (logically)
+     * resolved as described for {@link #file(Object)}, from which a relative path is calculated.</p>
+     *
+     * @param path The path to convert to a relative path.
+     * @return The relative path. Never returns null.
+     */
+    String relativePath(Object path);
+
+    /**
+     * <p>Returns a {@link ConfigurableFileCollection} containing the given files. You can pass any of the following
+     * types to this method:</p>
+     *
+     * <ul> <li>A {@code String}. 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 {@code 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
+     * {@code file:} URLs are supported.
+     *
+     * <li>A {@code Collection} or an array. May contain any of the types listed here. The elements of the collection
+     * are recursively converted to files.</li>
+     *
+     * <li>A {@link org.gradle.api.file.FileCollection}. The contents of the collection are included in the returned
+     * collection.</li>
+     *
+     * <li>A {@link java.util.concurrent.Callable}. The {@code call()} method may return any of the types listed here.
+     * The return value of the {@code call()} method is recursively converted to files. A {@code null} return value is
+     * treated as an empty collection.</li>
+     *
+     * <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>
+     *
+     * <li>An Object. Its {@code toString()} value is treated the same way as a String, as for {@link
+     * #file(Object)}.</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>
+     *
+     * <p>The returned file collection maintains the iteration order of the supplied paths.</p>
+     *
+     * @param paths The paths to the files. May be empty.
+     * @return The file collection. Never returns null.
+     */
+    ConfigurableFileCollection files(Object... paths);
+
+    /**
+     * <p>Creates a new {@code ConfigurableFileCollection} using the given paths. The paths are evaluated as for {@link
+     * #files(Object...)}. The file collection is configured using the given closure. The file collection is passed to
+     * the closure as its delegate. Example:</p>
+     * <pre>
+     * files "$buildDir/classes" {
+     *     builtBy 'compile'
+     * }
+     * </pre>
+     * <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>
+     *
+     * @param paths The contents of the file collection. Evaluated as for {@link #files(Object...)}.
+     * @param configureClosure The closure to use to configure the file collection.
+     * @return the configured file tree. Never returns null.
+     */
+    ConfigurableFileCollection files(Object paths, Closure configureClosure);
+
+    /**
+     * <p>Creates a new {@code ConfigurableFileTree} using the given base directory. The given baseDir path is evaluated
+     * as for {@link #file(Object)}.</p>
+     *
+     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
+     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
+     * queried.</p>
+     *
+     * @param baseDir The base directory of the file tree. Evaluated as for {@link #file(Object)}.
+     * @return the file tree. Never returns null.
+     */
+    ConfigurableFileTree fileTree(Object baseDir);
+
+    /**
+     * <p>Creates a new {@code ConfigurableFileTree} using the provided map of arguments.  The map will be applied as
+     * properties on the new file tree.  Example:</p>
+     *
+     * <pre>
+     * fileTree(dir:'src', excludes:['**/ignore/**','**/.svn/**'])
+     * </pre>
+     *
+     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
+     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
+     * queried.</p>
+     *
+     * @param args map of property assignments to {@code ConfigurableFileTree} object
+     * @return the configured file tree. Never returns null.
+     */
+    ConfigurableFileTree fileTree(Map<String, ?> args);
+
+    /**
+     * <p>Creates a new {@code ConfigurableFileTree} using the provided closure.  The closure will be used to configure
+     * the new file tree. The file tree is passed to the closure as its delegate.  Example:</p>
+     *
+     * <pre>
+     * fileTree {
+     *    from 'src'
+     *    exclude '**/.svn/**'
+     * }.copy { into 'dest'}
+     * </pre>
+     *
+     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
+     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
+     * queried.</p>
+     *
+     * @param closure Closure to configure the {@code ConfigurableFileTree} object
+     * @return the configured file tree. Never returns null.
+     */
+    ConfigurableFileTree fileTree(Closure closure);
+
+    /**
+     * <p>Creates a new {@code FileTree} which contains the contents of the given ZIP file. The given zipPath path is
+     * evaluated as for {@link #file(Object)}. You can combine this method with the {@link #copy(groovy.lang.Closure)}
+     * method to unzip a ZIP file.</p>
+     *
+     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
+     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
+     * queried.</p>
+     *
+     * @param zipPath The ZIP file. Evaluated as for {@link #file(Object)}.
+     * @return the file tree. Never returns null.
+     */
+    FileTree zipTree(Object zipPath);
+
+    /**
+     * <p>Creates a new {@code FileTree} which contains the contents of the given TAR file. The given tarPath path is
+     * evaluated as for {@link #file(Object)}. You can combine this method with the {@link #copy(groovy.lang.Closure)}
+     * method to untar a TAR file.</p>
+     *
+     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
+     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
+     * queried.</p>
+     *
+     * @param tarPath The TAR file. Evaluated as for {@link #file(Object)}.
+     * @return the file tree. Never returns null.
+     */
+    FileTree tarTree(Object tarPath);
+
+    /**
+     * Creates a directory and returns a file pointing to it.
+     *
+     * @param path The path for the directory to be created. Evaluated as for {@link #file(Object)}.
+     * @return the created directory
+     * @throws org.gradle.api.InvalidUserDataException If the path points to an existing file.
+     */
+    File mkdir(Object path);
+
+    /**
+     * Deletes files and directories.
+     *
+     * @param paths Any type of object accepted by {@link org.gradle.api.Project#files(Object...)}
+     * @return true if anything got deleted, false otherwise
+     */
+    boolean delete(Object... paths);
+
+    /**
+     * Executes a Java main class. The closure configures a {@link org.gradle.process.JavaExecSpec}.
+     *
+     * @param closure The closure for configuring the execution.
+     *
+     * @return the result of the execution
+     */
+    ExecResult javaexec(Closure closure);
+
+    /**
+     * Executes an external command. The closure configures a {@link org.gradle.process.ExecSpec}.
+     *
+     * @param closure The closure for configuring the execution.
+     *
+     * @return the result of the execution
+     */
+    ExecResult exec(Closure closure);
+
+    /**
+     * <p>Converts a name to an absolute project path, resolving names relative to this project.</p>
+     *
+     * @param path The path to convert.
+     * @return The absolute path.
+     */
+    String absolutePath(String path);
+
+    /**
+     * <p>Returns the <code>AntBuilder</code> for this project. You can use this in your build file to execute ant
+     * tasks.</p> <p/> <p>You can access this property in your build file using <code>ant</code></p>
+     *
+     * @return The <code>AntBuilder</code> for this project. Never returns null.
+     */
+    AntBuilder getAnt();
+
+    /**
+     * <p>Creates an additional <code>AntBuilder</code> for this project. You can use this in your build file to execute
+     * ant tasks.</p>
+     *
+     * @return Creates an <code>AntBuilder</code> for this project. Never returns null.
+     * @see #getAnt()
+     */
+    AntBuilder createAntBuilder();
+
+    /**
+     * <p>Executes the given closure against the <code>AntBuilder</code> for this project. You can use this in your
+     * build file to execute ant tasks. The <code>AntBuild</code> is passed to the closure as the closure's
+     * delegate.</p> <p/> <p>You can call this method in your build file using <code>ant</code> followed by a code
+     * block.</p>
+     *
+     * @param configureClosure The closure to execute against the <code>AntBuilder</code>.
+     * @return The <code>AntBuilder</code>. Never returns null.
+     */
+    AntBuilder ant(Closure configureClosure);
+
+    /**
+     * Returns the configurations of this project.
+     *
+     * @return The configuration of this project.
+     */
+    ConfigurationContainer getConfigurations();
+
+    /**
+     * Configures the dependency configurations for this project. Executes the given closure against the {@link
+     * org.gradle.api.artifacts.ConfigurationContainer} for this project. The {@link
+     * org.gradle.api.artifacts.ConfigurationContainer} is passed to the closure as the closure's delegate.
+     *
+     * @param configureClosure the closure to use to configure the dependency configurations.
+     */
+    void configurations(Closure configureClosure);
+
+    /**
+     * Returns a handler for assigning artifacts produced by the project to configurations.
+     */
+    ArtifactHandler getArtifacts();
+
+    /**
+     * Configures the published artifacts for this project. Executes the given closure against the {@link
+     * ArtifactHandler} for this project. The {@link ArtifactHandler} is passed to the closure as the closure's
+     * delegate.
+     *
+     * @param configureClosure the closure to use to configure the published artifacts.
+     */
+    void artifacts(Closure configureClosure);
+
+    /**
+     * <p>Return the {@link Convention} for this project.</p> <p/> <p>You can access this property in your build file
+     * using <code>convention</code>. You can also can also access the properties and methods of the convention object
+     * as if they were properties and methods of this project. See <a href="#properties">here</a> for more details</p>
+     *
+     * @return The <code>Convention</code>. Never returns null.
+     */
+    Convention getConvention();
+
+    /**
+     * <p>Compares the nesting level of this project with another project of the multi-project hierarchy.</p>
+     *
+     * @param otherProject The project to compare the nesting level with.
+     * @return a negative integer, zero, or a positive integer as this project has a nesting level less than, equal to,
+     *         or greater than the specified object.
+     * @see #getDepth()
+     */
+    int depthCompare(Project otherProject);
+
+    /**
+     * <p>Returns the nesting level of a project in a multi-project hierarchy. For single project builds this is always
+     * 0. In a multi-project hierarchy 0 is returned for the root project.</p>
+     */
+    int getDepth();
+
+    /**
+     * <p>Returns the tasks of this project.</p>
+     *
+     * @return the tasks of this project.
+     */
+    TaskContainer getTasks();
+
+    /**
+     * <p>Executes the given {@link Action} against the subprojects of this project.</p>
+     *
+     * @param action The action to execute.
+     */
+    void subprojects(Action<? super Project> action);
+
+    /**
+     * <p>Executes the given closure against each of the sub-projects of this project. The target project is passed to
+     * the closure as the closure's delegate. </p> <p/> <p>You can call this method in your build file using
+     * <code>subprojects</code> followed by a code block.</p>
+     *
+     * @param configureClosure The closure to execute. The closure receives no parameters.
+     */
+    void subprojects(Closure configureClosure);
+
+    /**
+     * <p>Executes the given {@link Action} against this project and its subprojects.</p>
+     *
+     * @param action The action to execute.
+     */
+    void allprojects(Action<? super Project> action);
+
+    /**
+     * <p>Executes the given closure against this project and its sub-projects. The target project is passed to the
+     * closure as the closure's delegate.</p> <p/> <p>You can call this method in your build file using
+     * <code>allprojects</code> followed by a code block.</p>
+     *
+     * @param configureClosure The closure to execute. The closure receives no parameters.
+     */
+    void allprojects(Closure configureClosure);
+
+    /**
+     * Adds an action to execute immediately before this project is evaluated.
+     *
+     * @param action the action to execute.
+     */
+    void beforeEvaluate(Action<? super Project> action);
+
+    /**
+     * Adds an action to execute immediately after this project is evaluated.
+     *
+     * @param action the action to execute.
+     */
+    void afterEvaluate(Action<? super Project> action);
+
+    /**
+     * <p>Adds a closure to be called immediately before this project is evaluated. The project is passed to the closure
+     * as a parameter.</p>
+     *
+     * @param closure The closure to call.
+     */
+    void beforeEvaluate(Closure closure);
+
+    /**
+     * <p>Adds a closure to be called immediately after this project has been evaluated. The project is passed to the
+     * closure as a parameter. Such a listener gets notified when the build file belonging to this project has been
+     * executed. A parent project may for example add such a listener to its child project. Such a listener can futher
+     * configure those child projects based on the state of the child projects after their build files have been
+     * run.</p>
+     *
+     * @param closure The closure to call.
+     */
+    void afterEvaluate(Closure closure);
+
+    /**
+     * <p>Determines if this project has the given property. See <a href="#properties">here</a> for details of the
+     * properties which are available for a project.</p>
+     *
+     * @param propertyName The name of the property to locate.
+     * @return True if this project has the given property, false otherwise.
+     */
+    boolean hasProperty(String propertyName);
+
+    /**
+     * <p>Returns the properties of this project. See <a href="#properties">here</a> for details of the properties which
+     * are available for a project.</p>
+     *
+     * @return A map from property name to value.
+     */
+    Map<String, ?> getProperties();
+
+    /**
+     * Returns the value of the given property.  This method locates a property as follows:</p> <p/> <ol> <p/> <li>If
+     * this project object has a property with the given name, return the value of the property.</li> <p/> <li>If this
+     * project's convention object has a property with the given name, return the value of the property.</li> <p/>
+     * <li>If this project has an additional property with the given name, return the value of the property.</li> <p/>
+     * <li>If this project has a task with the given name, return the task.</li> <p/> <li>Search up through this
+     * project's ancestor projects for a convention property or additional property with the given name.</li> <p/>
+     * <li>If not found, throw {@link MissingPropertyException}</li> <p/> </ol>
+     *
+     * @param propertyName The name of the property.
+     * @return The value of the property, possibly null.
+     * @throws MissingPropertyException When the given property is unknown.
+     */
+    Object property(String propertyName) throws MissingPropertyException;
+
+    /**
+     * <p>Returns the logger for this project. You can use this in your build file to write log messages.</p> <p/>
+     * <p>You can use this property in your build file using <code>logger</code>.</p>
+     *
+     * @return The logger. Never returns null.
+     */
+    Logger getLogger();
+
+    /**
+     * <p>Returns the {@link org.gradle.api.invocation.Gradle} which this project belongs to.</p> <p/> <p>You can use
+     * this property in your build file using <code>gradle</code>.</p>
+     *
+     * @return The Gradle object. Never returns null.
+     */
+    Gradle getGradle();
+
+    /**
+     * Returns the {@link org.gradle.api.logging.LoggingManager} which can be used to control the logging level and
+     * standard output/error capture for this project's build script. By default, System.out is redirected to the
+     * Gradle logging system at the QUIET log level, and System.err is redirected at the ERROR log level.
+     *
+     * @return the LoggingManager. Never returns null.
+     */
+    LoggingManager getLogging();
+
+    /**
+     * Disables redirection of standard output during project evaluation. By default redirection is enabled.
+     *
+     * @see #captureStandardOutput(org.gradle.api.logging.LogLevel)
+     */
+    @Deprecated
+    void disableStandardOutputCapture();
+
+    /**
+     * <p>Starts redirection of standard output during to the logging system during project evaluation. By default
+     * redirection is enabled and the output is redirected to the QUIET level. System.err is always redirected to the
+     * ERROR level. Redirection of output at execution time can be configured via the tasks.</p>
+     * 
+     * <p>In a multi-project this is a per-project setting.</p>
+     *
+     * @param level The level standard out should be logged to.
+     * @see #disableStandardOutputCapture()
+     * @see Task#captureStandardOutput(org.gradle.api.logging.LogLevel)
+     * @see org.gradle.api.Task#disableStandardOutputCapture()
+     * @deprecated Use the {@link org.gradle.api.logging.LoggingManager} returned by {@link #getLogging()} instead
+     */
+    @Deprecated
+    void captureStandardOutput(LogLevel level);
+
+    /**
+     * <p>Configures an object via a closure, with the closure's delegate set to the supplied object. This way you don't
+     * have to specify the context of a configuration statement multiple times. <p/> Instead of:</p>
+     * <pre>
+     * MyType myType = new MyType()
+     * myType.doThis()
+     * myType.doThat()
+     * </pre>
+     * <p/> you can do:
+     * <pre>
+     * MyType myType = configure(new MyType()) {
+     *     doThis()
+     *     doThat()
+     * }
+     * </pre>
+     *
+     * <p>The object being configured is also passed to the closure as a parameter, so you can access it explicitly if
+     * required:</p>
+     * <pre>
+     * configure(someObj) { obj -> obj.doThis() }
+     * </pre>
+     *
+     * @param object The object to configure
+     * @param configureClosure The closure with configure statements
+     * @return The configured object
+     */
+    Object configure(Object object, Closure configureClosure);
+
+    /**
+     * Configures a collection of objects via a closure. This is equivalent to calling {@link #configure(Object,
+     * groovy.lang.Closure)} for each of the given objects.
+     *
+     * @param objects The objects to configure
+     * @param configureClosure The closure with configure statements
+     * @return The configured objects.
+     */
+    Iterable<?> configure(Iterable<?> objects, Closure configureClosure);
+
+    /**
+     * Configures a collection of objects via an action.
+     *
+     * @param objects The objects to configure
+     * @param configureAction The action to apply to each object
+     * @return The configured objects.
+     */
+    <T> Iterable<T> configure(Iterable<T> objects, Action<? super T> configureAction);
+
+    /**
+     * Returns a handler to create repositories which are used for retrieving dependencies and uploading artifacts
+     * produced by the project.
+     *
+     * @return the repository handler. Never returns null.
+     */
+    RepositoryHandler getRepositories();
+
+    /**
+     * Configures the repositories for this project. Executes the given closure against the {@link RepositoryHandler}
+     * for this project. The {@link RepositoryHandler} is passed to the closure as the closure's delegate.
+     *
+     * @param configureClosure the closure to use to configure the repositories.
+     */
+    void repositories(Closure configureClosure);
+
+    /**
+     * Creates a new repository handler. <p/> Each repository handler is a factory and container for repositories. For
+     * example each instance of an upload task has its own repository handler.
+     *
+     * @return a new repository handler
+     */
+    RepositoryHandler createRepositoryHandler();
+
+    /**
+     * Returns the dependency handler of this project. The returned dependency handler instance can be used for adding
+     * new dependencies. For accessing already declared dependencies, the configurations can be used.
+     *
+     * @return the dependency handler. Never returns null.
+     * @see #getConfigurations()
+     */
+    DependencyHandler getDependencies();
+
+    /**
+     * Configures the dependencies for this project. Executes the given closure against the {@link DependencyHandler}
+     * for this project. The {@link DependencyHandler} is passed to the closure as the closure's delegate.
+     *
+     * @param configureClosure the closure to use to configure the dependencies.
+     */
+    void dependencies(Closure configureClosure);
+
+    /**
+     * Returns the plugins container for this project. The returned container can be used to manage the plugins which
+     * are used by this project.
+     *
+     * @return the plugin container. Never returns null.
+     */
+    PluginContainer getPlugins();
+
+    /**
+     * Returns the build script handler for this project. You can use this handler to query details about the build
+     * script for this project, and manage the classpath used to compile and execute the project's build script.
+     *
+     * @return the classpath handler. Never returns null.
+     */
+    ScriptHandler getBuildscript();
+
+    /**
+     * Configures the build script classpath for this project. The given closure is executed against this project's
+     * {@link ScriptHandler}. The {@link ScriptHandler} is passed to the closure as the closure's delegate.
+     *
+     * @param configureClosure the closure to use to configure the build script classpath.
+     */
+    void buildscript(Closure configureClosure);
+
+    /**
+     * Copy the specified files.  The given closure is used to configure a {@link CopySpec}, which is then used to copy
+     * the files. Example:
+     * <pre>
+     * copy {
+     *    from configurations.runtime
+     *    into 'build/deploy/lib'
+     * }
+     * </pre>
+     * Note that CopySpecs can be nested:
+     * <pre>
+     * copy {
+     *    into 'build/webroot'
+     *    exclude '**/.svn/**'
+     *    from('src/main/webapp') {
+     *       include '**/*.jsp'
+     *       filter(ReplaceTokens, tokens:[copyright:'2009', version:'2.3.1'])
+     *    }
+     *    from('src/main/js') {
+     *       include '**/*.js'
+     *    }
+     * }
+     * </pre>
+     *
+     * @param closure Closure to configure the CopySpec
+     * @return {@link WorkResult} that can be used to check if the copy did any work.
+     */
+    WorkResult copy(Closure closure);
+
+    /**
+     * Creates a {@link CopySpec} which can later be used to copy files or create an archive. The given closure is used
+     * to configure the {@link CopySpec} before it is returned by this method.
+     *
+     * @param closure Closure to configure the CopySpec
+     * @return The CopySpec
+     */
+    CopySpec copySpec(Closure closure);
+
+    /**
+     * <p>Configures this project using plugins or scripts. The given closure is used to configure an {@link
+     * org.gradle.api.plugins.ObjectConfigurationAction} which is then used to configure this project.</p>
+     *
+     * @param closure The closure to configure the {@code ObjectConfigurationAction}.
+     */
+    void apply(Closure closure);
+
+    /**
+     * <p>Configures this project using plugins or scripts. The following options are available:</p>
+     *
+     * <ul><li>{@code from}: A script to apply to the project. Accepts any path supported by {@link #uri(Object)}.</li>
+     *
+     * <li>{@code plugin}: The id or implementation class of the plugin to apply to the project.</li>
+     *
+     * <li>{@code to}: The target delegate object or objects. Use this to configure objects other than the
+     * project.</li></ul>
+     *
+     * <p>For more detail, see {@link org.gradle.api.plugins.ObjectConfigurationAction}.</p>
+     *
+     * @param options The options to use to configure the {@code ObjectConfigurationAction}.
+     */
+    void apply(Map<String, ?> options);
+
+    /**
+     * Returns the evaluation state of this project. You can use this to access information about the evaluation of this
+     * project, such as whether it has failed.
+     *
+     * @return the project state. Never returns null.
+     */
+    ProjectState getState();
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/ProjectEvaluationListener.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/ProjectEvaluationListener.java
new file mode 100644
index 0000000..785ae68
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/ProjectEvaluationListener.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+/**
+ * <p>An {@code ProjectEvaluationListener} is notified when a project is evaluated. You add can add an {@code
+ * ProjectEvaluationListener} to a {@link org.gradle.api.invocation.Gradle} using {@link
+ * org.gradle.api.invocation.Gradle#addProjectEvaluationListener(ProjectEvaluationListener)}.</p>
+ *
+ * @author Hans Dockter
+ */
+public interface ProjectEvaluationListener {
+    /**
+     * This method is called immediately before a project is evaluated.
+     *
+     * @param project The which is to be evaluated. Never null.
+     */
+    void beforeEvaluate(Project project);
+
+    /**
+     * This method is called when a project has been evaluated, and before the evaluated project is made available to
+     * other projects.</p>
+     *
+     * @param project The project which was evaluated. Never null.
+     * @param state The project evaluation state. If project evaluation failed, the exception is available in this
+     * state. Never null.
+     */
+    void afterEvaluate(Project project, ProjectState state);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/ProjectState.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/ProjectState.java
new file mode 100644
index 0000000..5fb4a1d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/ProjectState.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+/**
+ * {@code ProjectState} provides information about the execution state of a project.
+ */
+public interface ProjectState {
+    /**
+     * <p>Returns true if this project has been evaluated.</p>
+     *
+     * @return true if this project has been evaluated.
+     */
+    boolean getExecuted();
+
+    /**
+     * Returns the exception describing the project failure, if any.
+     *
+     * @return The exception, or null if project evaluation did not fail.
+     */
+    Throwable getFailure();
+
+    /**
+     * Throws the project failure, if any. Does nothing if the project did not fail.
+     */
+    void rethrowFailure();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/Rule.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/Rule.java
new file mode 100644
index 0000000..b589e76
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/Rule.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+/**
+ * <p>A {@code Rule} represents some action to perform when an unknown domain object is referenced. The rule can use the
+ * domain object name to add an implicit domain object.</p>
+ *
+ * @author Hans Dockter
+ */
+public interface Rule {
+    /**
+     * Returns the description of the rule. This is used for reporting purposes.
+     *
+     * @return the description. should not return null.
+     */
+    String getDescription();
+
+    /**
+     * Applies this rule for the given unknown domain object. The rule can choose to ignore this name, or add a domain
+     * object with the given name.
+     *
+     * @param domainObjectName The name of the unknown domain object.
+     */
+    void apply(String domainObjectName);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/Script.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/Script.java
new file mode 100644
index 0000000..ec94053
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/Script.java
@@ -0,0 +1,326 @@
+/*
+ * 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;
+
+import groovy.lang.Closure;
+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.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.LoggingManager;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.process.ExecResult;
+
+import java.io.File;
+import java.net.URI;
+import java.util.Map;
+
+/**
+ * <p>The base class for all scripts executed by Gradle. This is a extension to the Groovy {@code Script} class, which
+ * adds in some Gradle-specific methods. As your compiled script class will implement this interface, you can use the
+ * methods and properties declared here directly in your script.</p>
+ *
+ * <p>Generally, a {@code Script} object will have a delegate object attached to it. For example, a build script will
+ * have a {@link Project} instance attached to it, and an initialization script will have a {@link
+ * org.gradle.api.invocation.Gradle} instance attached to it. Any property reference or method call which is not found
+ * on this {@code Script} object is forwarded to the delegate object.</p>
+ */
+public interface Script {
+    /**
+     * <p>Configures the delegate object for this script using plugins or scripts. The given closure is used to
+     * configure an {@link org.gradle.api.plugins.ObjectConfigurationAction} which is then used to configure the
+     * delegate object.</p>
+     *
+     * @param closure The closure to configure the {@code ObjectConfigurationAction}.
+     */
+    void apply(Closure closure);
+
+    /**
+     * <p>Configures the delegate object for this script using plugins or scripts. The following options are
+     * available:</p>
+     *
+     * <ul><li>{@code from}: A script to apply to the delegate object. Accepts any path supported by {@link
+     * #uri(Object)}.</li>
+     *
+     * <li>{@code plugin}: The id or implementation class of the plugin to apply to the delegate object.</li>
+     *
+     * <li>{@code to}: The target delegate object or objects.</li></ul> <p/> <p>For more detail, see {@link
+     * org.gradle.api.plugins.ObjectConfigurationAction}.</p>
+     *
+     * @param options The options to use to configure the {@code ObjectConfigurationAction}.
+     */
+    void apply(Map<String, ?> options);
+
+    /**
+     * Returns the script handler for this script. You can use this handler to manage the classpath used to compile and
+     * execute this script.
+     *
+     * @return the classpath handler. Never returns null.
+     */
+    ScriptHandler getBuildscript();
+
+    /**
+     * Configures the classpath for this script. The given closure is executed against this script's {@link
+     * ScriptHandler}. The {@link ScriptHandler} is passed to the closure as the closure's delegate.
+     *
+     * @param configureClosure the closure to use to configure the script classpath.
+     */
+    void buildscript(Closure configureClosure);
+
+    /**
+     * <p>Resolves a file path relative to the directory containing this script. This works as described for {@link
+     * Project#file(Object)}</p>
+     *
+     * @param path The object to resolve as a File.
+     * @return The resolved file. Never returns null.
+     */
+    File file(Object path);
+
+    /**
+     * <p>Resolves a file path relative to the directory containing this script and validates it using the given scheme.
+     * See {@link PathValidation} for the list of possible validations.</p>
+     *
+     * @param path An object to resolve as a File.
+     * @param validation The validation to perform on the file.
+     * @return The resolved file. Never returns null.
+     * @throws InvalidUserDataException When the file does not meet the given validation constraint.
+     */
+    File file(Object path, PathValidation validation) throws InvalidUserDataException;
+
+    /**
+     * <p>Resolves a file path to a URI, relative to the directory containing this script. Evaluates the provided path
+     * object as described for {@link #file(Object)}, with the exception that any URI scheme is supported, not just
+     * 'file:' URIs.</p>
+     *
+     * @param path The object to resolve as a URI.
+     * @return The resolved URI. Never returns null.
+     */
+    URI uri(Object path);
+
+    /**
+     * <p>Returns a {@link ConfigurableFileCollection} containing the given files. This works as described for {@link
+     * Project#files(Object...)}. Relative paths are resolved relative to the directory containing this script.</p>
+     *
+     * @param paths The paths to the files. May be empty.
+     * @return The file collection. Never returns null.
+     */
+    ConfigurableFileCollection files(Object... paths);
+
+    /**
+     * <p>Creates a new {@code ConfigurableFileCollection} using the given paths. The file collection is configured
+     * using the given closure. This method works as described for {@link Project#files(Object, groovy.lang.Closure)}.
+     * Relative paths are resolved relative to the directory containing this script.</p>
+     *
+     * @param paths The contents of the file collection. Evaluated as for {@link #files(Object...)}.
+     * @param configureClosure The closure to use to configure the file collection.
+     * @return the configured file tree. Never returns null.
+     */
+    ConfigurableFileCollection files(Object paths, Closure configureClosure);
+
+    /**
+     * <p>Returns the relative path from the directory containing this script to the given path. The given path object
+     * is (logically) resolved as described for {@link #file(Object)}, from which a relative path is calculated.</p>
+     *
+     * @param path The path to convert to a relative path.
+     * @return The relative path. Never returns null.
+     */
+    String relativePath(Object path);
+
+    /**
+     * <p>Creates a new {@code ConfigurableFileTree} using the given base directory. The given baseDir path is evaluated
+     * as for {@link #file(Object)}.</p>
+     *
+     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
+     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
+     * queried.</p>
+     *
+     * @param baseDir The base directory of the file tree. Evaluated as for {@link #file(Object)}.
+     * @return the file tree. Never returns null.
+     */
+    ConfigurableFileTree fileTree(Object baseDir);
+
+    /**
+     * <p>Creates a new {@code ConfigurableFileTree} using the provided map of arguments.  The map will be applied as
+     * properties on the new file tree.  Example:</p>
+     * <pre>
+     * fileTree(dir:'src', excludes:['**/ignore/**','**/.svn/**'])
+     * </pre>
+     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
+     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
+     * queried.</p>
+     *
+     * @param args map of property assignments to {@code ConfigurableFileTree} object
+     * @return the configured file tree. Never returns null.
+     */
+    ConfigurableFileTree fileTree(Map<String, ?> args);
+
+    /**
+     * <p>Creates a new {@code ConfigurableFileTree} using the provided closure.  The closure will be used to configure
+     * the new file tree. The file tree is passed to the closure as its delegate.  Example:</p>
+     * <pre>
+     * fileTree {
+     *    from 'src'
+     *    exclude '**/.svn/**'
+     * }.copy { into 'dest'}
+     * </pre>
+     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
+     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
+     * queried.</p>
+     *
+     * @param closure Closure to configure the {@code ConfigurableFileTree} object
+     * @return the configured file tree. Never returns null.
+     */
+    ConfigurableFileTree fileTree(Closure closure);
+
+    /**
+     * <p>Creates a new {@code FileTree} which contains the contents of the given ZIP file. The given zipPath path is
+     * evaluated as for {@link #file(Object)}. You can combine this method with the {@link #copy(groovy.lang.Closure)}
+     * method to unzip a ZIP file.</p>
+     *
+     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
+     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
+     * queried.</p>
+     *
+     * @param zipPath The ZIP file. Evaluated as for {@link #file(Object)}.
+     * @return the file tree. Never returns null.
+     */
+    FileTree zipTree(Object zipPath);
+
+    /**
+     * <p>Creates a new {@code FileTree} which contains the contents of the given TAR file. The given tarPath path is
+     * evaluated as for {@link #file(Object)}. You can combine this method with the {@link #copy(groovy.lang.Closure)}
+     * method to untar a TAR file.</p>
+     *
+     * <p>The returned file tree is lazy, so that it scans for files only when the contents of the file tree are
+     * queried. The file tree is also live, so that it scans for files each time the contents of the file tree are
+     * queried.</p>
+     *
+     * @param tarPath The TAR file. Evaluated as for {@link #file(Object)}.
+     * @return the file tree. Never returns null.
+     */
+    FileTree tarTree(Object tarPath);
+
+    /**
+     * Copy the specified files.  The given closure is used to configure a {@link org.gradle.api.file.CopySpec}, which
+     * is then used to copy the files. Example:
+     * <pre>
+     * copy {
+     *    from configurations.runtime
+     *    into 'build/deploy/lib'
+     * }
+     * </pre>
+     * Note that CopySpecs can be nested:
+     * <pre>
+     * copy {
+     *    into 'build/webroot'
+     *    exclude '**/.svn/**'
+     *    from('src/main/webapp') {
+     *       include '**/*.jsp'
+     *       filter(ReplaceTokens, tokens:[copyright:'2009', version:'2.3.1'])
+     *    }
+     *    from('src/main/js') {
+     *       include '**/*.js'
+     *    }
+     * }
+     * </pre>
+     *
+     * @param closure Closure to configure the CopySpec
+     * @return {@link org.gradle.api.tasks.WorkResult} that can be used to check if the copy did any work.
+     */
+    WorkResult copy(Closure closure);
+
+    /**
+     * Creates a {@link org.gradle.api.file.CopySpec} which can later be used to copy files or create an archive. The
+     * given closure is used to configure the {@link org.gradle.api.file.CopySpec} before it is returned by this
+     * method.
+     *
+     * @param closure Closure to configure the CopySpec
+     * @return The CopySpec
+     */
+    CopySpec copySpec(Closure closure);
+
+    /**
+     * Creates a directory and returns a file pointing to it.
+     *
+     * @param path The path for the directory to be created. Evaluated as for {@link #file(Object)}.
+     * @return the created directory
+     * @throws org.gradle.api.InvalidUserDataException If the path points to an existing file.
+     */
+    File mkdir(Object path);
+
+    /**
+     * Deletes files and directories.
+     *
+     * @param paths Any type of object accepted by {@link org.gradle.api.Project#files(Object...)}
+     * @return true if anything got deleted, false otherwise
+     */
+    boolean delete(Object... paths);
+
+    /**
+     * Executes a Java main class. The closure configures a {@link org.gradle.process.JavaExecSpec}.
+     *
+     * @param closure The closure for configuring the execution.
+     * @return the result of the execution
+     */
+    ExecResult javaexec(Closure closure);
+
+    /**
+     * Executes an external command. The closure configures a {@link org.gradle.process.ExecSpec}.
+     *
+     * @param closure The closure for configuring the execution.
+     * @return the result of the execution
+     */
+    ExecResult exec(Closure closure);
+
+    /**
+     * Returns the {@link org.gradle.api.logging.LoggingManager} which can be used to control the logging level and
+     * standard output/error capture for this script. By default, System.out is redirected to the Gradle logging system
+     * at the QUIET log level, and System.err is redirected at the ERROR log level.
+     *
+     * @return the LoggingManager. Never returns null.
+     */
+    LoggingManager getLogging();
+
+    /**
+     * Disables redirection of standard output during script execution. By default redirection is enabled.
+     *
+     * @see #captureStandardOutput(org.gradle.api.logging.LogLevel)
+     */
+    @Deprecated
+    void disableStandardOutputCapture();
+
+    /**
+     * <p>Starts redirection of standard output during to the logging system during script execution. By default
+     * redirection is enabled and the output is redirected to the QUIET level. System.err is always redirected to the
+     * ERROR level.</p>
+     *
+     * @param level The level standard out should be logged to.
+     * @see #disableStandardOutputCapture()
+     * @deprecated Use the {@link org.gradle.api.logging.LoggingManager} returned by {@link #getLogging()} instead
+     */
+    @Deprecated
+    void captureStandardOutput(LogLevel level);
+
+    /**
+     * Returns the logger for this script. You can use this in your script to write log messages.
+     *
+     * @return The logger. Never returns null.
+     */
+    Logger getLogger();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/ScriptCompilationException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/ScriptCompilationException.java
new file mode 100644
index 0000000..3359d60
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/ScriptCompilationException.java
@@ -0,0 +1,46 @@
+/*
+ * 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.groovy.scripts.ScriptSource;
+
+/**
+ * A {@code ScriptCompilationException} is thrown when a script cannot be compiled.
+ */
+public class ScriptCompilationException extends GradleScriptException {
+    private final ScriptSource scriptSource;
+    private final Integer lineNumber;
+
+    public ScriptCompilationException(ScriptCompilationException source) {
+        super(source.getMessage(), source.getCause());
+        scriptSource = source.scriptSource;
+        lineNumber = source.lineNumber;
+    }
+
+    public ScriptCompilationException(String message, Throwable cause, ScriptSource scriptSource, Integer lineNumber) {
+        super(message, cause);
+        this.scriptSource = scriptSource;
+        this.lineNumber = lineNumber;
+    }
+
+    public ScriptSource getScriptSource() {
+        return scriptSource;
+    }
+
+    public Integer getLineNumber() {
+        return lineNumber;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/Task.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/Task.java
new file mode 100644
index 0000000..2ee74cf
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/Task.java
@@ -0,0 +1,523 @@
+/*
+ * 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;
+
+import groovy.lang.Closure;
+import groovy.lang.MissingPropertyException;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.LoggingManager;
+import org.gradle.api.plugins.Convention;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.api.tasks.TaskInputs;
+import org.gradle.api.tasks.TaskOutputs;
+import org.gradle.api.tasks.TaskState;
+
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * <p>A <code>Task</code> represents a single atomic piece of work for a build, such as compiling classes or generating
+ * javadoc.</p>
+ *
+ * <p>Each task belongs to a {@link Project}. You can use the various methods on {@link
+ * org.gradle.api.tasks.TaskContainer} to create and lookup task instances. For example, {@link
+ * org.gradle.api.tasks.TaskContainer#add(String)} creates an empty task with the given name. You can also use the
+ * {@code task} keyword in your build file: </p>
+ * <pre>
+ * task myTask
+ * task myTask { configure closure }
+ * task myType << { task action }
+ * task myTask(type: SomeType)
+ * task myTask(type: SomeType) { configure closure }
+ * </pre>
+ *
+ * <p>Each task has a name, which can be used to refer to the task within its owning project, and a fully qualified
+ * path, which is unique across all tasks in all projects. The path is the concatenation of the owning project's path
+ * and the task's name. Path elements are separated using the {@value org.gradle.api.Project#PATH_SEPARATOR}
+ * character.</p>
+ *
+ * <h3>Task Actions</h3>
+ *
+ * <p>A <code>Task</code> is made up of a sequence of {@link Action} objects. When the task is executed, each of the
+ * actions is executed in turn, by calling {@link Action#execute}.  You can add actions to a task by calling {@link
+ * #doFirst(Action)} or {@link #doLast(Action)}.</p>
+ *
+ * <p>Groovy closures can also be used to provide a task action. When the action is executed, the closure is called with
+ * the task as parameter.  You can add action closures to a task by calling {@link #doFirst(groovy.lang.Closure)} or
+ * {@link #doLast(groovy.lang.Closure)}  or using the left-shift << operator.</p>
+ *
+ * There are 2 special exceptions which a task action can throw to abort execution and continue without failing the
+ * build. A task action can abort execution of the action and continue to the next action of the task by throwing a
+ * {@link org.gradle.api.tasks.StopActionException}. A task action can abort execution of the task and continue to the
+ * next task by throwing a {@link org.gradle.api.tasks.StopExecutionException}. Using these exceptions allows you to
+ * have precondition actions which skip execution of the task, or part of the task, if not true.</p>
+ *
+ * <a name="dependencies"/><h3>Dependencies</h3>
+ *
+ * <p>A task may have dependencies on other tasks. Gradle ensures that tasks are executed in dependency order, so that
+ * the dependencies of a task are executed before the task is executed.  You can add dependencies to a task using {@link
+ * #dependsOn(Object...)} or {@link #setDependsOn(Iterable)}.  You can add objects of any of the following types as a
+ * dependency:</p>
+ *
+ * <ul>
+ *
+ * <li>A {@code String} task path or name. A relative path is interpreted relative to the task's {@link Project}. This
+ * allows you to refer to tasks in other projects.</li>
+ *
+ * <li>A {@link Task}.</li>
+ *
+ * <li>A closure. The closure may take a {@code Task} as parameter. It may return any of the types listed here. Its
+ * return value is recursively converted to tasks. A {@code null} return value is treated as an empty collection.</li>
+ *
+ * <li>A {@link TaskDependency} object.</li>
+ *
+ * <li>A {@link Buildable} object.</li>
+ *
+ * <li>A {@code Collection}, {@code Map} or an array. May contain any of the types listed here. The elements of the
+ * collection/map/array are recursively converted to tasks.</li>
+ *
+ * <li>A {@code Callable}. The {@code call()} method may return any of the types listed here. Its return value is
+ * recursively converted to tasks. A {@code null} return value is treated as an empty collection.</li>
+ *
+ * <li>An {@code Object}. The object's {@code toString()} method is interpreted as a task path or name.</li>
+ *
+ * </ul>
+ *
+ * <h3>Using a Task in a Build File</h3>
+ *
+ * <a name="properties"/> <h4>Dynamic Properties</h4>
+ *
+ * <p>A {@code Task} has 3 'scopes' for properties. You can access these properties by name from the build file or by
+ * calling the {@link #property(String)} method.</p>
+ *
+ * <ul>
+ *
+ * <li>The {@code Task} object itself. This includes any property getters and setters declared by the {@code Task}
+ * implementation class.  The properties of this scope are readable or writable based on the presence of the
+ * corresponding getter and setter methods.</li>
+ *
+ * <li>The <em>additional properties</em> of the task. Each task object maintains a map of additional properties. These
+ * are arbitrary name -> value pairs which you can use to dynamically add properties to a task object.  The properties
+ * of this scope are readable and writable.</li>
+ *
+ * <li>The <em>convention</em> properties added to the task by each {@link Plugin} applied to the project. A {@link
+ * Plugin} can add properties and methods to a task through the task's {@link Convention} object.  The properties of
+ * this scope may be readable or writable, depending on the convention objects.</li>
+ *
+ * </ul>
+ *
+ * <h4>Dynamic Methods</h4>
+ *
+ * <p>A {@link Plugin} may add methods to a {@code Task} using its {@link Convention} object.</p>
+ *
+ * @author Hans Dockter
+ */
+public interface Task extends Comparable<Task> {
+    public static final String TASK_NAME = "name";
+
+    public static final String TASK_DESCRIPTION = "description";
+
+    public static final String TASK_TYPE = "type";
+
+    public static final String TASK_DEPENDS_ON = "dependsOn";
+
+    public static final String TASK_OVERWRITE = "overwrite";
+
+    public static final String TASK_ACTION = "action";
+
+    /**
+     * </p>Returns the name of this task. The name uniquely identifies the task within its {@link Project}.</p>
+     *
+     * @return The name of the task. Never returns null.
+     */
+    String getName();
+
+    /**
+     * <p>Returns the {@link Project} which this task belongs to.</p>
+     *
+     * @return The project this task belongs to. Never returns null.
+     */
+    Project getProject();
+
+    /**
+     * <p>Returns the sequence of {@link Action} objects which will be executed by this task, in the order of
+     * execution.</p>
+     *
+     * @return The task actions in the order they are executed. Returns an empty list if this task has no actions.
+     */
+    List<Action<? super Task>> getActions();
+
+    /**
+     * <p>Sets the sequence of {@link Action} objects which will be executed by this task.</p>
+     *
+     * @param actions The actions.
+     */
+    void setActions(List<Action<? super Task>> actions);
+
+    /**
+     * <p>Returns a {@link TaskDependency} which contains all the tasks that this task depends on.</p>
+     *
+     * @return The dependencies of this task. Never returns null.
+     */
+    TaskDependency getTaskDependencies();
+
+    /**
+     * <p>Returns the dependencies of this task.</p>
+     *
+     * @return The dependencies of this task. Returns an empty set if this task has no dependencies.
+     */
+    Set<Object> getDependsOn();
+
+    /**
+     * <p>Sets the dependencies of this task. See <a href="#dependencies">here</a> for a description of the types of
+     * objects which can be used as task dependencies.</p>
+     *
+     * @param dependsOnTasks The set of task paths.
+     */
+    void setDependsOn(Iterable<?> dependsOnTasks);
+
+    /**
+     * <p>Adds the given dependencies to this task. See <a href="#dependencies">here</a> for a description of the types
+     * of objects which can be used as task dependencies.</p>
+     *
+     * @param paths The dependencies to add to this task.
+     * @return the task object this method is applied to
+     */
+    Task dependsOn(Object... paths);
+
+    /**
+     * <p>Execute the task only if the given closure returns true.  The closure will be evaluated at task execution
+     * time, not during configuration.  The closure will be passed a single parameter, this task. If the closure returns
+     * false, the task will be skipped.</p>
+     *
+     * <p>You may add multiple such predicates. The task is skipped if any of the predicates return false.</p>
+     *
+     * <p>Typical usage:<code>myTask.onlyIf{ dependsOnTaskDidWork() } </code></p>
+     *
+     * @param onlyIfClosure code to execute to determine if task should be run
+     */
+    void onlyIf(Closure onlyIfClosure);
+
+    /**
+     * <p>Execute the task only if the given spec is satisfied. The spec will be evaluated at task execution time, not
+     * during configuration. If the Spec is not satisfied, the task will be skipped.</p>
+     *
+     * <p>You may add multiple such predicates. The task is skipped if any of the predicates return false.</p>
+     *
+     * <p>Typical usage (from Java):</p>
+     * <pre>myTask.onlyIf(new Spec<Task>() {
+     *    boolean isSatisfiedBy(Task task) {
+     *       return task.dependsOnTaskDidWork();
+     *    }
+     * });
+     * </pre>
+     *
+     * @param onlyIfSpec specifies if a task should be run
+     */
+    void onlyIf(Spec<? super Task> onlyIfSpec);
+
+    /**
+     * <p>Execute the task only if the given closure returns true.  The closure will be evaluated at task execution
+     * time, not during configuration.  The closure will be passed a single parameter, this task. If the closure returns
+     * false, the task will be skipped.</p>
+     *
+     * <p>The given predicate replaces all such predicates for this task.</p>
+     *
+     * @param onlyIfClosure code to execute to determine if task should be run
+     */
+    void setOnlyIf(Closure onlyIfClosure);
+
+    /**
+     * <p>Execute the task only if the given spec is satisfied. The spec will be evaluated at task execution time, not
+     * during configuration. If the Spec is not satisfied, the task will be skipped.</p>
+     *
+     * <p>The given predicate replaces all such predicates for this task.</p>
+     *
+     * @param onlyIfSpec specifies if a task should be run
+     */
+    void setOnlyIf(Spec<? super Task> onlyIfSpec);
+
+    /**
+     * Returns the execution state of this task. This provides information about the execution of this task, such as
+     * whether it has executed, been skipped, has failed, etc.
+     *
+     * @return The execution state of this task. Never returns null.
+     */
+    TaskState getState();
+
+    /**
+     * <p>Checks if the task actually did any work.  Even if a Task executes, it may determine that it has nothing to
+     * do.  For example, the Compile task may determine that source files have not changed since the last time a the
+     * task was run.</p>
+     *
+     * @return true if this task did any work
+     */
+    boolean getDidWork();
+
+    /**
+     * <p>Returns the path of the task, which is a fully qualified name for the task. The path of a task is the path of
+     * its {@link Project} plus the name of the task, separated by <code>:</code>.</p>
+     *
+     * @return the path of the task, which is equal to the path of the project plus the name of the task.
+     */
+    String getPath();
+
+    /**
+     * <p>Adds the given {@link Action} to the beginning of this task's action list.</p>
+     *
+     * @param action The action to add
+     * @return the task object this method is applied to
+     */
+    Task doFirst(Action<? super Task> action);
+
+    /**
+     * <p>Adds the given closure to the beginning of this task's action list. The closure is passed this task as a
+     * parameter when executed.</p>
+     *
+     * @param action The action closure to execute.
+     * @return This task.
+     */
+    Task doFirst(Closure action);
+
+    /**
+     * <p>Adds the given {@link Action} to the end of this task's action list.</p>
+     *
+     * @param action The action to add.
+     * @return the task object this method is applied to
+     */
+    Task doLast(Action<? super Task> action);
+
+    /**
+     * <p>Adds the given closure to the end of this task's action list.  The closure is passed this task as a parameter
+     * when executed.</p>
+     *
+     * @param action The action closure to execute.
+     * @return This task.
+     */
+    Task doLast(Closure action);
+
+    /**
+     * <p>Adds the given closure to the end of this task's action list.  The closure is passed this task as a parameter
+     * when executed. You can call this method from your build script using the << left shift operator.</p>
+     *
+     * @param action The action closure to execute.
+     * @return This task.
+     */
+    Task leftShift(Closure action);
+
+    /**
+     * <p>Removes all the actions of this task.</p>
+     *
+     * @return the task object this method is applied to
+     */
+    Task deleteAllActions();
+
+    /**
+     * <p>Returns if this task is enabled or not.</p>
+     *
+     * @see #setEnabled(boolean)
+     */
+    boolean getEnabled();
+
+    /**
+     * <p>Set the enabled state of a task. If a task is disabled none of the its actions are executed. Note that
+     * disabling a task does not prevent the execution of the tasks which this task depends on.</p>
+     *
+     * @param enabled The enabled state of this task (true or false)
+     */
+    void setEnabled(boolean enabled);
+
+    /**
+     * <p>Applies the statements of the closure against this task object. The delegate object for the closure is set to
+     * this task.</p>
+     *
+     * @param configureClosure The closure to be applied (can be null).
+     * @return This task
+     */
+    Task configure(Closure configureClosure);
+
+    /**
+     * <p>Returns the <code>AntBuilder</code> for this task.  You can use this in your build file to execute ant
+     * tasks.</p>
+     *
+     * @return The <code>AntBuilder</code>
+     */
+    AntBuilder getAnt();
+
+    /**
+     * <p>Returns the logger for this task. You can use this in your build file to write log messages.</p>
+     *
+     * @return The logger. Never returns null.
+     */
+    Logger getLogger();
+
+    /**
+     * Returns the {@link org.gradle.api.logging.LoggingManager} which can be used to control the logging level and
+     * standard output/error capture for this task. By default, System.out is redirected to the Gradle logging system at
+     * the QUIET log level, and System.err is redirected at the ERROR log level.
+     *
+     * @return the LoggingManager. Never returns null.
+     */
+    LoggingManager getLogging();
+
+    /**
+     * Disables redirection of standard output during task execution. By default redirection is enabled.
+     *
+     * @return this
+     * @see #captureStandardOutput(org.gradle.api.logging.LogLevel)
+     */
+    @Deprecated
+    Task disableStandardOutputCapture();
+
+    /**
+     * <p>Enables redirection of standard output during task execution to the logging system. By default redirection is
+     * enabled and the task output is redirected to the QUIET level. System.err is always redirected to the ERROR level.
+     * </p>
+     *
+     * @param level The level standard out should be logged to.
+     * @return this
+     * @see #disableStandardOutputCapture()
+     * @deprecated Use the {@link org.gradle.api.logging.LoggingManager} returned by {@link #getLogging()} instead.
+     */
+    @Deprecated
+    Task captureStandardOutput(LogLevel level);
+
+    /**
+     * Returns the value of the given property of this task.  This method locates a property as follows:</p>
+     *
+     * <ol>
+     *
+     * <li>If this task object has a property with the given name, return the value of the property.</li>
+     *
+     * <li>If this task has an additional property with the given name, return the value of the property.</li>
+     *
+     * <li>If this task's convention object has a property with the given name, return the value of the property.</li>
+     *
+     * <li>If not found, throw {@link MissingPropertyException}</li>
+     *
+     * </ol>
+     *
+     * @param propertyName The name of the property.
+     * @return The value of the property, possibly null.
+     * @throws MissingPropertyException When the given property is unknown.
+     */
+    Object property(String propertyName) throws MissingPropertyException;
+
+    /**
+     * <p>Determines if this task has the given property. See <a href="#properties">here</a> for details of the
+     * properties which are available for a task.</p>
+     *
+     * @param propertyName The name of the property to locate.
+     * @return True if this project has the given property, false otherwise.
+     */
+    boolean hasProperty(String propertyName);
+
+    /**
+     * <p>Sets a property of this task.  This method searches for a property with the given name in the following
+     * locations, and sets the property on the first location where it finds the property.</p>
+     *
+     * <ol>
+     *
+     * <li>The task object itself.  For example, the <code>enabled</code> project property.</li>
+     *
+     * <li>The task's convention object.</li>
+     *
+     * <li>The task's additional properties.</li>
+     *
+     * </ol>
+     *
+     * <p>If the property is not found in any of these locations, it is added to the project's additional
+     * properties.</p>
+     *
+     * @param name The name of the property
+     * @param value The value of the property
+     */
+    void setProperty(String name, Object value);
+
+    /**
+     * <p>Returns the {@link Convention} object for this task. A {@link Plugin} can use the convention object to
+     * contribute properties and methods to this task.</p>
+     *
+     * @return The convention object. Never returns null.
+     */
+    Convention getConvention();
+
+    /**
+     * Returns the description of a task.
+     *
+     * @see #setDescription(String)
+     */
+    String getDescription();
+
+    /**
+     * Adds a text to describe what the task does to the user of the build. The description will be displayed when
+     * <code>gradle -t</code> is called.
+     *
+     * @param description The description of the task. Might be null.
+     */
+    void setDescription(String description);
+
+    /**
+     * Returns the task group which this task belongs to. The task group is used in reports and user interfaces to
+     * group related tasks together when presenting a list of tasks to the user.
+     *
+     * @return The task group for this task. Might be null.
+     */
+    String getGroup();
+
+    /**
+     * Sets the task group which this task belongs to. The task group is used in reports and user interfaces to
+     * group related tasks together when presenting a list of tasks to the user.
+     *
+     * @param group The task group for this task. Can be null.
+     */
+    void setGroup(String group);
+
+    /**
+     * <p>Checks if any of the tasks that this task depends on {@link Task#getDidWork() didWork}.</p>
+     *
+     * @return true if any task this task depends on did work.
+     */
+    boolean dependsOnTaskDidWork();
+
+    /**
+     * <p>Returns the inputs of this task.</p>
+     *
+     * @return The inputs. Never returns null.
+     */
+    TaskInputs getInputs();
+
+    /**
+     * <p>Returns the outputs of this task.</p>
+     *
+     * @return The outputs. Never returns null.
+     */
+    TaskOutputs getOutputs();
+
+    /**
+     * <p>Returns a directory which this task can use to write temporary files to. Each task instance is provided with a
+     * separate temporary directory. There are no guarantees that the contents of this directory will be kept beyond the
+     * execution of the task.</p>
+     *
+     * @return The directory. Never returns null. The directory will already exist.
+     */
+    File getTemporaryDir();
+}
+
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/Transformer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/Transformer.java
new file mode 100644
index 0000000..3795be4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/Transformer.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 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;
+
+/**
+ * <p>A {@code Transformer} transforms objects of type T.</p>
+ */
+public interface Transformer<T> {
+    /**
+     * Transforms the given object, and returns the transformed value.
+     *
+     * @param original The object to transform.
+     * @return The transformed object.
+     */
+    T transform(T original);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/UncheckedIOException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/UncheckedIOException.java
new file mode 100644
index 0000000..5bbacbf
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/UncheckedIOException.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+/**
+ * <code>UncheckedIOException</code> is used to wrap an
+ * java.io.IOException into an unchecked exception.
+ * 
+ * @author Hans Dockter
+ */
+public class UncheckedIOException extends RuntimeException {
+    public UncheckedIOException() {
+    }
+
+    public UncheckedIOException(String message) {
+        super(message);
+    }
+
+    public UncheckedIOException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public UncheckedIOException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/UnknownDomainObjectException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/UnknownDomainObjectException.java
new file mode 100644
index 0000000..a77372a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/UnknownDomainObjectException.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+/**
+ * <p>A {@code UnknownDomainObjectException} is the super class of all exceptions thrown when a given domain object
+ * cannot be located.</p>
+ */
+public class UnknownDomainObjectException extends GradleException {
+    public UnknownDomainObjectException(String message) {
+        super(message);
+    }
+
+    public UnknownDomainObjectException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/UnknownProjectException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/UnknownProjectException.java
new file mode 100644
index 0000000..1653dbb
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/UnknownProjectException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.api;
+
+/**
+ * <p>An <code>UnknownProjectException</code> is thrown when a project referenced by path cannot be found.</p>
+ *
+ * @author Hans Dockter
+ */
+public class UnknownProjectException extends UnknownDomainObjectException {
+    public UnknownProjectException(String message) {
+        super(message);
+    }
+
+    public UnknownProjectException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/UnknownTaskException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/UnknownTaskException.java
new file mode 100644
index 0000000..70aa432
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/UnknownTaskException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.api;
+
+/**
+ * <p>An <code>UnknownTaskException</code> is thrown when a task referenced by path cannot be found.</p>
+ *
+ * @author Hans Dockter
+ */
+public class UnknownTaskException extends UnknownDomainObjectException {
+    public UnknownTaskException(String message) {
+        super(message);
+    }
+
+    public UnknownTaskException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ClientModule.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ClientModule.java
new file mode 100644
index 0000000..b91bfa5
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ClientModule.java
@@ -0,0 +1,59 @@
+/*
+ * 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.artifacts;
+
+import java.util.Set;
+
+/**
+ * To model a module in your dependency declarations. Usually you can either declare a single dependency
+ * artifact or you declare a module dependency that depends on a module descriptor in a repository. With
+ * a client module you can declare a module dependency without the need of a module descriptor in a
+ * remote repository.
+ *
+ * @author Hans Dockter
+ */
+public interface ClientModule extends ExternalDependency {
+    String CLIENT_MODULE_KEY = "org.gradle.clientModule";
+
+    /**
+     * Add a dependency to the client module. Such a dependency is transitive dependency for the
+     * project that has a dependency on the client module.
+     *  
+     * @param dependency The dependency to add to the client module.
+     * @see #getDependencies() 
+     */
+    void addDependency(ModuleDependency dependency);
+
+    /**
+     * Returns the id of the client module. This is usually only used for internal handling of the
+     * client module.
+     *
+     * @return The id of the client module
+     */
+    String getId();
+
+    /**
+     * Returns all the dependencies added to the client module.
+     *
+     * @see #addDependency(ModuleDependency)
+     */
+    Set<ModuleDependency> getDependencies();
+
+    /**
+     * {@inheritDoc}
+     */
+    ClientModule copy();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/Configuration.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/Configuration.java
new file mode 100644
index 0000000..3a15345
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/Configuration.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright 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.artifacts;
+
+import groovy.lang.Closure;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.TaskDependency;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * <p>A {@code Configuration} represents a group of artifacts and their dependencies.</p>
+ */
+public interface Configuration extends FileCollection {
+    /**
+     * The states a configuration can be into. A configuration is only mutable as long as it is
+     * in the unresolved state.
+     */
+    enum State { UNRESOLVED, RESOLVED, RESOLVED_WITH_FAILURES }
+
+    /**
+     * Returns the state of the configuration.
+     *
+     * @see org.gradle.api.artifacts.Configuration.State
+     */
+    State getState();
+
+    /**
+     * Returns the name of this configuration.
+     *
+     * @return The configuration name, never null.
+     */
+    String getName();
+
+    /**
+     * Returns true if this is a visible configuration. A visible configuration is usable outside the project it belongs
+     * to. The default value is true.
+     *
+     * @return true if this is a visible configuration.
+     */
+    boolean isVisible();
+
+    /**
+     * Sets the visibility of this configuration. When visible is set to true, this configuration is visibile outside
+     * the project it belongs to. The default value is true.
+     *
+     * @param visible true if this is a visible configuration
+     * @return this configuration
+     */
+    Configuration setVisible(boolean visible);
+
+    /**
+     * Returns the names of the configurations which this configuration extends from. The artifacts of the super
+     * configurations are also available in this configuration.
+     *
+     * @return The super configurations. Returns an empty set when this configuration does not extend any others.
+     */
+    Set<Configuration> getExtendsFrom();
+
+    /**
+     * Sets the configurations which this configuration extends from.
+     *
+     * @param superConfigs The super configuration. Should not be null.
+     * @return this configuration
+     */
+    Configuration setExtendsFrom(Set<Configuration> superConfigs);
+
+    /**
+     * Adds the given configurations to the set of configuration which this configuration extends from.
+     *
+     * @param superConfigs The super configurations.
+     * @return this configuration
+     */
+    Configuration extendsFrom(Configuration... superConfigs);
+
+    /**
+     * Returns the transitivity of this configuration. A transitive configuration contains the transitive closure of its
+     * direct dependencies, and all their dependencies. An intransitive configuration contains only the direct
+     * dependencies. The default value is true.
+     *
+     * @return true if this is a transitive configuration, false otherwise.
+     */
+    boolean isTransitive();
+
+    /**
+     * Sets the transitivity of this configuration. When set to true, this configuration will contain the transitive
+     * closure of its dependencies and their dependencies. The default value is true.
+     *
+     * @param t true if this is a transitive configuration.
+     * @return this configuration
+     */
+    Configuration setTransitive(boolean t);
+
+    /**
+     * Returns the description for this configuration.
+     *
+     * @return the description. May be null.
+     */
+    String getDescription();
+
+    /**
+     * Sets the description for this configuration.
+     *
+     * @param description the description. May be null
+     * @return this configuration
+     */
+    Configuration setDescription(String description);
+
+    /**
+     * Gets a ordered set including this configuration and all superconfigurations
+     * recursively.
+     * @return the list of all configurations
+     */
+    Set<Configuration> getHierarchy();
+
+    /**
+     * Resolves this configuration. This locates and downloads the files which make up this configuration, and returns
+     * the resulting set of files.
+     *
+     * @return The files of this configuration.
+     */
+    Set<File> resolve();
+
+    /**
+     * Takes a closure which gets coerced into a Spec. Behaves otherwise in the same way as
+     * {@link #files(org.gradle.api.specs.Spec)}.
+     *
+     * @param dependencySpecClosure The closure describing a filter applied to the all the dependencies of this configuration (including dependencies from extended configurations).
+     * @return The files of a subset of dependencies of this configuration.
+     */
+    Set<File> files(Closure dependencySpecClosure);
+
+    /**
+     * Resolves this configuration. This locates and downloads the files which make up this configuration.
+     * But only the resulting set of files belonging to the subset of dependencies specified by the dependencySpec
+     * is returned.
+     *
+     * @param dependencySpec The spec describing a filter applied to the all the dependencies of this configuration (including dependencies from extended configurations).
+     * @return The files of a subset of dependencies of this configuration.
+     */
+    Set<File> files(Spec<Dependency> dependencySpec);
+
+    /**
+     * Resolves this configuration. This locates and downloads the files which make up this configuration.
+     * But only the resulting set of files belonging to the specified dependencies
+     * is returned.
+     *
+     * @param dependencies The dependences to be resolved
+     * @return The files of a subset of dependencies of this configuration.
+     */
+    Set<File> files(Dependency... dependencies);
+
+    /**
+     * Resolves this configuration lazyly. The resolve happens when the elements of the returned FileCollection get accessed the first time.
+     * This locates and downloads the files which make up this configuration. Only the resulting set of files belonging to the subset
+     * of dependencies specified by the dependencySpec is contained in the FileCollection.
+     *
+     * @param dependencySpec The spec describing a filter applied to the all the dependencies of this configuration (including dependencies from extended configurations).
+     * @return The FileCollection with a subset of dependencies of this configuration.
+     */
+    FileCollection fileCollection(Spec<Dependency> dependencySpec);
+
+    /**
+     * Takes a closure which gets coerced into a Spec. Behaves otherwise in the same way as
+     * {@link #fileCollection(org.gradle.api.specs.Spec)}.
+     *
+     * @param dependencySpecClosure The closure describing a filter applied to the all the dependencies of this configuration (including dependencies from extended configurations).
+     * @return The FileCollection with a subset of dependencies of this configuration.
+     */
+    FileCollection fileCollection(Closure dependencySpecClosure);
+
+    /**
+     * Resolves this configuration lazyly. The resolve happens when the elements of the returned FileCollection get accessed the first time.
+     * This locates and downloads the files which make up this configuration. Only the resulting set of files belonging to specified
+     * dependencies is contained in the FileCollection.
+     *
+     * @param dependencies The dependencies for which the FileCollection should contain the files.
+     * @return The FileCollection with a subset of dependencies of this configuration.
+     */
+    FileCollection fileCollection(Dependency... dependencies);
+
+
+    /**
+     * Resolves this configuration. This locates and downloads the files which make up this configuration, and returns
+     * a ResolvedConfiguration that may be used to determine information about the resolve (including errors).
+     *
+     * @return The ResolvedConfiguration object
+     */
+    ResolvedConfiguration getResolvedConfiguration();
+
+    /**
+     * Returns the name of the task that upload the artifacts of this configuration to repositories
+     * declared by the user.
+     *
+     * @see org.gradle.api.tasks.Upload
+     */
+    String getUploadTaskName();
+
+    /**
+     * Returns a {@code TaskDependency} object containing all required dependencies to build the internal dependencies
+     * (e.g. project dependencies) belonging to this configuration or to one of its super configurations.
+     *
+     * @return a TaskDependency object
+     */
+    TaskDependency getBuildDependencies();
+
+    /**
+     * Returns a TaskDependency object containing dependencies on all tasks with the specified name from project
+     * dependencies related to this configuration or one of its super configurations.  These other projects may be
+     * projects this configuration depends on or projects with a similarly named configuation that depend on this one
+     * based on the useDependOn argument.
+     *
+     * @param useDependedOn if true, add tasks from project dependencies in this conifguration, otherwise use projects
+     *                      from configurations with the same name that depend on this one.
+     * @param taskName name of task to depend on
+     * @return the populated TaskDependency object
+     */
+    TaskDependency getTaskDependencyFromProjectDependency(boolean useDependedOn, final String taskName);
+
+    /**
+     * Returns a {@code TaskDependency} object containing all required dependencies to build the artifacts
+     * belonging to this configuration or to one of its super configurations.
+     *
+     * @return a task dependency object
+     */
+    TaskDependency getBuildArtifacts();
+
+    /**
+     * Publishes the artifacts of this configuration to the specified repositories. This
+     * method is usually used only internally as the users use the associated upload tasks to
+     * upload the artifacts.
+     *
+     * @param publishRepositories The repositories to publish the artifacts to.
+     * @param descriptorDestination The destination dir for the descriptor file (if null no descriptor file is written).
+     *
+     * @see org.gradle.api.tasks.Upload
+     * @see #getUploadTaskName()
+     */
+    void publish(List<DependencyResolver> publishRepositories, File descriptorDestination);
+
+    /**
+     * Gets the set of dependencies directly contained in this configuration
+     * (ignoring superconfigurations).
+     *
+     * @return the set of dependencies
+     */
+    Set<Dependency> getDependencies();
+
+    /**
+     * Gets the complete set of dependencies including those contributed by
+     * superconfigurations.
+     *
+     * @return the set of dependencies
+     */
+    Set<Dependency> getAllDependencies();
+
+    /**
+     * Gets the set of dependencies of type T directly contained in this configuration (ignoring superconfigurations).
+     *
+     * @param type the dependency type
+     * @param <T> the dependency type
+     * @return The set. Returns an empty set if there are no such dependencies.
+     */
+    <T extends Dependency> Set<T> getDependencies(Class<T> type);
+
+    /**
+     * Gets the set of dependencies of type T for this configuration including thos contributed by superconfigurations.
+     *
+     * @param type the dependency type
+     * @param <T> the dependency type
+     * @return The set. Returns an empty set if there are no such dependencies.
+     */
+    <T extends Dependency> Set<T> getAllDependencies(Class<T> type);
+
+    /**
+     * Adds a dependency to this configuration
+     *
+     * @param dependency The dependency to be added.
+     */
+    void addDependency(Dependency dependency);
+
+    /**
+     * Returns the artifacts of this configuration excluding the artifacts of extended configurations.
+     */
+    Set<PublishArtifact> getArtifacts();
+
+    /**
+     * Returns the artifacts of this configuration including the artifacts of extended configurations.
+     */
+    Set<PublishArtifact> getAllArtifacts();
+
+    /**
+     * Returns the artifacts of this configuration as a {@link FileCollection}, including artifacts of extended
+     * configurations.
+     *
+     * @return the artifact files.
+     */
+    FileCollection getAllArtifactFiles();
+
+    /**
+     * Returns the exclude rules applied for resolving any dependency of this configuration.
+     *
+     * @see #exclude(java.util.Map)
+     */
+    Set<ExcludeRule> getExcludeRules();
+
+    /**
+     * Adds an exclude rule to exclude transitive dependencies for all dependencies of this configuration.
+     * You can also add exclude rules per-dependency. See {@link ModuleDependency#exclude(java.util.Map)}.
+     *
+     * @param excludeProperties the properties to define the exclude rule.
+     * @return this
+     */
+    Configuration exclude(Map<String, String> excludeProperties);
+
+    /**
+     * Returns all the configurations belonging to the same configuration container as this
+     * configuration (including this configuration).
+     */
+    Set<Configuration> getAll();
+
+    /**
+     * Adds an artifact to be published to this configuration.
+     *
+     * @param artifact The artifact.
+     * @return this
+     */
+    Configuration addArtifact(PublishArtifact artifact);
+
+    /**
+     * Removes an artifact from the artifacts to be published to this configuration.
+     *
+     * @param artifact The artifact.
+     * @return this
+     */
+    Configuration removeArtifact(PublishArtifact artifact);
+
+    /**
+     * Creates a copy of this configuration that only contains the dependencies directly in this configuration
+     * (without contributions from superconfigurations).  The new configuation will be in the
+     * UNRESOLVED state, but will retain all other attributes of this configuration except superconfigurations.
+     * {@link #getHierarchy()} for the copy will not include any superconfigurations.
+     * @return copy of this configuration
+     */
+    Configuration copy();
+
+    /**
+     * Creates a copy of this configuration that contains the dependencies directly in this configuration
+     * and those derived from superconfigurations.  The new configuation will be in the
+     * UNRESOLVED state, but will retain all other attributes of this configuration except superconfigurations.
+     * {@link #getHierarchy()} for the copy will not include any superconfigurations.
+     * @return copy of this configuration
+     */
+    Configuration copyRecursive();
+
+    /**
+     * Creates a copy of this configuration ignoring superconfigurations (see {@link #copy()} but filtering
+     * the dependencies using the dependencySpec.  The dependencySpec may be obtained from
+     * {@link org.gradle.api.artifacts.specs.DependencySpecs DependencySpecs.type()} like
+     * DependencySpecs.type(Type.EXTERNAL)
+     * @param dependencySpec filtering requirements
+     * @return copy of this configuration
+     */
+    Configuration copy(Spec<Dependency> dependencySpec);
+
+    /**
+     * Creates a copy of this configuration with dependencies from superconfigurations (see {@link #copyRecursive()})
+     *  but filtering the dependencies using the dependencySpec.  The dependencySpec may be obtained from
+     * {@link org.gradle.api.artifacts.specs.DependencySpecs DependencySpecs.type()} like
+     * DependencySpecs.type(Type.EXTERNAL)
+     * @param dependencySpec filtering requirements
+     * @return copy of this configuration
+     */
+    Configuration copyRecursive(Spec<Dependency> dependencySpec);
+
+    /**
+     * Takes a closure which gets coerced into a Spec. Behaves otherwise in the same way as {@link #copy(org.gradle.api.specs.Spec)}
+     *
+     * @param dependencySpec filtering requirements
+     * @return copy of this configuration
+     */
+    Configuration copy(Closure dependencySpec);
+
+    /**
+     * Takes a closure which gets coerced into a Spec. Behaves otherwise in the same way as {@link #copyRecursive(org.gradle.api.specs.Spec)}
+     *
+     * @param dependencySpec filtering requirements
+     * @return copy of this configuration
+     */
+    Configuration copyRecursive(Closure dependencySpec);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ConfigurationContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ConfigurationContainer.java
new file mode 100644
index 0000000..9cc20ec
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ConfigurationContainer.java
@@ -0,0 +1,92 @@
+/*
+ * 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.artifacts;
+
+import groovy.lang.Closure;
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.NamedDomainObjectCollection;
+
+/**
+ * <p>A {@code ConfigurationContainer} is responsible for managing a set of {@link Configuration} instances.</p>
+ *
+ * <p>You can obtain a {@code ConfigurationContainer} instance by calling {@link org.gradle.api.Project#getConfigurations()},
+ * or using the {@code configurations} property in your build script.</p>
+ *
+ * <p>The configurations in a container are accessable as read-only properties of the container, using the name of the
+ * configuration as the property name. For example:</p>
+ *
+ * <pre>
+ * configurations.add('myConfiguration')
+ * configurations.myConfiguration.transitive = false
+ * </pre>
+ *
+ * <p>A dynamic method is added for each configuration which takes a configuration closure. This is equivalent to
+ * calling {@link #getByName(String, groovy.lang.Closure)}. For example:</p>
+ *
+ * <pre>
+ * configurations.add('myConfiguration')
+ * configurations.myConfiguration {
+ *     transitive = false
+ * }
+ * </pre>
+ *
+ * @author Hans Dockter
+ */
+public interface ConfigurationContainer extends NamedDomainObjectContainer<Configuration>, NamedDomainObjectCollection<Configuration> {
+    /**
+     * {@inheritDoc}
+     */
+    Configuration getByName(String name) throws UnknownConfigurationException;
+
+    /**
+     * {@inheritDoc}
+     */
+    Configuration getAt(String name) throws UnknownConfigurationException;
+
+    /**
+     * {@inheritDoc}
+     */
+    Configuration getByName(String name, Closure configureClosure) throws UnknownConfigurationException;
+
+    /**
+     * Adds a configuration with the given name.
+     *
+     * @param name The name of the new configuration.
+     * @return The newly added configuration.
+     * @throws InvalidUserDataException when a configuration with the given name already exists in this container.
+     */
+    Configuration add(String name) throws InvalidUserDataException;
+
+    /**
+     * Adds a configuration with the given name. The given configuration closure is executed against the configuration
+     * before it is returned from this method.
+     *
+     * @param name The name of the new configuration.
+     * @param configureClosure The closure to use to configure the configuration.
+     * @return The newly added configuration.
+     * @throws InvalidUserDataException when a configuration with the given name already exists in this container.
+     */
+    Configuration add(String name, Closure configureClosure) throws InvalidUserDataException;
+
+    /**
+     * Creates a configuration, but does not add it to this container.
+     *
+     * @param dependencies The dependencies of the configuration.
+     * @return The configuration.
+     */
+    Configuration detachedConfiguration(Dependency... dependencies);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/Dependency.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/Dependency.java
new file mode 100644
index 0000000..bdcf798
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/Dependency.java
@@ -0,0 +1,64 @@
+/*
+ * 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.api.artifacts;
+
+/**
+ * A {@code Dependency} represents a dependency on the artifacts from a particular source. A source can be an Ivy
+ * module, a Maven pom, another Gradle project, a collection of Files, etc... A source can have zero or more artifacts.
+ *
+ * @author Hans Dockter
+ */
+public interface Dependency {
+    String DEFAULT_CONFIGURATION = "default";
+    String ARCHIVES_CONFIGURATION = "archives";
+    // todo Remove to ivy layer in 1.0
+    String CLASSIFIER = "m:classifier";
+
+    /**
+     * Returns the group of this dependency. The group is often required to find the artifacts of a dependency in a
+     * repository. For example, the group name corresponds to a directory name in a Maven like repository. Might return
+     * null.
+     */
+    String getGroup();
+
+    /**
+     * Returns the name of this dependency. The name is almost always required to find the artifacts of a dependency in
+     * a repository. Never returns null.
+     */
+    String getName();
+
+    /**
+     * Returns the version of this dependency. The version is often required to find the artifacts of a dependency in a
+     * repository. For example the version name corresponds to a directory name in a Maven like repository. Might return
+     * null.
+     */
+    String getVersion();
+
+    /**
+     * Returns whether two dependencies have identical values for their properties. A dependency is an entity with a
+     * key. Therefore dependencies might be equal and yet have different properties.
+     *
+     * @param dependency The dependency to compare this dependency with
+     */
+    boolean contentEquals(Dependency dependency);
+
+    /**
+     * Creates and returns a new dependency with the property values of this one.
+     *
+     * @return The copy. Never returns null.
+     */
+    Dependency copy();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/DependencyArtifact.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/DependencyArtifact.java
new file mode 100644
index 0000000..9e8c9c9
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/DependencyArtifact.java
@@ -0,0 +1,60 @@
+/*
+ * 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.artifacts;
+
+/**
+ * <p>An {@code Artifact} represents an artifact included in a {@link org.gradle.api.artifacts.Dependency}.</p>
+ * An artifact is an (immutable) value object.
+ *
+ * @author Hans Dockter
+ */
+public interface DependencyArtifact {
+    String DEFAULT_TYPE = "jar";
+
+    /**
+     * Returns the name of the dependency artifact.
+     */
+    String getName();
+
+    /**
+     * Returns the type of the dependency artifact. Often the type is the same as the extension,
+     * but sometimes this is not the case. For example for an ivy xml module descriptor, the type is
+     * <em>ivy</em> and the extension is <em>xml</em>.
+     *
+     * @see #getExtension() 
+     */
+    String getType();
+
+    /**
+     * Returns the extension of this dependency artifact. Often the extension is the same as the type,
+     * but sometimes this is not the case. For example for an ivy xml module descriptor, the type is
+     * <em>ivy</em> and the extension is <em>xml</em>.
+     *
+     * @see #getType() 
+     */
+    String getExtension();
+
+    /**
+     * Returns the classifier of this dependency artifact. 
+     */
+    String getClassifier();
+
+    /**
+     * Returns an URL under which this dependency artifact can be retrieved. If not
+     * specified the user repositories are used for retrieving. 
+     */
+    String getUrl();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ExcludeRule.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ExcludeRule.java
new file mode 100644
index 0000000..91c328d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ExcludeRule.java
@@ -0,0 +1,39 @@
+/*
+ * 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.artifacts;
+
+import java.util.Map;
+
+/**
+ * An {@code ExcludeRule} is used to describe transitive dependencies that should be excluded when resolving
+ * dependencies.
+ *
+ * @author Hans Dockter
+ */
+public interface ExcludeRule {
+    String GROUP_KEY = "group";
+    String MODULE_KEY = "module";
+
+    /**
+     * Returns the arguments of an exclude rule. The possible keys for the map are:
+     *
+     * <ul>
+     * <li><code>group</code> - The exact name of the organization or group that should be excluded.
+     * <li><code>module</code> - The exact name of the module that should be excluded
+     * </ul>
+     */
+    Map<String, String> getExcludeArgs();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ExcludeRuleContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ExcludeRuleContainer.java
new file mode 100644
index 0000000..58e0425
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ExcludeRuleContainer.java
@@ -0,0 +1,45 @@
+/*
+ * 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.artifacts;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * <p>A container for adding exclude rules for dependencies.</p>
+ *
+ * @author Hans Dockter
+ */
+public interface ExcludeRuleContainer {
+    /**
+     * Returns all the exclude rules added to this container. If no exclude rules has been added an empty list is
+     * returned.
+     */
+    Set<ExcludeRule> getRules();
+
+    /**
+     * Adds an exclude rule to this container. The ExcludeRule object gets created internally based on the map values
+     * passed to this method. The possible keys for the map are:
+     *
+     * <ul>
+     * <li><code>group</code> - The exact name of the organization or group that should be excluded.
+     * <li><code>module</code> - The exact name of the module that should be excluded
+     * </ul>
+     * 
+     * @param args A map describing the exclude pattern.
+     */
+    void add(Map<String, String> args);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ExternalDependency.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ExternalDependency.java
new file mode 100644
index 0000000..0e027e3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ExternalDependency.java
@@ -0,0 +1,41 @@
+/*
+ * 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.artifacts;
+
+/**
+ * <p>An {@code ExternalDependency} is a {@link Dependency} on a source outside the current project hierarchy.</p>
+ *
+ * @author Hans Dockter
+ */
+public interface ExternalDependency extends ModuleDependency {
+    /**
+     * Returns whether or not the version of this dependency should be enforced in the case of version conflicts.
+     */
+    boolean isForce();
+
+    /**
+     * Sets whether or not the version of this dependency should be enforced in the case of version conflicts.
+     *
+     * @param force Whether to force this version or not.
+     * @return this
+     */
+    ExternalDependency setForce(boolean force);
+
+    /**
+     * {@inheritDoc}
+     */
+    ExternalDependency copy();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ExternalModuleDependency.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ExternalModuleDependency.java
new file mode 100644
index 0000000..6969c1b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ExternalModuleDependency.java
@@ -0,0 +1,45 @@
+/*
+ * 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.artifacts;
+
+/**
+ * <p>A {@code ModuleDependency} is a {@link Dependency} on a module outside the current project hierarchy.</p>
+ *
+ * @author Hans Dockter
+ */
+public interface ExternalModuleDependency extends ExternalDependency {
+    /**
+     * Returns whether or nor Gradle should always check for a change in the remote repository.
+     *
+     * @see #setChanging(boolean)
+     */
+    boolean isChanging();
+
+    /**
+     * Sets whether or nor Gradle should always check for a change in the remote repository. If set to true, Gradle will
+     * check the remote repository even if a dependency with the same version is already in the local cache. Defaults to
+     * false.
+     *
+     * @param changing Whether or nor Gradle should always check for a change in the remote repository
+     * @return this
+     */
+    ExternalModuleDependency setChanging(boolean changing);
+
+    /**
+     * {@inheritDoc}
+     */
+    ExternalModuleDependency copy();    
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/FileCollectionDependency.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/FileCollectionDependency.java
new file mode 100644
index 0000000..8d63e87
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/FileCollectionDependency.java
@@ -0,0 +1,23 @@
+/*
+ * 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.artifacts;
+
+/**
+ * A {@code FileCollectionDependency} is a {@link Dependency} on a collection of local files which are not stored in a
+ * repository.
+ */
+public interface FileCollectionDependency extends SelfResolvingDependency {
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/IvyObjectBuilder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/IvyObjectBuilder.java
new file mode 100644
index 0000000..4b8a1c3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/IvyObjectBuilder.java
@@ -0,0 +1,43 @@
+/*
+ * 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.artifacts;
+
+import groovy.lang.Closure;
+import org.gradle.api.Transformer;
+
+/**
+ * <p>A {@code IvyObjectBuilder} builds Ivy domain objects of type {@code T}. You can influence the construction of the
+ * Ivy objects by adding transformers to this builder. A transformer can either be a closure, or a {@link Transformer}
+ * implementation. The transformers are called in the order added.</p>
+ */
+public interface IvyObjectBuilder<T> {
+    /**
+     * <p>Adds a transformer to this builder.</p>
+     *
+     * @param transformer The transformer to add.
+     */
+    void addIvyTransformer(Transformer<T> transformer);
+
+    /**
+     * <p>Adds a transformation closure to this builder. The closure is passed the object to transform as a parameter.
+     * The closure can return an object of type T, which will be used as the transformed value. The object to transform
+     * is also set as the delegate of the closure.</p>
+     *
+     * @param transformer The transformation closure to add.
+     */
+    void addIvyTransformer(Closure transformer);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/Module.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/Module.java
new file mode 100644
index 0000000..484ca60
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/Module.java
@@ -0,0 +1,34 @@
+/*
+ * 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.artifacts;
+
+/**
+ * <p>A {@code Module} represents the meta-information about a project which should be used when publishing the
+ * module.</p>
+ *
+ * @author Hans Dockter
+ */
+public interface Module {
+    public static final String DEFAULT_STATUS = "integration";
+
+    String getGroup();
+
+    String getName();
+
+    String getVersion();
+
+    String getStatus();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ModuleDependency.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ModuleDependency.java
new file mode 100644
index 0000000..d1cd977
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ModuleDependency.java
@@ -0,0 +1,105 @@
+/*
+ * 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.artifacts;
+
+import groovy.lang.Closure;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * <p>A {@code ModuleDependency} is a {@link org.gradle.api.artifacts.Dependency} on a module outside the current
+ * project.</p>
+ *
+ * <p>A module dependency is an entity. Its key consists of the fields {@code group, name, version, configuration}.</p>
+ */
+public interface ModuleDependency extends Dependency {
+    /**
+     * Adds an exclude rule to exclude transitive dependencies of this dependency. You can also add exclude rules
+     * per-configuration. See {@link Configuration#getExcludeRules()}.
+     *
+     * @param excludeProperties the properties to define the exclude rule.
+     * @return this
+     */
+    ModuleDependency exclude(Map<String, String> excludeProperties);
+
+    /**
+     * Returns the exclude rules for this dependency.
+     *
+     * @see #exclude(java.util.Map)
+     */
+    Set<ExcludeRule> getExcludeRules();
+
+    /**
+     * Returns the artifacts belonging to this dependency.
+     *
+     * @see #addArtifact(DependencyArtifact)
+     */
+    Set<DependencyArtifact> getArtifacts();
+
+    /**
+     * <p>Adds an artifact to this dependency.</p>
+     *
+     * <p>If no artifact is added to a dependency, an implicit default artifact is used. This default artifact has the
+     * same name as the module and its type and extension is <em>jar</em>. If at least one artifact is explicitly added,
+     * the implicit default artifact won't be used any longer.</p>
+     *
+     * @return this
+     */
+    ModuleDependency addArtifact(DependencyArtifact artifact);
+
+    /**
+     * <p>Adds an artifact to this dependency. The given closure is passed a {@link
+     * org.gradle.api.artifacts.DependencyArtifact} instance, which it can configure.</p>
+     *
+     * <p>If no artifact is added to a dependency, an implicit default artifact is used. This default artifact has the
+     * same name as the module and its type and extension is <em>jar</em>. If at least one artifact is explicitly added,
+     * the implicit default artifact won't be used any longer.</p>
+     *
+     * @return this
+     */
+    DependencyArtifact artifact(Closure configureClosure);
+
+    /**
+     * Returns whether this dependency should be resolved including or excluding its transitive dependencies.
+     *
+     * @see #setTransitive(boolean)
+     */
+    boolean isTransitive();
+
+    /**
+     * Sets whether this dependency should be resolved including or excluding its transitive dependencies. The artifacts
+     * belonging to this dependency might themselve have dependencies on other artifacts. The latter are called
+     * transitive dependencies.
+     *
+     * @param transitive Whether transitive dependencies should be resolved.
+     * @return this
+     */
+    ModuleDependency setTransitive(boolean transitive);
+
+    /**
+     * Returns the configuration of this dependency module (not the configurations this dependency belongs too). Never
+     * returns null. The default value for the configuration is {@link #DEFAULT_CONFIGURATION}. A dependency source
+     * might have multiple configurations. Every configuration represents a different set of artifacts and dependencies
+     * for this dependency module.
+     */
+    String getConfiguration();
+
+    /**
+     * {@inheritDoc}
+     */
+    ModuleDependency copy();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ProjectDependenciesBuildInstruction.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ProjectDependenciesBuildInstruction.java
new file mode 100644
index 0000000..bbb4b06
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ProjectDependenciesBuildInstruction.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.api.artifacts;
+
+import java.util.List;
+import java.util.Collections;
+
+/**
+ * @author Hans Dockter
+ */
+public class ProjectDependenciesBuildInstruction {
+    private List<String> taskNames;
+
+    public ProjectDependenciesBuildInstruction(List<String> taskNames) {
+        this.taskNames = taskNames;
+    }
+
+    public List<String> getTaskNames() {
+        if (taskNames == null) {
+            return Collections.emptyList();
+        }
+        return taskNames;
+    }
+
+    public boolean isRebuild() {
+        return taskNames != null;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        ProjectDependenciesBuildInstruction that = (ProjectDependenciesBuildInstruction) o;
+
+        if (taskNames != null ? !taskNames.equals(that.taskNames) : that.taskNames != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return taskNames != null ? taskNames.hashCode() : 0;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ProjectDependency.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ProjectDependency.java
new file mode 100644
index 0000000..aff0211
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ProjectDependency.java
@@ -0,0 +1,43 @@
+/*
+ * 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.artifacts;
+
+import org.gradle.api.Project;
+
+/**
+ * <p>A {@code ProjectDependency} is a {@link Dependency} on another project in the current project hierarchy.</p>
+ *
+ * @author Hans Dockter
+ */
+public interface ProjectDependency extends ModuleDependency, SelfResolvingDependency {
+    /**
+     * Returns the project associated with this project dependency
+     */
+    Project getDependencyProject();
+
+    /**
+     * Returns the configuration associated with this project dependency. The configuration belongs to the project
+     * associated with this project dependency.
+     *
+     * @see #getDependencyProject()
+     */
+    Configuration getProjectConfiguration();
+
+    /**
+     * {@inheritDoc}
+     */
+    ProjectDependency copy();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/PublishArtifact.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/PublishArtifact.java
new file mode 100644
index 0000000..01ddef5
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/PublishArtifact.java
@@ -0,0 +1,70 @@
+/*
+ * 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.api.artifacts;
+
+import org.gradle.api.Buildable;
+
+import java.io.File;
+import java.util.Date;
+
+/**
+ * <p>A {@code PublishArtifact} is an artifact produced by a project.</p>
+ *
+ * @author Hans Dockter
+ */
+public interface PublishArtifact extends Buildable {
+    /**
+     * Returns the name of the artifact.
+     */
+    String getName();
+
+    /**
+     * Returns the extension of this published artifact. Often the extendsion is the same as the type,
+     * but sometimes this is not the case. For example for an ivy xml module decsriptor, the type is
+     * <em>ivy</em> and the extension is <em>xml</em>.
+     *
+     * @see #getType()
+     */
+    String getExtension();
+
+    /**
+     * Returns the type of the published artifact. Often the type is the same as the extension,
+     * but sometimes this is not the case. For example for an ivy xml module decsriptor, the type is
+     * <em>ivy</em> and the extension is <em>xml</em>.
+     *
+     * @see #getExtension()
+     */
+    String getType();
+
+    /**
+     * Returns the classifier of this published artifact. 
+     */
+    String getClassifier();
+
+    /**
+     * Returns the file of this artifact.
+     */
+    File getFile();
+
+    /**
+     * Returns the date that should be used when publishing this artifact. This is used
+     * in the module descriptor accompanying this artifact (the ivy.xml). If the date is
+     * not specified, the current date is used. If this artifact
+     * is published without an module descriptor, this property has no relevance. 
+     */
+    Date getDate();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/PublishInstruction.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/PublishInstruction.java
new file mode 100644
index 0000000..fd4d0bd
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/PublishInstruction.java
@@ -0,0 +1,99 @@
+/*
+ * 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.artifacts;
+
+import org.gradle.api.InvalidUserDataException;
+
+import java.io.File;
+
+/**
+ * Uploading details for artifacts produced by a project.
+ *
+ * @author Hans Dockter
+ */
+public class PublishInstruction {
+    private boolean uploadDescriptor;
+    private File descriptorDestination;
+
+    /**
+     * Creates a publish instruction for not uploading an module descriptor file.
+     */
+    public PublishInstruction() {
+        uploadDescriptor = false;
+        descriptorDestination = null;
+    }
+
+    /**
+     * Creates a publish instruction. If <code>uploadDescriptor</code> is set to true and the target destination
+     * is an ivy repository, the ivy file destination needs to be specified. 
+     *
+     * @param uploadDescriptor
+     * @param descriptorDestination
+     */
+    public PublishInstruction(boolean uploadDescriptor, File descriptorDestination) {
+        if (uploadDescriptor && descriptorDestination == null) {
+            throw new InvalidUserDataException("You must specify a module descriptor destination, if a module descriptor should be uploaded.");
+        }
+        if (!uploadDescriptor && descriptorDestination != null) {
+            throw new InvalidUserDataException("You must not specify a module descriptor destination, if a module descriptor should not be uploaded.");
+        }
+        this.uploadDescriptor = uploadDescriptor;
+        this.descriptorDestination = descriptorDestination;
+    }
+
+    /**
+     * Returns whether an xml module descriptor file should be uploaded or not. 
+     */
+    public boolean isUploadDescriptor() {
+        return uploadDescriptor;
+    }
+
+    /**
+     * Returns the file destination where to create the ivy.xml file. Can be null.
+     */
+    public File getDescriptorDestination() {
+        return descriptorDestination;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        PublishInstruction that = (PublishInstruction) o;
+
+        if (uploadDescriptor != that.uploadDescriptor) {
+            return false;
+        }
+        if (descriptorDestination != null ? !descriptorDestination.equals(that.descriptorDestination)
+                : that.descriptorDestination != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = uploadDescriptor ? 1 : 0;
+        result = 31 * result + (descriptorDestination != null ? descriptorDestination.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ResolveException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ResolveException.java
new file mode 100644
index 0000000..9b1c0dc
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ResolveException.java
@@ -0,0 +1,49 @@
+/*
+ * 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.artifacts;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.Contextual;
+import org.gradle.util.GUtil;
+
+/**
+ * <p>A <code>ResolveException</code> is thrown when a dependency configuration cannot be resolved for some reason.</p>
+ *
+ * @author Hans Dockter
+ */
+ at Contextual
+public class ResolveException extends GradleException {
+    // Required for @Contextual
+    public ResolveException() {
+    }
+
+    public ResolveException(Configuration configuration, String message) {
+        super(buildMessage(configuration, message));
+    }
+
+    public ResolveException(Configuration configuration, Throwable cause) {
+        super(buildMessage(configuration, null), cause);
+    }
+
+    private static String buildMessage(Configuration configuration, String message) {
+        if (GUtil.isTrue(message)) {
+            return String.format("Could not resolve all dependencies for %s:%n%s", configuration, message);
+        } else {
+            return String.format("Could not resolve all dependencies for %s.", configuration);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ResolvedArtifact.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ResolvedArtifact.java
new file mode 100644
index 0000000..150c753
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ResolvedArtifact.java
@@ -0,0 +1,33 @@
+/*
+ * 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.artifacts;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ResolvedArtifact {
+    File getFile();
+
+    ResolvedDependency getResolvedDependency();
+
+    String getName();
+
+    String getType();
+
+    String getExtension();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ResolvedConfiguration.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ResolvedConfiguration.java
new file mode 100644
index 0000000..12fa35b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ResolvedConfiguration.java
@@ -0,0 +1,68 @@
+/*
+ * 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.artifacts;
+
+import org.gradle.api.specs.Spec;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * A {@code ResolvedConfiguration} represents the result of resolving a {@link Configuration}, and provides access
+ * to both the artifacts and the meta-data of the result.
+ */
+public interface ResolvedConfiguration {
+    /**
+     * Returns whether all dependencies were successfully retrieved or not.
+     */
+    boolean hasError();
+
+    /**
+     * A resolve of a configuration that is not successful does not automatically throws an exception.
+     * Such a exception is only thrown if the result of a resolve is accessed. You can force the throwing
+     * of such an exception by calling this method.  
+     *
+     * @throws ResolveException when the resolve was not successful.
+     */
+    void rethrowFailure() throws ResolveException;
+
+    /**
+     * Returns the files for the specified subset of configuration dependencies.
+     * 
+     * @param dependencySpec The filter for the configuration dependencies.
+     * @return The artifact files of the specified dependencies.
+     * @throws ResolveException when the resolve was not successful.
+     */
+    Set<File> getFiles(Spec<Dependency> dependencySpec) throws ResolveException;
+
+    /**
+     * Returns the {@link ResolvedDependency} instances for each direct dependency of the configuration. Via those
+     * you have access to all {@link ResolvedDependency} instances, including the transitive dependencies of the
+     * configuration.
+     *
+     * @return A {@code ResolvedDependency} instance for each direct dependency.
+     * @throws ResolveException when the resolve was not successful.
+     */
+    Set<ResolvedDependency> getFirstLevelModuleDependencies() throws ResolveException;
+
+    /**
+     * Returns the set of artifact meta-data for this configuration.
+     *
+     * @return The set of artifacts.
+     * @throws ResolveException when the resolve was not successful.
+     */
+    Set<ResolvedArtifact> getResolvedArtifacts() throws ResolveException;
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ResolvedDependency.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ResolvedDependency.java
new file mode 100644
index 0000000..ff40422
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ResolvedDependency.java
@@ -0,0 +1,95 @@
+/*
+ * 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.artifacts;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ResolvedDependency {
+    /**
+     * Returns the name of the resolved dependency
+     */
+    String getName();
+
+    /**
+     * Returns the module group of the resolved dependency
+     */
+    String getModuleGroup();
+
+    /**
+     * Returns the module name of the resolved dependency.
+     */
+    String getModuleName();
+
+    /**
+     * Returns the module version of the resolved dependency.
+     */
+    String getModuleVersion();
+
+    /**
+     * Returns the configuration under which this instance was resolved.
+     */
+    String getConfiguration();
+
+    /**
+     * Returns the transitive ResolvedDependency instances of this resolved dependency. Returns never null.
+     */
+    Set<ResolvedDependency> getChildren();
+
+    /**
+     * Returns the ResolvedDependency instances that have this instance as a transitive dependency. Returns never null.
+     */
+    Set<ResolvedDependency> getParents();
+
+    /**
+     * Returns the module artifacts belonging to this ResolvedDependency. A module artifact is an artifact that belongs
+     * to a ResolvedDependency independent of a particular parent. Returns never null. 
+     */
+    Set<ResolvedArtifact> getModuleArtifacts();
+
+    /**
+     * Returns the module artifacts belonging to this ResolvedDependency and recursively to its children. Returns never null.
+     *
+     * @see #getModuleArtifacts()
+     */
+    Set<ResolvedArtifact> getAllModuleArtifacts();
+
+    /**
+     * Returns the artifacts belonging to this ResolvedDependency which it only has for a particular parent. Returns never null.
+     *
+     * @param parent A parent of the ResolvedDependency. Must not be null.
+     * @throws org.gradle.api.InvalidUserDataException If the parent is unknown or null
+     */
+    Set<ResolvedArtifact> getParentArtifacts(ResolvedDependency parent);
+
+    /**
+     * Returns a union of the module and parent artifacts of this dependency. Never returns null.
+     *
+     * @param parent A parent of the ResolvedDependency. Must not be null.
+     * @throws org.gradle.api.InvalidUserDataException If the parent is unknown or null
+     */
+    Set<ResolvedArtifact> getArtifacts(ResolvedDependency parent);
+
+    /**
+     * Returns a union of the module and parent artifacts of this dependency and its children. Never returns null.
+     *
+     * @param parent A parent of the ResolvedDependency. Must not be null.
+     * @throws org.gradle.api.InvalidUserDataException If the parent is unknown or null
+     */
+    Set<ResolvedArtifact> getAllArtifacts(ResolvedDependency parent);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ResolverContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ResolverContainer.java
new file mode 100644
index 0000000..89d8ae8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/ResolverContainer.java
@@ -0,0 +1,198 @@
+/*
+ * 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.artifacts;
+
+import groovy.lang.Closure;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.NamedDomainObjectCollection;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * <p>A {@code ResolverContainer} is responsible for managing a set of {@link org.apache.ivy.plugins.resolver.DependencyResolver}
+ * instances. Resolvers are arranged in a sequence.</p>
+ *
+ * <p>You can obtain a {@code ResolverContainer} instance by calling {@link org.gradle.api.Project#getRepositories()} or
+ * using the {@code repositories} property in your build script.</p>
+ *
+ * <p>The resolvers in a container are accessable as read-only properties of the container, using the name of the
+ * resolver as the property name. For example:</p>
+ *
+ * <pre>
+ * resolvers.add('myResolver')
+ * resolvers.myResolver.addArtifactPattern(somePattern)
+ * </pre>
+ *
+ * <p>A dynamic method is added for each resolver which takes a configuration closure. This is equivalent to calling
+ * {@link #getByName(String, groovy.lang.Closure)}. For example:</p>
+ *
+ * <pre>
+ * resolvers.add('myResolver')
+ * resolvers.myResolver {
+ *     addArtifactPattern(somePattern)
+ * }
+ * </pre>
+ *
+ * @author Hans Dockter
+ */
+public interface ResolverContainer extends NamedDomainObjectContainer<DependencyResolver>, NamedDomainObjectCollection<DependencyResolver> {
+    String DEFAULT_MAVEN_CENTRAL_REPO_NAME = "MavenRepo";
+    String MAVEN_CENTRAL_URL = "http://repo1.maven.org/maven2/";
+    String MAVEN_REPO_PATTERN = "[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier]).[ext]";
+    String FLAT_DIR_RESOLVER_PATTERN = "[artifact](-[revision])(-[classifier]).[ext]";
+    String DEFAULT_CACHE_ARTIFACT_PATTERN
+            = "[organisation]/[module](/[branch])/[type]s/[artifact]-[revision](-[classifier])(.[ext])";
+    String DEFAULT_CACHE_IVY_PATTERN = "[organisation]/[module](/[branch])/ivy-[revision].xml";
+    String DEFAULT_CACHE_NAME = "default-gradle-cache";
+    String INTERNAL_REPOSITORY_NAME = "internal-repository";
+    String DEFAULT_CACHE_DIR_NAME = "cache";
+    String RESOLVER_NAME = "name";
+    String RESOLVER_URL = "url";
+
+    /**
+     * Adds a resolver to this container, at the end of the resolver sequence. The given {@code userDescription} can be
+     * one of:
+     *
+     * <ul>
+     *
+     * <li>A String. This is treated as a URL, and used to create a maven resolver.</li>
+     *
+     * <li>A map. This is used to create a maven resolver. The map must contain an {@value #RESOLVER_NAME} entry and a
+     * {@value #RESOLVER_URL} entry.</li>
+     *
+     * <li>A {@link org.apache.ivy.plugins.resolver.DependencyResolver}.</li>
+     *
+     * </ul>
+     *
+     * @param userDescription The resolver definition.
+     * @return The added resolver.
+     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
+     */
+    DependencyResolver add(Object userDescription) throws InvalidUserDataException;
+
+    /**
+     * Adds a resolver to this container, at the end of the resolver sequence. The resolver is configured using the
+     * given configure closure.
+     *
+     * @param userDescription The resolver definition. See {@link #add(Object)} for details of this parameter.
+     * @param configureClosure The closure to use to configure the resolver.
+     * @return The added resolver.
+     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
+     */
+    DependencyResolver add(Object userDescription, Closure configureClosure) throws InvalidUserDataException;
+
+    /**
+     * Adds a resolver to this container, before the given resolver.
+     *
+     * @param userDescription The resolver definition. See {@link #add(Object)} for details of this parameter.
+     * @param nextResolver The existing resolver to add the new resolver before.
+     * @return The added resolver.
+     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
+     * @throws InvalidUserDataException when the given next resolver does not exist in this container.
+     */
+    DependencyResolver addBefore(Object userDescription, String nextResolver) throws InvalidUserDataException;
+
+    /**
+     * Adds a resolver to this container, before the given resolver. The resolver is configured using the given
+     * configure closure.
+     *
+     * @param userDescription The resolver definition. See {@link #add(Object)} for details of this parameter.
+     * @param nextResolver The existing resolver to add the new resolver before.
+     * @param configureClosure The closure to use to configure the resolver.
+     * @return The added resolver.
+     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
+     * @throws InvalidUserDataException when the given next resolver does not exist in this container.
+     */
+    DependencyResolver addBefore(Object userDescription, String nextResolver, Closure configureClosure)
+            throws InvalidUserDataException;
+
+    /**
+     * Adds a resolver to this container, after the given resolver.
+     *
+     * @param userDescription The resolver definition. See {@link #add(Object)} for details of this parameter.
+     * @param previousResolver The existing resolver to add the new resolver after.
+     * @return The added resolver.
+     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
+     * @throws InvalidUserDataException when the given previous resolver does not exist in this container.
+     */
+    DependencyResolver addAfter(Object userDescription, String previousResolver) throws InvalidUserDataException;
+
+    /**
+     * Adds a resolver to this container, after the given resolver. The resolver is configured using the given configure
+     * closure.
+     *
+     * @param userDescription The resolver definition. See {@link #add(Object)} for details of this parameter.
+     * @param previousResolver The existing resolver to add the new resolver after.
+     * @param configureClosure The closure to use to configure the resolver.
+     * @return The added resolver.
+     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
+     * @throws InvalidUserDataException when the given previous resolver does not exist in this container.
+     */
+    DependencyResolver addAfter(Object userDescription, String previousResolver, Closure configureClosure)
+            throws InvalidUserDataException;
+
+    /**
+     * Adds a resolver to this container, at the start of the resolver sequence.
+     *
+     * @param userDescription The resolver definition. See {@link #add(Object)} for details of this parameter.
+     * @return The added resolver.
+     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
+     */
+    DependencyResolver addFirst(Object userDescription) throws InvalidUserDataException;
+
+    /**
+     * Adds a resolver to this container, at the start of the resolver sequence. The resolver is configured using the
+     * given configure closure.
+     *
+     * @param userDescription The resolver definition. See {@link #add(Object)} for details of this parameter.
+     * @param configureClosure The closure to use to configure the resolver.
+     * @return The added resolver.
+     * @throws InvalidUserDataException when a resolver with the given name already exists in this container.
+     */
+    DependencyResolver addFirst(Object userDescription, Closure configureClosure) throws InvalidUserDataException;
+
+    /**
+     * {@inheritDoc}
+     */
+    DependencyResolver getByName(String name) throws UnknownRepositoryException;
+
+    /**
+     * {@inheritDoc}
+     */
+    DependencyResolver getByName(String name, Closure configureClosure) throws UnknownRepositoryException;
+
+    /**
+     * {@inheritDoc}
+     */
+    DependencyResolver getAt(String name) throws UnknownRepositoryException;
+
+    /**
+     * Returns the resolvers in this container, in sequence.
+     *
+     * @return The resolvers in sequence. Returns an empty list if this container is empty.
+     */
+    List<DependencyResolver> getResolvers();
+
+    void setMavenPomDir(File mavenPomDir);
+
+    Conf2ScopeMappingContainer getMavenScopeMappings();
+
+    File getMavenPomDir();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/SelfResolvingDependency.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/SelfResolvingDependency.java
new file mode 100644
index 0000000..c16cc01
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/SelfResolvingDependency.java
@@ -0,0 +1,48 @@
+/*
+ * 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.artifacts;
+
+import org.gradle.api.Buildable;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * A {@code SelfResolvingDependency} is a {@link Dependency} which is able to resolve itself, independent of a
+ * repository.
+ */
+public interface SelfResolvingDependency extends Dependency, Buildable {
+    /**
+     * Resolves this dependency. A {@link org.gradle.api.artifacts.ProjectDependency} is resolved with transitive equals true
+     * by this method. 
+     *
+     * @return The files which make up this dependency.
+     * @see #resolve(boolean) 
+     */
+    Set<File> resolve();
+
+    /**
+     * Resolves this dependency by specifying the transitive mode. This mode has only an effect if the self resolved dependency
+     * is of type {@link org.gradle.api.artifacts.ProjectDependency}. In this case, if transitive is <code>false</code>,
+     * only the self resolving dependencies of the project configuration which are no project dependencies are resolved. If transitive
+     * is set to true, other project dependencies belonging to the configuration of the resolved project dependency are
+     * resolved recursively. 
+     *
+     * @param transitive Whether to resolve transitively. Has only an effect on a {@link org.gradle.api.artifacts.ProjectDependency} 
+     * @return The files which make up this dependency.
+     */
+    Set<File> resolve(boolean transitive);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/UnknownConfigurationException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/UnknownConfigurationException.java
new file mode 100644
index 0000000..d757e98
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/UnknownConfigurationException.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 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.artifacts;
+
+import org.gradle.api.UnknownDomainObjectException;
+
+/**
+ * <p>An {@code UnknownConfigurationException} is thrown when a configuration referenced by name cannot be found.</p>
+ */
+public class UnknownConfigurationException extends UnknownDomainObjectException {
+    public UnknownConfigurationException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/UnknownRepositoryException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/UnknownRepositoryException.java
new file mode 100644
index 0000000..7908ced
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/UnknownRepositoryException.java
@@ -0,0 +1,29 @@
+/*
+ * 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.artifacts;
+
+import org.gradle.api.UnknownDomainObjectException;
+
+/**
+ * An {@code UnknownRepositoryException} is thrown when a repository referenced by name cannot be found.
+ *
+ * @author Hans Dockter
+ */
+public class UnknownRepositoryException extends UnknownDomainObjectException {
+    public UnknownRepositoryException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/dsl/ArtifactHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/dsl/ArtifactHandler.java
new file mode 100644
index 0000000..d743451
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/dsl/ArtifactHandler.java
@@ -0,0 +1,33 @@
+/*
+ * 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.artifacts.dsl;
+
+/**
+ * This class is for creating publish artifacts and adding them to configurations. Creating publish artifacts
+ * does not mean to create an archive. What is created is a domain object which represents a file to be published
+ * and information on how it should be published (e.g. the name). The publish artifact, that should be created,
+ * can only be described with an archive at the moment. We will add additional notations in the future.
+ *
+ * <p>To create an publish artifact and assign it to a configuration you can use the following syntax:</p>
+ *
+ * <code><ArtifactHandler>.<configurationName> <archive1>, <archive2>, ...</code>
+ *
+ *  <p>The information for publishing the artifact is extracted from the archive (e.g. name, extension, ...).</p>
+ *
+ * @author Hans Dockter
+ */
+public interface ArtifactHandler {
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/dsl/DependencyHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/dsl/DependencyHandler.java
new file mode 100644
index 0000000..89a76db
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/dsl/DependencyHandler.java
@@ -0,0 +1,147 @@
+/*
+ * 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.artifacts.dsl;
+
+import groovy.lang.Closure;
+import org.gradle.api.artifacts.Dependency;
+
+import java.util.Map;
+
+/**
+ * <p>A {@code DependencyHandler} is used to declare artifact dependencies. Artifact dependencies are grouped into
+ * configurations (see {@link org.gradle.api.artifacts.Configuration}), and a given dependency declarations is always
+ * attached to a single configuration.</p>
+ *
+ * <p>To declare a specific dependency for a configuration you can use the following syntax:</p>
+ *
+ * <pre>
+ * dependencies {
+ *     <i>configurationName</i> <i>dependencyNotation1</i>, <i>dependencyNotation2</i>, ...
+ * }
+ * </pre>
+ *
+ * <p>or, to configure a dependency when it is declared, you can additionally pass a configuration closure:</p>
+ *
+ * <pre>
+ * dependencies {
+ *     <i>configurationName</i> <i>dependencyNotation</i> {
+ *         <i>configStatement1</i>
+ *         <i>configStatement2</i>
+ *     }
+ * }
+ * </pre>
+ *
+ * <p>There are several supported dependency notations. These are described below. For each dependency declared this
+ * way, a {@link Dependency} object is created. You can use this object to query or further configure the
+ * dependency.</p>
+ *
+ * <h2>External Modules</h2>
+ *
+ * There are 3 notations supported for declaring a dependency on an external module. One is a string notation:</p>
+ *
+ * <code><i>configurationName</i> "<i>group</i>:<i>name</i>:<i>version</i>:<i>classifier</i>"</code>
+ *
+ * <p>The other is a map notation:</p>
+ *
+ * <code><i>configurationName</i> group: <i>group</i>:, name: <i>name</i>, version: <i>version</i>, classifier:
+ * <i>classifier</i></code>
+ *
+ * <p>In both notations, all properties, except name, are optional. External dependencies are represented using a {@link
+ * org.gradle.api.artifacts.ExternalModuleDependency}.</p>
+ *
+ * <h2>Client Modules</h2>
+ *
+ * <p>To add a client module to a configuration you can use the notation:</p>
+ *
+ * <pre>
+ * <i>configurationName</i> module(<i>moduleNotation</i>) {
+ *     <i>module dependencies</i>
+ * }
+ * </pre>
+ *
+ * The module notation is the same as the dependency notations described above, except that the classifier property is
+ * not available. Client modules are represented using a {@link org.gradle.api.artifacts.ClientModule}.
+ *
+ * <h2>Projects</h2>
+ *
+ * <p>To add a project dependency, you use the following notation</p>
+ *
+ * <code><i>configurationName</i> project(':someProject')</code>
+ *
+ * <p>Project dependencies are represented using a {@link org.gradle.api.artifacts.ProjectDependency}.</p>
+ *
+ * <h2>Files</h2>
+ *
+ * <p>You can also add a dependency using a {@link org.gradle.api.file.FileCollection}:</p>
+ * <code><i>configurationName</i> files('a file')</code>
+ *
+ * <p>File dependencies are represented using a {@link org.gradle.api.artifacts.SelfResolvingDependency}.</p>
+ *
+ * @author Hans Dockter
+ */
+public interface DependencyHandler {
+    /**
+     * Adds a dependency to the given configuration.
+     *
+     * @param configurationName The name of the configuration.
+     * @param dependencyNotation The dependency notation, in one of the notations described above.
+     * @return The dependency.
+     */
+    Dependency add(String configurationName, Object dependencyNotation);
+
+    /**
+     * Adds a dependency to the given configuration, and configures the dependency using the given closure/
+     *
+     * @param configurationName The name of the configuration.
+     * @param dependencyNotation The dependency notation, in one of the notations described above.
+     * @param configureClosure The closure to use to configure the dependency.
+     * @return The dependency.
+     */
+    Dependency add(String configurationName, Object dependencyNotation, Closure configureClosure);
+
+    /**
+     * Creates a dependency on a client module.
+     *
+     * @param notation The module notation, in one of the notations described above.
+     * @return The dependency.
+     */
+    Dependency module(Object notation);
+
+    /**
+     * Creates a dependency on a client module. The dependency is configured using the given closure before it is
+     * returned.
+     *
+     * @param notation The module notation, in one of the notations described above.
+     * @param configureClosure The closure to use to configure the dependency.
+     * @return The dependency.
+     */
+    Dependency module(Object notation, Closure configureClosure);
+
+    /**
+     * Creates a dependency on a project.
+     *
+     * @param notation The project notation, in one of the notations described above.
+     * @return The dependency.
+     */
+    Dependency project(Map notation);
+    
+    /**
+     * Creates a dependency on the API of the current version of Gradle.
+     *
+     * @return The dependency.
+     */
+    Dependency gradleApi();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/dsl/RepositoryHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/dsl/RepositoryHandler.java
new file mode 100644
index 0000000..088aac7
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/dsl/RepositoryHandler.java
@@ -0,0 +1,238 @@
+/*
+ * 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.artifacts.dsl;
+
+import groovy.lang.Closure;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.FileSystemResolver;
+import org.gradle.api.artifacts.ResolverContainer;
+import org.gradle.api.artifacts.maven.GroovyMavenDeployer;
+import org.gradle.api.artifacts.maven.MavenResolver;
+import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
+
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public interface RepositoryHandler extends ResolverContainer, ResolverProvider {
+    final String DEFAULT_MAVEN_DEPLOYER_NAME = "mavenDeployer";
+    final String DEFAULT_MAVEN_INSTALLER_NAME = "mavenInstaller";
+
+    /**
+     * Adds a resolver that looks into a number of directories for artifacts. The artifacts are expected to be located in the
+     * root of the specified directories. The resolver ignores any group/organization information specified in the
+     * dependency section of your build script. If you only use this kind of resolver you might specify your
+     * dependencies like <code>":junit:4.4"</code> instead of <code>"junit:junit:4.4"</code>.
+     *
+     * The following parameter are accepted as keys for the map:
+     *
+     * <table summary="Shows property keys and associated values">
+     * <tr><th>Key</th>
+     *     <th>Description of Associated Value</th></tr>
+     * <tr><td><code>name</code></td>
+     *     <td><em>(optional)</em> The name of the repository.
+     * The default is a Hash value of the rootdir paths. The name is used in the console output,
+     * to point to information related to a particular repository. A name must be unique amongst a repository group.</td></tr>
+     * <tr><td><code>dirs</code></td>
+     *     <td>Specifies a list of rootDirs where to look for dependencies.</td></tr>
+     * </table>
+     *
+     * <p>Examples:
+     * <pre>
+     * repositories {
+     *     flatDir name: 'libs', dirs: "$projectDir/libs"
+     *     flatDir dirs: ["$projectDir/libs1", "$projectDir/libs2"]
+     * }
+     * </pre>
+     * </p>
+     *
+     * @param args
+     * @return the added resolver
+     * @throws org.gradle.api.InvalidUserDataException In the case neither rootDir nor rootDirs is specified of if both
+     * are specified.
+     */
+    FileSystemResolver flatDir(Map args);
+
+    /**
+     * Adds a repository which looks in the Maven central repository for dependencies. The URL used to access this repository is
+     * always {@link #MAVEN_CENTRAL_URL}. The behavior of this resolver
+     * is otherwise the same as the ones added by {@link #mavenRepo(java.util.Map)}.
+     *
+     * The following parameter are accepted as keys for the map:
+     *
+     * <table summary="Shows property keys and associated values">
+     * <tr><th>Key</th>
+     *     <th>Description of Associated Value</th></tr>
+     * <tr><td><code>name</code></td>
+     *     <td><em>(optional)</em> The name of the repository. The default is {@link #DEFAULT_MAVEN_CENTRAL_REPO_NAME}
+     * is used as the name. A name must be unique amongst a repository group.
+     * </td></tr>
+     * <tr><td><code>urls</code></td>
+     *     <td>A single jar repository or a collection of jar repositories. Sometimes the artifact
+     * lives in a different repository than the pom. In such a case you can specify further locations to look for an artifact.
+     * But be aware that the pom is only looked up in the root repository</td></tr>
+     * </table>
+     *
+     * <p>Examples:
+     * <pre>
+     * repositories {
+     *     mavenCentral urls: ["http://www.mycompany.com/repository1", "http://www.mycompany.com/repository2"]
+     *     mavenCentral name: "nonDefaultName", urls: ["http://www.mycompany.com/repository"]
+     * }
+     * </pre>
+     * </p>
+     *
+     * @param args A list of urls of repositories to look for artifacts only.
+     * @return the added resolver
+     * @see #mavenRepo(java.util.Map)
+     */
+    DependencyResolver mavenCentral(Map args);
+
+    /**
+     * Adds a repository which looks in the Maven central repository for dependencies. The URL used to access this repository is
+     * {@link #MAVEN_CENTRAL_URL}. The name of the repository is {@link #DEFAULT_MAVEN_CENTRAL_REPO_NAME}.
+     *
+     * <p>Examples:
+     * <pre>
+     * repositories {
+     *     mavenCentral()
+     * }
+     * </pre>
+     * </p>
+     *
+     * @return the added resolver
+     * @see #mavenRepo(java.util.Map)
+     * @see #mavenCentral(java.util.Map)
+     */
+    DependencyResolver mavenCentral();
+
+    /**
+     * Adds a repository which is Maven compatible. The compatibility is in regard to layout, snapshothandling and
+     * dealing with the pom.xml. This repository can't be used for publishing in a Maven compatible way. For publishing
+     * to a Maven repository, have a look at {@link #mavenDeployer(java.util.Map)} or {@link #mavenInstaller(java.util.Map)}.
+     *
+     * By default the repository accepts to resolve artifacts without a pom. The repository always looks first for the pom
+     * in the root repository. It then looks for the artifact in the root repository. Sometimes the artifact
+     * lives in a different repository than the pom. In such a case you can specify further locations to look for an artifact.
+     * But be aware that the pom is only looked up in the root repository.
+     *
+     * The following parameter are accepted as keys for the map:
+     *
+     * <table summary="Shows property keys and associated values">
+     * <tr><th>Key</th>
+     *     <th>Description of Associated Value</th></tr>
+     * <tr><td><code>name</code></td>
+     *     <td><em>(optional)</em> The name of the repository. The default is the URL of the root repo.
+     * The name is used in the console output,
+     * to point to information related to a particular repository. A name must be unique amongst a repository group.
+     * </td></tr>
+     * <tr><td><code>urls</code></td>
+     *     <td>A single repository url or a list of urls. The first url is the the url of the root repo.
+     * Gradle always looks first for the pom in the root repository. After this it looks for the artifact in the root repository.
+     * If the artifact can't be found there, it looks for it in the other repositories.</td></tr>
+     * </table>
+     *
+     * <p>Examples:
+     * <pre>
+     * repositories {
+     *     mavenRepo urls: ["http://www.mycompany.com/repository1", "http://www.mycompany.com/repository2"]
+     *     mavenRepo name: "nonDefaultName", urls: ["http://www.mycompany.com/repository"]
+     * }
+     * </pre>
+     * </p>
+     *
+     * For Ivy related reasons, Maven Snapshot dependencies are only properly resolved if no additional jar locations
+     * are specified. This is unfortunate and we hope to improve this in a future release.
+     *
+     * @param args The argument to create the repository
+     * @return the added repository
+     * @see #mavenCentral(java.util.Map)
+     */
+    DependencyResolver mavenRepo(Map args);
+
+    GroovyMavenDeployer mavenDeployer();
+
+    GroovyMavenDeployer mavenDeployer(Closure configureClosure);
+
+    /**
+     * Adds a repository for publishing to a Maven repository. This repository can not be used for reading from a Maven
+     * repository.
+     *
+     * The following parameter are accepted as keys for the map:
+     *
+     * <table summary="Shows property keys and associated values">
+     * <tr><th>Key</th>
+     *     <th>Description of Associated Value</th></tr>
+     * <tr><td><code>name</code></td>
+     *     <td><em>(optional)</em> The name of the repository. The default is <em>mavenDeployer-{SOME_ID}</em>.
+     * The name is used in the console output,
+     * to point to information related to a particular repository. A name must be unique amongst a repository group.
+     * </td></tr>
+     * </table>
+     * 
+     * @param args The argument to create the repository
+     * @return The added repository
+     * @see #mavenDeployer(java.util.Map, groovy.lang.Closure)
+     */
+    GroovyMavenDeployer mavenDeployer(Map args);
+
+    /**
+     * Behaves the same way as {@link #mavenDeployer(java.util.Map)}. Additionally a closure can be passed to
+     * further configure the added repository.
+     *  
+     * @param args The argument to create the repository
+     * @param configureClosure
+     * @return The added repository
+     */
+    GroovyMavenDeployer mavenDeployer(Map args, Closure configureClosure);
+
+    MavenResolver mavenInstaller();
+
+    MavenResolver mavenInstaller(Closure configureClosure);
+
+    /**
+     * Adds a repository for installing to a local Maven cache. This repository can not be used for reading.
+     *
+     * The following parameter are accepted as keys for the map:
+     *
+     * <table summary="Shows property keys and associated values">
+     * <tr><th>Key</th>
+     *     <th>Description of Associated Value</th></tr>
+     * <tr><td><code>name</code></td>
+     *     <td><em>(optional)</em> The name of the repository. The default is <em>mavenInstaller-{SOME_ID}</em>.
+     * The name is used in the console output,
+     * to point to information related to a particular repository. A name must be unique amongst a repository group.
+     * </td></tr>
+     * </table>
+     *
+     * @param args The argument to create the repository
+     * @return The added repository
+     * @see #mavenInstaller(java.util.Map, groovy.lang.Closure) (java.util.Map, groovy.lang.Closure)
+     */
+    MavenResolver mavenInstaller(Map args);
+
+    /**
+     * Behaves the same way as {@link #mavenInstaller(java.util.Map)}. Additionally a closure can be passed to
+     * further configure the added repository.
+     *
+     * @param args The argument to create the repository
+     * @param configureClosure
+     * @return The added repository
+     */
+    MavenResolver mavenInstaller(Map args, Closure configureClosure);
+
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/dsl/RepositoryHandlerFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/dsl/RepositoryHandlerFactory.java
new file mode 100644
index 0000000..ab5f18b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/dsl/RepositoryHandlerFactory.java
@@ -0,0 +1,29 @@
+/*
+ * 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.artifacts.dsl;
+
+import org.gradle.api.plugins.Convention;
+
+/**
+ * @author Hans Dockter
+ */
+public interface RepositoryHandlerFactory {
+    /**
+     * Creates a repository handler. The returned handler will also implement {@link
+     * org.gradle.api.internal.IConventionAware}.
+     */
+    RepositoryHandler createRepositoryHandler(Convention convention);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/dsl/package-info.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/dsl/package-info.java
new file mode 100644
index 0000000..623ef9a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/dsl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes used in the artifact DSL.
+ */
+package org.gradle.api.artifacts.dsl;
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/indexing/IndexFileUtil.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/indexing/IndexFileUtil.java
new file mode 100644
index 0000000..f0e0b71
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/indexing/IndexFileUtil.java
@@ -0,0 +1,32 @@
+/*
+ * 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.artifacts.indexing;
+
+import java.io.File;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class IndexFileUtil {
+//    private final File jarsIndexDirectory;
+
+    
+
+    public File packageIndexFile(File jarFile) {
+        return new File(jarFile.getParent(), "." + jarFile.getName() + ".package.index");
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/indexing/JarArtifactIndexer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/indexing/JarArtifactIndexer.java
new file mode 100644
index 0000000..42227dc
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/indexing/JarArtifactIndexer.java
@@ -0,0 +1,82 @@
+/*
+ * 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.artifacts.indexing;
+
+import org.gradle.api.GradleException;
+import org.apache.commons.io.IOUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class JarArtifactIndexer {
+    private IndexFileUtil indexFileUtil;
+
+    public JarArtifactIndexer(IndexFileUtil indexFileUtil) {
+        this.indexFileUtil = indexFileUtil;
+    }
+
+    public File index(File jarFile) {
+        if (jarFile == null) {
+            throw new IllegalArgumentException("jarFile is null!");
+        }
+
+        final String jarFileAbsolutePath = jarFile.getAbsolutePath();
+
+        if (!jarFile.exists()) {
+            throw new IllegalArgumentException("jarFile doesn't exists! (" + jarFileAbsolutePath + ")");
+        }
+        if (!jarFile.isFile()) {
+            throw new IllegalArgumentException("jarFile is not a file! (" + jarFileAbsolutePath + ")");
+        }
+        if (!jarFile.getName().endsWith(".jar")) {
+            throw new IllegalArgumentException("jarFile is not a jarFile! (" + jarFileAbsolutePath + ")");
+        }
+
+        final File packageIndexFile = indexFileUtil.packageIndexFile(jarFile);
+
+        BufferedWriter indexFileWriter = null;
+        try {
+            indexFileWriter = new BufferedWriter(new FileWriter(packageIndexFile));
+
+            final BufferedWriter writer = indexFileWriter;
+
+            new JarFilePackageLister().listJarPackages(jarFile, new JarFilePackageListener() {
+                public void receivePackage(String packageName) {
+                    try {
+                        writer.write(packageName);
+                        writer.newLine();
+                    } catch (IOException e) {
+                        throw new GradleException("failed to write to index file", e);
+                    }
+                }
+            });
+
+            indexFileWriter.flush();
+        } catch (IOException e) {
+            throw new GradleException("failed to index jar file (" + jarFileAbsolutePath + ")", e);
+        } finally {
+            IOUtils.closeQuietly(indexFileWriter);
+        }
+
+        return packageIndexFile;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/indexing/JarFilePackageListener.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/indexing/JarFilePackageListener.java
new file mode 100644
index 0000000..b1bba3b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/indexing/JarFilePackageListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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.artifacts.indexing;
+
+/**
+ * @author Tom Eyckmans
+ */
+public interface JarFilePackageListener {
+    void receivePackage(String packageName);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/indexing/JarFilePackageLister.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/indexing/JarFilePackageLister.java
new file mode 100644
index 0000000..0402be2
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/indexing/JarFilePackageLister.java
@@ -0,0 +1,71 @@
+/*
+ * 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.artifacts.indexing;
+
+import org.gradle.api.GradleException;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class JarFilePackageLister {
+    public void listJarPackages(File jarFile, JarFilePackageListener listener) {
+        if (jarFile == null) {
+            throw new IllegalArgumentException("jarFile is null!");
+        }
+
+        final String jarFileAbsolutePath = jarFile.getAbsolutePath();
+
+        if (!jarFile.exists()) {
+            throw new IllegalArgumentException("jarFile doesn't exists! (" + jarFileAbsolutePath + ")");
+        }
+        if (!jarFile.isFile()) {
+            throw new IllegalArgumentException("jarFile is not a file! (" + jarFileAbsolutePath + ")");
+        }
+        if (!jarFile.getName().endsWith(".jar")) {
+            throw new IllegalArgumentException("jarFile is not a jarFile! (" + jarFileAbsolutePath + ")");
+        }
+
+        try {
+            ZipFile zipFile = new ZipFile(jarFile);
+            try {
+                final Enumeration<? extends ZipEntry> zipFileEntries = zipFile.entries();
+
+                while (zipFileEntries.hasMoreElements()) {
+                    final ZipEntry zipFileEntry = zipFileEntries.nextElement();
+
+                    if (zipFileEntry.isDirectory()) {
+                        final String zipFileEntryName = zipFileEntry.getName();
+
+                        if (!zipFileEntryName.startsWith("META-INF")) {
+                            listener.receivePackage(zipFileEntryName);
+                        }
+                    }
+                }
+            } finally {
+                zipFile.close();
+            }
+        } catch (IOException e) {
+            throw new GradleException("failed to scan jar file for packages (" + jarFileAbsolutePath + ")", e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMapping.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMapping.java
new file mode 100644
index 0000000..33a4d8d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMapping.java
@@ -0,0 +1,95 @@
+/*
+ * 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.artifacts.maven;
+
+import org.gradle.api.artifacts.Configuration;
+
+/**
+ * An immutable mapping to map a dependency configuration to a Maven scope. This class has implemented equality and
+ * hashcode based on its values not on object identity.
+ *
+ * @author Hans Dockter
+ * @see org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer
+ */
+public class Conf2ScopeMapping {
+    private Integer priority;
+    private Configuration configuration;
+    private String scope;
+
+    /**
+     * @param priority The priority of this mapping
+     * @param configuration The configuration name
+     * @param scope The Maven scope name
+     */
+    public Conf2ScopeMapping(Integer priority, Configuration configuration, String scope) {
+        this.priority = priority;
+        this.configuration = configuration;
+        this.scope = scope;
+    }
+
+    /**
+     * Returns the priority
+     */
+    public Integer getPriority() {
+        return priority;
+    }
+
+    /**
+     * Returns the dependency configuration name
+     */
+    public Configuration getConfiguration() {
+        return configuration;
+    }
+
+    /**
+     * Returns the Maven scope name.
+     */
+    public String getScope() {
+        return scope;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        Conf2ScopeMapping that = (Conf2ScopeMapping) o;
+
+        if (!configuration.equals(that.configuration)) {
+            return false;
+        }
+        if (priority != null ? !priority.equals(that.priority) : that.priority != null) {
+            return false;
+        }
+        if (scope != null ? !scope.equals(that.scope) : that.scope != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = priority != null ? priority.hashCode() : 0;
+        result = 31 * result + configuration.hashCode();
+        result = 31 * result + (scope != null ? scope.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingContainer.java
new file mode 100644
index 0000000..81961ee
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingContainer.java
@@ -0,0 +1,88 @@
+/*
+ * 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.artifacts.maven;
+
+import org.gradle.api.artifacts.Configuration;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Defines a set of rules for how to map the Gradle dependendencies to a pom. This mapping is based
+ * on the configuration the dependencies belong to.
+ *
+ * @author Hans Dockter
+ */
+public interface Conf2ScopeMappingContainer {
+    String PROVIDED = "provided";
+    String COMPILE = "compile";
+    String RUNTIME = "runtime";
+    String TEST = "test";
+
+    /**
+     * <p>Specifies that dependencies of a certain configuration should be mapped against a certain
+     * Maven scope. A configuration can be mapped to one and only one scope. If this method is called
+     * more than once for a particular configuration, the last call wins.</p>
+     *
+     * See {@link #getMapping(java.util.Collection)} for the rules how a scope is choosen from a set of mappings.
+     *
+     * @param priority a number that is used for comparison with the priority of other scopes.
+     * @param configuration a Gradle configuration name (must not be null).
+     * @param scope A Maven scope name (must not be null)
+     * @return this
+     * @see #getMapping(java.util.Collection) 
+     */
+    Conf2ScopeMappingContainer addMapping(int priority, Configuration configuration, String scope);
+
+    /**
+     * Returns a scope that corresponds to the given configurations. Dependencies of different configurations can
+     * be equal. But only one of those equals dependencies (which might differ in content) can be mapped to a pom
+     * (due to the the nature of a Maven pom).
+     *
+     * <p>Which scope is returned depends on the existing mappings. See {@link #addMapping(int, Configuration, String)}. If
+     * only one configuration is mapped, this mapping is used to choose the scope. If more than one configuration of a
+     * dependency is mapped, and those mappings all map to the same scope, this scope is used. If more than one
+     * configuration is mapped and the mappings map to different scopes, the mapping with the highest priority is used.
+     * If there is more than one mapping with the highest priority and those mappings map to different scopes, an
+     * exception is thrown.</p>
+     *
+     * @param configurations The configuration
+     * @return The scope corresponding to the given configurations. Returns null if no such scope can be found.
+     * @see #addMapping(int, Configuration, String)
+     */
+    Conf2ScopeMapping getMapping(Collection<Configuration> configurations);
+
+    /**
+     * Returns a map with all the configuration to scope mappings. If no such mapping has been defined,
+     * an empty map is returned.
+     *
+     * @see #addMapping(int, Configuration, String) 
+     */
+    Map<Configuration, Conf2ScopeMapping> getMappings();
+
+    /**
+     * Returns whether unmapped configuration should be skipped or not. Defaults to true.
+     * @see #setSkipUnmappedConfs(boolean) 
+     */
+    boolean isSkipUnmappedConfs();
+
+    /**
+     * Sets, whether unmapped configuration should be skipped or not. If this is set to
+     * false, dependencies belonging to unmapped configurations will be added to the Maven pom with no
+     * scope specified. This means they belong to the Maven default scope, which is 'compile'.
+     */
+    void setSkipUnmappedConfs(boolean skipDependenciesWithUnmappedConfiguration);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/GroovyMavenDeployer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/GroovyMavenDeployer.java
new file mode 100644
index 0000000..b04edfe
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/GroovyMavenDeployer.java
@@ -0,0 +1,39 @@
+/*
+ * 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.artifacts.maven;
+
+/**
+ * Adds Groovy configuration convenience methods on top of the {@link MavenDeployer}.
+ *
+ * This class provides also a builder for repository and snapshot-repository:
+ *
+ * <pre>
+ * mavenUploader.repository(url: 'file://repoDir') {
+ *    authentication(userName: 'myName')
+ *    releases(updatePolicy: 'never')
+ *    snapshots(updatePolicy: 'always')
+ * }
+ * </pre>
+ *
+ * This call set the repository object and also returns an instance of this object. If you use 'snapshotRepository'
+ * instead of repository, the snapshot repository is build.
+ *
+ * @author Hans Dockter
+ * @see MavenDeployer
+ */
+public interface GroovyMavenDeployer extends MavenDeployer {
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/GroovyPomFilterContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/GroovyPomFilterContainer.java
new file mode 100644
index 0000000..fc1d7e3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/GroovyPomFilterContainer.java
@@ -0,0 +1,62 @@
+/*
+ * 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.artifacts.maven;
+
+import groovy.lang.Closure;
+
+/**
+ * @author Hans Dockter
+ */
+public interface GroovyPomFilterContainer extends PomFilterContainer {
+    /**
+     * Adds a publish filter.
+     *
+     * @param name   The name of the filter
+     * @param filter The filter
+     * @return The Maven pom associated with the closure
+     * @see PublishFilter
+     * @see PomFilterContainer#addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)
+     */
+    MavenPom addFilter(String name, Closure filter);
+
+    /**
+     * Sets the default publish filter.
+     *
+     * @param filter The filter to be set
+     * @see PublishFilter
+     * @see PomFilterContainer#setFilter(org.gradle.api.artifacts.maven.PublishFilter)
+     */
+    void filter(Closure filter);
+
+    /**
+     * Configures a pom by a closure. The closure statements are delegated to the pom object associated with the given name.
+     *
+     * @param name
+     * @param configureClosure
+     * @return The pom object associated with the given name.
+     * @see PomFilterContainer#pom(String)
+     */
+    MavenPom pom(String name, Closure configureClosure);
+
+    /**
+     * Configures the default pom by a closure. The closure statements are delegated to the default pom.
+     *
+     * @param configureClosure
+     * @return The default pom.
+     * @see PomFilterContainer#getPom()
+     */
+    MavenPom pom(Closure configureClosure);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployer.java
new file mode 100644
index 0000000..94aa3ac
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/MavenDeployer.java
@@ -0,0 +1,89 @@
+/*
+ * 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.artifacts.maven;
+
+import org.apache.maven.artifact.ant.RemoteRepository;
+
+import java.io.File;
+import java.util.Collection;
+
+/**
+ * <p>A resolver that can only be used for uploading artifacts to a Maven repository. If you use this resolver for getting
+ * dependencies from a Maven repository, an exception is thrown. This resolver support all aspects of Maven deployment,
+ * including snapshot deployment and metadata.xml manipulation.</p>
+ * <p/>
+ * <p>You have to specify at least one repository. Otherwise, if there is only one artifact, usually there is not more to do.
+ * If there is more than one artifact you have to decide what to do about this, as a Maven pom can only deal with one artifact.
+ * There are two strategies. If you want to deploy only one artifact you have to specify a filter to select this artifact. If you
+ * want to deploy more than one artifact, you have to specify filters which select each artifact. Associated with each filter is
+ * a separate configurable pom.</p>
+ *
+ * <p>You can create an instance of this type via the {@link org.gradle.api.tasks.Upload#getRepositories()} container</p> 
+ *
+ * @author Hans Dockter
+ */
+public interface MavenDeployer extends MavenResolver {
+
+    /**
+     * Returns the repository o be used for uploading artifacts.
+     *
+     * @see #setRepository(org.apache.maven.artifact.ant.RemoteRepository) 
+     */
+    RemoteRepository getRepository();
+
+    /**
+     * Sets the repository to be used for uploading artifacts. If {@link #getRepository()} is not set, this repository is
+     * also used for uploading snapshot artifacts.
+     *
+     * @param repository The repository to be used
+     */
+    void setRepository(RemoteRepository repository);
+
+    /**
+     * Returns the repository o be used for uploading snapshot artifacts.
+     * 
+     * @see #setSnapshotRepository(org.apache.maven.artifact.ant.RemoteRepository)
+     */
+    RemoteRepository getSnapshotRepository();
+
+    /**
+     * Sets the repository to be used for uploading snapshot artifacts. If this is not set, the {@link #getRepository()} is used
+     * for uploading snapshot artifacts.
+     *  
+     * @param snapshotRepository The repository to be used
+     */
+    void setSnapshotRepository(RemoteRepository snapshotRepository);
+
+    /**
+     * Out of the box only uploading to the filesysten and via http is supported. If other protocolls should be used, the
+     * appropriate Maven wagon jars have to be passed via this method.
+     * 
+     * @param jars
+     */
+    void addProtocolProviderJars(Collection<File> jars);
+
+    /**
+     * Returns whether to assign snapshots a unique version comprised of the timestamp and build number, or to use
+     * the same version each time. Defaults to true.
+     */
+    boolean isUniqueVersion();
+
+    /**
+     * Sets whether to assign snapshots a unique version comprised of the timestamp and build number, or to use
+     * the same version each time. Defaults to true.
+     */
+    void setUniqueVersion(boolean uniqueVersion);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/MavenPom.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/MavenPom.java
new file mode 100644
index 0000000..56ee058
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/MavenPom.java
@@ -0,0 +1,205 @@
+/*
+ * 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.artifacts.maven;
+
+import groovy.lang.Closure;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Model;
+import org.gradle.api.Action;
+import org.gradle.api.artifacts.ConfigurationContainer;
+
+import java.io.Writer;
+import java.util.List;
+
+/**
+ * Is used for generating a Maven pom file and customizing the generation.
+ * To learn about the Maven pom see: <a href="http://maven.apache.org/pom.html">http://maven.apache.org/pom.html</a>
+ *
+ * @author Hans Dockter
+ */
+public interface MavenPom {
+    /**
+     * Returns the scope mappings used for generating this pom.
+     */
+    Conf2ScopeMappingContainer getScopeMappings();
+
+    /**
+     * Provides a builder for the Maven pom for adding or modifying properties of the Maven {@link #getModel()}.
+     * The syntax is exactly the same as used by polyglot Maven. For example:
+     *
+     * <pre>
+     * pom.project {
+     *    inceptionYear '2008'
+     *    licenses {
+     *       license {
+     *          name 'The Apache Software License, Version 2.0'
+     *          url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+     *          distribution 'repo'
+     *       }
+     *    }
+     * }
+     * </pre>
+     *
+     * @param pom
+     * @return this
+     */
+    MavenPom project(Closure pom);
+
+    /**
+     * @see org.apache.maven.model.Model#setGroupId(String)
+     */
+    String getGroupId();
+
+    /**
+     * org.apache.maven.model.Model#getGroupId
+     * @return this
+     */
+    MavenPom setGroupId(String groupId);
+
+    /**
+     * @see org.apache.maven.model.Model#getArtifactId()
+     */
+    String getArtifactId();
+
+    /**
+     * @see org.apache.maven.model.Model#setArtifactId(String)
+     * @return this
+     */
+    MavenPom setArtifactId(String artifactId);
+
+    /**
+     * @see org.apache.maven.model.Model#getVersion()
+     */
+    String getVersion();
+
+    /**
+     * @see org.apache.maven.model.Model#setVersion(String)
+     * @return this
+     */
+    MavenPom setVersion(String version);
+
+    /**
+     * @see org.apache.maven.model.Model#getPackaging()
+     */
+    String getPackaging();
+
+    /**
+     * @see org.apache.maven.model.Model#setPackaging(String)
+     * @return this
+     */
+    MavenPom setPackaging(String packaging);
+
+    /**
+     * @see org.apache.maven.model.Model#setDependencies(java.util.List)
+     * @return this
+     */
+    MavenPom setDependencies(List<Dependency> dependencies);
+
+    /**
+     * @see org.apache.maven.model.Model#getDependencies()
+     */
+    List<Dependency> getDependencies();
+
+    /**
+     * Returns the underlying native Maven {@link org.apache.maven.model.Model} object. The MavenPom object
+     * delegates all the configuration information to this object. There Gradle MavenPom objects provides
+     * delegation methods just for setting the groupId, artifactId, version and packaging. For all other
+     * elements, either use the model object or {@link #project(groovy.lang.Closure)}.
+     *
+     * @return the underlying native Maven object
+     */
+    Model getModel();
+
+    /**
+     * Sets the underlying native Maven {@link org.apache.maven.model.Model} object.
+     *
+     * @param model
+     * @return this
+     * @see #getModel() 
+     */
+    MavenPom setModel(Model model);
+
+    /**
+     * Writes the {@link #getEffectivePom()} xml to a writer while applying the {@link #withXml(org.gradle.api.Action)} actions.
+     *
+     * @param writer The writer to write the pom xml.
+     * @return this
+     */
+    MavenPom writeTo(Writer writer);
+
+    /**
+     * Writes the {@link #getEffectivePom()} xml to a file while applying the {@link #withXml(org.gradle.api.Action)} actions.
+     * The path is resolved as defined by {@link org.gradle.api.Project#files(Object...)}
+     *
+     * @param path The path of the file to write the pom xml into.
+     * @return this
+     */
+    MavenPom writeTo(Object path);
+
+    /**
+     * <p>Adds a closure to be called when the pom has been configured. The pom is passed to the closure as a
+     * parameter.</p>
+     *
+     * @param closure The closure to execute when the pom has been configured.
+     * @return this
+     */
+    MavenPom whenConfigured(Closure closure);
+
+    /**
+     * <p>Adds an action to be called when the pom has been configured. The pom is passed to the action as a
+     * parameter.</p>
+     *
+     * @param action The action to execute when the pom has been configured.
+     * @return this
+     */
+    MavenPom whenConfigured(Action<MavenPom> action);
+
+    /**
+     * <p>Adds a closure to be called when the pom xml has been created. The xml is passed to the closure as a
+     * parameter in form of a {@link org.gradle.api.artifacts.maven.XmlProvider}. The xml might be modified.</p>
+     *
+     * @param closure The closure to execute when the pom xml has been created.
+     * @return this
+     */
+    MavenPom withXml(Closure closure);
+
+    /**
+     * <p>Adds an action to be called when the pom xml has been created. The xml is passed to the action as a
+     * parameter in form of a {@link org.gradle.api.artifacts.maven.XmlProvider}. The xml might be modified.</p>
+     *
+     * @param action The action to execute when the pom xml has been created.
+     * @return this
+     */
+    MavenPom withXml(Action<XmlProvider> action);
+
+    /**
+     * Returns the configuration container used for mapping configurations to maven scopes.
+     */
+    ConfigurationContainer getConfigurations();
+
+    /**
+     * Sets the configuration container used for mapping configurations to maven scopes.
+     * @return this
+     */
+    MavenPom setConfigurations(ConfigurationContainer configurations);
+
+    /**
+     * Returns a pom with the generated dependencies and the {@link #whenConfigured(org.gradle.api.Action)} actions applied.
+     *
+     * @return the effective pom
+     */
+    MavenPom getEffectivePom();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/MavenResolver.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/MavenResolver.java
new file mode 100644
index 0000000..8c4abb4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/MavenResolver.java
@@ -0,0 +1,30 @@
+/*
+ * 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.artifacts.maven;
+
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.maven.settings.Settings;
+
+/**
+ * @author Hans Dockter
+ */
+public interface MavenResolver extends DependencyResolver, PomFilterContainer {
+    /**
+     * Returns a maven settings object. This can be used for example to figure out where the local repository is located.
+     * This property is filled after publishing. Before this property is null.
+     */
+    Settings getSettings();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/PomFilterContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/PomFilterContainer.java
new file mode 100644
index 0000000..62a6af5
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/PomFilterContainer.java
@@ -0,0 +1,94 @@
+/*
+ * 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.artifacts.maven;
+
+import org.gradle.api.internal.artifacts.publish.maven.deploy.PomFilter;
+
+/**
+ * @author Hans Dockter
+ */
+public interface PomFilterContainer {
+    String DEFAULT_ARTIFACT_POM_NAME = "default";
+
+    /**
+     * Returns the default filter being used. .
+     *
+     * @see #setFilter(org.gradle.api.artifacts.maven.PublishFilter)
+     */
+    PublishFilter getFilter();
+
+    /**
+     * <p>Sets the default filter to be used. This filter is active if no custom filters have been added (see {@link #addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)}).
+     * If at least one custom filter has been added the default filter is not used any longer.</p>
+     * <p>The default for this property is {@link PublishFilter#ALWAYS_ACCEPT}.
+     * If there is only one artifact you are fine with this filter. If there is more than one artifact, deployment will lead to
+     * an exception, if you don't specify a filter that selects the artifact to deploy. If you want to deploy more than one artiact you have
+     * to use the (see {@link #addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)} method.</p>
+     *
+     * @param defaultFilter
+     * @see #getFilter()
+     */
+    void setFilter(PublishFilter defaultFilter);
+
+    /**
+     * Returns the pom property of the custom filter.
+     * The pom property can be used to customize the pom generation. By default the properties of such a pom object are all null.
+     * Null means that Gradle will use default values for generating the Maven pom. Those default values are derived from the deployable artifact
+     * and from the project type (e.g. java, war, ...). If you explicitly set a pom property, Gradle will use those instead.
+     *
+     * @return The Maven Pom
+     */
+    MavenPom getPom();
+
+    /**
+     * <p>Sets the default pom to be used. This pom is active if no custom filters have been added (see {@link #addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)}).
+     * If at least one custom filter has been added the default pom is not used any longer.</p>
+     * <p>Usually you don't need to set this property as the default value provides you a pom object you might use for configuration.
+     * By default the properties of such a pom object are all null.
+     * If they are null, Gradle will use default values for generating the Maven pom. Those default values are derived from the deployable artifact
+     * and from the project type (e.g. java, war, ...). If you explicitly set a pom property, Gradle will use this instead.</p>
+     *
+     * @param defaultPom
+     */
+    void setPom(MavenPom defaultPom);
+
+    /**
+     * If you want to deploy more than one artifact you need to define filters to select each of those artifacts. The method
+     * returns a pom object associated with this filter, that allows you to customize the pom generation for the artifact selected
+     * by the filter.
+     *
+     * @param name The name of the filter
+     * @param publishFilter The filter to use
+     * @return The pom associated with the filter
+     */
+    MavenPom addFilter(String name, PublishFilter publishFilter);
+
+    /**
+     * Returns a filter added with {@link #addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)}
+     *
+     * @param name The name of the filter
+     */
+    PublishFilter filter(String name);
+
+    /**
+     * Returns the pom associated with a filter added with {@link #addFilter(String, org.gradle.api.artifacts.maven.PublishFilter)}.
+     *
+     * @param name The name of the filter.
+     */
+    MavenPom pom(String name);
+
+    Iterable<PomFilter> getActivePomFilters();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/PublishFilter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/PublishFilter.java
new file mode 100644
index 0000000..41a469c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/PublishFilter.java
@@ -0,0 +1,34 @@
+/*
+ * 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.artifacts.maven;
+
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public interface PublishFilter {
+    PublishFilter ALWAYS_ACCEPT = new PublishFilter() {
+        public boolean accept(Artifact artifact, File src) {
+            return true;
+        }
+    };
+
+    boolean accept(Artifact artifact, File src);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/XmlProvider.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/XmlProvider.java
new file mode 100644
index 0000000..77d68d2
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/XmlProvider.java
@@ -0,0 +1,28 @@
+/*
+ * 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.artifacts.maven;
+
+/**
+ * @author Hans Dockter
+ */
+public interface XmlProvider {
+    /**
+     * Returns the xml in form of a StringBuilder. Changes to the StringBuilder instance will be applied against the XML.
+     *
+     * @return A StringBuilder representation of the XML
+     */
+    StringBuilder asString();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/package-info.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/package-info.java
new file mode 100644
index 0000000..5a3a728
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/maven/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 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.
+ */
+
+/**
+ * Maven specific classes for dependency management.
+ */
+package org.gradle.api.artifacts.maven;
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/package-info.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/package-info.java
new file mode 100644
index 0000000..f566ba9
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 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.
+ */
+
+/**
+ * Classes for declaring and using artifacts and artifact dependencies.
+ */
+package org.gradle.api.artifacts;
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/repositories/InternalRepository.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/repositories/InternalRepository.java
new file mode 100644
index 0000000..5e7383e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/repositories/InternalRepository.java
@@ -0,0 +1,24 @@
+/*
+ * 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.artifacts.repositories;
+
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+/**
+ * @author Hans Dockter
+ */
+public interface InternalRepository extends DependencyResolver {
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/repositories/WebdavResolver.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/repositories/WebdavResolver.java
new file mode 100644
index 0000000..90cf860
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/repositories/WebdavResolver.java
@@ -0,0 +1,44 @@
+/*
+ * 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.artifacts.repositories;
+
+import org.apache.ivy.plugins.resolver.RepositoryResolver;
+import org.gradle.api.internal.artifacts.repositories.WebdavRepository;
+
+/**
+ * @author Hans Dockter
+ */
+public class WebdavResolver extends RepositoryResolver {
+    public WebdavResolver() {
+         setRepository(new WebdavRepository());
+    }
+
+    private WebdavRepository getWebdavRepository() {
+        return (WebdavRepository) getRepository();
+    }
+
+    public String getTypeName() {
+        return "webdav";
+    }
+
+    public void setUserPassword(String userPassword) {
+       getWebdavRepository().setUserPassword(userPassword);
+    }
+
+    public void setUser(String user) {
+        getWebdavRepository().setUser(user);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/repositories/package-info.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/repositories/package-info.java
new file mode 100644
index 0000000..f8e96f3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/repositories/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes for declaring and using artifact repositories.
+ */
+package org.gradle.api.artifacts.repositories;
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/specs/DependencySpecs.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/specs/DependencySpecs.java
new file mode 100644
index 0000000..9739e6a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/specs/DependencySpecs.java
@@ -0,0 +1,25 @@
+/*
+ * 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.artifacts.specs;
+
+/**
+ * @author Hans Dockter
+ */
+public class DependencySpecs {
+    public static DependencyTypeSpec type(Type type) {
+        return new DependencyTypeSpec(type);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/specs/DependencyTypeSpec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/specs/DependencyTypeSpec.java
new file mode 100644
index 0000000..fafc0c4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/specs/DependencyTypeSpec.java
@@ -0,0 +1,62 @@
+/*
+ * 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.artifacts.specs;
+
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.specs.Spec;
+
+/**
+ * @author Hans Dockter
+ */
+public class DependencyTypeSpec<T extends Dependency> implements Spec<T> {
+
+    private Type type;
+
+    public DependencyTypeSpec(Type type) {
+        this.type = type;
+    }
+
+    public boolean isSatisfiedBy(Dependency dependency) {
+        return type.isOf(dependency);
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DependencyTypeSpec typeSpec = (DependencyTypeSpec) o;
+
+        if (type != typeSpec.type) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return type != null ? type.hashCode() : 0;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/specs/Type.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/specs/Type.java
new file mode 100644
index 0000000..cfcea71
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/specs/Type.java
@@ -0,0 +1,28 @@
+/*
+ * 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.artifacts.specs;
+
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.artifacts.ExternalDependency;
+
+public enum Type {
+    EXTERNAL {public boolean isOf(Dependency dependency) {return dependency instanceof ExternalDependency;}},
+    PROJECT {public boolean isOf(Dependency dependency) {return dependency instanceof ProjectDependency;}};
+
+    public abstract boolean isOf(Dependency dependency);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/specs/package-info.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/specs/package-info.java
new file mode 100644
index 0000000..2e75726
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/artifacts/specs/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 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.
+ */
+
+/**
+ * Artifact dependency criteria.
+ */
+package org.gradle.api.artifacts.specs;
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/execution/TaskActionListener.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/execution/TaskActionListener.java
new file mode 100644
index 0000000..7c8c879
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/execution/TaskActionListener.java
@@ -0,0 +1,37 @@
+/*
+ * 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.execution;
+
+import org.gradle.api.Task;
+
+/**
+ * <p>A {@code TaskWorkListener} is notified of the actions that a task performs.</p>
+ */
+public interface TaskActionListener {
+    /**
+     * This method is called immediately before the task starts performing its actions.
+     *
+     * @param task The task which is to perform some actions.
+     */
+    void beforeActions(Task task);
+
+    /**
+     * This method is called immediately after the task has completed performing its actions.
+     *
+     * @param task The task which has performed some actions.
+     */
+    void afterActions(Task task);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/execution/TaskExecutionGraph.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/execution/TaskExecutionGraph.java
new file mode 100644
index 0000000..ed32316
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/execution/TaskExecutionGraph.java
@@ -0,0 +1,115 @@
+/*
+ * 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.execution;
+
+import groovy.lang.Closure;
+import org.gradle.api.Task;
+
+import java.util.List;
+
+/**
+ * <p>A <code>TaskExecutionGraph</code> is responsible for managing the execution of the {@link Task} instances which
+ * are part of the build. The <code>TaskExecutionGraph</code> maintains an execution plan of tasks to be executed (or
+ * which have been executed), and you can query this plan from your build file.</p>
+ *
+ * <p>You can access the {@code TaskExecutionGraph} by calling {@link org.gradle.api.invocation.Gradle#getTaskGraph()}.
+ * In your build file you can use {@code gradle.taskGraph} to access it.</p>
+ *
+ * <p>The <code>TaskExecutionGraph</code> is populated only after all the projects in the build have been evaulated. It
+ * is empty before then. You can receive a notification when the graph is populated, using {@link
+ * #whenReady(groovy.lang.Closure)} or {@link #addTaskExecutionGraphListener(TaskExecutionGraphListener)}.</p>
+ */
+public interface TaskExecutionGraph {
+    /**
+     * <p>Adds a listener to this graph, to be notified when this graph is ready.</p>
+     *
+     * @param listener The listener to add. Does nothing if this listener has already been added.
+     */
+    void addTaskExecutionGraphListener(TaskExecutionGraphListener listener);
+
+    /**
+     * <p>Remove a listener from this graph.</p>
+     *
+     * @param listener The listener to remove. Does nothing if this listener was never added to this graph.
+     */
+    void removeTaskExecutionGraphListener(TaskExecutionGraphListener listener);
+
+    /**
+     * <p>Adds a listener to this graph, to be notified as tasks are executed.</p>
+     *
+     * @param listener The listener to add. Does nothing if this listener has already been added.
+     */
+    void addTaskExecutionListener(TaskExecutionListener listener);
+
+    /**
+     * <p>Remove a listener from this graph.</p>
+     *
+     * @param listener The listener to remove. Does nothing if this listener was never added to this graph.
+     */
+    void removeTaskExecutionListener(TaskExecutionListener listener);
+
+    /**
+     * <p>Adds a closure to be called when this graph has been populated. This graph is passed to the closure as a
+     * parameter.</p>
+     *
+     * @param closure The closure to execute when this graph has been populated.
+     */
+    void whenReady(Closure closure);
+
+    /**
+     * <p>Adds a closure to be called immediately before a task is executed. The task is passed to the closure as a
+     * parameter.</p>
+     *
+     * @param closure The closure to execute when a task is about to be executed.
+     */
+    void beforeTask(Closure closure);
+
+    /**
+     * <p>Adds a closure to be called immediately after a task has executed. The task is passed to the closure as the
+     * first parameter. A {@link org.gradle.api.tasks.TaskState} is passed as the second parameter. Both parameters are
+     * optional.</p>
+     *
+     * @param closure The closure to execute when a task has been executed
+     */
+    void afterTask(Closure closure);
+
+    /**
+     * <p>Determines whether the given task is included in the execution plan.</p>
+     *
+     * @param path the <em>absolute</em> path of the task.
+     * @return true if a task with the given path is included in the execution plan.
+     * @throws IllegalStateException When this graph has not been populated.
+     */
+    boolean hasTask(String path);
+
+    /**
+     * <p>Determines whether the given task is included in the execution plan.</p>
+     *
+     * @param task the task
+     * @return true if the given task is included in the execution plan.
+     * @throws IllegalStateException When this graph has not been populated.
+     */
+    boolean hasTask(Task task);
+
+    /**
+     * <p>Returns the tasks which are included in the execution plan. The tasks are returned in the order that they will
+     * be executed.</p>
+     *
+     * @return The tasks. Returns an empty set if no tasks are to be executed.
+     * @throws IllegalStateException When this graph has not been populated.
+     */
+    List<Task> getAllTasks();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/execution/TaskExecutionGraphListener.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/execution/TaskExecutionGraphListener.java
new file mode 100644
index 0000000..e0a3643
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/execution/TaskExecutionGraphListener.java
@@ -0,0 +1,32 @@
+/*
+ * 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.execution;
+
+/**
+ * <p>A {@code TaskExecutionGraphListener} is notified when the {@link TaskExecutionGraph} has been populated. You can
+ * use this interface in your build file to perform some action based on the contents of the graph, before any tasks are
+ * actually executed.</p>
+ */
+public interface TaskExecutionGraphListener {
+    /**
+     * <p>This method is called when the {@link TaskExecutionGraph} has been populated, and before any tasks are
+     * executed.
+     *
+     * @param graph The graph. Never null.
+     */
+    void graphPopulated(TaskExecutionGraph graph);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/execution/TaskExecutionListener.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/execution/TaskExecutionListener.java
new file mode 100644
index 0000000..c4a8e34
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/execution/TaskExecutionListener.java
@@ -0,0 +1,44 @@
+/*
+ * 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.execution;
+
+import org.gradle.api.Task;
+import org.gradle.api.tasks.TaskState;
+
+/**
+ * <p>A {@code TaskExecutionListener} is notified of the execution of the tasks in a build.</p>
+ *
+ * <p>You can add a {@code TaskExecutionListener} to a build using {@link org.gradle.api.execution.TaskExecutionGraph#addTaskExecutionListener}
+ */
+public interface TaskExecutionListener {
+    /**
+     * This method is called immediately before a task is executed.
+     *
+     * @param task The task about to be executed. Never null.
+     */
+    void beforeExecute(Task task);
+
+    /**
+     * This method is call immediately after a task has been executed. It is always called, regardless of whether the
+     * task completed successfully, or failed with an exception.
+     *
+     * @param task The task which was executed. Never null.
+     * @param state The task state. If the task failed with an exception, the exception is available in this
+     * state. Never null.
+     */
+    void afterExecute(Task task, TaskState state);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/execution/package-info.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/execution/package-info.java
new file mode 100644
index 0000000..0b036d4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/execution/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes for managing and monitoring build execution. 
+ */
+package org.gradle.api.execution;
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/ConfigurableFileCollection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/ConfigurableFileCollection.java
new file mode 100644
index 0000000..f359320
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/ConfigurableFileCollection.java
@@ -0,0 +1,58 @@
+/*
+ * 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.file;
+
+import java.util.Set;
+
+/**
+ * <p>A {@code ConfigurableFileCollection} is a mutable {@code FileCollection}.</p>
+ *
+ * <p>You can obtain an instance of {@code ConfigurableFileCollection} by calling {@link
+ * org.gradle.api.Project#files(Object...)}</p>
+ */
+public interface ConfigurableFileCollection extends FileCollection {
+    /**
+     * Adds a set of files to this collection. The given paths are evaluated as for {@link
+     * org.gradle.api.Project#files(Object...)}.
+     *
+     * @param paths The files to add.
+     * @return this
+     */
+    ConfigurableFileCollection from(Object... paths);
+
+    /**
+     * Returns the set of tasks which build the files of this collection.
+     *
+     * @return The set. Returns an empty set when there are no such tasks.
+     */
+    Set<Object> getBuiltBy();
+
+    /**
+     * Sets the tasks which build the files of this collection.
+     *
+     * @param tasks The tasks. These are evaluated as for {@link org.gradle.api.Task#dependsOn(Object...)}.
+     * @return this
+     */
+    ConfigurableFileCollection setBuiltBy(Iterable<?> tasks);
+
+    /**
+     * Registers some tasks which build the files of this collection.
+     *
+     * @param tasks The tasks. These are evaluated as for {@link org.gradle.api.Task#dependsOn(Object...)}.
+     * @return this
+     */
+    ConfigurableFileCollection builtBy(Object... tasks);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/ConfigurableFileTree.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/ConfigurableFileTree.java
new file mode 100644
index 0000000..91775e9
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/ConfigurableFileTree.java
@@ -0,0 +1,77 @@
+/*
+ * 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.file;
+
+import org.gradle.api.Buildable;
+import org.gradle.api.tasks.util.PatternFilterable;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * <p>A {@link FileTree} with a single base directory, which can be configured and modified.</p>
+ *
+ * <p>You can obtain a {@code ConfigurableFileTree} instance by calling {@link org.gradle.api.Project#fileTree(java.util.Map)}.</p>
+ */
+public interface ConfigurableFileTree extends FileTree, PatternFilterable, Buildable {
+    /**
+     * Specifies base directory for this file tree using the given path. The path is evaluated as for {@link
+     * org.gradle.api.Project#file(Object)}.
+     *
+     * @param dir The base directory.
+     * @return this
+     */
+    ConfigurableFileTree from(Object dir);
+
+    /**
+     * Returns the base directory of this file tree.
+     *
+     * @return The base directory. Never returns null.
+     */
+    File getDir();
+
+    /**
+     * Specifies base directory for this file tree using the given path. The path is evaluated as for {@link
+     * org.gradle.api.Project#file(Object)}.
+     *
+     * @param dir The base directory.
+     * @return this
+     */
+    ConfigurableFileTree setDir(Object dir);
+
+    /**
+     * Returns the set of tasks which build the files of this collection.
+     *
+     * @return The set. Returns an empty set when there are no such tasks.
+     */
+    Set<Object> getBuiltBy();
+
+    /**
+     * Sets the tasks which build the files of this collection.
+     *
+     * @param tasks The tasks. These are evaluated as for {@link org.gradle.api.Task#dependsOn(Object...)}.
+     * @return this
+     */
+    ConfigurableFileTree setBuiltBy(Iterable<?> tasks);
+
+    /**
+     * Registers some tasks which build the files of this collection.
+     *
+     * @param tasks The tasks. These are evaluated as for {@link org.gradle.api.Task#dependsOn(Object...)}.
+     * @return this
+     */
+    ConfigurableFileTree builtBy(Object... tasks);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/ContentFilterable.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/ContentFilterable.java
new file mode 100644
index 0000000..5e30ca3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/ContentFilterable.java
@@ -0,0 +1,78 @@
+/*
+ * 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.file;
+
+import groovy.lang.Closure;
+
+import java.io.FilterReader;
+import java.util.Map;
+
+public interface ContentFilterable {
+    /**
+     * <p>Adds a content filter to be used during the copy.  Multiple calls to filter, add additional filters to the
+     * filter chain.  Each filter should implement {@code java.io.FilterReader}. Include {@code
+     * org.apache.tools.ant.filters.*} for access to all the standard Ant filters.</p>
+     *
+     * <p>Filter properties may be specified using groovy map syntax.</p>
+     *
+     * <p> Examples:
+     * <pre>
+     *    filter(HeadFilter, lines:25, skip:2)
+     *    filter(ReplaceTokens, tokens:[copyright:'2009', version:'2.3.1'])
+     * </pre>
+     *
+     * @param properties map of filter properties
+     * @param filterType Class of filter to add
+     * @return this
+     */
+    ContentFilterable filter(Map<String, ?> properties, Class<? extends FilterReader> filterType);
+
+    /**
+     * <p>Adds a content filter to be used during the copy.  Multiple calls to filter, add additional filters to the
+     * filter chain.  Each filter should implement {@code java.io.FilterReader}. Include {@code
+     * org.apache.tools.ant.filters.*} for access to all the standard Ant filters.</p>
+     *
+     * <p> Examples:
+     * <pre>
+     *    filter(StripJavaComments)
+     *    filter(com.mycompany.project.CustomFilter)
+     * </pre>
+     *
+     * @param filterType Class of filter to add
+     * @return this
+     */
+    ContentFilterable filter(Class<? extends FilterReader> filterType);
+
+    /**
+     * Adds a content filter based on the provided closure.  The Closure will be called with each line (stripped of line
+     * endings) and should return a String to replace the line.
+     *
+     * @param closure to implement line based filtering
+     * @return this
+     */
+    ContentFilterable filter(Closure closure);
+
+    /**
+     * <p>Expands property references in each file as it is copied. More specifically, each file is transformed using
+     * Groovy's {@link groovy.text.SimpleTemplateEngine}. This means you can use simple property references, such as
+     * <code>$property</code> or <code>${property}</code> in the file. You can also include arbitrary Groovy code in the
+     * file, such as <code>${version ?: 'unknown'}</code> or <code>${classpath*.name.join(' ')}</code>
+     *
+     * @param properties to implement line based filtering
+     * @return this
+     */
+    ContentFilterable expand(Map<String, ?> properties);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/CopyAction.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/CopyAction.java
new file mode 100644
index 0000000..1605a2b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/CopyAction.java
@@ -0,0 +1,24 @@
+/*
+ * 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.file;
+
+import org.gradle.api.tasks.WorkResult;
+
+/**
+ * @author Steve Appling
+ */
+public interface CopyAction extends CopySpec, WorkResult {
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/CopyProcessingSpec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/CopyProcessingSpec.java
new file mode 100644
index 0000000..16e4901
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/CopyProcessingSpec.java
@@ -0,0 +1,122 @@
+/*
+ * 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.file;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+
+import java.util.regex.Pattern;
+
+public interface CopyProcessingSpec extends ContentFilterable {
+    /**
+     * Specifies the destination directory for a copy. The destination is evaluated as for {@link
+     * org.gradle.api.Project#file(Object)}.
+     *
+     * @param destPath Path to the destination directory for a Copy
+     * @return this
+     */
+    CopyProcessingSpec into(Object destPath);
+
+    /**
+     * Renames a source file to a different relative location under the target directory. The closure will be called
+     * with a single parameter, the name of the file.  The closure should return a String object with a new target name.
+     * The closure may return null, in which case the original name will be used.
+     *
+     * @param closure rename closure
+     * @return this
+     */
+    CopyProcessingSpec rename(Closure closure);
+
+    /**
+     * Renames files based on a regular expression.  Uses java.util.regex type of regular expressions.  Note that the
+     * replace string should use the '$1' syntax to refer to capture groups in the source regular expression.  Files
+     * that do not match the source regular expression will be copied with the original name.
+     *
+     * <p> Example:
+     * <pre>
+     * rename '(.*)_OEM_BLUE_(.*)', '$1$2'
+     * </pre>
+     * would map the file 'style_OEM_BLUE_.css' to 'style.css'
+     *
+     * @param sourceRegEx Source regular expression
+     * @param replaceWith Replacement string (use $ syntax for capture groups)
+     * @return this
+     */
+    CopyProcessingSpec rename(String sourceRegEx, String replaceWith);
+
+    /**
+     * Renames files based on a regular expression. See {@link #rename(String, String)}.
+     *
+     * @param sourceRegEx Source regular expression
+     * @param replaceWith Replacement string (use $ syntax for capture groups)
+     * @return this
+     */
+    CopyProcessingSpec rename(Pattern sourceRegEx, String replaceWith);
+
+    /**
+     * Returns the Unix permissions to use for the target files. It is dependent on the copy action implementation
+     * whether these permissions will actually be applied.
+     *
+     * @return The file permissions.
+     */
+    int getFileMode();
+
+    /**
+     * Sets the Unix permissions to use for the target files. It is dependent on the copy action implementation whether
+     * these permissions will actually be applied.
+     *
+     * @param mode The file permissions.
+     * @return this
+     */
+    CopyProcessingSpec setFileMode(int mode);
+
+    /**
+     * Returns the Unix permissions to use for the target directories. It is dependent on the copy action implementation
+     * whether these permissions will actually be applied.
+     *
+     * @return The directory permissions.
+     */
+    int getDirMode();
+
+    /**
+     * Sets the Unix permissions to use for the target directories. It is dependent on the copy action implementation
+     * whether these permissions will actually be applied.
+     *
+     * @param mode The directory permissions.
+     * @return this
+     */
+    CopyProcessingSpec setDirMode(int mode);
+
+    /**
+     * Adds an action to be applied to each file as it about to be copied into its destination. The action can change
+     * the destination path of the file, filter the contents of the file, or exclude the file from the result entirely.
+     * Actions are executed in the order added, and are inherited from the parent spec.
+     *
+     * @param action The action to execute.
+     * @return this
+     */
+    CopyProcessingSpec eachFile(Action<? super FileCopyDetails> action);
+
+    /**
+     * Adds an action to be applied to each file as it about to be copied into its destination. The given closure is
+     * called with a {@link org.gradle.api.file.FileCopyDetails} as its parameter. Actions are executed in the order
+     * added, and are inherited from the parent spec.
+     *
+     * @param closure The action to execute.
+     * @return this
+     */
+    CopyProcessingSpec eachFile(Closure closure);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/CopySourceSpec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/CopySourceSpec.java
new file mode 100644
index 0000000..a1ac6f1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/CopySourceSpec.java
@@ -0,0 +1,42 @@
+/*
+ * 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.file;
+
+import groovy.lang.Closure;
+
+/**
+ * Specifies sources for a file copy
+ *
+ * @author Steve Appling
+ */
+public interface CopySourceSpec {
+    /**
+     * Specifies source files or directories for a copy. The given paths are evaluated as for {@link
+     * org.gradle.api.Project#files(Object...)}.
+     *
+     * @param sourcePaths Paths to source files for the copy
+     */
+    CopySourceSpec from(Object... sourcePaths);
+
+    /**
+     * Specifies the source files or directories for a copy and creates a child {@code CopySourceSpec}. The given source
+     * path is evaluated as for {@link org.gradle.api.Project#files(Object...)} .
+     *
+     * @param sourcePath Path to source for the copy
+     * @param configureClosure closure for configuring the child CopySourceSpec
+     */
+    CopySourceSpec from(Object sourcePath, Closure configureClosure);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/CopySpec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/CopySpec.java
new file mode 100644
index 0000000..872036c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/CopySpec.java
@@ -0,0 +1,236 @@
+/*
+ * 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 groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.tasks.util.PatternFilterable;
+import org.gradle.api.specs.Spec;
+
+import java.util.Map;
+import java.io.FilterReader;
+import java.util.regex.Pattern;
+
+/**
+ * A set of specifications for copying files.  This includes:
+ *
+ * <ul>
+ *
+ * <li>source directories (multiples allowed)
+ *
+ * <li>destination directory
+ *
+ * <li>ANT like include patterns
+ *
+ * <li>ANT like exclude patterns
+ *
+ * <li>File relocating rules
+ *
+ * <li>renaming rules
+ *
+ * <li>content filters
+ *
+ * </ul>
+ *
+ * CopySpecs may be nested by passing a closure to one of the from methods.  The closure creates a child CopySpec and
+ * delegates methods in the closure to the child. Child CopySpecs inherit any values specified in the parent. This
+ * allows constructs like:
+ * <pre>
+ * into('webroot')
+ * exclude('**/.svn/**')
+ * from('src/main/webapp') {
+ *    include '**/*.jsp'
+ * }
+ * from('src/main/js') {
+ *    include '**/*.js'
+ * }
+ * </pre>
+ *
+ * In this example, the <code>into</code> and <code>exclude</code> specifications at the root level are inherited by the
+ * two child CopySpecs.
+ *
+ * @author Steve Appling
+ * @see org.gradle.api.tasks.Copy Copy Task
+ * @see org.gradle.api.Project#copy(groovy.lang.Closure) Project.copy()
+ */
+public interface CopySpec extends CopySourceSpec, CopyProcessingSpec, PatternFilterable {
+    /**
+     * Returns true if this CopySpec uses case-sensitive pattern matching. The default is true.
+     *
+     * @return true for case-sensitive matching.
+     */
+    boolean isCaseSensitive();
+
+    /**
+     * Specifies whether case-sensitive pattern matching should be used for this CopySpec.
+     *
+     * @param caseSensitive true for case-sensitive matching.
+     */
+    void setCaseSensitive(boolean caseSensitive);
+
+    /**
+     * Adds the given specs as a child of this spec.
+     * @param sourceSpecs The specs to add
+     * @return this
+     */
+    CopySpec with(CopySpec... sourceSpecs);
+
+    // CopySourceSpec overrides to broaden return type
+
+    /**
+     * {@inheritDoc}
+     */
+    CopySpec from(Object... sourcePaths);
+
+    /**
+     * {@inheritDoc}
+     */
+    CopySpec from(Object sourcePath, Closure c);
+
+    // PatternFilterable overrides to broaden return type
+
+    /**
+     * {@inheritDoc}
+     *
+     * @see org.gradle.api.tasks.util.PatternFilterable Pattern Format
+     */
+    CopySpec setIncludes(Iterable<String> includes);
+
+    /**
+     * {@inheritDoc}
+     *
+     * @see org.gradle.api.tasks.util.PatternFilterable Pattern Format
+     */
+    CopySpec setExcludes(Iterable<String> excludes);
+
+    /**
+     * {@inheritDoc}
+     *
+     * @see org.gradle.api.tasks.util.PatternFilterable Pattern Format
+     */
+    CopySpec include(String... includes);
+
+    /**
+     * {@inheritDoc}
+     *
+     * @see org.gradle.api.tasks.util.PatternFilterable Pattern Format
+     */
+    CopySpec include(Iterable<String> includes);
+
+    /**
+     * {@inheritDoc}
+     *
+     * @see org.gradle.api.tasks.util.PatternFilterable Pattern Format
+     */
+    CopySpec include(Spec<FileTreeElement> includeSpec);
+
+    /**
+     * {@inheritDoc}
+     *
+     * @see org.gradle.api.tasks.util.PatternFilterable Pattern Format
+     */
+    CopySpec include(Closure includeSpec);
+
+    /**
+     * {@inheritDoc}
+     *
+     * @see org.gradle.api.tasks.util.PatternFilterable Pattern Format
+     */
+    CopySpec exclude(String... excludes);
+
+    /**
+     * {@inheritDoc}
+     *
+     * @see org.gradle.api.tasks.util.PatternFilterable Pattern Format
+     */
+    CopySpec exclude(Iterable<String> excludes);
+
+    /**
+     * {@inheritDoc}
+     *
+     * @see org.gradle.api.tasks.util.PatternFilterable Pattern Format
+     */
+    CopySpec exclude(Spec<FileTreeElement> excludeSpec);
+
+    /**
+     * {@inheritDoc}
+     *
+     * @see org.gradle.api.tasks.util.PatternFilterable Pattern Format
+     */
+    CopySpec exclude(Closure excludeSpec);
+
+    // CopyProcessingSpec overrides to broaden return type
+
+    /**
+     * {@inheritDoc}
+     */
+    CopySpec into(Object destPath);
+
+    /**
+     * Creates and configures a child {@code CopySpec} with the given destination path. The destination is evaluated as
+     * for {@link org.gradle.api.Project#file(Object)}.
+     *
+     * @param destPath Path to the destination directory for a Copy
+     * @param configureClosure The closure to use to configure the child {@code CopySpec}.
+     * @return this
+     */
+    CopySpec into(Object destPath, Closure configureClosure);
+
+    /**
+     * {@inheritDoc}
+     */
+    CopySpec rename(Closure closure);
+
+    /**
+     * {@inheritDoc}
+     */
+    CopySpec rename(String sourceRegEx, String replaceWith);
+
+    /**
+     * {@inheritDoc}
+     */
+    CopyProcessingSpec rename(Pattern sourceRegEx, String replaceWith);
+
+    /**
+     * {@inheritDoc}
+     */
+    CopySpec filter(Map<String, ?> properties, Class<? extends FilterReader> filterType);
+
+    /**
+     * {@inheritDoc}
+     */
+    CopySpec filter(Class<? extends FilterReader> filterType);
+
+    /**
+     * {@inheritDoc}
+     */
+    CopySpec filter(Closure closure);
+
+    /**
+     * {@inheritDoc}
+     */
+    CopySpec expand(Map<String, ?> properties);
+
+    /**
+     * {@inheritDoc}
+     */
+    CopySpec eachFile(Action<? super FileCopyDetails> action);
+
+    /**
+     * {@inheritDoc}
+     */
+    CopySpec eachFile(Closure closure);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/DeleteAction.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/DeleteAction.java
new file mode 100644
index 0000000..9d536cd
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/DeleteAction.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.api.file;
+
+/**
+ * @author Hans Dockter
+ */
+public interface DeleteAction {
+    /**
+     * Deletes files and directories.
+     *
+     * @param paths Any type of object accepted by {@link org.gradle.api.Project#files(Object...)}
+     * @return true if anything got deleted, false otherwise
+     */
+    boolean delete(Object... paths);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/EmptyFileVisitor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/EmptyFileVisitor.java
new file mode 100644
index 0000000..4c386df
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/EmptyFileVisitor.java
@@ -0,0 +1,29 @@
+/*
+ * 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.api.file;
+
+/**
+ * The EmptyFileVisitor can be extends by implementations that only require to implement one of the 2 visit methods
+ * (dir or file). This is just to limit the amount of code clutter when not both visit methods need to be implemented.
+ *
+ * @author Tom Eyckmans
+ */
+public class EmptyFileVisitor implements FileVisitor {
+
+    public void visitDir(FileVisitDetails dirDetails) { }
+
+    public void visitFile(FileVisitDetails fileDetails) { }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/FileCollection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/FileCollection.java
new file mode 100644
index 0000000..d3bcd06
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/FileCollection.java
@@ -0,0 +1,189 @@
+/*
+ * 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 groovy.lang.Closure;
+import org.gradle.api.Buildable;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.AntBuilderAware;
+import org.gradle.api.tasks.StopExecutionException;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * <p>A {@code FileCollection} represents a collection of files which you can query in certain ways. A file collection
+ * is often used to define a classpath, or to add files to a container.</p>
+ *
+ * <p>You can obtain a {@code FileCollection} instance using {@link org.gradle.api.Project#files}.</p>
+ */
+public interface FileCollection extends Iterable<File>, AntBuilderAware, Buildable {
+    /**
+     * Returns the content of this collection, asserting it contains exactly one file.
+     *
+     * @return The file.
+     * @throws IllegalStateException when this collection does not contain exactly one file.
+     */
+    File getSingleFile() throws IllegalStateException;
+
+    /**
+     * Returns the contents of this collection as a Set.
+     *
+     * @return The files. Returns an empty set if this collection is empty.
+     */
+    Set<File> getFiles();
+
+    /**
+     * Determines whether this collection contains the given file. Generally, this method is more efficient than calling
+     * {@code getFiles().contains(file)}.
+     *
+     * @param file The file to check for.
+     * @return true if this collection contains the given file, false otherwise.
+     */
+    boolean contains(File file);
+
+    /**
+     * Returns the contents of this collection as a platform-specific path. This can be used, for example, in an Ant
+     * <path> element.
+     *
+     * @return The path. Returns an empty string if this collection is empty.
+     */
+    String getAsPath();
+
+    /**
+     * <p>Returns a {@code FileCollection} which contains the union of this collection and the given collection. The
+     * returned collection is live, and tracks changes to both source collections.</p>
+     *
+     * <p>You can call this method in your build script using the {@code +} operator.</p>
+     *
+     * @param collection The other collection. Should not be null.
+     * @return A new collection containing the union.
+     */
+    FileCollection plus(FileCollection collection);
+
+    /**
+     * <p>Returns a {@code FileCollection} which contains the intersection of this collection and the given collection.
+     * The returned collection is live, and tracks changes to both source collections.</p>
+     *
+     * <p>You can call this method in your build script using the {@code -} operator.</p>
+     *
+     * @param collection The other collection. Should not be null.
+     * @return A new collection containing the intersection.
+     */
+    FileCollection minus(FileCollection collection);
+
+    /**
+     * <p>Restricts the contents of this collection to those files which match the given criteria. The filtered
+     * collection is live, so that it reflects any changes to this collection.</p>
+     *
+     * <p>The given closure is passed the File as a parameter, and should return a boolean value.</p>
+     *
+     * @param filterClosure The closure to use to select the contents of the filtered collection.
+     * @return The filtered collection.
+     */
+    FileCollection filter(Closure filterClosure);
+
+    /**
+     * <p>Restricts the contents of this collection to those files which match the given criteria. The filtered
+     * collection is live, so that it reflects any changes to this collection.</p>
+     *
+     * @param filterSpec The criteria to use to select the contents of the filtered collection.
+     * @return The filtered collection.
+     */
+    FileCollection filter(Spec<? super File> filterSpec);
+
+    /**
+     * <p>Converts this collection into an object of the specified type. Supported types are: {@code Collection}, {@code
+     * List}, {@code Set}, {@code Object[]}, {@code File[]}, {@code File}, and {@link FileTree}.</p>
+     *
+     * <p>You can call this method in your build script using the {@code as} operator.</p>
+     *
+     * @param type The type to convert to.
+     * @return The converted value.
+     * @throws UnsupportedOperationException When an unsupported type is specified.
+     */
+    Object asType(Class<?> type) throws UnsupportedOperationException;
+
+    /**
+     * <p>Adds another collection to this collection. This is an optional operation.</p>
+     *
+     * @param collection The collection to add.
+     * @return This
+     * @throws UnsupportedOperationException When this collection does not allow modification.
+     */
+    FileCollection add(FileCollection collection) throws UnsupportedOperationException;
+
+    /**
+     * Returns true if this collection is empty. Generally, calling this method is more efficient than calling {@code
+     * getFiles().isEmpty()}.
+     *
+     * @return true if this collection is empty, false otherwise.
+     */
+    boolean isEmpty();
+
+    /**
+     * Throws a {@link StopExecutionException} if this collection is empty.
+     *
+     * @return this
+     * @throws StopExecutionException When this collection is empty.
+     */
+    FileCollection stopExecutionIfEmpty() throws StopExecutionException;
+
+    /**
+     * Converts this collection to a {@link FileTree}. Generally, for each file in this collection, the resulting file
+     * tree will contain the source file at the root of the tree. For each directory in this collection, the resulting
+     * file tree will contain all the files under the source directory.
+     *
+     * @return this collection as a {@link FileTree}. Never returns null.
+     */
+    FileTree getAsFileTree();
+
+    enum AntType {
+        MatchingTask, FileSet, ResourceCollection
+    }
+
+    /**
+     * Adds this collection to an Ant task as a nested node. The given type determines how this collection is added:
+     *
+     * <ul>
+     *
+     * <li>{@link AntType#MatchingTask}: adds this collection to an Ant MatchingTask. The collection is converted to a
+     * set of source directories and include and exclude patterns. The source directories as added as an Ant Path with
+     * the given node name. The patterns are added using 'include' and 'exclude' nodes.</li>
+     *
+     * <li>{@link AntType#FileSet}: adds this collection as zero or more Ant FileSets with the given node name.</li>
+     *
+     * <li>{@link AntType#ResourceCollection}: adds this collection as zero or more Ant ResourceCollections with the
+     * given node name.</li>
+     *
+     * </ul>
+     *
+     * You should prefer using {@link AntType#ResourceCollection}, if the target Ant task supports it, as this is
+     * generally the most efficient. Using the other types may involve copying the contents of this collection to a
+     * temporary directory.
+     *
+     * @param builder The builder to add this collection to.
+     * @param nodeName The target node name.
+     * @param type The target Ant type
+     */
+    void addToAntBuilder(Object builder, String nodeName, AntType type);
+
+    /**
+     * Adds this collection to an Ant task as a nested node. Equivalent to calling {@code addToAntBuilder(builder,
+     *nodeName,AntType.ResourceCollection)}.
+     */
+    Object addToAntBuilder(Object builder, String nodeName);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/FileCopyDetails.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/FileCopyDetails.java
new file mode 100644
index 0000000..bd794b0
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/FileCopyDetails.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.api.file;
+
+/**
+ * <p>Provides details about a file or directory about to be copied, and allows some aspects of the destination file to
+ * be modified.</p>
+ *
+ * <p>Using this interface, you can change the destination path of the file, filter the content of the file, or exclude
+ * the file from the result entirely.</p>
+ */
+public interface FileCopyDetails extends FileTreeElement, ContentFilterable {
+    /**
+     * Excludes this file from the copy.
+     */
+    void exclude();
+
+    /**
+     * Sets the destination name of this file.
+     *
+     * @param name The name of this file.
+     */
+    void setName(String name);
+
+    /**
+     * Sets the destination path of this file.
+     *
+     * @param path The path of this file.
+     */
+    void setPath(String path);
+
+    /**
+     * Sets the destination path of this file.
+     *
+     * @param path the new path for this file.
+     */
+    void setRelativePath(RelativePath path);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/FileTree.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/FileTree.java
new file mode 100644
index 0000000..505da29
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/FileTree.java
@@ -0,0 +1,92 @@
+/*
+ * 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.file;
+
+import groovy.lang.Closure;
+import org.gradle.api.tasks.util.PatternFilterable;
+
+/**
+ * <p>A {@code FileTree} represents a hierarchy of files. It extends {@link FileCollection} to add hierarchy query and
+ * manipulation methods. You typically use a {@code FileTree} to represent files to copy or the contents of an
+ * archive.</p>
+ *
+ * <p>You can obtain a {@code FileTree} instance using {@link org.gradle.api.Project#fileTree(java.util.Map)},
+ * {@link org.gradle.api.Project#zipTree(Object)} or {@link org.gradle.api.Project#tarTree(Object)}.
+ * </p>
+ */
+public interface FileTree extends FileCollection {
+    /**
+     * <p>Restricts the contents of this tree to those files matching the given filter. The filtered tree is live, so
+     * that any changes to this tree are reflected in the filtered tree.</p>
+     *
+     * <p>The given closure is used to configure the filter. A {@link org.gradle.api.tasks.util.PatternFilterable} is
+     * passed to the closure as it's delegate. Only files which match the specified include patterns will be included in
+     * the filtered tree. Any files which match the specified exclude patterns will be excluded from the filtered
+     * tree.</p>
+     *
+     * @param filterConfigClosure the closure to use to configure the filter.
+     * @return The filtered tree.
+     */
+    FileTree matching(Closure filterConfigClosure);
+
+    /**
+     * <p>Restricts the contents of this tree to those files matching the given filter. The filtered tree is live, so
+     * that any changes to this tree are reflected in the filtered tree.</p>
+     *
+     * <p>The given pattern set is used to configure the filter. Only files which match the specified include patterns
+     * will be included in the filtered tree. Any files which match the specified exclude patterns will be excluded from
+     * the filtered tree.</p>
+     *
+     * @param patterns the pattern set to use to configure the filter.
+     * @return The filtered tree.
+     */
+    FileTree matching(PatternFilterable patterns);
+
+    /**
+     * Visits the files and directories in this file tree. Files are visited in breadth-wise order, so that a directory
+     * is visited before its children.
+     *
+     * @param visitor The visitor.
+     * @return this
+     */
+    FileTree visit(FileVisitor visitor);
+
+    /**
+     * Visits the files and directories in this file tree. Files are visited in breadth-wise order, so that a directory
+     * is visited before its children. The file/directory to be visited is passed to the given closure as a {@link
+     * FileVisitDetails}
+     *
+     * @param visitor The visitor.
+     * @return this
+     */
+    FileTree visit(Closure visitor);
+
+    /**
+     * Returns a {@code FileTree} which contains the union of this tree and the given tree. The returned tree is live,
+     * so that changes to either this tree or the other source tree are reflected in the returned tree.
+     *
+     * @param fileTree The tree. Should not be null.
+     * @return The union of this tree and the given tree.
+     */
+    FileTree plus(FileTree fileTree);
+
+    /**
+     * Returns this.
+     *
+     * @return this
+     */
+    FileTree getAsFileTree();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/FileTreeElement.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/FileTreeElement.java
new file mode 100644
index 0000000..17d6af0
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/FileTreeElement.java
@@ -0,0 +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.api.file;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Information about a file in a {@link FileTree}.
+ */
+public interface FileTreeElement {
+    /**
+     * Returns the file being visited.
+     *
+     * @return The file. Never returns null.
+     */
+    File getFile();
+
+    /**
+     * Returns true if this element is a directory, or false if this element is a regular file.
+     *
+     * @return true if this element is a directory.
+     */
+    boolean isDirectory();
+
+    /**
+     * Returns the last modified time of this file. Generally, calling this method is more performant than calling
+     * {@code getFile().lastModified()}
+     *
+     * @return The last modified time.
+     */
+    long getLastModified();
+
+    /**
+     * Returns the size of this file. Generally, calling this method is more performant than calling {@code
+     * getFile().length()}.
+     *
+     * @return The size, in bytes.
+     */
+    long getSize();
+
+    /**
+     * Opens this file as an input stream. Generally, calling this method is more performant than calling {@code new
+     * FileInputStream(getFile())}.
+     *
+     * @return The input stream. Never returns null. The caller is responsible for closing this stream.
+     */
+    InputStream open();
+
+    /**
+     * Copies the content of this file to an output stream. Generally, calling this method is more performant than
+     * calling {@code new FileInputStream(getFile())}.
+     *
+     * @param outstr The output stream to write to. The caller is responsible for closing this stream.
+     */
+    void copyTo(OutputStream outstr);
+
+    /**
+     * Copies this file to the given target file. Does not copy the file if the target is already a copy of this file.
+     *
+     * @param target the target file.
+     * @return true if this file was copied, false if it was up-to-date
+     */
+    boolean copyTo(File target);
+
+    /**
+     * Returns the base name of this file.
+     *
+     * @return The name. Never returns null.
+     */
+    String getName();
+
+    /**
+     * Returns the path of this file, relative to the root of the containing file tree. Always uses '/' as the hierarchy
+     * separator, regardless of platform file separator. Same as calling <code>getRelativePath().getPathString()</code>.
+     *
+     * @return The path. Never returns null.
+     */
+    String getPath();
+
+    /**
+     * Returns the path of this file, relative to the root of the containing file tree.
+     *
+     * @return The path. Never returns null.
+     */
+    RelativePath getRelativePath();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/FileVisitDetails.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/FileVisitDetails.java
new file mode 100644
index 0000000..cfc6fea
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/FileVisitDetails.java
@@ -0,0 +1,29 @@
+/*
+ * 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.file;
+
+/**
+ * Provides access to details about a file or directory being visited by a {@link FileVisitor}.
+ *
+ * @see FileTree#visit(groovy.lang.Closure)
+ */
+public interface FileVisitDetails extends FileTreeElement {
+
+    /**
+     * Requests that file visiting terminate after the current file.
+     */
+    void stopVisiting();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/FileVisitor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/FileVisitor.java
new file mode 100644
index 0000000..0972d74
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/FileVisitor.java
@@ -0,0 +1,37 @@
+/*
+ * 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.file;
+
+/**
+ * <p>A {@code FileVisitor} is used to visit each of the files in a {@link FileTree}.</p>
+ *
+ * @author Steve Appling
+ */
+public interface FileVisitor {
+    /**
+     * Visits a directory.
+     *
+     * @param dirDetails Meta-info about the directory.
+     */
+    public void visitDir(FileVisitDetails dirDetails);
+
+    /**
+     * Visits a file.
+     *
+     * @param fileDetails Meta-info about the file.
+     */
+    public void visitFile(FileVisitDetails fileDetails);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/RelativePath.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/RelativePath.java
new file mode 100644
index 0000000..f26671f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/RelativePath.java
@@ -0,0 +1,199 @@
+/*
+ * 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;
+
+    /**
+     * CTOR
+     *
+     * @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/gradle-core/src/main/groovy/org/gradle/api/file/SourceDirectorySet.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/SourceDirectorySet.java
new file mode 100644
index 0000000..55cb9dc
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/file/SourceDirectorySet.java
@@ -0,0 +1,71 @@
+/*
+ * 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.file;
+
+import org.gradle.api.tasks.util.PatternFilterable;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * <p>A {@code SourceDirectorySet} represents a set of source files composed from a set of source directories, along
+ * with associated include and exclude patterns.</p>
+ *
+ * TODO - configure includes/excludes for individual source dirs, and sync up with CopySpec
+ * TODO - allow add FileTree
+ */
+public interface SourceDirectorySet extends FileTree, PatternFilterable {
+
+    /**
+     * Adds the given source directory to this set.
+     *
+     * @param srcPath The source directory. This is evaluated as for {@link org.gradle.api.Project#file(Object)}
+     * @return this
+     */
+    SourceDirectorySet srcDir(Object srcPath);
+
+    /**
+     * Adds the given source directories to this set.
+     *
+     * @param srcPaths The source directories. These are evaluated as for {@link org.gradle.api.Project#files(Object...)}
+     * @return this
+     */
+    SourceDirectorySet srcDirs(Object... srcPaths);
+
+    /**
+     * Returns the source directories which make up this set.
+     *
+     * @return The source directories. Returns an empty set when this set contains no source directories.
+     */
+    Set<File> getSrcDirs();
+
+    /**
+     * Sets the source directories for this set.
+     *
+     * @param srcPaths The source directories. These are evaluated as for {@link org.gradle.api.Project#files(Object...)}
+     * @return this
+     */
+    SourceDirectorySet setSrcDirs(Iterable<Object> srcPaths);
+
+    /**
+     * Returns the filter used to select the source from the source directories. These filter patterns are applied after
+     * the include and exclude patterns of the source directory set itself. Generally, the filter patterns are used to
+     * select certain types of files, eg {@code *.java}.
+     *
+     * @return The filter patterns.
+     */
+    PatternFilterable getFilter();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/initialization/ProjectDescriptor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/initialization/ProjectDescriptor.java
new file mode 100644
index 0000000..cffbe5e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/initialization/ProjectDescriptor.java
@@ -0,0 +1,102 @@
+/*
+ * 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.initialization;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * <p>A {@code ProjectDescriptor} declares the configuration required to create and evaluate a {@link
+ * org.gradle.api.Project}.</p>
+ *
+ * <p> A {@code ProjectDescriptor} is created when you add a project to the build from the settings script, using {@link
+ * Settings#include(String[])} or {@link Settings#includeFlat(String[])}. You can access the descriptors using one of
+ * the lookup methods on the {@link Settings} object.</p>
+ *
+ * @author Hans Dockter
+ */
+public interface ProjectDescriptor {
+    /**
+     * Returns the name of this project.
+     *
+     * @return The name of the project. Never returns null.
+     */
+    String getName();
+
+    /**
+     * Sets the name of this project.
+     *
+     * @param name The new name for the project. Should not be null
+     */
+    void setName(String name);
+
+    /**
+     * Returns the project directory of this project.
+     *
+     * @return The project directory. Never returns null.
+     */
+    File getProjectDir();
+
+    /**
+     * Sets the project directory of this project.
+     *
+     * @param dir The new project directory. Should not be null.
+     */
+    void setProjectDir(File dir);
+
+    /**
+     * Returns the name of the build file for this project. This name is interpreted relative to the project
+     * directory.
+     *
+     * @return The build file name.
+     */
+    String getBuildFileName();
+
+    /**
+     * Sets the name of the build file. This name is interpreted relative to the project directory.
+     *
+     * @param name The build file name. Should not be null.
+     */
+    void setBuildFileName(String name);
+
+    /**
+     * Returns the build file for this project.
+     *
+     * @return The build file. Never returns null.
+     */
+    File getBuildFile();
+
+    /**
+     * Returns the parent of this project, if any. Returns null if this project is the root project.
+     *
+     * @return The parent, or null if this is the root project.
+     */
+    ProjectDescriptor getParent();
+
+    /**
+     * Returns the children of this project, if any.
+     *
+     * @return The children. Returns an empty set if this project does not have any children.
+     */
+    Set<ProjectDescriptor> getChildren();
+
+    /**
+     * Returns the path of this project. The path can be used as a unique identifier for this project.
+     *
+     * @return The path. Never returns null.
+     */
+    String getPath();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/initialization/Settings.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/initialization/Settings.java
new file mode 100644
index 0000000..e2189b6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/initialization/Settings.java
@@ -0,0 +1,176 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+import org.gradle.api.UnknownProjectException;
+import org.gradle.api.invocation.Gradle;
+
+import java.io.File;
+
+/**
+ * <p><code>Settings</code> declares the configuration required to instantiate and evaluate the hierarchy of {@link
+ * org.gradle.api.Project} instances which are to participate in a build.</p>
+ *
+ * <p>There is a one-to-one correspondence between a <code>Settings</code> instance and a <code>{@value
+ * #DEFAULT_SETTINGS_FILE}</code> settings file. Before Gradle assembles the projects for a build, it creates a
+ * <code>Settings</code> instance and executes the settings file against it.</p>
+ *
+ * <h3>Assembling a Multi-Project Build</h3>
+ *
+ * <p>One of the purposes of the <code>Settings</code> object is to allow you to declare the projects which are to be
+ * included in the build. You add projects to the build using the {@link #include(String[])} method.  There is always a
+ * root project included in a build.  It is added automatically when the <code>Settings</code> object is created.  The
+ * root project's name defaults to the name of the directory containing the settings file. The root project's project
+ * 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>
+ *
+ * <h3>Using Settings in a Settings File</h3>
+ *
+ * <h4>Dynamic Properties</h4>
+ *
+ * <p>In addition to the properties of this interface, the {@code Settings} object makes some additional read-only
+ * properties available to the settings script. This includes properties from the following sources:</p>
+ *
+ * <ul>
+ *
+ * <li>Defined in the {@value org.gradle.api.Project#GRADLE_PROPERTIES} file located in the settings directory of the
+ * build.</li>
+ *
+ * <li>Defined the {@value org.gradle.api.Project#GRADLE_PROPERTIES} file located in the user's {@code .gradle}
+ * directory.</li>
+ *
+ * <li>Provided on the command-line using the -P option.</li>
+ *
+ * </ul>
+ *
+ * @author Hans Dockter
+ */
+public interface Settings {
+    /**
+     * <p>The default name for the settings file.</p>
+     */
+    String DEFAULT_SETTINGS_FILE = "settings.gradle";
+
+    /**
+     * <p>Adds the given projects to the build. Each path in the supplied list is treated as the path of a project to
+     * add to the build. Note that these path are not file paths, but instead specify the location of the new project in
+     * the project heirarchy. As such, the supplied paths must use the ':' character as separator.</p>
+     *
+     * <p>The last element of the supplied path is used as the project name. The supplied path is converted to a project
+     * directory relative to the root project directory.</p>
+     *
+     * <p>As an example, the path {@code a:b} adds a project with path {@code :a:b}, name {@code b} and project
+     * directory {@code $rootDir/a/b}.</p>
+     *
+     * @param projectPaths the projects to add.
+     */
+    void include(String[] projectPaths);
+
+    /**
+     * <p>Adds the given projects to the build. Each name in the supplied list is treated as the name of a project to
+     * add to the build.</p>
+     *
+     * <p>The supplied name is converted to a project directory relative to the <em>parent</em> directory of the root
+     * project directory.</p>
+     *
+     * <p>As an example, the name {@code a} add a project with path {@code :a}, name {@code a} and project directory
+     * {@code $rootDir/../a}.</p>
+     *
+     * @param projectNames the projects to add.
+     */
+    void includeFlat(String[] projectNames);
+
+    /**
+     * <p>Returns this settings object.</p>
+     *
+     * @return This settings object. Never returns null.
+     */
+    Settings getSettings();
+
+    /**
+     * <p>Returns the settings directory of the build. The settings directory is the directory containing the settings
+     * file.</p>
+     *
+     * @return The settings directory. Never returns null.
+     */
+    File getSettingsDir();
+
+    /**
+     * <p>Returns the root directory of the build. The root directory is the project directory of the root project.</p>
+     *
+     * @return The root directory. Never returns null.
+     */
+    File getRootDir();
+
+    /**
+     * <p>Returns the root project of the build.</p>
+     *
+     * @return The root project. Never returns null.
+     */
+    ProjectDescriptor getRootProject();
+
+    /**
+     * <p>Returns the project with the given path.</p>
+     *
+     * @param path The path.
+     * @return The project with the given path. Never returns null.
+     * @throws UnknownProjectException If no project with the given path exists.
+     */
+    ProjectDescriptor project(String path) throws UnknownProjectException;
+
+    /**
+     * <p>Returns the project with the given path.</p>
+     *
+     * @param path The path
+     * @return The project with the given path. Returns null if no such project exists.
+     */
+    ProjectDescriptor findProject(String path);
+
+    /**
+     * <p>Returns the project with the given project directory.</p>
+     *
+     * @param projectDir The project directory.
+     * @return The project with the given project directory. Never returns null.
+     * @throws UnknownProjectException If no project with the given path exists.
+     */
+    ProjectDescriptor project(File projectDir) throws UnknownProjectException;
+
+    /**
+     * <p>Returns the project with the given project directory.</p>
+     *
+     * @param projectDir The project directory.
+     * @return The project with the given project directory. Returns null if no such project exists.
+     */
+    ProjectDescriptor findProject(File projectDir);
+
+    /**
+     * <p>Returns the set of parameters used to invoke this instance of Gradle.</p>
+     *
+     * @return The parameters. Never returns null.
+     */
+    StartParameter getStartParameter();
+
+    /**
+     * Returns the {@link Gradle} instance for the current build.
+     * 
+     * @return The Gradle instance. Never returns null.
+     */
+    Gradle getGradle();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/initialization/dsl/ScriptHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/initialization/dsl/ScriptHandler.java
new file mode 100644
index 0000000..a35d4c3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/initialization/dsl/ScriptHandler.java
@@ -0,0 +1,109 @@
+/*
+ * 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.initialization.dsl;
+
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.artifacts.dsl.DependencyHandler;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import groovy.lang.Closure;
+
+import java.io.File;
+import java.net.URI;
+
+/**
+ * <p>A {@code ScriptHandler} allows you to manage the compilation and execution of a build script. You can declare the
+ * classpath used to compile and execute a build script. This classpath is also used to load the plugins which the build
+ * script uses.</p>
+ *
+ * <p>You can obtain a {@code ScriptHandler} instance using {@link org.gradle.api.Project#getBuildscript()} or {@link
+ * org.gradle.api.Script#getBuildscript()}.</p>
+ *
+ * <p>To declare the script classpath, you use the {@link org.gradle.api.artifacts.dsl.DependencyHandler} provided by
+ * {@link #getDependencies()} to attach dependencies to the {@value #CLASSPATH_CONFIGURATION} configuration. These
+ * dependencies are resolved just prior to script compilation, and assembled into the classpath for the script.</p>
+ *
+ * <p>For most external dependencies you will also need to declare one or more repositories where the dependencies can
+ * be found, using the {@link org.gradle.api.artifacts.dsl.RepositoryHandler} provided by {@link
+ * #getRepositories()}.</p>
+ */
+public interface ScriptHandler {
+    /**
+     * The name of the configuration used to assemble the script classpath.
+     */
+    String CLASSPATH_CONFIGURATION = "classpath";
+
+    /**
+     * Returns the file containing the source for the script, if any.
+     *
+     * @return The source file. Returns null if the script source is not a file.
+     */
+    File getSourceFile();
+
+    /**
+     * Returns the URI for the script source, if any.
+     *
+     * @return The source URI. Returns null if the script source has no URI.
+     */
+    URI getSourceURI();
+
+    /**
+     * Returns a handler to create repositories which are used for retrieving dependencies for the script classpath.
+     *
+     * @return the repository handler. Never returns null.
+     */
+    RepositoryHandler getRepositories();
+
+    /**
+     * Configures the repositories for the script dependencies. Executes the given closure against the {@link
+     * RepositoryHandler} for this handler. The {@link RepositoryHandler} is passed to the closure as the closure's
+     * delegate.
+     *
+     * @param configureClosure the closure to use to configure the repositories.
+     */
+    void repositories(Closure configureClosure);
+
+    /**
+     * Returns the dependencies of the script. The returned dependency handler instance can be used for adding new
+     * dependencies. For accessing already declared dependencies, the configurations can be used.
+     *
+     * @return the dependency handler. Never returns null.
+     * @see #getConfigurations()
+     */
+    DependencyHandler getDependencies();
+
+    /**
+     * Configures the dependencies for the script. Executes the given closure against the {@link DependencyHandler} for
+     * this handler. The {@link DependencyHandler} is passed to the closure as the closure's delegate.
+     *
+     * @param configureClosure the closure to use to configure the dependencies.
+     */
+    void dependencies(Closure configureClosure);
+
+    /**
+     * Returns the configurations of this handler. This usually contains a single configuration, called {@value
+     * #CLASSPATH_CONFIGURATION}.
+     *
+     * @return The configuration of this handler.
+     */
+    ConfigurationContainer getConfigurations();
+
+    /**
+     * Returns the {@code ClassLoader} which contains the classpath for this script.
+     *
+     * @return The ClassLoader. Never returns null.
+     */
+    ClassLoader getClassLoader();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/initialization/package-info.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/initialization/package-info.java
new file mode 100644
index 0000000..993d7bb
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/initialization/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes for managing and monitoring build initialization.
+ */
+package org.gradle.api.initialization;
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AbstractClassGenerator.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AbstractClassGenerator.java
new file mode 100644
index 0000000..0519ca1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AbstractClassGenerator.java
@@ -0,0 +1,159 @@
+/*
+ * 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.*;
+import org.gradle.api.GradleException;
+import org.gradle.util.ReflectionUtil;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class AbstractClassGenerator implements ClassGenerator {
+    private static final Map<Class, Map<Class, Class>> GENERATED_CLASSES = new HashMap<Class, Map<Class, Class>>();
+
+    public <T> T newInstance(Class<T> type, Object... parameters) {
+        return type.cast(ReflectionUtil.newInstance(generate(type), parameters));
+    }
+
+    public <T> Class<? extends T> generate(Class<T> type) {
+        Map<Class, Class> cache = GENERATED_CLASSES.get(getClass());
+        if (cache == null) {
+            cache = new HashMap<Class, Class>();
+            GENERATED_CLASSES.put(getClass(), cache);
+        }
+        Class generatedClass = cache.get(type);
+        if (generatedClass != null) {
+            return generatedClass;
+        }
+
+        if (Modifier.isPrivate(type.getModifiers())) {
+            throw new GradleException(String.format("Cannot create a proxy class for private class '%s'.",
+                    type.getSimpleName()));
+        }
+        if (Modifier.isAbstract(type.getModifiers())) {
+            throw new GradleException(String.format("Cannot create a proxy class for abstract class '%s'.",
+                    type.getSimpleName()));
+        }
+
+        Class<? extends T> subclass;
+        try {
+            ClassBuilder<T> builder = start(type);
+
+            boolean isConventionAware = type.getAnnotation(NoConventionMapping.class) == null;
+            boolean isDynamicAware = type.getAnnotation(NoDynamicObject.class) == null;
+
+            builder.startClass(isConventionAware, isDynamicAware);
+
+            if (isDynamicAware && !DynamicObjectAware.class.isAssignableFrom(type)) {
+                builder.mixInDynamicAware();
+            }
+            if (isDynamicAware && !GroovyObject.class.isAssignableFrom(type)) {
+                builder.mixInGroovyObject();
+            }
+            if (isDynamicAware) {
+                builder.addDynamicMethods();
+            }
+            if (isConventionAware && !IConventionAware.class.isAssignableFrom(type)) {
+                builder.mixInConventionAware();
+            }
+
+            Class noMappingClass = Object.class;
+            for (Class<?> c = type; c != null && noMappingClass == Object.class; c = c.getSuperclass()) {
+                if (c.getAnnotation(NoConventionMapping.class) != null) {
+                    noMappingClass = c;
+                }
+            }
+
+            Collection<String> skipProperties = Arrays.asList("metaClass", "conventionMapping", "convention", "asDynamicObject");
+
+            MetaClass metaClass = GroovySystem.getMetaClassRegistry().getMetaClass(type);
+            for (MetaProperty property : metaClass.getProperties()) {
+                if (skipProperties.contains(property.getName())) {
+                    continue;
+                }
+                if (property instanceof MetaBeanProperty) {
+                    MetaBeanProperty metaBeanProperty = (MetaBeanProperty) property;
+                    MetaMethod getter = metaBeanProperty.getGetter();
+                    if (getter == null) {
+                        continue;
+                    }
+                    if (Modifier.isFinal(getter.getModifiers()) || Modifier.isPrivate(getter.getModifiers())) {
+                        continue;
+                    }
+                    if (getter.getReturnType().isPrimitive()) {
+                        continue;
+                    }
+                    Class declaringClass = getter.getDeclaringClass().getTheClass();
+                    if (declaringClass.isAssignableFrom(noMappingClass)) {
+                        continue;
+                    }
+                    builder.addGetter(metaBeanProperty);
+
+                    MetaMethod setter = metaBeanProperty.getSetter();
+                    if (setter == null) {
+                        continue;
+                    }
+                    if (Modifier.isFinal(setter.getModifiers()) || Modifier.isPrivate(setter.getModifiers())) {
+                        continue;
+                    }
+
+                    builder.addSetter(metaBeanProperty);
+                }
+            }
+
+            for (Constructor<?> constructor : type.getConstructors()) {
+                if (Modifier.isPublic(constructor.getModifiers())) {
+                    builder.addConstructor(constructor);
+                }
+            }
+
+            subclass = builder.generate();
+        } catch (Throwable e) {
+            throw new GradleException(String.format("Could not generate a proxy class for class %s.", type.getName()), e);
+        }
+
+        cache.put(type, subclass);
+        return subclass;
+    }
+
+    protected abstract <T> ClassBuilder<T> start(Class<T> type);
+
+    protected interface ClassBuilder<T> {
+        void startClass(boolean isConventionAware, boolean isDynamicAware);
+
+        void addConstructor(Constructor<?> constructor) throws Exception;
+
+        void mixInDynamicAware() throws Exception;
+
+        void mixInConventionAware() throws Exception;
+
+        void mixInGroovyObject() throws Exception;
+
+        void addDynamicMethods() throws Exception;
+
+        void addGetter(MetaBeanProperty property) throws Exception;
+
+        void addSetter(MetaBeanProperty property) throws Exception;
+
+        Class<? extends T> generate() throws Exception;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AbstractClassPathProvider.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AbstractClassPathProvider.java
new file mode 100644
index 0000000..f461074
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AbstractClassPathProvider.java
@@ -0,0 +1,157 @@
+/*
+ * 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 org.gradle.api.GradleException;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.util.ClasspathUtil;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.*;
+import java.util.regex.Pattern;
+
+public abstract class AbstractClassPathProvider implements ClassPathProvider, GradleDistributionLocator {
+    private final List<Pattern> all = Arrays.asList(Pattern.compile(".+"));
+    private final Map<String, List<Pattern>> classPaths = new HashMap<String, List<Pattern>>();
+    private final Scanner pluginLibs;
+    private final Scanner runtimeLibs;
+    private final File gradleHome;
+
+    protected AbstractClassPathProvider() {
+        File codeSource = findThisClass();
+        if (codeSource.isFile()) {
+            // Loaded from a JAR - assume we're running from the distribution
+            gradleHome = codeSource.getParentFile().getParentFile();
+            runtimeLibs = new DirScanner(new File(gradleHome + "/lib"));
+            pluginLibs = new DirScanner(new File(gradleHome + "/lib/plugins"));
+        } else {
+            // Loaded from a classes dir - assume we're running from the ide or tests
+            gradleHome = null;
+            runtimeLibs = new ClassPathScanner(codeSource);
+            pluginLibs = runtimeLibs;
+        }
+    }
+
+    public File getGradleHome() {
+        return gradleHome;
+    }
+
+    protected void add(String name, List<Pattern> patterns) {
+        classPaths.put(name, patterns);
+    }
+
+    protected static List<Pattern> toPatterns(String... patternStrings) {
+        List<Pattern> patterns = new ArrayList<Pattern>();
+        for (String patternString : patternStrings) {
+            patterns.add(Pattern.compile(patternString + "-.+"));
+        }
+        return patterns;
+    }
+
+    public Set<File> findClassPath(String name) {
+        Set<File> matches = new LinkedHashSet<File>();
+        if (name.equals("GRADLE_PLUGINS")) {
+            pluginLibs.find(all, matches);
+            return matches;
+        }
+        if (name.equals("GRADLE_RUNTIME")) {
+            runtimeLibs.find(all, matches);
+            return matches;
+        }
+        List<Pattern> classPathPatterns = classPaths.get(name);
+        if (classPathPatterns != null) {
+            runtimeLibs.find(classPathPatterns, matches);
+            pluginLibs.find(classPathPatterns, matches);
+            return matches;
+        }
+        return null;
+    }
+
+    private File findThisClass() {
+        URI location;
+        try {
+            location = DefaultClassPathProvider.class.getProtectionDomain().getCodeSource().getLocation().toURI();
+        } catch (URISyntaxException e) {
+            throw new UncheckedIOException(e);
+        }
+        if (!location.getScheme().equals("file")) {
+            throw new GradleException(String.format("Cannot determine Gradle home using codebase '%s'.", location));
+        }
+        return new File(location.getPath());
+    }
+
+    private static boolean matches(Iterable<Pattern> patterns, String name) {
+        for (Pattern pattern : patterns) {
+            if (pattern.matcher(name).matches()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private interface Scanner {
+        void find(Iterable<Pattern> patterns, Collection<File> into);
+    }
+
+    private static class DirScanner implements Scanner {
+        private final File dir;
+
+        private DirScanner(File dir) {
+            this.dir = dir;
+        }
+
+        public void find(Iterable<Pattern> patterns, Collection<File> into) {
+            for (File file : dir.listFiles()) {
+                if (matches(patterns, file.getName())) {
+                    into.add(file);
+                }
+            }
+        }
+    }
+
+    // This is used when running from the IDE or junit tests
+    private static class ClassPathScanner implements Scanner {
+        private final File classesDir;
+        private final Collection<URL> classpath;
+
+        private ClassPathScanner(File classesDir) {
+            this.classesDir = classesDir;
+            this.classpath = ClasspathUtil.getClasspath(getClass().getClassLoader());
+        }
+
+        public void find(Iterable<Pattern> patterns, Collection<File> into) {
+            if (matches(patterns, "gradle-core-version.jar")) {
+                into.add(classesDir);
+            }
+            for (URL url : classpath) {
+                if (url.getProtocol().equals("file")) {
+                    try {
+                        File file = new File(url.toURI());
+                        if (matches(patterns, file.getName())) {
+                            into.add(file);
+                        }
+                    } catch (URISyntaxException e) {
+                        throw new UncheckedIOException(e);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AbstractDomainObjectCollection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AbstractDomainObjectCollection.java
new file mode 100644
index 0000000..74b8077
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AbstractDomainObjectCollection.java
@@ -0,0 +1,136 @@
+/*
+ * 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 groovy.lang.Closure;
+import org.codehaus.groovy.runtime.DefaultGroovyMethods;
+import org.gradle.api.Action;
+import org.gradle.api.DomainObjectCollection;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+
+import java.util.*;
+
+public abstract class AbstractDomainObjectCollection<T> implements DomainObjectCollection<T> {
+    private final Store<T> store;
+
+    protected AbstractDomainObjectCollection(Store<T> store) {
+        this.store = store;
+    }
+
+    public Set<T> getAll() {
+        return new LinkedHashSet<T>(store.getAll());
+    }
+
+    public Set<T> findAll(Spec<? super T> spec) {
+        return Specs.filterIterable(store.getAll(), spec);
+    }
+
+    public Iterator<T> iterator() {
+        return getAll().iterator();
+    }
+
+    public void allObjects(Action<? super T> action) {
+        whenObjectAdded(action);
+        for (T t : new ArrayList<T>(store.getAll())) {
+            action.execute(t);
+        }
+    }
+
+    public void allObjects(Closure action) {
+        allObjects(toAction(action));
+    }
+
+    public Action<? super T> whenObjectAdded(Action<? super T> action) {
+        store.objectAdded(action);
+        return action;
+    }
+
+    public Action<? super T> whenObjectRemoved(Action<? super T> action) {
+        store.objectRemoved(action);
+        return action;
+    }
+
+    public void whenObjectAdded(Closure action) {
+        whenObjectAdded(toAction(action));
+    }
+
+    private Action<? super T> toAction(Closure action) {
+        return (Action<? super T>) DefaultGroovyMethods.asType(action, Action.class);
+    }
+
+    protected interface Store<S> {
+        Collection<? extends S> getAll();
+
+        void objectAdded(Action<? super S> action);
+
+        void objectRemoved(Action<? super S> action);
+    }
+
+    protected static class FilteredStore<S> implements Store<S> {
+        private final Store<? super S> store;
+        private final Class<S> type;
+        private final Spec<? super S> spec;
+
+        public FilteredStore(Store<? super S> store, Class<S> type, Spec<? super S> spec) {
+            this.store = store;
+            this.type = type;
+            this.spec = spec;
+        }
+
+        public Collection<? extends S> getAll() {
+            List<S> values = new ArrayList<S>();
+            for (Object s : store.getAll()) {
+                S filtered = filter(s);
+                if (filtered != null) {
+                    values.add(filtered);
+                }
+            }
+            return values;
+        }
+
+        public void objectAdded(Action<? super S> action) {
+            store.objectAdded(filter(action));
+        }
+
+        public void objectRemoved(Action<? super S> action) {
+            store.objectRemoved(filter(action));
+        }
+
+        protected S filter(Object object) {
+            if (!type.isInstance(object)) {
+                return null;
+            }
+            S s = type.cast(object);
+            if (!spec.isSatisfiedBy(s)) {
+                return null;
+            }
+            return s;
+        }
+
+        protected Action<Object> filter(final Action<? super S> action) {
+            return new Action<Object>() {
+                public void execute(Object object) {
+                    S s = filter(object);
+                    if (s != null) {
+                        action.execute(s);
+                    }
+                }
+            };
+        }
+    }
+
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AbstractDynamicObject.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AbstractDynamicObject.java
new file mode 100644
index 0000000..c488693
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AbstractDynamicObject.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 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;
+
+import groovy.lang.*;
+
+import java.util.Map;
+import java.util.Collections;
+import java.util.Arrays;
+
+/**
+ * An empty {@link DynamicObject}.
+ */
+public abstract class AbstractDynamicObject implements DynamicObject {
+    protected abstract String getDisplayName();
+
+    public boolean hasProperty(String name) {
+        return false;
+    }
+
+    public Object getProperty(String name) throws MissingPropertyException {
+        throw propertyMissingException(name);
+    }
+
+    public void setProperty(String name, Object value) throws MissingPropertyException {
+        throw propertyMissingException(name);
+    }
+
+    protected MissingPropertyException propertyMissingException(String name) {
+        throw new MissingPropertyException(String.format("Could not find property '%s' on %s.", name,
+                getDisplayName()));
+    }
+
+    public Map<String, ?> getProperties() {
+        return Collections.emptyMap();
+    }
+
+    public boolean hasMethod(String name, Object... arguments) {
+        return false;
+    }
+
+    public Object invokeMethod(String name, Object... arguments) throws groovy.lang.MissingMethodException {
+        throw methodMissingException(name, arguments);
+    }
+
+    protected groovy.lang.MissingMethodException methodMissingException(String name, Object... params) {
+        return new MissingMethodException(this, getDisplayName(), name, params);
+    }
+}
+
+class MissingMethodException extends groovy.lang.MissingMethodException {
+    private final DynamicObject target;
+    private final String displayName;
+
+    public MissingMethodException(DynamicObject target, String displayName, String name, Object... arguments) {
+        super(name, null, arguments);
+        this.target = target;
+        this.displayName = displayName;
+    }
+
+    public DynamicObject getTarget() {
+        return target;
+    }
+
+    public String getMessage() {
+        return String.format("Could not find method %s() for arguments %s on %s.", getMethod(), Arrays.toString(
+                getArguments()), displayName);
+    }
+}
+
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AbstractTask.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AbstractTask.java
new file mode 100644
index 0000000..42f4f83
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AbstractTask.java
@@ -0,0 +1,457 @@
+/*
+ * 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.MissingPropertyException;
+import org.codehaus.groovy.runtime.InvokerInvocationException;
+import org.gradle.api.*;
+import org.gradle.api.internal.file.TemporaryFileProvider;
+import org.gradle.api.internal.plugins.DefaultConvention;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ServiceRegistry;
+import org.gradle.api.internal.tasks.DefaultTaskDependency;
+import org.gradle.api.internal.tasks.TaskDependencyInternal;
+import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.api.internal.tasks.TaskStateInternal;
+import org.gradle.api.logging.*;
+import org.gradle.api.plugins.Convention;
+import org.gradle.api.specs.AndSpec;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.api.tasks.TaskInputs;
+import org.gradle.api.tasks.TaskState;
+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;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractTask implements TaskInternal, DynamicObjectAware {
+    private static Logger buildLogger = Logging.getLogger(Task.class);
+    private static ThreadLocal<TaskInfo> nextInstance = new ThreadLocal<TaskInfo>();
+    private ProjectInternal project;
+
+    private String name;
+
+    private List<Action<? super Task>> actions = new ArrayList<Action<? super Task>>();
+
+    private String path;
+
+    private boolean enabled = true;
+
+    private DefaultTaskDependency dependencies;
+
+    private DynamicObjectHelper dynamicObjectHelper;
+
+    private String description;
+
+    private String group;
+
+    private AndSpec<Task> onlyIfSpec = new AndSpec<Task>(createNewOnlyIfSpec());
+
+    private final TaskOutputsInternal outputs;
+
+    private final TaskInputs inputs;
+
+    private TaskExecuter executer;
+
+    private final ServiceRegistry services;
+
+    private final TaskStateInternal state;
+
+    private final LoggingManagerInternal loggingManager;
+
+    protected AbstractTask() {
+        this(taskInfo());
+    }
+
+    private static TaskInfo taskInfo() {
+        TaskInfo taskInfo = nextInstance.get();
+        assert taskInfo != null;
+        return taskInfo;
+    }
+
+    private AbstractTask(TaskInfo taskInfo) {
+        this.project = taskInfo.project;
+        this.name = taskInfo.name;
+        assert project != null;
+        assert name != null;
+        path = project.absolutePath(name);
+        state = new TaskStateInternal(toString());
+        dynamicObjectHelper = new DynamicObjectHelper(this, new DefaultConvention());
+        dependencies = new DefaultTaskDependency(project.getTasks());
+        services = project.getServiceRegistryFactory().createFor(this);
+        outputs = services.get(TaskOutputsInternal.class);
+        inputs = services.get(TaskInputs.class);
+        executer = services.get(TaskExecuter.class);
+        loggingManager = services.get(LoggingManagerInternal.class);
+    }
+
+    public static <T extends Task> T injectIntoNewInstance(ProjectInternal project, String name, Callable<T> factory) {
+        nextInstance.set(new TaskInfo(project, name));
+        try {
+            try {
+                return factory.call();
+            } catch (RuntimeException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        } finally {
+            nextInstance.set(null);
+        }
+    }
+
+    public TaskState getState() {
+        return state;
+    }
+
+    public AntBuilder getAnt() {
+        return project.getAnt();
+    }
+
+    public Project getProject() {
+        return project;
+    }
+
+    public void setProject(Project project) {
+        this.project = (ProjectInternal) project;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public List<Action<? super Task>> getActions() {
+        return actions;
+    }
+
+    public void setActions(List<Action<? super Task>> actions) {
+        this.actions = actions;
+    }
+
+    public TaskDependencyInternal getTaskDependencies() {
+        return dependencies;
+    }
+
+    public Set<Object> getDependsOn() {
+        return dependencies.getValues();
+    }
+
+    public void setDependsOn(Iterable<?> dependsOn) {
+        dependencies.setValues(dependsOn);
+    }
+
+    public void onlyIf(Closure onlyIfClosure) {
+        this.onlyIfSpec = this.onlyIfSpec.and(onlyIfClosure);
+    }
+
+    public void onlyIf(Spec<? super Task> onlyIfSpec) {
+        this.onlyIfSpec = this.onlyIfSpec.and(onlyIfSpec);
+    }
+
+    public void setOnlyIf(Spec<? super Task> spec) {
+        onlyIfSpec = createNewOnlyIfSpec().and(spec);
+    }
+
+    public void setOnlyIf(Closure onlyIfClosure) {
+        onlyIfSpec = createNewOnlyIfSpec().and(onlyIfClosure);
+    }
+
+    private AndSpec<Task> createNewOnlyIfSpec() {
+        return new AndSpec<Task>(new Spec<Task>() {
+            public boolean isSatisfiedBy(Task element) {
+                return element == AbstractTask.this && enabled;
+            }
+        });
+    }
+
+    public Spec<? super TaskInternal> getOnlyIf() {
+        return onlyIfSpec;
+    }
+
+    public boolean getDidWork() {
+        return state.getDidWork();
+    }
+
+    public void setDidWork(boolean didWork) {
+        state.setDidWork(didWork);
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public boolean getEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public Task deleteAllActions() {
+        actions.clear();
+        return this;
+    }
+
+    public void execute() {
+        executer.execute(this, state);
+        state.rethrowFailure();
+    }
+
+    public TaskExecuter getExecuter() {
+        return executer;
+    }
+
+    public void setExecuter(TaskExecuter executer) {
+        this.executer = executer;
+    }
+
+    public Task dependsOn(Object... paths) {
+        dependencies.add(paths);
+        return this;
+    }
+
+    public Task doFirst(Action<? super Task> action) {
+        if (action == null) {
+            throw new InvalidUserDataException("Action must not be null!");
+        }
+        actions.add(0, action);
+        return this;
+    }
+
+    public Task doLast(Action<? super Task> action) {
+        if (action == null) {
+            throw new InvalidUserDataException("Action must not be null!");
+        }
+        actions.add(action);
+        return this;
+    }
+
+    public boolean equals(Object other) {
+        if (other == this) {
+            return true;
+        }
+        if (other == null || other.getClass() != getClass()) {
+            return false;
+        }
+        AbstractTask otherTask = (AbstractTask) other;
+        return getPath().equals(otherTask.getPath());
+    }
+
+    public int hashCode() {
+        return path.hashCode();
+    }
+
+    public int compareTo(Task otherTask) {
+        int depthCompare = project.compareTo(otherTask.getProject());
+        if (depthCompare == 0) {
+            return getPath().compareTo(otherTask.getPath());
+        } else {
+            return depthCompare;
+        }
+    }
+
+    public String toString() {
+        return String.format("task '%s'", path);
+    }
+
+    public Logger getLogger() {
+        return buildLogger;
+    }
+
+    public Task disableStandardOutputCapture() {
+        DeprecationLogger.nagUser("Task.disableStandardOutputCapture()");
+        loggingManager.disableStandardOutputCapture();
+        return this;
+    }
+
+    public Task captureStandardOutput(LogLevel level) {
+        DeprecationLogger.nagUser("Task.captureStandardOutput()", "getLogging().captureStandardOutput()");
+        loggingManager.captureStandardOutput(level);
+        return this;
+    }
+
+    public LoggingManager getLogging() {
+        return loggingManager;
+    }
+
+    public StandardOutputCapture getStandardOutputCapture() {
+        return loggingManager;
+    }
+
+    public Map<String, Object> getAdditionalProperties() {
+        return dynamicObjectHelper.getAdditionalProperties();
+    }
+
+    public DynamicObjectHelper getDynamicObjectHelper() {
+        return dynamicObjectHelper;
+    }
+
+    public Object property(String propertyName) throws MissingPropertyException {
+        return dynamicObjectHelper.getProperty(propertyName);
+    }
+
+    public boolean hasProperty(String propertyName) {
+        return dynamicObjectHelper.hasProperty(propertyName);
+    }
+
+    public void setProperty(String name, Object value) {
+        dynamicObjectHelper.setProperty(name, value);
+    }
+
+    public Convention getConvention() {
+        return dynamicObjectHelper.getConvention();
+    }
+
+    public void setConvention(Convention convention) {
+        dynamicObjectHelper.setConvention(convention);
+    }
+
+    public DynamicObject getAsDynamicObject() {
+        return dynamicObjectHelper;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public void setGroup(String group) {
+        this.group = group;
+    }
+
+    public TaskInputs getInputs() {
+        return inputs;
+    }
+
+    public TaskOutputsInternal getOutputs() {
+        return outputs;
+    }
+
+    protected ServiceRegistry getServices() {
+        return services;
+    }
+
+    public boolean dependsOnTaskDidWork() {
+        TaskDependency dependency = getTaskDependencies();
+        for (Task depTask : dependency.getDependencies(this)) {
+            if (depTask.getDidWork()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public Task doFirst(Closure action) {
+        if (action == null) {
+            throw new InvalidUserDataException("Action must not be null!");
+        }
+        doFirst(convertClosureToAction(action));
+        return this;
+    }
+
+    public Task doLast(Closure action) {
+        if (action == null) {
+            throw new InvalidUserDataException("Action must not be null!");
+        }
+        doLast(convertClosureToAction(action));
+        return this;
+    }
+
+    public Task leftShift(Closure action) {
+        return doLast(action);
+    }
+
+    public Task configure(Closure closure) {
+        return ConfigureUtil.configure(closure, this);
+    }
+
+    public File getTemporaryDir() {
+        File dir = getServices().get(TemporaryFileProvider.class).newTemporaryFile(getName());
+        dir.mkdirs();
+        return dir;
+    }
+
+    private Action<Task> convertClosureToAction(Closure actionClosure) {
+        actionClosure.setDelegate(this);
+        actionClosure.setResolveStrategy(Closure.DELEGATE_FIRST);
+        return new ClosureTaskAction(actionClosure);
+    }
+
+    private static class TaskInfo {
+        private final ProjectInternal project;
+        private final String name;
+
+        private TaskInfo(ProjectInternal project, String name) {
+            this.name = name;
+            this.project = project;
+        }
+    }
+
+    private static class ClosureTaskAction implements Action<Task> {
+        private final Closure closure;
+
+        private ClosureTaskAction(Closure closure) {
+            this.closure = closure;
+        }
+
+        public void execute(Task task) {
+            try {
+                if (closure.getMaximumNumberOfParameters() == 0) {
+                    closure.call();
+                }
+                else {
+                    closure.call(task);
+                }
+            } catch (InvokerInvocationException e) {
+                Throwable cause = e.getCause();
+                if (cause instanceof RuntimeException) {
+                    throw (RuntimeException) cause;
+                }
+                throw e;
+            }
+        }
+    }
+
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AsmBackedClassGenerator.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AsmBackedClassGenerator.java
new file mode 100644
index 0000000..07cd01e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AsmBackedClassGenerator.java
@@ -0,0 +1,581 @@
+/*
+ * 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.*;
+import org.gradle.api.internal.plugins.DefaultConvention;
+import org.gradle.api.plugins.Convention;
+import org.gradle.util.ReflectionUtil;
+import org.objectweb.asm.*;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+public class AsmBackedClassGenerator extends AbstractClassGenerator {
+    @Override
+    protected <T> ClassBuilder<T> start(Class<T> type) {
+        return new ClassBuilderImpl<T>(type);
+    }
+
+    private static class ClassBuilderImpl<T> implements ClassBuilder<T> {
+        private final ClassWriter visitor;
+        private final Class<T> type;
+        private final String typeName;
+        private final Type generatedType;
+        private final Type superclassType;
+        private MethodCodeBody initDynamicObjectHelper;
+        private MethodCodeBody initConventionAwareHelper;
+        private MethodCodeBody initMetaClass;
+        private final Type conventionAwareType = Type.getType(IConventionAware.class);
+        private final Type dynamicObjectAwareType = Type.getType(DynamicObjectAware.class);
+        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) {
+            this.type = type;
+
+            visitor = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+            typeName = type.getName() + "_Decorated";
+            generatedType = Type.getType("L" + typeName.replaceAll("\\.", "/") + ";");
+            superclassType = Type.getType(type);
+        }
+
+        public void startClass(boolean isConventionAware, boolean isDynamicAware) {
+            dynamicAware = isDynamicAware;
+            List<String> interfaceTypes = new ArrayList<String>();
+            if (isConventionAware) {
+                interfaceTypes.add(conventionAwareType.getInternalName());
+            }
+            if (isDynamicAware) {
+                interfaceTypes.add(dynamicObjectAwareType.getInternalName());
+            }
+            if (isDynamicAware) {
+                interfaceTypes.add(groovyObjectType.getInternalName());
+            }
+
+            visitor.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, generatedType.getInternalName(), null,
+                    superclassType.getInternalName(), interfaceTypes.toArray(new String[interfaceTypes.size()]));
+        }
+
+        public void addConstructor(Constructor<?> constructor) throws Exception {
+            List<Type> paramTypes = new ArrayList<Type>();
+            for (Class<?> paramType : constructor.getParameterTypes()) {
+                paramTypes.add(Type.getType(paramType));
+            }
+            String methodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, paramTypes.toArray(
+                    new Type[paramTypes.size()]));
+            MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, "<init>", methodDescriptor, null,
+                    new String[0]);
+            methodVisitor.visitCode();
+
+            // super(p0 .. pn)
+            for (int i = 0; i <= constructor.getParameterTypes().length; i++) {
+                methodVisitor.visitVarInsn(Opcodes.ALOAD, i);
+            }
+            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superclassType.getInternalName(), "<init>",
+                    methodDescriptor);
+
+            if (initDynamicObjectHelper != null) {
+                initDynamicObjectHelper.add(methodVisitor);
+            }
+            if (initConventionAwareHelper != null) {
+                initConventionAwareHelper.add(methodVisitor);
+            }
+            if (initMetaClass != null) {
+                initMetaClass.add(methodVisitor);
+            }
+
+            methodVisitor.visitInsn(Opcodes.RETURN);
+            methodVisitor.visitMaxs(0, 0);
+            methodVisitor.visitEnd();
+        }
+
+        public void mixInDynamicAware() throws Exception {
+            final Type helperType = Type.getType(MixInDynamicObject.class);
+
+            // GENERATE private MixInDynamicObject dynamicObjectHelper = new MixInDynamicObject(this, super.getAsDynamicObject())
+
+            final String fieldSignature = "L" + MixInDynamicObject.class.getName().replaceAll("\\.", "/") + ";";
+            visitor.visitField(Opcodes.ACC_PRIVATE, "dynamicObjectHelper", fieldSignature, null, null);
+            initDynamicObjectHelper = new MethodCodeBody() {
+                public void add(MethodVisitor visitor) throws Exception {
+                    String helperTypeConstructorDesc = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{
+                            Type.getType(Object.class), dynamicObjectType
+                    });
+
+                    // GENERATE dynamicObjectHelper = new MixInDynamicObject(this, super.getAsDynamicObject())
+
+                    visitor.visitVarInsn(Opcodes.ALOAD, 0);
+
+                    // GENERATE new DynamicObjectHelper(this, super.getAsDynamicObject())
+                    visitor.visitTypeInsn(Opcodes.NEW, helperType.getInternalName());
+                    visitor.visitInsn(Opcodes.DUP);
+
+                    visitor.visitVarInsn(Opcodes.ALOAD, 0);
+
+                    boolean useInheritedDynamicObject = GroovySystem.getMetaClassRegistry().getMetaClass(type).pickMethod("getAsDynamicObject", new Class[0]) != null;
+
+                    if (useInheritedDynamicObject) {
+                        // GENERATE super.getAsDynamicObject()
+                        visitor.visitVarInsn(Opcodes.ALOAD, 0);
+                        visitor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getType(type).getInternalName(),
+                                "getAsDynamicObject", Type.getMethodDescriptor(dynamicObjectType, new Type[0]));
+                    } else {
+                        // GENERATE null
+                        visitor.visitInsn(Opcodes.ACONST_NULL);
+                    }
+
+                    visitor.visitMethodInsn(Opcodes.INVOKESPECIAL, helperType.getInternalName(), "<init>",
+                            helperTypeConstructorDesc);
+                    // END
+
+                    visitor.visitFieldInsn(Opcodes.PUTFIELD, generatedType.getInternalName(), "dynamicObjectHelper",
+                            fieldSignature);
+                    // END
+                }
+            };
+
+            // END
+
+            // GENERATE public Convention getConvention() { return dynamicObjectHelper.getConvention(); }
+
+            addGetter(DynamicObjectAware.class.getDeclaredMethod("getConvention"), new MethodCodeBody() {
+                public void add(MethodVisitor visitor) throws Exception {
+
+                    // GENERATE dynamicObjectHelper.getConvention()
+
+                    visitor.visitVarInsn(Opcodes.ALOAD, 0);
+                    visitor.visitFieldInsn(Opcodes.GETFIELD, generatedType.getInternalName(), "dynamicObjectHelper",
+                            fieldSignature);
+                    String getterDescriptor = Type.getMethodDescriptor(DynamicObjectHelper.class.getDeclaredMethod(
+                            "getConvention"));
+                    visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, helperType.getInternalName(), "getConvention",
+                            getterDescriptor);
+                }
+            });
+
+            // END
+
+            // GENERATE public DynamicObject.getAsDynamicObject() { return dynamicObjectHelper; }
+
+            addGetter(DynamicObjectAware.class.getDeclaredMethod("getAsDynamicObject"), new MethodCodeBody() {
+                public void add(MethodVisitor visitor) {
+
+                    // GENERATE dynamicObjectHelper
+
+                    visitor.visitVarInsn(Opcodes.ALOAD, 0);
+                    visitor.visitFieldInsn(Opcodes.GETFIELD, generatedType.getInternalName(), "dynamicObjectHelper",
+                            fieldSignature);
+                }
+            });
+
+            // END
+
+            // GENERATE public void setConvention(Convention c) { dynamicObjectHelper.setConvention(c); getConventionMapping().setConvention(c); }
+
+            addSetter(DynamicObjectAware.class.getDeclaredMethod("setConvention", Convention.class),
+                    new MethodCodeBody() {
+                        public void add(MethodVisitor visitor) {
+                            String setConventionDesc = Type.getMethodDescriptor(Type.VOID_TYPE,
+                                    new Type[]{conventionType});
+
+                            visitor.visitVarInsn(Opcodes.ALOAD, 0);
+                            visitor.visitFieldInsn(Opcodes.GETFIELD, generatedType.getInternalName(),
+                                    "dynamicObjectHelper", fieldSignature);
+
+                            visitor.visitVarInsn(Opcodes.ALOAD, 1);
+                            visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, helperType.getInternalName(),
+                                    "setConvention", setConventionDesc);
+
+                            visitor.visitVarInsn(Opcodes.ALOAD, 0);
+                            visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedType.getInternalName(),
+                                    "getConventionMapping", Type.getMethodDescriptor(conventionMappingType,
+                                            new Type[0]));
+
+                            visitor.visitVarInsn(Opcodes.ALOAD, 1);
+                            visitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, conventionMappingType.getInternalName(),
+                                    "setConvention", setConventionDesc);
+                        }
+                    });
+
+            // END
+        }
+
+        public void mixInConventionAware() throws Exception {
+            // GENERATE private ConventionMapping mapping = new ConventionAwareHelper(this, getConvention())
+
+            final String mappingFieldSignature = "L" + ConventionMapping.class.getName().replaceAll("\\.", "/") + ";";
+            final String getConventionDesc = Type.getMethodDescriptor(conventionType, new Type[0]);
+
+            visitor.visitField(Opcodes.ACC_PRIVATE, "mapping", mappingFieldSignature, null, null);
+
+            initConventionAwareHelper = new MethodCodeBody() {
+                public void add(MethodVisitor visitor) throws Exception {
+                    Type helperType = Type.getType(ConventionAwareHelper.class);
+
+                    // GENERATE mapping = new ConventionAwareHelper(this, getConvention())
+                    visitor.visitVarInsn(Opcodes.ALOAD, 0);
+
+                    visitor.visitTypeInsn(Opcodes.NEW, helperType.getInternalName());
+                    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()
+
+                        visitor.visitTypeInsn(Opcodes.NEW, defaultConventionType.getInternalName());
+                        visitor.visitInsn(Opcodes.DUP);
+                        visitor.visitMethodInsn(Opcodes.INVOKESPECIAL, defaultConventionType.getInternalName(),
+                                "<init>", "()V");
+
+                        // END
+                    }
+
+                    String constructorDesc = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{
+                            conventionAwareType, conventionType
+                    });
+                    visitor.visitMethodInsn(Opcodes.INVOKESPECIAL, helperType.getInternalName(), "<init>",
+                            constructorDesc);
+
+                    visitor.visitFieldInsn(Opcodes.PUTFIELD, generatedType.getInternalName(), "mapping",
+                            mappingFieldSignature);
+
+                    // END
+                }
+            };
+
+            // END
+
+            // GENERATE public ConventionMapping getConventionMapping() { return mapping; }
+
+            addGetter(IConventionAware.class.getDeclaredMethod("getConventionMapping"), new MethodCodeBody() {
+                public void add(MethodVisitor visitor) {
+                    visitor.visitVarInsn(Opcodes.ALOAD, 0);
+                    visitor.visitFieldInsn(Opcodes.GETFIELD, generatedType.getInternalName(), "mapping",
+                            mappingFieldSignature);
+                }
+            });
+
+            // GENERATE public void setConventionMapping(ConventionMapping m) { mapping = m; }
+
+            addSetter(IConventionAware.class.getDeclaredMethod("setConventionMapping", ConventionMapping.class),
+                    new MethodCodeBody() {
+                        public void add(MethodVisitor visitor) {
+                            visitor.visitVarInsn(Opcodes.ALOAD, 0);
+                            visitor.visitVarInsn(Opcodes.ALOAD, 1);
+                            visitor.visitFieldInsn(Opcodes.PUTFIELD, generatedType.getInternalName(), "mapping",
+                                    mappingFieldSignature);
+                        }
+                    });
+        }
+
+        public void mixInGroovyObject() throws Exception {
+
+            // GENERATE private MetaClass metaClass = GroovySystem.getMetaClassRegistry().getMetaClass(getClass())
+
+            final String metaClassFieldSignature = "L" + MetaClass.class.getName().replaceAll("\\.", "/") + ";";
+            visitor.visitField(Opcodes.ACC_PRIVATE, "metaClass", metaClassFieldSignature, null, null);
+
+            initMetaClass = new MethodCodeBody() {
+                public void add(MethodVisitor visitor) throws Exception {
+                    visitor.visitVarInsn(Opcodes.ALOAD, 0);
+
+                    // GroovySystem.getMetaClassRegistry()
+                    String getMetaClassRegistryDesc = Type.getMethodDescriptor(GroovySystem.class.getDeclaredMethod(
+                            "getMetaClassRegistry"));
+                    visitor.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getType(GroovySystem.class).getInternalName(),
+                            "getMetaClassRegistry", getMetaClassRegistryDesc);
+
+                    // this.getClass()
+                    visitor.visitVarInsn(Opcodes.ALOAD, 0);
+                    String getClassDesc = Type.getMethodDescriptor(Object.class.getDeclaredMethod("getClass"));
+                    visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getType(Object.class).getInternalName(),
+                            "getClass", getClassDesc);
+
+                    // getMetaClass(..)
+                    String getMetaClassDesc = Type.getMethodDescriptor(MetaClassRegistry.class.getDeclaredMethod(
+                            "getMetaClass", Class.class));
+                    visitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getType(
+                            MetaClassRegistry.class).getInternalName(), "getMetaClass", getMetaClassDesc);
+
+                    visitor.visitFieldInsn(Opcodes.PUTFIELD, generatedType.getInternalName(), "metaClass",
+                            metaClassFieldSignature);
+                }
+            };
+
+            // GENERATE public MetaClass getMetaClass() { return metaClass }
+
+            addGetter(GroovyObject.class.getDeclaredMethod("getMetaClass"), new MethodCodeBody() {
+                public void add(MethodVisitor visitor) throws Exception {
+                    visitor.visitVarInsn(Opcodes.ALOAD, 0);
+                    visitor.visitFieldInsn(Opcodes.GETFIELD, generatedType.getInternalName(), "metaClass",
+                            metaClassFieldSignature);
+                }
+            });
+
+            // GENERATE public void setMetaClass(MetaClass class) { this.metaClass = class; }
+
+            addSetter(GroovyObject.class.getDeclaredMethod("setMetaClass", MetaClass.class), new MethodCodeBody() {
+                public void add(MethodVisitor visitor) throws Exception {
+                    visitor.visitVarInsn(Opcodes.ALOAD, 0);
+                    visitor.visitVarInsn(Opcodes.ALOAD, 1);
+                    visitor.visitFieldInsn(Opcodes.PUTFIELD, generatedType.getInternalName(), "metaClass",
+                            metaClassFieldSignature);
+                }
+            });
+        }
+
+        private void addSetter(Method method, MethodCodeBody body) throws Exception {
+            String methodDescriptor = Type.getMethodDescriptor(method);
+            MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, method.getName(), methodDescriptor,
+                    null, new String[0]);
+            methodVisitor.visitCode();
+            body.add(methodVisitor);
+            methodVisitor.visitInsn(Opcodes.RETURN);
+            methodVisitor.visitMaxs(0, 0);
+            methodVisitor.visitEnd();
+        }
+
+        private void addGetter(Method method, MethodCodeBody body) throws Exception {
+            String methodDescriptor = Type.getMethodDescriptor(method);
+            MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, method.getName(), methodDescriptor,
+                    null, new String[0]);
+            methodVisitor.visitCode();
+            body.add(methodVisitor);
+            methodVisitor.visitInsn(Opcodes.ARETURN);
+            methodVisitor.visitMaxs(0, 0);
+            methodVisitor.visitEnd();
+        }
+
+        public void addDynamicMethods() throws Exception {
+
+            // GENERATE public Object getProperty(String name) { return getAsDynamicObject().getProperty(name); }
+
+            addGetter(GroovyObject.class.getDeclaredMethod("getProperty", String.class), new MethodCodeBody() {
+                public void add(MethodVisitor methodVisitor) throws Exception {
+                    // GENERATE getAsDynamicObject().getProperty(name);
+
+                    methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+                    String getAsDynamicObjectDesc = Type.getMethodDescriptor(DynamicObjectAware.class.getDeclaredMethod(
+                            "getAsDynamicObject"));
+                    methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedType.getInternalName(),
+                            "getAsDynamicObject", getAsDynamicObjectDesc);
+
+                    methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
+                    String getPropertyDesc = Type.getMethodDescriptor(DynamicObject.class.getDeclaredMethod(
+                            "getProperty", String.class));
+                    methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, dynamicObjectType.getInternalName(),
+                            "getProperty", getPropertyDesc);
+
+                    // END
+                }
+            });
+
+            // GENERATE public void setProperty(String name, Object value) { getAsDynamicObject().setProperty(name, value); }
+
+            addSetter(GroovyObject.class.getDeclaredMethod("setProperty", String.class, Object.class),
+                    new MethodCodeBody() {
+                        public void add(MethodVisitor methodVisitor) throws Exception {
+                            // GENERATE getAsDynamicObject().setProperty(name, value)
+
+                            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+                            String getAsDynamicObjectDesc = Type.getMethodDescriptor(
+                                    DynamicObjectAware.class.getDeclaredMethod("getAsDynamicObject"));
+                            methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedType.getInternalName(),
+                                    "getAsDynamicObject", getAsDynamicObjectDesc);
+
+                            methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
+                            methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
+                            String setPropertyDesc = Type.getMethodDescriptor(DynamicObject.class.getDeclaredMethod(
+                                    "setProperty", String.class, Object.class));
+                            methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, dynamicObjectType.getInternalName(),
+                                    "setProperty", setPropertyDesc);
+
+                            // END
+                        }
+                    });
+
+            // GENERATE public Object invokeMethod(String name, Object params) { return getAsDynamicObject().invokeMethod(name, (Object[])params); }
+
+            addGetter(GroovyObject.class.getDeclaredMethod("invokeMethod", String.class, Object.class),
+                    new MethodCodeBody() {
+                        public void add(MethodVisitor methodVisitor) throws Exception {
+                            String invokeMethodDesc = Type.getMethodDescriptor(Type.getType(Object.class), new Type[]{
+                                    Type.getType(String.class), Type.getType(Object[].class)
+                            });
+                            String objArrayDesc = Type.getType(Object[].class).getDescriptor();
+
+                            // GENERATE getAsDynamicObject().invokeMethod(name, (args instanceof Object[]) ? args : new Object[] { args })
+
+                            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+                            String getAsDynamicObjectDesc = Type.getMethodDescriptor(
+                                    DynamicObjectAware.class.getDeclaredMethod("getAsDynamicObject"));
+                            methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedType.getInternalName(),
+                                    "getAsDynamicObject", getAsDynamicObjectDesc);
+
+                            methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
+
+                            // GENERATE (args instanceof Object[]) ? args : new Object[] { args }
+                            methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
+                            methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, objArrayDesc);
+                            Label end = new Label();
+                            Label notArray = new Label();
+                            methodVisitor.visitJumpInsn(Opcodes.IFEQ, notArray);
+
+                            // Generate args
+//                            methodVisitor.visitInsn(Opcodes.ACONST_NULL);
+                            methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
+                            methodVisitor.visitTypeInsn(Opcodes.CHECKCAST,  objArrayDesc);
+                            methodVisitor.visitJumpInsn(Opcodes.GOTO, end);
+
+                            // Generate new Object[] { args }
+                            methodVisitor.visitLabel(notArray);
+                            methodVisitor.visitInsn(Opcodes.ICONST_1);
+                            methodVisitor.visitTypeInsn(Opcodes.ANEWARRAY, Type.getType(Object.class).getInternalName());
+                            methodVisitor.visitInsn(Opcodes.DUP);
+                            methodVisitor.visitInsn(Opcodes.ICONST_0);
+                            methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
+                            methodVisitor.visitInsn(Opcodes.AASTORE);
+
+                            methodVisitor.visitLabel(end);
+
+                            methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, dynamicObjectType.getInternalName(),
+                                    "invokeMethod", invokeMethodDesc);
+                        }
+                    });
+        }
+
+        public void addGetter(MetaBeanProperty property) throws Exception {
+            MetaMethod getter = property.getGetter();
+
+            // GENERATE private boolean <prop>Set;
+
+            String flagName = String.format("%sSet", property.getName());
+            visitor.visitField(Opcodes.ACC_PRIVATE, flagName, Type.BOOLEAN_TYPE.getDescriptor(), null, null);
+
+            // GENERATE public <type> <getter>() { return (<type>)getConventionMapping().getConventionValue(super.<getter>(), '<prop>', <prop>Set); }
+
+            Type returnType = Type.getType(getter.getReturnType());
+            String methodDescriptor = Type.getMethodDescriptor(returnType, new Type[0]);
+            MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, getter.getName(), methodDescriptor,
+                    null, new String[0]);
+            methodVisitor.visitCode();
+
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, conventionAwareType.getInternalName(),
+                    "getConventionMapping", Type.getMethodDescriptor(conventionMappingType, new Type[0]));
+
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superclassType.getInternalName(), getter.getName(),
+                    methodDescriptor);
+
+            methodVisitor.visitLdcInsn(property.getName());
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            methodVisitor.visitFieldInsn(Opcodes.GETFIELD, generatedType.getInternalName(), flagName,
+                    Type.BOOLEAN_TYPE.getDescriptor());
+
+            String getConventionValueDesc = Type.getMethodDescriptor(ConventionMapping.class.getMethod(
+                    "getConventionValue", Object.class, String.class, Boolean.TYPE));
+            methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, conventionMappingType.getInternalName(),
+                    "getConventionValue", getConventionValueDesc);
+
+            methodVisitor.visitTypeInsn(Opcodes.CHECKCAST,
+                    getter.getReturnType().isArray() ? "[" + returnType.getElementType().getDescriptor()
+                            : returnType.getInternalName());
+
+            methodVisitor.visitInsn(Opcodes.ARETURN);
+            methodVisitor.visitMaxs(0, 0);
+            methodVisitor.visitEnd();
+        }
+
+        public void addSetter(MetaBeanProperty property) throws Exception {
+            MetaMethod setter = property.getSetter();
+
+            // GENERATE public <return-type> <setter>(<type> v) { <return-type> v = super.<setter>(v); <prop>Set = true; return v; }
+
+            Type paramType = Type.getType(setter.getParameterTypes()[0].getTheClass());
+            Type returnType = Type.getType(setter.getReturnType());
+            boolean isVoid = setter.getReturnType().equals(Void.TYPE);
+            String methodDescriptor = Type.getMethodDescriptor(returnType, new Type[]{paramType});
+            MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, setter.getName(), methodDescriptor,
+                    null, new String[0]);
+            methodVisitor.visitCode();
+
+            // GENERATE super.<setter>(v)
+
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
+
+            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superclassType.getInternalName(), setter.getName(),
+                    methodDescriptor);
+
+            // END
+
+            // GENERATE <prop>Set = true
+
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            methodVisitor.visitLdcInsn(true);
+            methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, generatedType.getInternalName(), String.format("%sSet",
+                    property.getName()), Type.BOOLEAN_TYPE.getDescriptor());
+
+            // END
+
+            methodVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
+            methodVisitor.visitMaxs(0, 0);
+            methodVisitor.visitEnd();
+        }
+
+        public Class<? extends T> generate() {
+            visitor.visitEnd();
+
+            byte[] bytecode = visitor.toByteArray();
+            return (Class<T>) ReflectionUtil.invoke(type.getClassLoader(), "defineClass", new Object[]{
+                    typeName, bytecode, 0, bytecode.length
+            });
+        }
+    }
+
+    private interface MethodCodeBody {
+        void add(MethodVisitor visitor) throws Exception;
+    }
+
+    public static class MixInDynamicObject extends DynamicObjectHelper {
+        public MixInDynamicObject(Object delegateObject, DynamicObject dynamicObject) {
+            super(wrap(delegateObject, dynamicObject), new DefaultConvention());
+        }
+
+        private static AbstractDynamicObject wrap(Object delegateObject, DynamicObject dynamicObject) {
+            if (dynamicObject != null) {
+                return (AbstractDynamicObject) dynamicObject;
+            }
+            return new BeanDynamicObject(delegateObject);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AutoCreateDomainObjectContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AutoCreateDomainObjectContainer.java
new file mode 100644
index 0000000..36e407e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AutoCreateDomainObjectContainer.java
@@ -0,0 +1,51 @@
+/*
+ * 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 org.gradle.api.InvalidUserDataException;
+import org.gradle.util.Configurable;
+import org.gradle.util.ConfigureUtil;
+
+public abstract class AutoCreateDomainObjectContainer<T> extends DefaultNamedDomainObjectContainer<T> implements
+        Configurable<AutoCreateDomainObjectContainer<T>> {
+    protected AutoCreateDomainObjectContainer(Class<T> type, ClassGenerator classGenerator) {
+        super(type, classGenerator);
+    }
+
+    protected abstract T create(String name);
+
+    public T add(String name) {
+        return add(name, null);
+    }
+
+    public T add(String name, Closure configureClosure) {
+        if (findByName(name) != null) {
+            throw new InvalidUserDataException(String.format("Cannot add %s '%s' as a %s with that name already exists.",
+                    getTypeDisplayName(), name, getTypeDisplayName()));
+        }
+        T object = create(name);
+        addObject(name, object);
+        ConfigureUtil.configure(configureClosure, object);
+        return object;
+    }
+
+    public AutoCreateDomainObjectContainer<T> configure(Closure configureClosure) {
+        ConfigureUtil.configure(configureClosure, new AutoCreateDomainObjectContainerDelegate(
+                configureClosure.getOwner(), this));
+        return this;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AutoCreateDomainObjectContainerDelegate.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AutoCreateDomainObjectContainerDelegate.groovy
new file mode 100644
index 0000000..8b8b570
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/AutoCreateDomainObjectContainerDelegate.groovy
@@ -0,0 +1,73 @@
+/*
+ * 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
+
+class AutoCreateDomainObjectContainerDelegate {
+    private final Object owner;
+    private final AutoCreateDomainObjectContainer container;
+    private final DynamicObject delegate;
+    private final ThreadLocal<Boolean> configuring = new ThreadLocal<Boolean>()
+
+    public AutoCreateDomainObjectContainerDelegate(Object owner, AutoCreateDomainObjectContainer container) {
+        this.container = container
+        delegate = container.asDynamicObject
+        this.owner = owner
+    }
+
+    public Object invokeMethod(String name, Object params) {
+        boolean isTopLevelCall = !configuring.get()
+        configuring.set(true)
+        try {
+            if (delegate.hasMethod(name, params)) {
+                return delegate.invokeMethod(name, params)
+            }
+
+            // try the owner
+            try {
+                return owner.invokeMethod(name, params)
+            } catch (groovy.lang.MissingMethodException e) {
+                // ignore
+            }
+
+            boolean isConfigureMethod = params.length == 1 && params[0] instanceof Closure
+            if (isTopLevelCall && isConfigureMethod) {
+                // looks like a configure method - add the object and try the delegate again
+                container.add(name)
+            }
+
+            return delegate.invokeMethod(name, params);
+        } finally {
+            configuring.set(!isTopLevelCall)
+        }
+    }
+
+    public Object get(String name) {
+        if (delegate.hasProperty(name)) {
+            return delegate.getProperty(name)
+        }
+
+        // try the owner
+        try {
+            return owner."$name"
+        } catch (groovy.lang.MissingPropertyException e) {
+            // Ignore
+        }
+
+        // try the delegate again
+        container.add(name)
+        return delegate.getProperty(name)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/BeanDynamicObject.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/BeanDynamicObject.java
new file mode 100644
index 0000000..3af1e82
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/BeanDynamicObject.java
@@ -0,0 +1,168 @@
+/*
+ * 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.*;
+import groovy.lang.MissingMethodException;
+import org.codehaus.groovy.runtime.InvokerInvocationException;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A {@link DynamicObject} which uses groovy reflection to provide access to the properties and methods of a bean.
+ */
+public class BeanDynamicObject extends AbstractDynamicObject {
+    private final Object bean;
+    private final boolean includeProperties;
+
+    public BeanDynamicObject(Object bean) {
+        this.bean = bean;
+        includeProperties = true;
+    }
+
+    private BeanDynamicObject(Object bean, boolean includeProperties) {
+        this.bean = bean;
+        this.includeProperties = includeProperties;
+    }
+
+    public BeanDynamicObject withNoProperties() {
+        return new BeanDynamicObject(bean, false);
+    }
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+
+    protected String getDisplayName() {
+        return bean.toString();
+    }
+
+    private MetaClass getMetaClass() {
+        if (bean instanceof GroovyObject) {
+            return ((GroovyObject) bean).getMetaClass();
+        } else {
+            return GroovySystem.getMetaClassRegistry().getMetaClass(bean.getClass());
+        }
+    }
+
+    @Override
+    public boolean hasProperty(String name) {
+        return includeProperties && getMetaClass().hasProperty(bean, name) != null;
+    }
+
+    @Override
+    public Object getProperty(String name) throws MissingPropertyException {
+        if (!includeProperties) {
+            throw propertyMissingException(name);
+        }
+
+        MetaProperty property = getMetaClass().hasProperty(bean, name);
+        if (property == null) {
+            throw propertyMissingException(name);
+        }
+        if (property instanceof MetaBeanProperty && ((MetaBeanProperty) property).getGetter() == null) {
+            throw new GroovyRuntimeException(String.format(
+                    "Cannot get the value of write-only property '%s' on %s.", name, getDisplayName()));
+        }
+
+        try {
+            return property.getProperty(bean);
+        } catch (InvokerInvocationException e) {
+            if (e.getCause() instanceof RuntimeException) {
+                throw (RuntimeException) e.getCause();
+            }
+            throw e;
+        }
+    }
+
+    @Override
+    public void setProperty(final String name, Object value) throws MissingPropertyException {
+        if (!includeProperties) {
+            throw propertyMissingException(name);
+        }
+
+        MetaClass metaClass = getMetaClass();
+        MetaProperty property = metaClass.hasProperty(bean, name);
+        if (property == null) {
+            throw propertyMissingException(name);
+        }
+
+        if (property instanceof MetaBeanProperty && ((MetaBeanProperty) property).getSetter() == null) {
+            throw new ReadOnlyPropertyException(name, bean.getClass()) {
+                @Override
+                public String getMessage() {
+                    return String.format("Cannot set the value of read-only property '%s' on %s.", name,
+                            getDisplayName());
+                }
+            };
+        }
+        try {
+            metaClass.setProperty(bean, name, value);
+        } catch (InvokerInvocationException e) {
+            if (e.getCause() instanceof RuntimeException) {
+                throw (RuntimeException) e.getCause();
+            }
+            throw e;
+        }
+    }
+
+    @Override
+    public Map<String, Object> getProperties() {
+        if (!includeProperties) {
+            return Collections.emptyMap();
+        }
+
+        Map<String, Object> properties = new HashMap<String, Object>();
+        List<MetaProperty> classProperties = getMetaClass().getProperties();
+        for (MetaProperty metaProperty : classProperties) {
+            if (metaProperty.getName().equals("properties")) {
+                properties.put("properties", properties);
+                continue;
+            }
+            if (metaProperty instanceof MetaBeanProperty) {
+                MetaBeanProperty beanProperty = (MetaBeanProperty) metaProperty;
+                if (beanProperty.getGetter() == null) {
+                    continue;
+                }
+            }
+            properties.put(metaProperty.getName(), metaProperty.getProperty(bean));
+        }
+        return properties;
+    }
+
+    @Override
+    public boolean hasMethod(String name, Object... arguments) {
+        return !getMetaClass().respondsTo(bean, name, arguments).isEmpty();
+    }
+
+    @Override
+    public Object invokeMethod(String name, Object... arguments) throws MissingMethodException {
+        try {
+            return getMetaClass().invokeMethod(bean, name, arguments);
+        } catch (InvokerInvocationException e) {
+            if (e.getCause() instanceof RuntimeException) {
+                throw (RuntimeException) e.getCause();
+            }
+            throw e;
+        } catch (MissingMethodException e) {
+            throw methodMissingException(name, arguments);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/CachingDirectedGraphWalker.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/CachingDirectedGraphWalker.java
new file mode 100644
index 0000000..f5b8ce7
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/CachingDirectedGraphWalker.java
@@ -0,0 +1,178 @@
+/*
+ * 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 org.gradle.util.GUtil;
+
+import java.util.*;
+
+/**
+ * A graph walker which collects the values reachable from a given set of start nodes. Handles cycles in the graph. Can
+ * be reused to perform multiple searches, and reuses the results of previous searches.
+ *
+ * Uses a variation of Tarjan's algorithm: http://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
+ */
+public class CachingDirectedGraphWalker<N, T> {
+    private final DirectedGraphWithEdgeValues<N, T> graph;
+    private List<N> startNodes = new LinkedList<N>();
+    private final Map<N, Set<T>> cachedNodeValues = new HashMap<N, Set<T>>();
+
+    public CachingDirectedGraphWalker(DirectedGraph<N, T> graph) {
+        this.graph = new GraphWithEmpyEdges<N, T>(graph);
+    }
+
+    public CachingDirectedGraphWalker(DirectedGraphWithEdgeValues<N, T> graph) {
+        this.graph = graph;
+    }
+
+    /**
+     * Adds some start nodes.
+     */
+    public CachingDirectedGraphWalker<N, T> add(N... values) {
+        add(Arrays.asList(values));
+        return this;
+    }
+
+    /**
+     * Adds some start nodes.
+     */
+    public CachingDirectedGraphWalker add(Iterable<? extends N> values) {
+        GUtil.addToCollection(startNodes, values);
+        return this;
+    }
+
+    /**
+     * Calculates the set of values of nodes reachable from the start nodes.
+     */
+    public Set<T> findValues() {
+        try {
+            return doSearch();
+        } finally {
+            startNodes.clear();
+        }
+    }
+
+    private Set<T> doSearch() {
+        int componentCount = 0;
+        Map<N, NodeDetails<N, T>> seenNodes = new HashMap<N, NodeDetails<N, T>>();
+        Map<Integer, NodeDetails<N, T>> components = new HashMap<Integer, NodeDetails<N, T>>();
+        LinkedList<N> queue = new LinkedList<N>(startNodes);
+
+        while (!queue.isEmpty()) {
+            N node = queue.getFirst();
+            NodeDetails<N, T> details = seenNodes.get(node);
+            if (details == null) {
+                // Have not visited this node yet. Push its successors onto the queue in front of this node and visit
+                // them
+
+                details = new NodeDetails<N, T>(node, componentCount++);
+                seenNodes.put(node, details);
+                components.put(details.component, details);
+
+                Set<T> cacheValues = cachedNodeValues.get(node);
+                if (cacheValues != null) {
+                    // Already visited this node
+                    details.values = cacheValues;
+                    details.finished = true;
+                    queue.removeFirst();
+                    continue;
+                }
+
+                graph.getNodeValues(node, details.values, details.successors);
+                for (N connectedNode : details.successors) {
+                    if (!seenNodes.containsKey(connectedNode)) {
+                        queue.add(0, connectedNode);
+                    }
+                    // Else, already visiting or have visited the successor node (we're in a cycle)
+                }
+            } else {
+                // Have visited all of this node's successors
+                queue.removeFirst();
+
+                if (cachedNodeValues.containsKey(node)) {
+                    continue;
+                }
+
+                for (N connectedNode : details.successors) {
+                    NodeDetails<N, T> connectedNodeDetails = seenNodes.get(connectedNode);
+                    if (!connectedNodeDetails.finished) {
+                        // part of a cycle
+                        details.minSeen = Math.min(details.minSeen, connectedNodeDetails.minSeen);
+                    }
+                    details.values.addAll(connectedNodeDetails.values);
+                    graph.getEdgeValues(node, connectedNode, details.values);
+                }
+
+                if (details.minSeen != details.component) {
+                    // Part of a strongly connected component (ie cycle) - move values to root of the component
+                    // The root is the first node of the component we encountered
+                    NodeDetails<N, T> rootDetails = components.get(details.minSeen);
+                    rootDetails.values.addAll(details.values);
+                    details.values.clear();
+                    rootDetails.strongComponentMembers.addAll(details.strongComponentMembers);
+                } else {
+                    // Not part of a strongly connected component or the root of a strongly connected component
+                    for (NodeDetails<N, T> componentMember : details.strongComponentMembers) {
+                        cachedNodeValues.put(componentMember.node, details.values);
+                        componentMember.finished = true;
+                        components.remove(componentMember.component);
+                    }
+                }
+            }
+        }
+
+        Set<T> values = new LinkedHashSet<T>();
+        for (N startNode : startNodes) {
+            values.addAll(cachedNodeValues.get(startNode));
+        }
+        return values;
+    }
+
+    private static class NodeDetails<N, T> {
+        private final int component;
+        private final N node;
+        private Set<T> values = new LinkedHashSet<T>();
+        private List<N> successors = new ArrayList<N>();
+        private Set<NodeDetails<N, T>> strongComponentMembers = new LinkedHashSet<NodeDetails<N, T>>();
+        private int minSeen;
+        private boolean finished;
+
+        public NodeDetails(N node, int component) {
+            this.node = node;
+            this.component = component;
+            minSeen = component;
+            strongComponentMembers.add(this);
+        }
+    }
+
+    private static class GraphWithEmpyEdges<N, T> implements DirectedGraphWithEdgeValues<N, T> {
+        private final DirectedGraph<N, T> graph;
+
+        public GraphWithEmpyEdges(DirectedGraph<N, T> graph) {
+            this.graph = graph;
+        }
+
+        @Override
+        public void getEdgeValues(N from, N to, Collection<T> values) {
+        }
+
+        @Override
+        public void getNodeValues(N node, Collection<T> values, Collection<N> connectedNodes) {
+            graph.getNodeValues(node, values, connectedNodes);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ChainingTransformer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ChainingTransformer.java
new file mode 100644
index 0000000..676ce0c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ChainingTransformer.java
@@ -0,0 +1,66 @@
+/*
+ * 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 groovy.lang.Closure;
+import groovy.lang.GString;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import org.gradle.api.Transformer;
+
+public class ChainingTransformer<T> implements Transformer<T> {
+    private final List<Transformer<T>> transformers = new ArrayList<Transformer<T>>();
+    private final Class<T> type;
+
+    public ChainingTransformer(Class<T> type) {
+        this.type = type;
+    }
+
+    public T transform(T original) {
+        T value = original;
+        for (Transformer<T> transformer : transformers) {
+            value = type.cast(transformer.transform(value));
+        }
+        return value;
+    }
+
+    public void add(Transformer<T> transformer) {
+        transformers.add(transformer);
+    }
+
+    public void add(final Closure transformer) {
+        transformers.add(new Transformer<T>() {
+            public T transform(T original) {
+                transformer.setDelegate(original);
+                transformer.setResolveStrategy(Closure.DELEGATE_FIRST);
+                Object value = transformer.call(original);
+                if (type.isInstance(value)) {
+                    return type.cast(value);
+                }
+                if (type == String.class && value instanceof GString) {
+                    return type.cast(value.toString());
+                }
+                return original;
+            }
+        });
+    }
+
+    public boolean hasTransformers() {
+        return !transformers.isEmpty();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ClassGenerator.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ClassGenerator.java
new file mode 100644
index 0000000..b69161b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ClassGenerator.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+public interface ClassGenerator {
+    /**
+     * Generates a proxy class for the given class. May decorate the given class or may generate a subclass.
+     */
+    <T> Class<? extends T> generate(Class<T> type);
+
+    /**
+     * Creates an instance of the proxy class for the given class.
+     */
+    <T> T newInstance(Class<T> type, Object... parameters);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ClassPathProvider.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ClassPathProvider.java
new file mode 100644
index 0000000..c4cf80c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ClassPathProvider.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.api.internal;
+
+import java.io.File;
+import java.util.Set;
+
+public interface ClassPathProvider {
+    /**
+     * Returns the files for the given classpath, if known. Returns null for unknown classpath.
+     */
+    Set<File> findClassPath(String name);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ClassPathRegistry.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ClassPathRegistry.java
new file mode 100644
index 0000000..e8965a8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ClassPathRegistry.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.api.internal;
+
+import java.io.File;
+import java.net.URL;
+import java.util.Set;
+
+public interface ClassPathRegistry {
+    Set<URL> getClassPath(String name);
+
+    URL[] getClassPathUrls(String name);
+
+    Set<File> getClassPathFiles(String name);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/CompositeDynamicObject.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/CompositeDynamicObject.java
new file mode 100644
index 0000000..39feb1e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/CompositeDynamicObject.java
@@ -0,0 +1,108 @@
+/*
+ * 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 groovy.lang.*;
+import groovy.lang.MissingMethodException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class CompositeDynamicObject extends AbstractDynamicObject {
+    private DynamicObject[] objects = new DynamicObject[0];
+    private DynamicObject[] updateObjects = new DynamicObject[0];
+
+    protected void setObjects(DynamicObject... objects) {
+        this.objects = objects;
+        updateObjects = objects;
+    }
+
+    protected void setObjectsForUpdate(DynamicObject... objects) {
+        this.updateObjects = objects;
+    }
+
+    @Override
+    public boolean hasProperty(String name) {
+        for (DynamicObject object : objects) {
+            if (object.hasProperty(name)) {
+                return true;
+            }
+        }
+        return super.hasProperty(name);
+    }
+
+    @Override
+    public Object getProperty(String name) throws MissingPropertyException {
+        for (DynamicObject object : objects) {
+            if (object.hasProperty(name)) {
+                return object.getProperty(name);
+            }
+        }
+        return super.getProperty(name);
+    }
+
+    @Override
+    public void setProperty(String name, Object value) throws MissingPropertyException {
+        for (DynamicObject object : updateObjects) {
+            if (object.hasProperty(name)) {
+                object.setProperty(name, value);
+                return;
+            }
+        }
+        updateObjects[updateObjects.length - 1].setProperty(name, value);
+    }
+
+    @Override
+    public Map<String, Object> getProperties() {
+        Map<String, Object> properties = new HashMap<String, Object>();
+        for (int i = objects.length - 1; i >= 0; i--) {
+            DynamicObject object = objects[i];
+            properties.putAll(object.getProperties());
+        }
+        properties.put("properties", properties);
+        return properties;
+    }
+
+    @Override
+    public boolean hasMethod(String name, Object... arguments) {
+        for (DynamicObject object : objects) {
+            if (object.hasMethod(name, arguments)) {
+                return true;
+            }
+        }
+        return super.hasMethod(name, arguments);
+    }
+
+    @Override
+    public Object invokeMethod(String name, Object... arguments) throws MissingMethodException {
+        for (DynamicObject object : objects) {
+            if (object.hasMethod(name, arguments)) {
+                return object.invokeMethod(name, arguments);
+            }
+        }
+
+        if (hasProperty(name)) {
+            Object property = getProperty(name);
+            if (property instanceof Closure) {
+                Closure closure = (Closure) property;
+                closure.setResolveStrategy(Closure.DELEGATE_FIRST);
+                return closure.call(arguments);
+            }
+        }
+
+        return super.invokeMethod(name, arguments);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/Contextual.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/Contextual.java
new file mode 100644
index 0000000..fd29917
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/Contextual.java
@@ -0,0 +1,32 @@
+/*
+ * 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.*;
+
+/**
+ * This annotation is attached to an exception class to indicate that it provides contextual information about the
+ * exception which might help the user determine what the failed operation was, or where it took place. Generally, this
+ * annotation is only attached to exceptions which chain lower-level exceptions.
+ *
+ * A contextual exception class should declare a no-args constructor or a copy constructor, to allow automated
+ * generation of subclasses by an {@link org.gradle.api.internal.ExceptionAnalyser}.
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.TYPE)
+ at Inherited
+public @interface Contextual {
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ConventionAwareHelper.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ConventionAwareHelper.java
new file mode 100644
index 0000000..a0ebf6a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ConventionAwareHelper.java
@@ -0,0 +1,179 @@
+/*
+ * 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.api.internal;
+
+import groovy.lang.Closure;
+import groovy.lang.MissingPropertyException;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.plugins.Convention;
+import org.gradle.api.tasks.ConventionValue;
+import org.gradle.util.ReflectionUtil;
+import org.gradle.util.UncheckedException;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+/**
+ * @author Hans Dockter
+ */
+public class ConventionAwareHelper implements ConventionMapping {
+    private Convention convention;
+
+    private IConventionAware source;
+
+    private Map<String, ConventionValue> conventionMapping = new HashMap<String, ConventionValue>();
+
+    public ConventionAwareHelper(IConventionAware source, Convention convention) {
+        this.source = source;
+        this.convention = convention;
+    }
+
+    public MappedProperty map(String propertyName, ConventionValue value) {
+        MappedPropertyImpl property = new MappedPropertyImpl(value);
+        map(Collections.singletonMap(propertyName, property));
+        return property;
+    }
+
+    public MappedProperty map(String propertyName, final Closure value) {
+        return map(propertyName, new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                switch (value.getMaximumNumberOfParameters()) {
+                    case 0:
+                        return value.call();
+                    case 1:
+                        return value.call(convention);
+                    default:
+                        return value.call(new Object[]{convention, conventionAwareObject});
+                }
+            }
+        });
+    }
+
+    public MappedProperty map(String propertyName, final Callable<?> value) {
+        return map(propertyName, new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                try {
+                    return value.call();
+                } catch (Exception e) {
+                    throw UncheckedException.asUncheckedException(e);
+                }
+            }
+        });
+    }
+
+    public ConventionMapping map(Map<String, ? extends ConventionValue> mapping) {
+        for (Map.Entry<String, ? extends ConventionValue> entry : mapping.entrySet()) {
+            String propertyName = entry.getKey();
+            if (!ReflectionUtil.hasProperty(source, propertyName)) {
+                throw new InvalidUserDataException(
+                        "You can't map a property that does not exist: propertyName=" + propertyName);
+            }
+            if (entry.getValue() == null) {
+                throw new IllegalArgumentException("No convention value provided: propertyName= " + propertyName);
+            }
+        }
+        this.conventionMapping.putAll(mapping);
+        return this;
+    }
+
+    public void propertyMissing(String name, Object value) {
+        if (value instanceof Closure) {
+            map(name, (Closure) value);
+        } else if (value instanceof ConventionValue) {
+            map(name, (ConventionValue) value);
+        } else {
+            throw new MissingPropertyException(name, getClass());
+        }
+    }
+
+    public Object getConventionValue(String propertyName) {
+        Object value = ReflectionUtil.getProperty(source, propertyName);
+        return getConventionValue(value, propertyName);
+    }
+
+    public <T> T getConventionValue(T internalValue, String propertyName) {
+        Object returnValue = internalValue;
+        boolean useMapping = internalValue == null
+                || internalValue instanceof Collection && ((Collection) internalValue).isEmpty()
+                || internalValue instanceof Map && ((Map) internalValue).isEmpty();
+        if (useMapping && conventionMapping.keySet().contains(propertyName)) {
+            returnValue = conventionMapping.get(propertyName).getValue(convention, source);
+        }
+        return (T) returnValue;
+    }
+
+    public <T> T getConventionValue(T actualValue, String propertyName, boolean isExplicitValue) {
+        if (isExplicitValue) {
+            return actualValue;
+        }
+        return getConventionValue(actualValue, propertyName);
+    }
+
+    public Convention getConvention() {
+        return convention;
+    }
+
+    public void setConvention(Convention convention) {
+        this.convention = convention;
+    }
+
+    public IConventionAware getSource() {
+        return source;
+    }
+
+    public void setSource(IConventionAware source) {
+        this.source = source;
+    }
+
+    public Map getConventionMapping() {
+        return conventionMapping;
+    }
+
+    public void setConventionMapping(Map conventionMapping) {
+        this.conventionMapping = conventionMapping;
+    }
+
+    private static class MappedPropertyImpl implements MappedProperty, ConventionValue {
+        private final ConventionValue value;
+        private boolean haveValue;
+        private boolean cache;
+        private Object cachedValue;
+
+        private MappedPropertyImpl(ConventionValue value) {
+            this.value = value;
+        }
+
+        public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+            if (!cache) {
+                return value.getValue(convention, conventionAwareObject);
+            }
+            if (!haveValue) {
+                cachedValue = value.getValue(convention, conventionAwareObject);
+                haveValue = true;
+            }
+            return cachedValue;
+        }
+
+        public void cache() {
+            cache = true;
+            cachedValue = null;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ConventionMapping.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ConventionMapping.java
new file mode 100644
index 0000000..cfe5a6b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ConventionMapping.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.api.internal;
+
+import org.gradle.api.tasks.ConventionValue;
+import org.gradle.api.plugins.Convention;
+
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import groovy.lang.Closure;
+
+/**
+ * <p>A {@code ConventionMapping} maintains the convention mappings for the properties of a particular object.</p>
+ *
+ * <p>Implementations should also allow mappings to be set using dynamic properties.</p>
+ */
+public interface ConventionMapping {
+    Convention getConvention();
+
+    void setConvention(Convention convention);
+
+    MappedProperty map(String propertyName, ConventionValue value);
+
+    MappedProperty map(String propertyName, Closure value);
+
+    MappedProperty map(String propertyName, Callable<?> value);
+
+    ConventionMapping map(Map<String, ? extends ConventionValue> properties);
+
+    <T> T getConventionValue(T actualValue, String propertyName);
+
+    <T> T getConventionValue(T actualValue, String propertyName, boolean isExplicitValue);
+
+    interface MappedProperty {
+        void cache();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ConventionTask.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ConventionTask.java
new file mode 100644
index 0000000..2d1ae66
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ConventionTask.java
@@ -0,0 +1,45 @@
+/*
+ * 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.api.internal;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.ConventionValue;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class ConventionTask extends DefaultTask implements IConventionAware {
+    private ConventionMapping conventionMapping;
+
+    protected ConventionTask() {
+        conventionMapping = new ConventionAwareHelper(this, getProject().getConvention());
+    }
+
+    public Task conventionMapping(String property, ConventionValue mapping) {
+        conventionMapping.map(property, mapping);
+        return this;
+    }
+
+    public ConventionMapping getConventionMapping() {
+        return conventionMapping;
+    }
+
+    public void setConventionMapping(ConventionMapping mapping) {
+        this.conventionMapping = mapping;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DefaultClassPathProvider.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DefaultClassPathProvider.java
new file mode 100644
index 0000000..8341960
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DefaultClassPathProvider.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.api.internal;
+
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class DefaultClassPathProvider extends AbstractClassPathProvider {
+    public DefaultClassPathProvider() {
+        List<Pattern> groovyPatterns = toPatterns("groovy-all");
+
+        add("LOCAL_GROOVY", groovyPatterns);
+        List<Pattern> gradleApiPatterns = toPatterns("gradle-\\w+", "ivy", "slf4j", "ant");
+        gradleApiPatterns.addAll(groovyPatterns);
+        // Add the test fixture runtime, too
+        gradleApiPatterns.addAll(toPatterns("commons-io", "asm", "commons-lang", "commons-collections"));
+        add("GRADLE_API", gradleApiPatterns);
+        add("GRADLE_CORE", toPatterns("gradle-core"));
+        add("ANT", toPatterns("ant", "ant-launcher"));
+        add("COMMONS_CLI", toPatterns("commons-cli"));
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DefaultClassPathRegistry.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DefaultClassPathRegistry.java
new file mode 100644
index 0000000..7b72fcb
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DefaultClassPathRegistry.java
@@ -0,0 +1,75 @@
+/*
+ * 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 org.gradle.api.UncheckedIOException;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
+
+public class DefaultClassPathRegistry implements ClassPathRegistry {
+    private final List<ClassPathProvider> providers = new ArrayList<ClassPathProvider>();
+
+    public DefaultClassPathRegistry(ClassPathProvider... providers) {
+        this.providers.addAll(Arrays.asList(providers));
+        this.providers.add(new DefaultClassPathProvider());
+    }
+
+    public URL[] getClassPathUrls(String name) {
+        return toURLArray(getClassPathFiles(name));
+    }
+
+    public Set<URL> getClassPath(String name) {
+        return toUrlSet(getClassPathFiles(name));
+    }
+
+    public Set<File> getClassPathFiles(String name) {
+        for (ClassPathProvider provider : providers) {
+            Set<File> classpath = provider.findClassPath(name);
+            if (classpath != null) {
+                return classpath;
+            }
+        }
+        throw new IllegalArgumentException(String.format("unknown classpath '%s' requested.", name));
+    }
+
+    private Set<URL> toUrlSet(Set<File> classPathFiles) {
+        Set<URL> urls = new LinkedHashSet<URL>();
+        for (File file : classPathFiles) {
+            try {
+                urls.add(file.toURI().toURL());
+            } catch (MalformedURLException e) {
+                throw new UncheckedIOException(e);
+            }
+        }
+        return urls;
+    }
+
+    private URL[] toURLArray(Collection<File> files) {
+        List<URL> urls = new ArrayList<URL>(files.size());
+        for (File file : files) {
+            try {
+                urls.add(file.toURI().toURL());
+            } catch (MalformedURLException e) {
+                throw new UncheckedIOException(e);
+            }
+        }
+        return urls.toArray(new URL[urls.size()]);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DefaultDomainObjectContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DefaultDomainObjectContainer.java
new file mode 100644
index 0000000..f860796
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DefaultDomainObjectContainer.java
@@ -0,0 +1,110 @@
+/*
+ * 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 groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.DomainObjectCollection;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.listener.ListenerBroadcast;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class DefaultDomainObjectContainer<T> extends AbstractDomainObjectCollection<T> {
+    private final Class<T> type;
+    private final ObjectStore<T> store;
+
+    public DefaultDomainObjectContainer(Class<T> type) {
+        this(type, new SetStore<T>());
+    }
+
+    protected DefaultDomainObjectContainer(Class<T> type, ObjectStore<T> store) {
+        super(store);
+        this.type = type;
+        this.store = store;
+    }
+
+    public Class<T> getType() {
+        return type;
+    }
+
+    public DomainObjectCollection<T> matching(final Spec<? super T> spec) {
+        return new DefaultDomainObjectContainer<T>(type, storeWithSpec(spec));
+    }
+
+    public DomainObjectCollection<T> matching(Closure spec) {
+        return matching(Specs.<T>convertClosureToSpec(spec));
+    }
+
+    public <S extends T> DomainObjectCollection<S> withType(final Class<S> type) {
+        return new DefaultDomainObjectContainer<S>(type, storeWithType(type));
+    }
+
+    protected ObjectStore<T> storeWithSpec(Spec<? super T> spec) {
+        return new FilteredObjectStore<T>(store, type, spec);
+    }
+
+    protected <S extends T> ObjectStore<S> storeWithType(Class<S> type) {
+        return new FilteredObjectStore<S>(store, type, Specs.satisfyAll());
+    }
+
+    public void addObject(T value) {
+        store.add(value);
+    }
+
+    protected interface ObjectStore<S> extends Store<S> {
+        void add(S object);
+    }
+
+    private static class SetStore<S> implements ObjectStore<S> {
+        private final ListenerBroadcast<Action> addActions = new ListenerBroadcast<Action>(Action.class);
+        private final ListenerBroadcast<Action> removeActions = new ListenerBroadcast<Action>(Action.class);
+        private final Map<S, S> objects = new LinkedHashMap<S, S>();
+
+        public void add(S object) {
+            S oldValue = objects.put(object, object);
+            if (oldValue != null) {
+                removeActions.getSource().execute(oldValue);
+            }
+            addActions.getSource().execute(object);
+        }
+
+        public Collection<? extends S> getAll() {
+            return objects.values();
+        }
+
+        public void objectAdded(Action<? super S> action) {
+            addActions.add(action);
+        }
+
+        public void objectRemoved(Action<? super S> action) {
+            removeActions.add(action);
+        }
+    }
+
+    private static class FilteredObjectStore<S> extends FilteredStore<S> implements ObjectStore<S> {
+        public FilteredObjectStore(ObjectStore<? super S> store, Class<S> type, Spec<? super S> spec) {
+            super(store, type, spec);
+        }
+
+        public void add(S object) {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectContainer.java
new file mode 100644
index 0000000..26356f6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DefaultNamedDomainObjectContainer.java
@@ -0,0 +1,313 @@
+/*
+ * 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.MissingPropertyException;
+import org.gradle.api.*;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.listener.ListenerBroadcast;
+import org.gradle.util.ConfigureUtil;
+
+import java.util.*;
+
+public class DefaultNamedDomainObjectContainer<T> extends AbstractDomainObjectCollection<T>
+        implements NamedDomainObjectContainer<T> {
+    private final DynamicObject dynamicObject = new ContainerDynamicObject();
+    private final List<Rule> rules = new ArrayList<Rule>();
+    private final ClassGenerator classGenerator;
+    private final NamedObjectStore<T> store;
+    private final Class<T> type;
+    private Set<String> applyingRulesFor = new HashSet<String>();
+
+    public DefaultNamedDomainObjectContainer(Class<T> type, ClassGenerator classGenerator) {
+        this(type, classGenerator, new MapStore<T>());
+    }
+                                         
+    public DefaultNamedDomainObjectContainer(Class<T> type, ClassGenerator classGenerator, NamedObjectStore<T> store) {
+        super(store);
+        this.type = type;
+        this.classGenerator = classGenerator;
+        this.store = store;
+    }
+
+    protected Class<T> getType() {
+        return type;
+    }
+
+    protected ClassGenerator getClassGenerator() {
+        return classGenerator;
+    }
+
+    /**
+     * Adds a domain object to this container.
+     *
+     * @param name The name of the domain object.
+     * @param object The object to add
+     */
+    protected void addObject(String name, T object) {
+        assert object != null && name != null;
+        store.put(name, object);
+    }
+
+    public String getDisplayName() {
+        return String.format("%s container", getTypeDisplayName());
+    }
+
+    public Map<String, T> getAsMap() {
+        return Collections.unmodifiableMap(store.getAsMap());
+    }
+
+    public T findByName(String name) {
+        T value = store.find(name);
+        if (value != null) {
+            return value;
+        }
+        applyRules(name);
+        return store.find(name);
+    }
+
+    public NamedDomainObjectCollection<T> matching(Spec<? super T> spec) {
+        return classGenerator.newInstance(DefaultNamedDomainObjectContainer.class, type, classGenerator, storeWithSpec(spec));
+    }
+
+    public NamedDomainObjectCollection<T> matching(Closure spec) {
+        return matching(Specs.convertClosureToSpec(spec));
+    }
+
+    public <S extends T> NamedDomainObjectCollection<S> withType(Class<S> type) {
+        return classGenerator.newInstance(DefaultNamedDomainObjectContainer.class, type, classGenerator, storeWithType(type));
+    }
+
+    protected NamedObjectStore<T> storeWithSpec(Spec<? super T> spec) {
+        return new FilteredObjectStore<T>(store, type, spec);
+    }
+
+    protected <S extends T> NamedObjectStore<S> storeWithType(Class<S> type) {
+        return new FilteredObjectStore<S>(store, type, Specs.satisfyAll());
+    }
+
+    public T getByName(String name) throws UnknownDomainObjectException {
+        T t = findByName(name);
+        if (t == null) {
+            throw createNotFoundException(name);
+        }
+        return t;
+    }
+
+    public T getByName(String name, Closure configureClosure) throws UnknownDomainObjectException {
+        T t = getByName(name);
+        ConfigureUtil.configure(configureClosure, t);
+        return t;
+    }
+
+    public T getAt(String name) throws UnknownDomainObjectException {
+        return getByName(name);
+    }
+
+    /**
+     * Returns a {@link DynamicObject} which can be used to access the domain objects as dynamic properties and
+     * methods.
+     *
+     * @return The dynamic object
+     */
+    public DynamicObject getAsDynamicObject() {
+        return dynamicObject;
+    }
+
+    private void applyRules(String name) {
+        if (applyingRulesFor.contains(name)) {
+            return;
+        }
+        applyingRulesFor.add(name);
+        try {
+            for (Rule rule : rules) {
+                rule.apply(name);
+            }
+        } finally {
+            applyingRulesFor.remove(name);
+        }
+    }
+
+    public Rule addRule(Rule rule) {
+        rules.add(rule);
+        return rule;
+    }
+
+    public Rule addRule(final String description, final Closure ruleAction) {
+        Rule rule = new Rule() {
+            public String getDescription() {
+                return description;
+            }
+
+            public void apply(String taskName) {
+                ruleAction.call(taskName);
+            }
+
+            @Override
+            public String toString() {
+                return "Rule: " + description;
+            }
+        };
+        rules.add(rule);
+        return rule;
+    }
+
+    public List<Rule> getRules() {
+        return Collections.unmodifiableList(rules);
+    }
+
+    protected UnknownDomainObjectException createNotFoundException(String name) {
+        return new UnknownDomainObjectException(String.format("%s with name '%s' not found.", getTypeDisplayName(),
+                name));
+    }
+
+    protected String getTypeDisplayName() {
+        return type.getSimpleName();
+    }
+
+    protected interface NamedObjectStore<S> extends Store<S> {
+        S put(String name, S value);
+
+        S find(String name);
+
+        Map<String, S> getAsMap();
+    }
+
+    private static class MapStore<S> implements NamedObjectStore<S> {
+        private final ListenerBroadcast<Action> addActions = new ListenerBroadcast<Action>(Action.class);
+        private final ListenerBroadcast<Action> removeActions = new ListenerBroadcast<Action>(Action.class);
+        private final Map<String, S> objects = new TreeMap<String, S>();
+
+        public S put(String name, S value) {
+            S oldValue = objects.put(name, value);
+            if (oldValue != null) {
+                removeActions.getSource().execute(oldValue);
+            }
+            addActions.getSource().execute(value);
+            return oldValue;
+        }
+
+        public S find(String name) {
+            return objects.get(name);
+        }
+
+        public Collection<? extends S> getAll() {
+            return getAsMap().values();
+        }
+
+        public Map<String, S> getAsMap() {
+            return objects;
+        }
+
+        public void objectAdded(Action<? super S> action) {
+            addActions.add(action);
+        }
+
+        public void objectRemoved(Action<? super S> action) {
+            removeActions.add(action);
+        }
+    }
+
+    private static class FilteredObjectStore<S> extends FilteredStore<S> implements NamedObjectStore<S> {
+        private final NamedObjectStore<? super S> store;
+
+        public FilteredObjectStore(NamedObjectStore<? super S> store, Class<S> type, Spec<? super S> spec) {
+            super(store, type, spec);
+            this.store = store;
+        }
+
+        public S put(String name, S value) {
+            throw new UnsupportedOperationException();
+        }
+
+        public S find(String name) {
+            return filter(store.find(name));
+        }
+
+        public Collection<? extends S> getAll() {
+            return getAsMap().values();
+        }
+
+        public Map<String, S> getAsMap() {
+            Map<String, S> filteredMap = new LinkedHashMap<String, S>();
+            for (Map.Entry<String, ? super S> entry : store.getAsMap().entrySet()) {
+                S s = filter(entry.getValue());
+                if (s != null) {
+                    filteredMap.put(entry.getKey(), s);
+                }
+            }
+            return filteredMap;
+        }
+    }
+
+    private class ContainerDynamicObject extends CompositeDynamicObject {
+        private ContainerDynamicObject() {
+            setObjects(new BeanDynamicObject(DefaultNamedDomainObjectContainer.this),
+                    new ContainerElementsDynamicObject());
+        }
+
+        @Override
+        protected String getDisplayName() {
+            return DefaultNamedDomainObjectContainer.this.getDisplayName();
+        }
+    }
+
+    private class ContainerElementsDynamicObject extends AbstractDynamicObject {
+        @Override
+        protected String getDisplayName() {
+            return DefaultNamedDomainObjectContainer.this.getDisplayName();
+        }
+
+        @Override
+        public boolean hasProperty(String name) {
+            return findByName(name) != null;
+        }
+
+        @Override
+        public T getProperty(String name) throws MissingPropertyException {
+            T t = findByName(name);
+            if (t == null) {
+                return (T) super.getProperty(name);
+            }
+            return t;
+        }
+
+        @Override
+        public Map<String, T> getProperties() {
+            return getAsMap();
+        }
+
+        @Override
+        public boolean hasMethod(String name, Object... arguments) {
+            return isConfigureMethod(name, arguments);
+        }
+
+        @Override
+        public Object invokeMethod(String name, Object... arguments) throws groovy.lang.MissingMethodException {
+            if (isConfigureMethod(name, arguments)) {
+                return ConfigureUtil.configure((Closure) arguments[0], getByName(name));
+            } else {
+                return super.invokeMethod(name, arguments);
+            }
+        }
+
+        private boolean isConfigureMethod(String name, Object... arguments) {
+            return (arguments.length == 1 && arguments[0] instanceof Closure) && hasProperty(name);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DirectedGraph.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DirectedGraph.java
new file mode 100644
index 0000000..930dd5c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DirectedGraph.java
@@ -0,0 +1,26 @@
+/*
+ * 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 java.util.Collection;
+
+/**
+ * A directed graph with nodes of type N. Each node has a collection of values of type V.
+ */
+public interface DirectedGraph<N, V> {
+    void getNodeValues(N node, Collection<V> values, Collection<N> connectedNodes);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DirectedGraphWithEdgeValues.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DirectedGraphWithEdgeValues.java
new file mode 100644
index 0000000..16a5d0d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DirectedGraphWithEdgeValues.java
@@ -0,0 +1,25 @@
+/*
+ * 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 java.util.Collection;
+
+/**
+ * A directed graph with nodes of type N. Each edge has a collection of values of type V
+ */
+public interface DirectedGraphWithEdgeValues<N, V> extends DirectedGraph<N, V> {
+    void getEdgeValues(N from, N to, Collection<V> values);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DomainObjectContext.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DomainObjectContext.java
new file mode 100644
index 0000000..657572b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DomainObjectContext.java
@@ -0,0 +1,20 @@
+/*
+ * 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;
+
+public interface DomainObjectContext {
+    String absolutePath(String name);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DynamicObject.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DynamicObject.java
new file mode 100644
index 0000000..0cc2a14
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DynamicObject.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 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;
+
+import groovy.lang.MissingMethodException;
+import groovy.lang.MissingPropertyException;
+
+import java.util.Map;
+
+public interface DynamicObject {
+    boolean hasProperty(String name);
+
+    Object getProperty(String name) throws MissingPropertyException;
+
+    void setProperty(String name, Object value) throws MissingPropertyException;
+
+    Map<String, ?> getProperties();
+
+    boolean hasMethod(String name, Object... arguments);
+
+    Object invokeMethod(String name, Object... arguments) throws MissingMethodException;
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DynamicObjectAware.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DynamicObjectAware.java
new file mode 100644
index 0000000..644c379
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DynamicObjectAware.java
@@ -0,0 +1,49 @@
+/*
+ * 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 org.gradle.api.plugins.Convention;
+
+/**
+ * <p>Allows properties and method to be added to an object at run-time. Most implementations are generated at run-time
+ * from existing classes, by a {@link org.gradle.api.internal.ClassGenerator} implementation.</p>
+ *
+ * <p>A {@code DynamicObjectAware} implementation should add meta-object methods which provide the properties and
+ * methods returned by {@link #getAsDynamicObject()}.</p>
+ */
+public interface DynamicObjectAware {
+    /**
+     * Returns the convention object used by this dynamic object.
+     *
+     * @return The convention object. Never returns null.
+     */
+    Convention getConvention();
+
+    /**
+     * Sets the convention object used by this dynamic object.
+     *
+     * @param convention The convention object. Should not be null.
+     */
+    void setConvention(Convention convention);
+
+    /**
+     * Returns a {@link DynamicObject} which describes all the static and dynamic properties and methods of this
+     * object.
+     *
+     * @return The meta-info for this object.
+     */
+    DynamicObject getAsDynamicObject();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DynamicObjectHelper.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DynamicObjectHelper.java
new file mode 100644
index 0000000..2df2a06
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/DynamicObjectHelper.java
@@ -0,0 +1,167 @@
+/*
+ * 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.MissingPropertyException;
+import org.gradle.api.plugins.Convention;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class DynamicObjectHelper extends CompositeDynamicObject {
+    public enum Location {
+        BeforeConvention, AfterConvention
+    }
+
+    private final AbstractDynamicObject delegateObject;
+    private DynamicObject parent;
+    private Convention convention;
+    private DynamicObject beforeConvention;
+    private DynamicObject afterConvention;
+    private MapBackedDynamicObject additionalProperties;
+
+    public DynamicObjectHelper(Object delegateObject) {
+        this(new BeanDynamicObject(delegateObject), null);
+    }
+
+    public DynamicObjectHelper(Object delegateObject, Convention convention) {
+        this(new BeanDynamicObject(delegateObject), convention);
+    }
+
+    public DynamicObjectHelper(AbstractDynamicObject delegateObject, Convention convention) {
+        this.delegateObject = delegateObject;
+        additionalProperties = new MapBackedDynamicObject(delegateObject);
+        setConvention(convention);
+    }
+
+    private void updateDelegates() {
+        List<DynamicObject> delegates = new ArrayList<DynamicObject>();
+        delegates.add(delegateObject);
+        delegates.add(additionalProperties);
+        if (beforeConvention != null) {
+            delegates.add(beforeConvention);
+        }
+        if (convention != null) {
+            delegates.add(convention);
+        }
+        if (afterConvention != null) {
+            delegates.add(afterConvention);
+        }
+        if (parent != null) {
+            delegates.add(parent);
+        }
+        setObjects(delegates.toArray(new DynamicObject[delegates.size()]));
+
+        delegates.remove(parent);
+        delegates.add(additionalProperties);
+        setObjectsForUpdate(delegates.toArray(new DynamicObject[delegates.size()]));
+    }
+
+    protected String getDisplayName() {
+        return delegateObject.getDisplayName();
+    }
+
+    public Map<String, Object> getAdditionalProperties() {
+        return additionalProperties.getProperties();
+    }
+
+    public DynamicObject getParent() {
+        return parent;
+    }
+
+    public void setParent(DynamicObject parent) {
+        this.parent = parent;
+        updateDelegates();
+    }
+
+    public Convention getConvention() {
+        return convention;
+    }
+
+    public void setConvention(Convention convention) {
+        this.convention = convention;
+        updateDelegates();
+    }
+
+    public void addObject(DynamicObject object, Location location) {
+        switch (location) {
+            case BeforeConvention:
+                beforeConvention = object;
+                break;
+            case AfterConvention:
+                afterConvention = object;
+        }
+        updateDelegates();
+    }
+
+    /**
+     * Returns the inheritable properties and methods of this object.
+     *
+     * @return an object containing the inheritable properties and methods of this object.
+     */
+    public DynamicObject getInheritable() {
+        return new InheritedDynamicObject();
+    }
+
+    private DynamicObjectHelper snapshotInheritable() {
+        AbstractDynamicObject emptyBean = new AbstractDynamicObject() {
+            @Override
+            protected String getDisplayName() {
+                return delegateObject.getDisplayName();
+            }
+        };
+
+        DynamicObjectHelper helper = new DynamicObjectHelper(emptyBean);
+
+        helper.parent = parent;
+        helper.convention = convention;
+        helper.additionalProperties = additionalProperties;
+        if (beforeConvention != null) {
+            helper.beforeConvention = beforeConvention;
+        }
+        helper.updateDelegates();
+        return helper;
+    }
+
+    private class InheritedDynamicObject implements DynamicObject {
+        public void setProperty(String name, Object value) {
+            throw new MissingPropertyException(String.format("Could not find property '%s' inherited from %s.", name,
+                    delegateObject.getDisplayName()));
+        }
+
+        public boolean hasProperty(String name) {
+            return snapshotInheritable().hasProperty(name);
+        }
+
+        public Object getProperty(String name) {
+            return snapshotInheritable().getProperty(name);
+        }
+
+        public Map<String, Object> getProperties() {
+            return snapshotInheritable().getProperties();
+        }
+
+        public boolean hasMethod(String name, Object... arguments) {
+            return snapshotInheritable().hasMethod(name, arguments);
+        }
+
+        public Object invokeMethod(String name, Object... arguments) {
+            return snapshotInheritable().invokeMethod(name, arguments);
+        }
+    }
+}
+
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ExceptionAnalyser.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ExceptionAnalyser.java
new file mode 100644
index 0000000..0004ced
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/ExceptionAnalyser.java
@@ -0,0 +1,20 @@
+/*
+ * 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;
+
+public interface ExceptionAnalyser {
+    Throwable transform(Throwable exception);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/GradleDistributionLocator.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/GradleDistributionLocator.java
new file mode 100644
index 0000000..5659828
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/GradleDistributionLocator.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.api.internal;
+
+import java.io.File;
+
+public interface GradleDistributionLocator {
+    File getGradleHome();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/GradleInternal.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/GradleInternal.java
new file mode 100644
index 0000000..655eac1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/GradleInternal.java
@@ -0,0 +1,77 @@
+/*
+ * 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 org.gradle.api.ProjectEvaluationListener;
+import org.gradle.api.internal.project.*;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.execution.TaskGraphExecuter;
+import org.gradle.BuildListener;
+import org.gradle.util.MultiParentClassLoader;
+
+/**
+ * An internal interface for Gradle that exposed objects and concepts that are not intended for public
+ * consumption.  
+ */
+public interface GradleInternal extends Gradle {
+    /**
+     * {@inheritDoc}
+     */
+    ProjectInternal getRootProject();
+
+    /**
+     * {@inheritDoc}
+     */
+    TaskGraphExecuter getTaskGraph();
+
+    /**
+     * Returns the default project. This is used to resolve relative names and paths provided on the UI.
+     */
+    ProjectInternal getDefaultProject();
+
+    IProjectRegistry<ProjectInternal> getProjectRegistry();
+
+    /**
+     * Returns the root {@code ClassLoader} to use for the scripts of this build.
+     */
+    MultiParentClassLoader getScriptClassLoader();
+
+    /**
+     * Returns the broadcaster for {@link ProjectEvaluationListener} events for this build
+     */
+    ProjectEvaluationListener getProjectEvaluationBroadcaster();
+
+    /**
+     * Called by the BuildLoader after the default project is determined.  Until the BuildLoader
+     * is executed, {@link #getDefaultProject()} will return null.
+     * @param defaultProject The default project for this build.
+     */
+    void setDefaultProject(ProjectInternal defaultProject);
+
+    /**
+     * Called by the BuildLoader after the root project is determined.  Until the BuildLoader
+     * is executed, {@link #getRootProject()} will return null.
+      @param rootProject The root project for this build.
+     */
+    void setRootProject(ProjectInternal rootProject);
+
+    /**
+     * Returns the broadcaster for {@link BuildListener} events
+     */
+    BuildListener getBuildListenerBroadcaster();
+
+    ServiceRegistryFactory getServiceRegistryFactory();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/GraphAggregator.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/GraphAggregator.java
new file mode 100644
index 0000000..71cb3ea
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/GraphAggregator.java
@@ -0,0 +1,91 @@
+/*
+ * 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 java.util.*;
+
+/**
+ * Groups the nodes of a graph based on their reachability from a set of starting nodes.
+ */
+public class GraphAggregator<N> {
+    private final CachingDirectedGraphWalker<N, N> graphWalker;
+
+    public GraphAggregator(DirectedGraph<N, ?> graph) {
+        graphWalker = new CachingDirectedGraphWalker<N,N>(new ConnectedNodesAsValuesDirectedGraph<N>(graph));
+    }
+
+    public Result<N> group(Collection<? extends N> startNodes, Collection<? extends N> allNodes) {
+        Map<N, Set<N>> reachableByNode = new HashMap<N, Set<N>>();
+        Set<N> topLevelNodes = new LinkedHashSet<N>(allNodes);
+        for (N node : allNodes) {
+            Set<N> reachableNodes = graphWalker.add(node).findValues();
+            reachableByNode.put(node, reachableNodes);
+            topLevelNodes.removeAll(reachableNodes);
+        }
+        topLevelNodes.addAll(startNodes);
+        Map<N, Set<N>> nodes = new HashMap<N, Set<N>>();
+        for (N node : topLevelNodes) {
+            nodes.put(node, calculateReachableNodes(reachableByNode, node, topLevelNodes));
+        }
+        return new Result<N>(nodes, topLevelNodes);
+    }
+
+    private Set<N> calculateReachableNodes(Map<N, Set<N>> nodes, N node, Set<N> topLevelNodes) {
+        Set<N> reachableNodes = nodes.get(node);
+        reachableNodes.add(node);
+        Set<N> reachableStartNodes = new LinkedHashSet<N>(topLevelNodes);
+        reachableStartNodes.retainAll(reachableNodes);
+        reachableStartNodes.remove(node);
+        for (N startNode : reachableStartNodes) {
+            reachableNodes.removeAll(calculateReachableNodes(nodes, startNode, topLevelNodes));
+        }
+        return reachableNodes;
+    }
+
+    public static class Result<N> {
+        private final Map<N, Set<N>> nodes;
+        private final Set<N> topLevelNodes;
+
+        public Result(Map<N, Set<N>> nodes, Set<N> topLevelNodes) {
+            this.nodes = nodes;
+            this.topLevelNodes = topLevelNodes;
+        }
+
+        public Set<N> getNodes(N startNode) {
+            return nodes.get(startNode);
+        }
+
+        public Set<N> getTopLevelNodes() {
+            return topLevelNodes;
+        }
+    }
+
+    private static class ConnectedNodesAsValuesDirectedGraph<N> implements DirectedGraph<N, N> {
+        private final DirectedGraph<N, ?> graph;
+
+        private ConnectedNodesAsValuesDirectedGraph(DirectedGraph<N, ?> graph) {
+            this.graph = graph;
+        }
+
+        @Override
+        public void getNodeValues(N node, Collection<N> values, Collection<N> connectedNodes) {
+            Set<N> edges = new LinkedHashSet<N>();
+            graph.getNodeValues(node, new ArrayList(), edges);
+            values.addAll(edges);
+            connectedNodes.addAll(edges);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/GroovySourceGenerationBackedClassGenerator.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/GroovySourceGenerationBackedClassGenerator.java
new file mode 100644
index 0000000..74cbe3b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/GroovySourceGenerationBackedClassGenerator.java
@@ -0,0 +1,133 @@
+/*
+ * 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.GroovyClassLoader;
+import groovy.lang.MetaBeanProperty;
+import groovy.lang.MetaMethod;
+
+import java.lang.reflect.Constructor;
+import java.util.Formatter;
+
+public class GroovySourceGenerationBackedClassGenerator extends AbstractClassGenerator {
+
+    protected <T> ClassBuilder<T> start(Class<T> type) {
+        return new ClassBuilderImpl<T>(type);
+    }
+
+    private static class ClassBuilderImpl<T> implements ClassBuilder<T> {
+        private final Formatter src;
+        private final Class<T> type;
+        private final String className;
+        private boolean dynamicAware;
+
+        private ClassBuilderImpl(Class<T> type) {
+            className = type.getSimpleName() + "_Generated";
+            src = new Formatter();
+            this.type = type;
+        }
+
+        public void startClass(boolean isConventionAware, boolean isDynamicAware) {
+            dynamicAware = isDynamicAware;
+            if (type.getPackage() != null) {
+                src.format("package %s;%n", type.getPackage().getName());
+            }
+            src.format("public class %s extends %s ", className, type.getName().replaceAll("\\$", "."));
+            if (isConventionAware) {
+                src.format("implements org.gradle.api.internal.IConventionAware ");
+            }
+            if (isDynamicAware) {
+                src.format(isConventionAware ? ", " : "implements ");
+                src.format("org.gradle.api.internal.DynamicObjectAware ");
+            }
+            src.format("{%n");
+        }
+
+        public void addConstructor(Constructor<?> constructor) {
+            src.format("public %s(", className);
+            for (int i = 0; i < constructor.getParameterTypes().length; i++) {
+                Class<?> paramType = constructor.getParameterTypes()[i];
+                if (i > 0) {
+                    src.format(",");
+                }
+                src.format("%s p%d", paramType.getCanonicalName(), i);
+            }
+            src.format(") { super(");
+            for (int i = 0; i < constructor.getParameterTypes().length; i++) {
+                if (i > 0) {
+                    src.format(",");
+                }
+                src.format("p%d", i);
+            }
+            src.format("); }%n");
+        }
+
+        public void mixInDynamicAware() {
+            src.format("private org.gradle.api.internal.DynamicObjectHelper dynamicObject = new org.gradle.api.internal.DynamicObjectHelper(this, new org.gradle.api.internal.plugins.DefaultConvention())%n");
+            src.format("public void setConvention(org.gradle.api.plugins.Convention convention) { dynamicObject.setConvention(convention); getConventionMapping().setConvention(convention) }%n");
+            src.format("public org.gradle.api.plugins.Convention getConvention() { return dynamicObject.getConvention() }%n");
+            src.format("public org.gradle.api.internal.DynamicObject getAsDynamicObject() { return dynamicObject }%n");
+        }
+
+        public void mixInConventionAware() {
+            if (dynamicAware) {
+                src.format("private org.gradle.api.internal.ConventionMapping mapping = new org.gradle.api.internal.ConventionAwareHelper(this, getConvention())%n");
+            } else {
+                src.format("private org.gradle.api.internal.ConventionMapping mapping = new org.gradle.api.internal.ConventionAwareHelper(this, new org.gradle.api.internal.plugins.DefaultConvention())%n");
+            }
+            src.format("public void setConventionMapping(org.gradle.api.internal.ConventionMapping conventionMapping) { this.mapping = conventionMapping }%n");
+            src.format("public org.gradle.api.internal.ConventionMapping getConventionMapping() { return mapping }%n");
+        }
+
+        public void mixInGroovyObject() {
+        }
+
+        public void addDynamicMethods() {
+            src.format("void setProperty(String name, Object value) { getAsDynamicObject().setProperty(name, value); }%n");
+            src.format("def propertyMissing(String name) { getAsDynamicObject().getProperty(name); }%n");
+            src.format("def methodMissing(String name, Object params) { getAsDynamicObject().invokeMethod(name, (Object[])params); }%n");
+        }
+
+        public void addGetter(MetaBeanProperty property) {
+            MetaMethod getter = property.getGetter();
+            String returnTypeName = getter.getReturnType().getCanonicalName();
+            src.format("private boolean %sSet;%n", property.getName());
+            src.format("public %s %s() { getConventionMapping().getConventionValue(super.%s(), '%s', %sSet); }%n",
+                    returnTypeName, getter.getName(), getter.getName(), property.getName(), property.getName());
+        }
+
+        public void addSetter(MetaBeanProperty property) throws Exception {
+            MetaMethod setter = property.getSetter();
+            if (setter.getReturnType().equals(Void.TYPE)) {
+                src.format("public void %s(%s v) { super.%s(v); %sSet = true; }%n", setter.getName(),
+                        setter.getParameterTypes()[0].getTheClass().getCanonicalName(), setter.getName(),
+                        property.getName());
+            } else {
+                String returnTypeName = setter.getReturnType().getCanonicalName();
+                src.format("public %s %s(%s v) { %s r = super.%s(v); %sSet = true; return r; }%n", returnTypeName,
+                        setter.getName(), setter.getParameterTypes()[0].getTheClass().getCanonicalName(),
+                        returnTypeName, setter.getName(), property.getName());
+            }
+        }
+
+        public Class<? extends T> generate() {
+            src.format("}");
+
+            GroovyClassLoader classLoader = new GroovyClassLoader(type.getClassLoader());
+            return classLoader.parseClass(src.toString());
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/IConventionAware.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/IConventionAware.java
new file mode 100644
index 0000000..46233ac
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/IConventionAware.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+/**
+ * <p>Allows default values for the properties of this object to be declared. Most implementations are generated at
+ * run-time from existing classes, by a {@link org.gradle.api.internal.ClassGenerator} implementation.</p>
+ *
+ * <p>Each getter of an {@code IConventionAware} object should use the mappings to determine the value for the property,
+ * when no value has been explicitly set for the property.</p>
+ *
+ * @author Hans Dockter
+ */
+public interface IConventionAware {
+    /**
+     * Sets the convention mapping for the properties of this object.
+     *
+     * @param mapping The mapping.
+     */
+    void setConventionMapping(ConventionMapping mapping);
+
+    /**
+     * Returns the convention mapping for the properties of this object. The returned mapping object can be used to
+     * manage the mapping for individual properties.
+     *
+     * @return The mapping. Never returns null.
+     */
+    ConventionMapping getConventionMapping();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/MapBackedDynamicObject.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/MapBackedDynamicObject.java
new file mode 100644
index 0000000..3ab0f90
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/MapBackedDynamicObject.java
@@ -0,0 +1,58 @@
+/*
+ * 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 groovy.lang.MissingPropertyException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class MapBackedDynamicObject extends AbstractDynamicObject {
+    private final Map<String, Object> properties = new HashMap<String, Object>();
+    private final AbstractDynamicObject owner;
+
+    public MapBackedDynamicObject(AbstractDynamicObject owner) {
+        this.owner = owner;
+    }
+
+    @Override
+    protected String getDisplayName() {
+        return owner.getDisplayName();
+    }
+
+    @Override
+    public Map<String, Object> getProperties() {
+        return properties;
+    }
+
+    @Override
+    public boolean hasProperty(String name) {
+        return properties.containsKey(name);
+    }
+
+    @Override
+    public Object getProperty(String name) throws MissingPropertyException {
+        if (hasProperty(name)) {
+            return properties.get(name);
+        }
+        return super.getProperty(name);
+    }
+
+    @Override
+    public void setProperty(String name, Object value) {
+        properties.put(name, value);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/NoConventionMapping.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/NoConventionMapping.java
new file mode 100644
index 0000000..c199311
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/NoConventionMapping.java
@@ -0,0 +1,31 @@
+/*
+ * 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.Target;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Disables the application of convention mapping for the class it is attached to, and all superclasses.
+ *
+ * @see org.gradle.api.internal.IConventionAware
+ */
+ at Target(ElementType.TYPE)
+ at Retention(RetentionPolicy.RUNTIME)
+public @interface NoConventionMapping {
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/NoDynamicObject.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/NoDynamicObject.java
new file mode 100644
index 0000000..e5dd17e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/NoDynamicObject.java
@@ -0,0 +1,29 @@
+/*
+ * 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/gradle-core/src/main/groovy/org/gradle/api/internal/SettingsInternal.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/SettingsInternal.java
new file mode 100644
index 0000000..04575f4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/SettingsInternal.java
@@ -0,0 +1,33 @@
+/*
+ * 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 org.gradle.StartParameter;
+import org.gradle.api.initialization.Settings;
+import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.initialization.DefaultProjectDescriptor;
+
+public interface SettingsInternal extends Settings {
+    ClassLoader getClassLoader();
+
+    StartParameter getStartParameter();
+
+    ScriptSource getSettingsScript();
+
+    IProjectRegistry<DefaultProjectDescriptor> getProjectRegistry();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/TaskExecutionHistory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/TaskExecutionHistory.java
new file mode 100644
index 0000000..4dd7bf5
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/TaskExecutionHistory.java
@@ -0,0 +1,25 @@
+/*
+ * 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 org.gradle.api.file.FileCollection;
+
+public interface TaskExecutionHistory {
+    /**
+     * Returns the set of output files which the task produced.
+     */
+    FileCollection getOutputFiles();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/TaskInternal.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/TaskInternal.java
new file mode 100644
index 0000000..53e2514
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/TaskInternal.java
@@ -0,0 +1,40 @@
+/*
+ * 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 org.gradle.api.Task;
+import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.logging.StandardOutputCapture;
+import org.gradle.api.specs.Spec;
+import org.gradle.util.Configurable;
+
+public interface TaskInternal extends Task, Configurable<Task> {
+    Spec<? super TaskInternal> getOnlyIf();
+
+    /**
+     * Executes this task.
+     */
+    void execute();
+
+    StandardOutputCapture getStandardOutputCapture();
+
+    TaskExecuter getExecuter();
+
+    void setExecuter(TaskExecuter executer);
+
+    TaskOutputsInternal getOutputs();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/TaskOutputsInternal.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/TaskOutputsInternal.java
new file mode 100644
index 0000000..5a079cb
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/TaskOutputsInternal.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.api.internal;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.TaskOutputs;
+
+public interface TaskOutputsInternal extends TaskOutputs {
+    Spec<? super TaskInternal> getUpToDateSpec();
+
+    FileCollection getPreviousFiles();
+
+    void setHistory(TaskExecutionHistory history);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactContainer.java
new file mode 100644
index 0000000..2a9e0b9
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ArtifactContainer.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.specs.Spec;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ArtifactContainer {
+    static final ArtifactContainer EMPTY_CONTAINER = new ArtifactContainer() {
+        public void addArtifacts(PublishArtifact... publishArtifacts) {
+            throw new UnsupportedOperationException("You can add elements to EMPTY container");
+        }
+
+        public Set<PublishArtifact> getArtifacts() {
+            return Collections.emptySet();
+        }
+
+        public Set<PublishArtifact> getArtifacts(Spec<PublishArtifact> spec) {
+            return Collections.emptySet();
+        }
+    };
+
+
+    void addArtifacts(PublishArtifact... publishArtifacts);
+
+    Set<PublishArtifact> getArtifacts();
+
+    Set<PublishArtifact> getArtifacts(Spec<PublishArtifact> spec);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/CachingDependencyResolveContext.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/CachingDependencyResolveContext.java
new file mode 100644
index 0000000..b487c82
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/CachingDependencyResolveContext.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.api.internal.artifacts;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.CachingDirectedGraphWalker;
+import org.gradle.api.internal.DirectedGraph;
+import org.gradle.api.internal.file.UnionFileCollection;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class CachingDependencyResolveContext implements DependencyResolveContext {
+    private final List<Object> queue = new ArrayList<Object>();
+    private final CachingDirectedGraphWalker<Object, FileCollection> walker
+            = new CachingDirectedGraphWalker<Object, FileCollection>(new DependencyGraph());
+    private final boolean transitive;
+
+    public CachingDependencyResolveContext(boolean transitive) {
+        this.transitive = transitive;
+    }
+
+    public boolean isTransitive() {
+        return transitive;
+    }
+
+    public FileCollection resolve() {
+        try {
+            walker.add(queue);
+            return new UnionFileCollection(walker.findValues());
+        } finally {
+            queue.clear();
+        }
+    }
+
+    public void add(Object dependency) {
+        queue.add(dependency);
+    }
+
+    private class DependencyGraph implements DirectedGraph<Object, FileCollection> {
+        public void getNodeValues(Object node, Collection<FileCollection> values, Collection<Object> connectedNodes) {
+            if (node instanceof FileCollection) {
+                FileCollection fileCollection = (FileCollection) node;
+                values.add(fileCollection);
+            }
+            else if (node instanceof DependencyInternal) {
+                DependencyInternal dependencyInternal = (DependencyInternal) node;
+                queue.clear();
+                dependencyInternal.resolve(CachingDependencyResolveContext.this);
+                connectedNodes.addAll(queue);
+                queue.clear();
+            }
+            else {
+                throw new IllegalArgumentException(String.format("Cannot resolve object of unknown type %s.", node.getClass().getSimpleName()));
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ConfigurationContainerFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ConfigurationContainerFactory.java
new file mode 100644
index 0000000..e4c8ad4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ConfigurationContainerFactory.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.internal.DomainObjectContext;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ConfigurationContainerFactory {
+    ConfigurationContainer createConfigurationContainer(ResolverProvider resolverProvider,
+                                                        DependencyMetaDataProvider dependencyMetaDataProvider,
+                                                        DomainObjectContext domainObjectContext);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultConfigurationContainerFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultConfigurationContainerFactory.java
new file mode 100644
index 0000000..84089b1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultConfigurationContainerFactory.java
@@ -0,0 +1,79 @@
+/*
+ * 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;
+
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.DomainObjectContext;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
+import org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer;
+import org.gradle.api.internal.artifacts.ivyservice.*;
+
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultConfigurationContainerFactory implements ConfigurationContainerFactory {
+    private Map clientModuleRegistry;
+    private SettingsConverter settingsConverter;
+    private ModuleDescriptorConverter resolveModuleDescriptorConverter;
+    private ModuleDescriptorConverter publishModuleDescriptorConverter;
+    private ModuleDescriptorConverter fileModuleDescriptorConverter;
+    private IvyFactory ivyFactory;
+    private IvyDependencyResolver dependencyResolver;
+    private IvyDependencyPublisher dependencyPublisher;
+    private ClassGenerator classGenerator;
+
+    public DefaultConfigurationContainerFactory(Map clientModuleRegistry, SettingsConverter settingsConverter,
+                                                ModuleDescriptorConverter resolveModuleDescriptorConverter,
+                                                ModuleDescriptorConverter publishModuleDescriptorConverter,
+                                                ModuleDescriptorConverter fileModuleDescriptorConverter,
+                                                IvyFactory ivyFactory,
+                                                IvyDependencyResolver dependencyResolver, IvyDependencyPublisher dependencyPublisher,
+                                                ClassGenerator classGenerator) {
+        this.clientModuleRegistry = clientModuleRegistry;
+        this.settingsConverter = settingsConverter;
+        this.resolveModuleDescriptorConverter = resolveModuleDescriptorConverter;
+        this.publishModuleDescriptorConverter = publishModuleDescriptorConverter;
+        this.fileModuleDescriptorConverter = fileModuleDescriptorConverter;
+        this.ivyFactory = ivyFactory;
+        this.dependencyResolver = dependencyResolver;
+        this.dependencyPublisher = dependencyPublisher;
+        this.classGenerator = classGenerator;
+    }
+
+    public ConfigurationContainer createConfigurationContainer(ResolverProvider resolverProvider,
+                                                               DependencyMetaDataProvider dependencyMetaDataProvider,
+                                                               DomainObjectContext domainObjectContext) {
+        IvyService ivyService = new ErrorHandlingIvyService(
+                new ShortcircuitEmptyConfigsIvyService(
+                        new DefaultIvyService(
+                                dependencyMetaDataProvider,
+                                resolverProvider,
+                                settingsConverter,
+                                resolveModuleDescriptorConverter,
+                                publishModuleDescriptorConverter,
+                                fileModuleDescriptorConverter,
+                                ivyFactory,
+                                dependencyResolver,
+                                dependencyPublisher,
+                                clientModuleRegistry)));
+        return classGenerator.newInstance(DefaultConfigurationContainer.class, ivyService, classGenerator, domainObjectContext);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRule.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRule.java
new file mode 100644
index 0000000..db65a45
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRule.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import org.gradle.api.artifacts.ExcludeRule;
+
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ *
+ *         DefaultExcludeRule is a value object
+ */
+public class DefaultExcludeRule implements ExcludeRule {
+    private Map<String, String> excludeArgs;
+
+    public DefaultExcludeRule(Map<String, String> excludeArgs) {
+        this.excludeArgs = excludeArgs;
+    }
+
+    public Map<String, String> getExcludeArgs() {
+        return excludeArgs;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultExcludeRule that = (DefaultExcludeRule) o;
+
+        if (excludeArgs != null ? !excludeArgs.equals(that.excludeArgs) : that.excludeArgs != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return excludeArgs != null ? excludeArgs.hashCode() : 0;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRuleContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRuleContainer.java
new file mode 100644
index 0000000..a79ad0d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultExcludeRuleContainer.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.api.artifacts.ExcludeRuleContainer;
+
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultExcludeRuleContainer implements ExcludeRuleContainer {
+    private Set<ExcludeRule> addedRules = new LinkedHashSet<ExcludeRule>();
+
+    public DefaultExcludeRuleContainer() {
+    }
+
+    public DefaultExcludeRuleContainer(Set<ExcludeRule> addedRules) {
+        this.addedRules = new HashSet<ExcludeRule>(addedRules);
+    }
+
+    public void add(Map<String, String> args) {
+        addedRules.add(new DefaultExcludeRule(args));
+    }
+
+    public Set<ExcludeRule> getRules() {
+        return addedRules;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModule.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModule.java
new file mode 100644
index 0000000..5190958
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultModule.java
@@ -0,0 +1,57 @@
+/*
+ * 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;
+
+import org.gradle.api.artifacts.Module;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultModule implements Module {
+    private String group;
+    private String name;
+    private String version;
+    private String status = DEFAULT_STATUS;
+
+    public DefaultModule(String group, String name, String version) {
+        this.group = group;
+        this.name = name;
+        this.version = version;
+    }
+
+    public DefaultModule(String group, String name, String version, String status) {
+        this.group = group;
+        this.name = name;
+        this.version = version;
+        this.status = status;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifact.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifact.java
new file mode 100644
index 0000000..835132c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifact.java
@@ -0,0 +1,79 @@
+/*
+ * 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;
+
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.artifacts.ResolvedDependency;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.resolve.ResolveEngine;
+import org.apache.ivy.core.resolve.DownloadOptions;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultResolvedArtifact implements ResolvedArtifact {
+    private ResolvedDependency resolvedDependency;
+    private Artifact artifact;
+    private ResolveEngine resolvedEngine;
+    private File file;
+
+    public DefaultResolvedArtifact(Artifact artifact, ResolveEngine resolvedEngine) {
+        this.artifact = artifact;
+        this.resolvedEngine = resolvedEngine;
+    }
+
+    public ResolvedDependency getResolvedDependency() {
+        return resolvedDependency;
+    }
+
+    public void setResolvedDependency(ResolvedDependency resolvedDependency) {
+        this.resolvedDependency = resolvedDependency;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s;%s", resolvedDependency, artifact.getName());
+    }
+
+    public String getName() {
+        return artifact.getName();
+    }
+
+    public String getType() {
+        return artifact.getType();
+    }
+
+    public String getExtension() {
+        return artifact.getExt();
+    }
+
+    public String getVersion() {
+        return getResolvedDependency() == null ? null : getResolvedDependency().getModuleVersion();
+    }
+
+    public String getDependencyName() {
+        return getResolvedDependency() == null ? null : getResolvedDependency().getModuleName();
+    }
+
+    public File getFile() {
+        if (file == null) {
+            file = resolvedEngine.download(artifact, new DownloadOptions()).getLocalFile();
+        }
+        return file;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependency.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependency.java
new file mode 100644
index 0000000..9745a41
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependency.java
@@ -0,0 +1,160 @@
+/*
+ * 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;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.artifacts.ResolvedDependency;
+import org.gradle.util.GUtil;
+
+import java.util.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultResolvedDependency implements ResolvedDependency {
+    private Set<ResolvedDependency> children = new LinkedHashSet<ResolvedDependency>();
+    private Set<ResolvedDependency> parents = new LinkedHashSet<ResolvedDependency>();
+    private Map<ResolvedDependency, Set<ResolvedArtifact>> parentArtifacts
+            = new LinkedHashMap<ResolvedDependency, Set<ResolvedArtifact>>();
+    private String name;
+    private final ResolvedConfigurationIdentifier id;
+    private Set<ResolvedArtifact> moduleArtifacts = new LinkedHashSet<ResolvedArtifact>();
+    private Map<ResolvedDependency, Set<ResolvedArtifact>> allArtifactsCache = new HashMap<ResolvedDependency, Set<ResolvedArtifact>>();
+    private Set<ResolvedArtifact> allModuleArtifactsCache;
+
+    public DefaultResolvedDependency(String name, String moduleGroup, String moduleName, String moduleVersion,
+                                     String configuration, Set<ResolvedArtifact> moduleArtifacts) {
+        assert name != null;
+        assert moduleArtifacts != null;
+
+        this.name = name;
+        id = new ResolvedConfigurationIdentifier(moduleGroup, moduleName, moduleVersion, configuration);
+        this.moduleArtifacts = moduleArtifacts;
+    }
+
+    public DefaultResolvedDependency(String moduleGroup, String moduleName, String moduleVersion, String configuration,
+                                     Set<ResolvedArtifact> moduleArtifacts) {
+        this(moduleGroup + ":" + moduleName + ":" + moduleVersion, moduleGroup, moduleName, moduleVersion,
+                configuration, moduleArtifacts);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public ResolvedConfigurationIdentifier getId() {
+        return id;
+    }
+
+    public String getModuleGroup() {
+        return id.getModuleGroup();
+    }
+
+    public String getModuleName() {
+        return id.getModuleName();
+    }
+
+    public String getModuleVersion() {
+        return id.getModuleVersion();
+    }
+
+    public String getConfiguration() {
+        return id.getConfiguration();
+    }
+
+    public Set<ResolvedDependency> getChildren() {
+        return children;
+    }
+
+    public Set<ResolvedArtifact> getModuleArtifacts() {
+        return moduleArtifacts;
+    }
+
+    public Set<ResolvedArtifact> getAllModuleArtifacts() {
+        if (allModuleArtifactsCache == null) {
+            Set<ResolvedArtifact> allArtifacts = new LinkedHashSet<ResolvedArtifact>();
+            allArtifacts.addAll(getModuleArtifacts());
+            for (ResolvedDependency childResolvedDependency : getChildren()) {
+                allArtifacts.addAll(childResolvedDependency.getAllModuleArtifacts());
+            }
+            allModuleArtifactsCache = allArtifacts;
+        }
+        return allModuleArtifactsCache;
+    }
+
+    public Set<ResolvedArtifact> getParentArtifacts(ResolvedDependency parent) {
+        if (!parents.contains(parent)) {
+            throw new InvalidUserDataException("Unknown Parent");
+        }
+        Set<ResolvedArtifact> artifacts = parentArtifacts.get(parent);
+        return artifacts == null ? Collections.<ResolvedArtifact>emptySet() : artifacts;
+    }
+
+    public Set<ResolvedArtifact> getArtifacts(ResolvedDependency parent) {
+        return GUtil.addSets(getParentArtifacts(parent), getModuleArtifacts());
+    }
+
+    public Set<ResolvedArtifact> getAllArtifacts(ResolvedDependency parent) {
+        if (allArtifactsCache.get(parent) == null) {
+            Set<ResolvedArtifact> allArtifacts = new LinkedHashSet<ResolvedArtifact>();
+            allArtifacts.addAll(getArtifacts(parent));
+            for (ResolvedDependency childResolvedDependency : getChildren()) {
+                for (ResolvedDependency childParent : childResolvedDependency.getParents()) {
+                    allArtifacts.addAll(childResolvedDependency.getAllArtifacts(childParent));
+                }
+            }
+            allArtifactsCache.put(parent, allArtifacts);
+        }
+        return allArtifactsCache.get(parent);
+    }
+
+    public Set<ResolvedDependency> getParents() {
+        return parents;
+    }
+
+    public String toString() {
+        return name + ";" + getConfiguration();
+    }
+
+    public void addParentSpecificArtifacts(ResolvedDependency parent, Set<ResolvedArtifact> artifacts) {
+        parentArtifacts.put(parent, artifacts);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultResolvedDependency that = (DefaultResolvedDependency) o;
+        return id.equals(that.id);
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+    public void addChild(DefaultResolvedDependency child) {
+        children.add(child);
+        child.parents.add(this);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolverContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolverContainer.java
new file mode 100644
index 0000000..4fc7931
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DefaultResolverContainer.java
@@ -0,0 +1,231 @@
+/*
+ * 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;
+
+import groovy.lang.Closure;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.UnknownDomainObjectException;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.ResolverContainer;
+import org.gradle.api.artifacts.UnknownRepositoryException;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.artifacts.maven.GroovyMavenDeployer;
+import org.gradle.api.artifacts.maven.MavenResolver;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.DefaultNamedDomainObjectContainer;
+import org.gradle.api.internal.artifacts.ivyservice.ResolverFactory;
+import org.gradle.api.internal.artifacts.publish.maven.MavenPomMetaInfoProvider;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.util.ConfigureUtil;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultResolverContainer extends DefaultNamedDomainObjectContainer<DependencyResolver>
+        implements ResolverContainer, MavenPomMetaInfoProvider {
+    private ResolverFactory resolverFactory;
+
+    private List<String> resolverNames = new ArrayList<String>();
+
+    private File mavenPomDir;
+
+    private FileResolver fileResolver;
+
+    private Conf2ScopeMappingContainer mavenScopeMappings;
+
+    private ConfigurationContainer configurationContainer;
+
+    public DefaultResolverContainer(ResolverFactory resolverFactory, ClassGenerator classGenerator) {
+        super(DependencyResolver.class, classGenerator);
+        this.resolverFactory = resolverFactory;
+    }
+
+    @Override
+    public String getTypeDisplayName() {
+        return "resolver";
+    }
+
+    public DependencyResolver add(Object userDescription) {
+        return add(userDescription, null);
+    }
+
+    public DependencyResolver add(Object userDescription, Closure configureClosure) {
+        return addInternal(userDescription, configureClosure, new OrderAction() {
+            public void apply(String resolverName) {
+                resolverNames.add(resolverName);
+            }
+        });
+    }
+
+    public DependencyResolver addBefore(Object userDescription, String afterResolverName) {
+        return addBefore(userDescription, afterResolverName, null);
+    }
+
+    public DependencyResolver addBefore(Object userDescription, final String afterResolverName, Closure configureClosure) {
+        if (!GUtil.isTrue(afterResolverName)) {
+            throw new InvalidUserDataException("You must specify userDescription and afterResolverName");
+        }
+        if (findByName(afterResolverName) == null) {
+            throw new InvalidUserDataException("Resolver $afterResolverName does not exists!");
+        }
+        return addInternal(userDescription, configureClosure, new OrderAction() {
+            public void apply(String resolverName) {
+                resolverNames.add(resolverNames.indexOf(afterResolverName), resolverName);
+            }
+        });
+    }
+
+    public DependencyResolver addAfter(Object userDescription, final String beforeResolverName) {
+        return addAfter(userDescription, beforeResolverName, null);
+    }
+
+    public DependencyResolver addAfter(Object userDescription, final String beforeResolverName, Closure configureClosure) {
+        if (!GUtil.isTrue(beforeResolverName)) {
+            throw new InvalidUserDataException("You must specify userDescription and beforeResolverName");
+        }
+        if (findByName(beforeResolverName) == null) {
+            throw new InvalidUserDataException("Resolver $beforeResolverName does not exists!");
+        }
+        return addInternal(userDescription, configureClosure, new OrderAction() {
+            public void apply(String resolverName) {
+                int insertPos = resolverNames.indexOf(beforeResolverName) + 1;
+                if (insertPos == resolverNames.size()) {
+                    resolverNames.add(resolverName);
+                } else {
+                    resolverNames.add(insertPos, resolverName);
+                }
+            }
+        });
+    }
+
+    public DependencyResolver addFirst(Object userDescription) {
+        return addFirst(userDescription, null);
+    }
+
+    public DependencyResolver addFirst(Object userDescription, Closure configureClosure) {
+        if (!GUtil.isTrue(userDescription)) {
+            throw new InvalidUserDataException("You must specify userDescription");
+        }
+        return addInternal(userDescription, configureClosure, new OrderAction() {
+            public void apply(String resolverName) {
+                if (resolverNames.size() == 0) {
+                    resolverNames.add(resolverName);
+                } else {
+                    resolverNames.add(0, resolverName);
+                }
+            }
+        });
+    }
+
+    private DependencyResolver addInternal(Object userDescription, Closure configureClosure, OrderAction orderAction) {
+        if (!GUtil.isTrue(userDescription)) {
+            throw new InvalidUserDataException("You must specify userDescription");
+        }
+        DependencyResolver resolver = resolverFactory.createResolver(userDescription);
+        ConfigureUtil.configure(configureClosure, resolver);
+        if (!GUtil.isTrue(resolver.getName())) {
+            throw new InvalidUserDataException("You must specify a name for the resolver. Resolver=" + userDescription);
+        }
+        if (findByName(resolver.getName()) != null) {
+            throw new InvalidUserDataException(String.format(
+                    "Cannot add a resolver with name '%s' as a resolver with that name already exists.", resolver.getName()));
+        }
+        addObject(resolver.getName(), resolver);
+        orderAction.apply(resolver.getName());
+        return resolver;
+    }
+
+    @Override
+    protected UnknownDomainObjectException createNotFoundException(String name) {
+        return new UnknownRepositoryException(String.format("Repository with name '%s' not found.", name));
+    }
+
+    public List<DependencyResolver> getResolvers() {
+        List<DependencyResolver> returnedResolvers = new ArrayList<DependencyResolver>();
+        for (String resolverName : resolverNames) {
+            returnedResolvers.add(getByName(resolverName));
+        }
+        return returnedResolvers;
+    }
+
+    private static interface OrderAction {
+        
+        void apply(String resolverName);
+    }
+
+    public ResolverFactory getResolverFactory() {
+        return resolverFactory;
+    }
+
+    public void setResolverFactory(ResolverFactory resolverFactory) {
+        this.resolverFactory = resolverFactory;
+    }
+
+    public List<String> getResolverNames() {
+        return resolverNames;
+    }
+
+    public void setResolverNames(List<String> resolverNames) {
+        this.resolverNames = resolverNames;
+    }
+
+    public FileResolver getFileResolver() {
+        return fileResolver;
+    }
+
+    public void setFileResolver(FileResolver fileResolver) {
+        this.fileResolver = fileResolver;
+    }
+
+    public ConfigurationContainer getConfigurationContainer() {
+        return configurationContainer;
+    }
+
+    public void setConfigurationContainer(ConfigurationContainer configurationContainer) {
+        this.configurationContainer = configurationContainer;
+    }
+
+    public GroovyMavenDeployer createMavenDeployer(String name) {
+        return resolverFactory.createMavenDeployer(name, this, getConfigurationContainer(), getMavenScopeMappings(), getFileResolver());
+    }
+
+    public MavenResolver createMavenInstaller(String name) {
+        return resolverFactory.createMavenInstaller(name, this, getConfigurationContainer(), getMavenScopeMappings(), getFileResolver());
+    }
+
+    public Conf2ScopeMappingContainer getMavenScopeMappings() {
+        return mavenScopeMappings;
+    }
+
+    public void setMavenScopeMappings(Conf2ScopeMappingContainer mavenScopeMappings) {
+        this.mavenScopeMappings = mavenScopeMappings;
+    }
+
+    public File getMavenPomDir() {
+        return mavenPomDir;
+    }
+
+    public void setMavenPomDir(File mavenPomDir) {
+        this.mavenPomDir = mavenPomDir;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyInternal.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyInternal.java
new file mode 100644
index 0000000..2f0d67c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyInternal.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+import org.gradle.api.artifacts.Dependency;
+
+public interface DependencyInternal extends Dependency {
+    void resolve(DependencyResolveContext context);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolveContext.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolveContext.java
new file mode 100644
index 0000000..c426c7b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/DependencyResolveContext.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+public interface DependencyResolveContext {
+    boolean isTransitive();
+    
+    void add(Object dependency);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/IvyService.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/IvyService.java
new file mode 100644
index 0000000..40ef9ed
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/IvyService.java
@@ -0,0 +1,35 @@
+/*
+ * 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;
+
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ResolveException;
+import org.gradle.api.artifacts.ResolvedConfiguration;
+
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public interface IvyService {
+    ResolvedConfiguration resolve(Configuration configuration) throws ResolveException;
+
+    void publish(Set<Configuration> configurationsToPublish, File descriptorDestination,
+                 List<DependencyResolver> publishResolvers);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifier.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifier.java
new file mode 100644
index 0000000..a86abde
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifier.java
@@ -0,0 +1,92 @@
+/*
+ * 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;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+
+public class ResolvedConfigurationIdentifier {
+    private final String moduleGroup;
+    private final String moduleName;
+    private final String moduleVersion;
+    private final String configuration;
+
+    public ResolvedConfigurationIdentifier(String moduleGroup, String moduleName, String moduleVersion,
+                                           String configuration) {
+        this.moduleGroup = moduleGroup;
+        this.moduleName = moduleName;
+        this.moduleVersion = moduleVersion;
+        this.configuration = configuration;
+    }
+
+    public ResolvedConfigurationIdentifier(ModuleRevisionId moduleRevisionId, String configuration) {
+        this(moduleRevisionId.getOrganisation(), moduleRevisionId.getName(), moduleRevisionId.getRevision(),
+                configuration);
+    }
+
+    public String getConfiguration() {
+        return configuration;
+    }
+
+    public String getModuleGroup() {
+        return moduleGroup;
+    }
+
+    public String getModuleName() {
+        return moduleName;
+    }
+
+    public String getModuleVersion() {
+        return moduleVersion;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s:%s:%s:%s", moduleGroup, moduleName, moduleVersion, configuration);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        ResolvedConfigurationIdentifier that = (ResolvedConfigurationIdentifier) o;
+
+        if (!moduleGroup.equals(that.moduleGroup)) {
+            return false;
+        }
+        if (!moduleName.equals(that.moduleName)) {
+            return false;
+        }
+        if (!moduleVersion.equals(that.moduleVersion)) {
+            return false;
+        }
+        if (!configuration.equals(that.configuration)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return moduleGroup.hashCode() ^ moduleName.hashCode() ^ configuration.hashCode();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/Configurations.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/Configurations.java
new file mode 100644
index 0000000..d77abe7
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/Configurations.java
@@ -0,0 +1,89 @@
+/*
+ * 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.configurations;
+
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.specs.Spec;
+
+import java.util.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class Configurations {
+    public static Set<String> getNames(Collection<Configuration> configurations, boolean includeExtended) {
+        Set<Configuration> allConfigurations = new HashSet<Configuration>(configurations);
+        if (includeExtended) {
+            allConfigurations = createAllConfigurations(configurations);
+        }
+        Set<String> names = new HashSet<String>();
+        for (Configuration configuration : allConfigurations) {
+            names.add(configuration.getName());
+        }
+        return names;
+    }
+
+    public static Set<String> getNames(Collection<Configuration> configurations) {
+        return getNames(configurations, false);
+    }
+
+    private static Set<Configuration> createAllConfigurations(Collection<Configuration> configurations) {
+        Set<Configuration> allConfigurations = new HashSet<Configuration>();
+        for (Configuration configuration : configurations) {
+            allConfigurations.addAll(configuration.getHierarchy());
+        }
+        return allConfigurations;
+    }
+
+    public static String uploadInternalTaskName(String configurationName) {
+        return String.format("upload%sInternal", getCapitalName(configurationName));
+    }
+
+    public static String uploadTaskName(String configurationName) {
+        return String.format("upload%s", getCapitalName(configurationName));
+    }
+
+
+    private static String getCapitalName(String configurationName) {
+        return configurationName.substring(0, 1).toUpperCase() + configurationName.substring(1);
+    }
+
+    public static Set<Dependency> getDependencies(Set<Configuration> configurations, Spec<Dependency> dependencySpec) {
+        Set<Dependency> dependencies = new LinkedHashSet<Dependency>();
+        for (Configuration configuration : configurations) {
+            for (Dependency dependency : configuration.getDependencies()) {
+                if (dependencySpec.isSatisfiedBy(dependency)) {
+                    dependencies.add(dependency);
+                }
+            }
+        }
+        return dependencies;
+    }
+
+    public static Set<PublishArtifact> getArtifacts(Set<Configuration> configurations, Spec<PublishArtifact> artifactSpec) {
+        Set<PublishArtifact> artifacts = new HashSet<PublishArtifact>();
+        for (Configuration configuration : configurations) {
+            for (PublishArtifact artifact : configuration.getArtifacts()) {
+                if (artifactSpec.isSatisfiedBy(artifact)) {
+                    artifacts.add(artifact);
+                }
+            }
+        }
+        return artifacts;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsProvider.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsProvider.java
new file mode 100644
index 0000000..15b95cc
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsProvider.java
@@ -0,0 +1,27 @@
+/*
+ * 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.configurations;
+
+import org.gradle.api.artifacts.Configuration;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ConfigurationsProvider {
+    Set<Configuration> getAll();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java
new file mode 100644
index 0000000..6e41407
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java
@@ -0,0 +1,535 @@
+/*
+ * 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.configurations;
+
+import groovy.lang.Closure;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.Action;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.*;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.DefaultDomainObjectContainer;
+import org.gradle.api.internal.artifacts.DefaultExcludeRule;
+import org.gradle.api.internal.artifacts.IvyService;
+import org.gradle.api.internal.file.AbstractFileCollection;
+import org.gradle.api.internal.tasks.AbstractTaskDependency;
+import org.gradle.api.internal.tasks.TaskDependencyInternal;
+import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.util.WrapUtil;
+
+import java.io.File;
+import java.util.*;
+
+import static org.apache.ivy.core.module.descriptor.Configuration.Visibility;
+
+public class DefaultConfiguration extends AbstractFileCollection implements Configuration {
+    private final String path;
+    private final String name;
+
+    private Visibility visibility = Visibility.PUBLIC;
+    private boolean transitive = true;
+    private Set<Configuration> extendsFrom = new LinkedHashSet<Configuration>();
+    private String description;
+    private ConfigurationsProvider configurationsProvider;
+
+    private IvyService ivyService;
+
+    private DefaultDomainObjectContainer<Dependency> dependencies =
+            new DefaultDomainObjectContainer<Dependency>(Dependency.class);
+
+    private Set<PublishArtifact> artifacts = new LinkedHashSet<PublishArtifact>();
+
+    private Set<ExcludeRule> excludeRules = new LinkedHashSet<ExcludeRule>();
+
+    private final ConfigurationTaskDependency taskDependency = new ConfigurationTaskDependency();
+
+    // This lock only protects the following fields
+    private final Object lock = new Object();
+    private State state = State.UNRESOLVED;
+    private ResolvedConfiguration cachedResolvedConfiguration;
+
+    public DefaultConfiguration(String path, String name, ConfigurationsProvider configurationsProvider,
+                                IvyService ivyService) {
+        this.path = path;
+        this.name = name;
+        this.configurationsProvider = configurationsProvider;
+        this.ivyService = ivyService;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public State getState() {
+        synchronized (lock) {
+            return state;
+        }
+    }
+
+    public boolean isVisible() {
+        return visibility == Visibility.PUBLIC;
+    }
+
+    public Configuration setVisible(boolean visible) {
+        throwExceptionIfNotInUnresolvedState();
+        this.visibility = visible ? Visibility.PUBLIC : Visibility.PRIVATE;
+        return this;
+    }
+
+    public Set<Configuration> getExtendsFrom() {
+        return Collections.unmodifiableSet(extendsFrom);
+    }
+
+    public Configuration setExtendsFrom(Set<Configuration> extendsFrom) {
+        throwExceptionIfNotInUnresolvedState();
+        this.extendsFrom = new HashSet<Configuration>();
+        for (Configuration configuration : extendsFrom) {
+            extendsFrom(configuration);
+        }
+        return this;
+    }
+
+    public Configuration extendsFrom(Configuration... extendsFrom) {
+        throwExceptionIfNotInUnresolvedState();
+        for (Configuration configuration : extendsFrom) {
+            if (configuration.getHierarchy().contains(this)) {
+                throw new InvalidUserDataException(String.format(
+                        "Cyclic extendsFrom from %s and %s is not allowed. See existing hierarchy: %s", this,
+                        configuration, configuration.getHierarchy()));
+            }
+            this.extendsFrom.add(configuration);
+        }
+        return this;
+    }
+
+    public boolean isTransitive() {
+        return transitive;
+    }
+
+    public Configuration setTransitive(boolean transitive) {
+        throwExceptionIfNotInUnresolvedState();
+        this.transitive = transitive;
+        return this;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public Configuration setDescription(String description) {
+        throwExceptionIfNotInUnresolvedState();
+        this.description = description;
+        return this;
+    }
+
+    public Set<Configuration> getHierarchy() {
+        Set<Configuration> result = WrapUtil.<Configuration>toLinkedSet(this);
+        collectSuperConfigs(this, result);
+        return result;
+    }
+
+    private void collectSuperConfigs(Configuration configuration, Set<Configuration> result) {
+        for (Configuration superConfig : configuration.getExtendsFrom()) {
+            if (result.contains(superConfig)) {
+                result.remove(superConfig);
+            }
+            result.add(superConfig);
+            collectSuperConfigs(superConfig, result);
+        }
+    }
+
+    public Set<Configuration> getAll() {
+        return configurationsProvider.getAll();
+    }
+
+    public Set<File> resolve() {
+        return getFiles();
+    }
+
+    public Set<File> getFiles() {
+        return fileCollection(Specs.SATISFIES_ALL).getFiles();
+    }
+
+    public Set<File> files(Dependency... dependencies) {
+        return fileCollection(dependencies).getFiles();
+    }
+
+    public Set<File> files(Closure dependencySpecClosure) {
+        return fileCollection(dependencySpecClosure).getFiles();
+    }
+
+    public Set<File> files(Spec<Dependency> dependencySpec) {
+        return fileCollection(dependencySpec).getFiles();
+    }
+
+    public FileCollection fileCollection(Spec<Dependency> dependencySpec) {
+        return new ConfigurationFileCollection(dependencySpec);
+    }
+
+    public FileCollection fileCollection(Closure dependencySpecClosure) {
+        return new ConfigurationFileCollection(dependencySpecClosure);
+    }
+
+    public FileCollection fileCollection(Dependency... dependencies) {
+        return new ConfigurationFileCollection(WrapUtil.toLinkedSet(dependencies));
+    }
+
+    public ResolvedConfiguration getResolvedConfiguration() {
+        synchronized (lock) {
+            if (state == State.UNRESOLVED) {
+                cachedResolvedConfiguration = ivyService.resolve(this);
+                if (cachedResolvedConfiguration.hasError()) {
+                    state = State.RESOLVED_WITH_FAILURES;
+                } else {
+                    state = State.RESOLVED;
+                }
+            }
+            return cachedResolvedConfiguration;
+        }
+    }
+
+    public void publish(List<DependencyResolver> publishResolvers, File descriptorDestination) {
+        ivyService.publish(getHierarchy(), descriptorDestination, publishResolvers);
+    }
+
+    public TaskDependency getBuildDependencies() {
+        return taskDependency;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public TaskDependency getTaskDependencyFromProjectDependency(final boolean useDependedOn, final String taskName) {
+        return new AbstractTaskDependency() {
+            public void resolve(TaskDependencyResolveContext context) {
+                if (useDependedOn) {
+                    addTaskDependenciesFromProjectsIDependOn(taskName, context);
+                } else {
+                    Project thisProject = context.getTask().getProject();
+                    addTaskDependenciesFromProjectsDependingOnMe(thisProject, taskName, context);
+                }
+            }
+
+            private void addTaskDependenciesFromProjectsIDependOn(final String taskName,
+                                                                  final TaskDependencyResolveContext context) {
+                Set<ProjectDependency> projectDependencies = getAllDependencies(ProjectDependency.class);
+                for (ProjectDependency projectDependency : projectDependencies) {
+                    Task nextTask = projectDependency.getDependencyProject().getTasks().findByName(taskName);
+                    if (nextTask != null) {
+                        context.add(nextTask);
+                    }
+                }
+            }
+
+            private void addTaskDependenciesFromProjectsDependingOnMe(final Project thisProject, final String taskName,
+                                                                      final TaskDependencyResolveContext context) {
+                Set<Task> tasksWithName = thisProject.getRootProject().getTasksByName(taskName, true);
+                for (Task nextTask : tasksWithName) {
+                    Configuration configuration = nextTask.getProject().getConfigurations().findByName(getName());
+                    if (configuration != null && doesConfigurationDependOnProject(configuration, thisProject)) {
+                        context.add(nextTask);
+                    }
+                }
+            }
+        };
+    }
+
+    private static boolean doesConfigurationDependOnProject(Configuration configuration, Project project) {
+        Set<ProjectDependency> projectDependencies = configuration.getAllDependencies(ProjectDependency.class);
+        for (ProjectDependency projectDependency : projectDependencies) {
+            if (projectDependency.getDependencyProject().equals(project)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public TaskDependency getBuildArtifacts() {
+        return getAllArtifactFiles().getBuildDependencies();
+    }
+
+    public Set<Dependency> getDependencies() {
+        return dependencies.getAll();
+    }
+
+    public Set<Dependency> getAllDependencies() {
+        return Configurations.getDependencies(getHierarchy(), Specs.<Dependency>satisfyAll());
+    }
+
+    public <T extends Dependency> Set<T> getDependencies(Class<T> type) {
+        return filter(type, getDependencies());
+    }
+
+    private <T extends Dependency> Set<T> filter(Class<T> type, Set<Dependency> dependencySet) {
+        Set<T> matches = new LinkedHashSet<T>();
+        for (Dependency dependency : dependencySet) {
+            if (type.isInstance(dependency)) {
+                matches.add(type.cast(dependency));
+            }
+        }
+        return matches;
+    }
+
+    public <T extends Dependency> Set<T> getAllDependencies(Class<T> type) {
+        return filter(type, getAllDependencies());
+    }
+
+    public void addDependency(Dependency dependency) {
+        throwExceptionIfNotInUnresolvedState();
+        dependencies.addObject(dependency);
+    }
+
+    public Configuration addArtifact(PublishArtifact artifact) {
+        throwExceptionIfNotInUnresolvedState();
+        artifacts.add(artifact);
+        return this;
+    }
+
+    public Configuration removeArtifact(PublishArtifact artifact) {
+        throwExceptionIfNotInUnresolvedState();
+        artifacts.remove(artifact);
+        return this;
+    }
+
+    public Set<PublishArtifact> getArtifacts() {
+        return Collections.unmodifiableSet(artifacts);
+    }
+
+    public Set<PublishArtifact> getAllArtifacts() {
+        return Configurations.getArtifacts(this.getHierarchy(), Specs.SATISFIES_ALL);
+    }
+
+    public FileCollection getAllArtifactFiles() {
+        return new ArtifactsFileCollection();
+    }
+
+    public Set<ExcludeRule> getExcludeRules() {
+        return Collections.unmodifiableSet(excludeRules);
+    }
+
+    public void setExcludeRules(Set<ExcludeRule> excludeRules) {
+        throwExceptionIfNotInUnresolvedState();
+        this.excludeRules = excludeRules;
+    }
+
+    public DefaultConfiguration exclude(Map<String, String> excludeRuleArgs) {
+        throwExceptionIfNotInUnresolvedState();
+        excludeRules.add(new DefaultExcludeRule(excludeRuleArgs));
+        return this;
+    }
+
+    public String getUploadTaskName() {
+        return Configurations.uploadTaskName(getName());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultConfiguration that = (DefaultConfiguration) o;
+        return path.equals(that.path);
+    }
+
+    @Override
+    public int hashCode() {
+        return path.hashCode();
+    }
+
+    public String getDisplayName() {
+        return String.format("configuration '%s'", path);
+    }
+
+    public Configuration getConfiguration(Dependency dependency) {
+        for (Configuration configuration : getHierarchy()) {
+            if (configuration.getDependencies().contains(dependency)) {
+                return configuration;
+            }
+        }
+        return null;
+    }
+
+    public Configuration copy() {
+        return createCopy(getDependencies());
+    }
+
+    public Configuration copyRecursive() {
+        return createCopy(getAllDependencies());
+    }
+
+    public Configuration copy(Spec<Dependency> dependencySpec) {
+        return createCopy(Specs.filterIterable(getDependencies(), dependencySpec));
+    }
+
+    public Configuration copyRecursive(Spec<Dependency> dependencySpec) {
+        return createCopy(Specs.filterIterable(getAllDependencies(), dependencySpec));
+    }
+
+    private DefaultConfiguration createCopy(Set<Dependency> dependencies) {
+        DetachedConfigurationsProvider configurationsProvider = new DetachedConfigurationsProvider();
+        DefaultConfiguration copiedConfiguration = new DefaultConfiguration(path + "Copy", name + "Copy",
+                configurationsProvider, ivyService);
+        configurationsProvider.setTheOnlyConfiguration(copiedConfiguration);
+        // state, cachedResolvedConfiguration, and extendsFrom intentionally not copied - must re-resolve copy
+        // copying extendsFrom could mess up dependencies when copy was re-resolved
+
+        copiedConfiguration.visibility = visibility;
+        copiedConfiguration.transitive = transitive;
+        copiedConfiguration.description = description;
+
+        for (PublishArtifact artifact : getAllArtifacts()) {
+            copiedConfiguration.addArtifact(artifact);
+        }
+
+        // todo An ExcludeRule is a value object but we don't enforce immutability for DefaultExcludeRule as strong as we
+        // should (we expose the Map). We should provide a better API for ExcludeRule (I don't want to use unmodifiable Map).
+        // As soon as DefaultExcludeRule is truly immutable, we don't need to create a new instance of DefaultExcludeRule. 
+        for (ExcludeRule excludeRule : getExcludeRules()) {
+            copiedConfiguration.excludeRules.add(new DefaultExcludeRule(excludeRule.getExcludeArgs()));
+        }
+
+        for (Dependency dependency : dependencies) {
+            copiedConfiguration.addDependency(dependency.copy());
+        }
+        return copiedConfiguration;
+    }
+
+    public Configuration copy(Closure dependencySpec) {
+        return copy(Specs.<Dependency>convertClosureToSpec(dependencySpec));
+    }
+
+    public Configuration copyRecursive(Closure dependencySpec) {
+        return copyRecursive(Specs.<Dependency>convertClosureToSpec(dependencySpec));
+    }
+
+    private void throwExceptionIfNotInUnresolvedState() {
+        if (getState() != State.UNRESOLVED) {
+            throw new InvalidUserDataException("You can't change a configuration which is not in unresolved state!");
+        }
+    }
+
+    class ArtifactsFileCollection extends AbstractFileCollection {
+        private final TaskDependencyInternal taskDependency = new AbstractTaskDependency() {
+            public void resolve(TaskDependencyResolveContext context) {
+                for (Configuration configuration : getExtendsFrom()) {
+                    context.add(configuration.getBuildArtifacts());
+                }
+                for (PublishArtifact publishArtifact : getArtifacts()) {
+                    context.add(publishArtifact);
+                }
+            }
+        };
+
+        public String getDisplayName() {
+            return String.format("%s artifacts", DefaultConfiguration.this);
+        }
+
+        @Override
+        public TaskDependency getBuildDependencies() {
+            return taskDependency;
+        }
+
+        public Set<File> getFiles() {
+            Set<File> files = new LinkedHashSet<File>();
+            for (PublishArtifact artifact : getAllArtifacts()) {
+                files.add(artifact.getFile());
+            }
+            return files;
+        }
+    }
+
+    class ConfigurationFileCollection extends AbstractFileCollection {
+        private Spec<Dependency> dependencySpec;
+
+        private ConfigurationFileCollection(Spec<Dependency> dependencySpec) {
+            this.dependencySpec = dependencySpec;
+        }
+
+        public ConfigurationFileCollection(Closure dependencySpecClosure) {
+            this.dependencySpec = Specs.convertClosureToSpec(dependencySpecClosure);
+        }
+
+        public ConfigurationFileCollection(final Set<Dependency> dependencies) {
+            this.dependencySpec = new Spec<Dependency>() {
+                public boolean isSatisfiedBy(Dependency element) {
+                    return dependencies.contains(element);
+                }
+            };
+        }
+
+        public Spec<Dependency> getDependencySpec() {
+            return dependencySpec;
+        }
+
+        public String getDisplayName() {
+            return String.format("%s dependencies", DefaultConfiguration.this);
+        }
+
+        public Set<File> getFiles() {
+            synchronized (lock) {
+                ResolvedConfiguration resolvedConfiguration = getResolvedConfiguration();
+                if (getState() == State.RESOLVED_WITH_FAILURES) {
+                    resolvedConfiguration.rethrowFailure();
+                }
+                return resolvedConfiguration.getFiles(dependencySpec);
+            }
+        }
+    }
+
+    public Action<? super Dependency> whenDependencyAdded(Action<? super Dependency> action) {
+        return dependencies.whenObjectAdded(action);
+    }
+
+    public void whenDependencyAdded(Closure closure) {
+        dependencies.whenObjectAdded(closure);
+    }
+
+    public void allDependencies(Action<? super Dependency> action) {
+        dependencies.allObjects(action);
+    }
+
+    public void allDependencies(Closure action) {
+        dependencies.allObjects(action);
+    }
+
+    private class ConfigurationTaskDependency extends AbstractTaskDependency {
+        @Override
+        public String toString() {
+            return String.format("build dependencies %s", DefaultConfiguration.this);
+        }
+
+        public void resolve(TaskDependencyResolveContext context) {
+            for (Configuration configuration : getExtendsFrom()) {
+                context.add(configuration);
+            }
+            for (SelfResolvingDependency dependency : DefaultConfiguration.this.getDependencies(
+                    SelfResolvingDependency.class)) {
+                context.add(dependency);
+            }
+        }
+    }
+}
+
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
new file mode 100644
index 0000000..dc72fa0
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainer.java
@@ -0,0 +1,75 @@
+/*
+ * 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.configurations;
+
+import org.gradle.api.UnknownDomainObjectException;
+import org.gradle.api.artifacts.*;
+import org.gradle.api.internal.AutoCreateDomainObjectContainer;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.DomainObjectContext;
+import org.gradle.api.internal.artifacts.IvyService;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultConfigurationContainer extends AutoCreateDomainObjectContainer<Configuration> 
+        implements ConfigurationContainer, ConfigurationsProvider {
+    public static final String DETACHED_CONFIGURATION_DEFAULT_NAME = "detachedConfiguration";
+    
+    private final IvyService ivyService;
+    private final ClassGenerator classGenerator;
+    private final DomainObjectContext context;
+
+    private int detachedConfigurationDefaultNameCounter = 1;
+
+    public DefaultConfigurationContainer(IvyService ivyService, ClassGenerator classGenerator, DomainObjectContext context) {
+        super(Configuration.class, classGenerator);
+        this.ivyService = ivyService;
+        this.classGenerator = classGenerator;
+        this.context = context;
+    }
+
+    @Override
+    protected Configuration create(String name) {
+        return classGenerator.newInstance(DefaultConfiguration.class, context.absolutePath(name), name, this, ivyService);
+    }
+
+    @Override
+    public String getTypeDisplayName() {
+        return "configuration";
+    }
+
+    @Override
+    protected UnknownDomainObjectException createNotFoundException(String name) {
+        return new UnknownConfigurationException(String.format("Configuration with name '%s' not found.", name));
+    }
+
+    public IvyService getIvyService() {
+        return ivyService;
+    }
+
+    public Configuration detachedConfiguration(Dependency... dependencies) {
+        DetachedConfigurationsProvider detachedConfigurationsProvider = new DetachedConfigurationsProvider();
+        String name = DETACHED_CONFIGURATION_DEFAULT_NAME + detachedConfigurationDefaultNameCounter++;
+        DefaultConfiguration detachedConfiguration = new DefaultConfiguration(name, name,
+                detachedConfigurationsProvider, ivyService);
+        for (Dependency dependency : dependencies) {
+            detachedConfiguration.addDependency(dependency.copy());
+        }
+        detachedConfigurationsProvider.setTheOnlyConfiguration(detachedConfiguration);
+        return detachedConfiguration;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DependencyMetaDataProvider.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DependencyMetaDataProvider.java
new file mode 100644
index 0000000..9a39f2d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DependencyMetaDataProvider.java
@@ -0,0 +1,32 @@
+/*
+ * 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.configurations;
+
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.artifacts.repositories.InternalRepository;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public interface DependencyMetaDataProvider {
+    File getGradleUserHomeDir();
+
+    InternalRepository getInternalRepository();
+
+    Module getModule();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DetachedConfigurationsProvider.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DetachedConfigurationsProvider.java
new file mode 100644
index 0000000..c8c83ff
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/DetachedConfigurationsProvider.java
@@ -0,0 +1,36 @@
+/*
+ * 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.configurations;
+
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.util.WrapUtil;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+*/
+class DetachedConfigurationsProvider implements ConfigurationsProvider {
+    private Configuration theOnlyConfiguration;
+
+    public Set<Configuration> getAll() {
+        return WrapUtil.toSet(theOnlyConfiguration);
+    }
+
+    public void setTheOnlyConfiguration(Configuration theOnlyConfiguration) {
+        this.theOnlyConfiguration = theOnlyConfiguration;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ResolverProvider.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ResolverProvider.java
new file mode 100644
index 0000000..0eb4870
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/configurations/ResolverProvider.java
@@ -0,0 +1,27 @@
+/*
+ * 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.configurations;
+
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ResolverProvider {
+    List<DependencyResolver> getResolvers();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractDependency.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractDependency.java
new file mode 100644
index 0000000..92f4d2e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractDependency.java
@@ -0,0 +1,39 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.internal.artifacts.DependencyInternal;
+import org.gradle.api.internal.artifacts.DependencyResolveContext;
+
+/**
+* @author Hans Dockter
+*/
+public abstract class AbstractDependency implements DependencyInternal {
+    protected void copyTo(AbstractDependency target) {
+    }
+
+    public void resolve(DependencyResolveContext context) {
+    }
+
+    @Override
+    public int hashCode() {
+        int result = getGroup() != null ? getGroup().hashCode() : 0;
+        result = 31 * result + getName().hashCode();
+        result = 31 * result + (getVersion() != null ? getVersion().hashCode() : 0);
+        return result;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractExternalDependency.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractExternalDependency.java
new file mode 100644
index 0000000..7bc6ef6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractExternalDependency.java
@@ -0,0 +1,36 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.artifacts.ExternalDependency;
+
+public abstract class AbstractExternalDependency extends AbstractModuleDependency implements ExternalDependency {
+    public AbstractExternalDependency(String configuration) {
+        super(configuration);
+    }
+
+    protected void copyTo(AbstractExternalDependency target) {
+        super.copyTo(target);
+        target.setForce(isForce());
+    }
+
+    protected boolean isContentEqualsFor(ExternalDependency dependencyRhs) {
+        if (!isKeyEquals(dependencyRhs) || !isCommonContentEquals(dependencyRhs)) {
+            return false;
+        }
+        return isForce() == dependencyRhs.isForce();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependency.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependency.java
new file mode 100644
index 0000000..e31c9c1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependency.java
@@ -0,0 +1,125 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.artifacts.*;
+import org.gradle.api.internal.artifacts.DefaultExcludeRuleContainer;
+import org.gradle.util.ConfigureUtil;
+import org.gradle.util.GUtil;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import groovy.lang.Closure;
+
+public abstract class AbstractModuleDependency extends AbstractDependency implements ModuleDependency {
+    private ExcludeRuleContainer excludeRuleContainer = new DefaultExcludeRuleContainer();
+    private Set<DependencyArtifact> artifacts = new HashSet<DependencyArtifact>();
+    private String configuration;
+    private boolean transitive = true;
+
+    protected AbstractModuleDependency(String configuration) {
+        this.configuration = GUtil.elvis(configuration, Dependency.DEFAULT_CONFIGURATION);
+    }
+
+    public boolean isTransitive() {
+        return transitive;
+    }
+
+    public ModuleDependency setTransitive(boolean transitive) {
+        this.transitive = transitive;
+        return this;
+    }
+
+    public String getConfiguration() {
+        return configuration;
+    }
+
+    public ModuleDependency exclude(Map<String, String> excludeProperties) {
+        excludeRuleContainer.add(excludeProperties);
+        return this;
+    }
+
+    public Set<ExcludeRule> getExcludeRules() {
+        return excludeRuleContainer.getRules();
+    }
+
+    private void setExcludeRuleContainer(ExcludeRuleContainer excludeRuleContainer) {
+        this.excludeRuleContainer = excludeRuleContainer;
+    }
+
+    public Set<DependencyArtifact> getArtifacts() {
+        return artifacts;
+    }
+
+    public void setArtifacts(Set<DependencyArtifact> artifacts) {
+        this.artifacts = artifacts;
+    }
+
+    public AbstractModuleDependency addArtifact(DependencyArtifact artifact) {
+        artifacts.add(artifact);
+        return this;
+    }
+
+    public DependencyArtifact artifact(Closure configureClosure) {
+        DependencyArtifact artifact = ConfigureUtil.configure(configureClosure, new DefaultDependencyArtifact());
+        artifacts.add(artifact);
+        return artifact;
+    }
+
+    protected void copyTo(AbstractModuleDependency target) {
+        super.copyTo(target);
+        target.setArtifacts(new HashSet<DependencyArtifact>(getArtifacts()));
+        target.setExcludeRuleContainer(new DefaultExcludeRuleContainer(getExcludeRules()));
+        target.setTransitive(isTransitive());
+    }
+
+    protected boolean isKeyEquals(ModuleDependency dependencyRhs) {
+        if (getGroup() != null ? !getGroup().equals(dependencyRhs.getGroup()) : dependencyRhs.getGroup() != null) {
+            return false;
+        }
+        if (!getName().equals(dependencyRhs.getName())) {
+            return false;
+        }
+        if (!getConfiguration().equals(dependencyRhs.getConfiguration())) {
+            return false;
+        }
+        if (getVersion() != null ? !getVersion().equals(dependencyRhs.getVersion())
+                : dependencyRhs.getVersion() != null) {
+            return false;
+        }
+        return true;
+    }
+
+    protected boolean isCommonContentEquals(ModuleDependency dependencyRhs) {
+        if (!isKeyEquals(dependencyRhs)) {
+            return false;
+        }
+        if (isTransitive() != dependencyRhs.isTransitive()) {
+            return false;
+        }
+        if (getArtifacts() != null ? !getArtifacts().equals(dependencyRhs.getArtifacts())
+                : dependencyRhs.getArtifacts() != null) {
+            return false;
+        }
+        if (getExcludeRules() != null ? !getExcludeRules().equals(dependencyRhs.getExcludeRules())
+                : dependencyRhs.getExcludeRules() != null) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModule.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModule.java
new file mode 100644
index 0000000..931f058
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModule.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.internal.artifacts.dependencies;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.ClientModule;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ModuleDependency;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultClientModule extends AbstractExternalDependency implements ClientModule {
+
+    private String group;
+
+    private String name;
+
+    private String version;
+
+    private boolean force;
+
+    private Set<ModuleDependency> dependencies = new HashSet<ModuleDependency>();
+
+    public DefaultClientModule(String group, String name, String version) {
+        this(group, name, version, null);
+    }
+
+    public DefaultClientModule(String group, String name, String version, String configuration) {
+        super(configuration);
+        if (name == null) {
+            throw new InvalidUserDataException("Name must not be null!");
+        }
+        this.group = group;
+        this.name = name;
+        this.version = version;
+    }
+
+    private String emptyStringIfNull(String value) {
+        return value == null ? "" : value;
+    }
+
+    public Set<ModuleDependency> getDependencies() {
+        return dependencies;
+    }
+
+    public String getId() {
+        return emptyStringIfNull(group) + ":" + emptyStringIfNull(name) + ":" + emptyStringIfNull(version);
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public ClientModule setGroup(String group) {
+        this.group = group;
+        return this;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public ClientModule setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public ClientModule setVersion(String version) {
+        this.version = version;
+        return this;
+    }
+
+    public boolean isForce() {
+        return force;
+    }
+
+    public ClientModule setForce(boolean force) {
+        this.force = force;
+        return this;
+    }
+
+    public void addDependency(ModuleDependency dependency) {
+        this.dependencies.add(dependency);
+    }
+
+    public ClientModule copy() {
+        DefaultClientModule copiedClientModule = new DefaultClientModule(getGroup(), getName(), getVersion(),
+                getConfiguration());
+        copyTo(copiedClientModule);
+        for (ModuleDependency dependency : dependencies) {
+            copiedClientModule.addDependency(dependency.copy());
+        }
+        return copiedClientModule;
+    }
+
+    public boolean contentEquals(Dependency dependency) {
+        if (this == dependency) {
+            return true;
+        }
+        if (dependency == null || getClass() != dependency.getClass()) {
+            return false;
+        }
+
+        ClientModule that = (ClientModule) dependency;
+        if (!isContentEqualsFor(that)) {
+            return false;
+        }
+
+        return dependencies.equals(that.getDependencies());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        ClientModule that = (ClientModule) o;
+
+        return isKeyEquals(that);
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultDependencyArtifact.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultDependencyArtifact.java
new file mode 100644
index 0000000..cac0ae5
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultDependencyArtifact.java
@@ -0,0 +1,97 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.artifacts.DependencyArtifact;
+
+public class DefaultDependencyArtifact implements DependencyArtifact {
+    private String name;
+    private String type;
+    private String extension;
+    private String classifier;
+    private String url;
+
+    public DefaultDependencyArtifact() {
+    }
+
+    public DefaultDependencyArtifact(String name, String type, String extension, String classifier, String url) {
+        this.name = name;
+        this.type = type;
+        this.extension = extension;
+        this.classifier = classifier;
+        this.url = url;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getExtension() {
+        return extension;
+    }
+
+    public String getClassifier() {
+        return classifier;
+    }
+
+    public String getUrl() {
+        return url;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultDependencyArtifact that = (DefaultDependencyArtifact) o;
+
+        if (classifier != null ? !classifier.equals(that.classifier) : that.classifier != null) {
+            return false;
+        }
+        if (extension != null ? !extension.equals(that.extension) : that.extension != null) {
+            return false;
+        }
+        if (!name.equals(that.name)) {
+            return false;
+        }
+        if (type != null ? !type.equals(that.type) : that.type != null) {
+            return false;
+        }
+        if (url != null ? !url.equals(that.url) : that.url != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = name.hashCode();
+        result = 31 * result + (type != null ? type.hashCode() : 0);
+        result = 31 * result + (extension != null ? extension.hashCode() : 0);
+        result = 31 * result + (classifier != null ? classifier.hashCode() : 0);
+        result = 31 * result + (url != null ? url.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependency.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependency.java
new file mode 100644
index 0000000..f717c6d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependency.java
@@ -0,0 +1,126 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ExternalModuleDependency;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultExternalModuleDependency extends AbstractExternalDependency implements ExternalModuleDependency {
+    private String group;
+    private String name;
+    private String version;
+
+    private boolean force;
+    private boolean changing;
+
+    public DefaultExternalModuleDependency(String group, String name, String version) {
+        this(group, name, version, null);
+    }
+
+    public DefaultExternalModuleDependency(String group, String name, String version, String configuration) {
+        super(configuration);
+        if (name == null) {
+            throw new InvalidUserDataException("Name must not be null!");
+        }
+        this.group = group;
+        this.name = name;
+        this.version = version;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public boolean isForce() {
+        return force;
+    }
+
+    public DefaultExternalModuleDependency setForce(boolean force) {
+        this.force = force;
+        return this;
+    }
+
+    public boolean isChanging() {
+        return changing;
+    }
+
+    public DefaultExternalModuleDependency setChanging(boolean changing) {
+        this.changing = changing;
+        return this;
+    }
+
+    public DefaultExternalModuleDependency copy() {
+        DefaultExternalModuleDependency copiedModuleDependency = new DefaultExternalModuleDependency(getGroup(),
+                getName(), getVersion(), getConfiguration());
+        copyTo(copiedModuleDependency);
+        copiedModuleDependency.setChanging(isChanging());
+        return copiedModuleDependency;
+    }
+
+    public boolean contentEquals(Dependency dependency) {
+        if (this == dependency) {
+            return true;
+        }
+        if (dependency == null || getClass() != dependency.getClass()) {
+            return false;
+        }
+
+        DefaultExternalModuleDependency that = (DefaultExternalModuleDependency) dependency;
+        if (!isContentEqualsFor(that)) {
+            return false;
+        }
+
+        return changing == that.isChanging();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultExternalModuleDependency that = (DefaultExternalModuleDependency) o;
+
+        return isKeyEquals(that);
+    }
+
+    @Override
+    public int hashCode() {
+        return super.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "DefaultExternalModuleDependency{" + "group='" + group + '\'' + ", name='" + name + '\'' + ", version='"
+                + version + '\'' + ", configuration='" + getConfiguration() + '\'' + '}';
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependency.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependency.java
new file mode 100644
index 0000000..7d7eaa4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependency.java
@@ -0,0 +1,168 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ProjectDependenciesBuildInstruction;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.artifacts.CachingDependencyResolveContext;
+import org.gradle.api.internal.artifacts.DependencyResolveContext;
+import org.gradle.api.internal.tasks.AbstractTaskDependency;
+import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
+import org.gradle.api.tasks.TaskDependency;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultProjectDependency extends AbstractModuleDependency implements ProjectDependency {
+    private Project dependencyProject;
+    private final ProjectDependenciesBuildInstruction instruction;
+    private final TaskDependencyImpl taskDependency = new TaskDependencyImpl();
+
+    public DefaultProjectDependency(Project dependencyProject, ProjectDependenciesBuildInstruction instruction) {
+        this(dependencyProject, null, instruction);
+    }
+
+    public DefaultProjectDependency(Project dependencyProject, String configuration,
+                                    ProjectDependenciesBuildInstruction instruction) {
+        super(configuration);
+        this.dependencyProject = dependencyProject;
+        this.instruction = instruction;
+    }
+
+    public Project getDependencyProject() {
+        return dependencyProject;
+    }
+
+    public String getGroup() {
+        return dependencyProject.getGroup().toString();
+    }
+
+    public String getName() {
+        return dependencyProject.getName();
+    }
+
+    public String getVersion() {
+        return dependencyProject.getVersion().toString();
+    }
+
+    public Configuration getProjectConfiguration() {
+        return dependencyProject.getConfigurations().getByName(getConfiguration());
+    }
+
+    public ProjectDependency copy() {
+        DefaultProjectDependency copiedProjectDependency = new DefaultProjectDependency(dependencyProject,
+                getConfiguration(), instruction);
+        copyTo(copiedProjectDependency);
+        return copiedProjectDependency;
+    }
+
+    public Set<File> resolve() {
+        return resolve(true);
+    }
+
+    public Set<File> resolve(boolean transitive) {
+        CachingDependencyResolveContext context = new CachingDependencyResolveContext(transitive);
+        context.add(this);
+        return context.resolve().getFiles();
+    }
+
+    @Override
+    public void resolve(DependencyResolveContext context) {
+        boolean transitive = isTransitive() && context.isTransitive();
+        for (Dependency dependency : getProjectConfiguration().getAllDependencies()) {
+            if (!(dependency instanceof ProjectDependency)) {
+                context.add(dependency);
+            } else if (transitive) {
+                context.add(dependency);
+            }
+            // else project dep and non-transitive, so skip
+        }
+    }
+
+    public TaskDependency getBuildDependencies() {
+        return taskDependency;
+    }
+
+    public boolean contentEquals(Dependency dependency) {
+        if (this == dependency) {
+            return true;
+        }
+        if (dependency == null || getClass() != dependency.getClass()) {
+            return false;
+        }
+
+        ProjectDependency that = (ProjectDependency) dependency;
+        if (!isCommonContentEquals(that)) {
+            return false;
+        }
+
+        return dependencyProject.equals(that.getDependencyProject());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultProjectDependency that = (DefaultProjectDependency) o;
+        if (!this.getDependencyProject().equals(that.getDependencyProject())) {
+            return false;
+        }
+        if (!this.getConfiguration().equals(that.getConfiguration())) {
+            return false;
+        }
+        if (!this.instruction.equals(that.instruction)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return getDependencyProject().hashCode() ^ getConfiguration().hashCode() ^ instruction.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "DefaultProjectDependency{" + "dependencyProject='" + dependencyProject + '\'' + ", configuration='"
+                + getConfiguration() + '\'' + '}';
+    }
+
+    private class TaskDependencyImpl extends AbstractTaskDependency {
+        public void resolve(TaskDependencyResolveContext context) {
+            if (!instruction.isRebuild()) {
+                return;
+            }
+            Configuration configuration = getProjectConfiguration();
+            context.add(configuration);
+            context.add(configuration.getBuildArtifacts());
+            for (String taskName : instruction.getTaskNames()) {
+                context.add(dependencyProject.getTasks().getByName(taskName));
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultSelfResolvingDependency.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultSelfResolvingDependency.java
new file mode 100644
index 0000000..f284768
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultSelfResolvingDependency.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.api.internal.artifacts.dependencies;
+
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.FileCollectionDependency;
+import org.gradle.api.artifacts.SelfResolvingDependency;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.artifacts.DependencyResolveContext;
+import org.gradle.api.tasks.TaskDependency;
+
+import java.io.File;
+import java.util.Set;
+
+public class DefaultSelfResolvingDependency extends AbstractDependency implements SelfResolvingDependency,
+        FileCollectionDependency {
+    private final FileCollection source;
+
+    public DefaultSelfResolvingDependency(FileCollection source) {
+        this.source = source;
+    }
+
+    public FileCollection getSource() {
+        return source;
+    }
+    
+    public boolean contentEquals(Dependency dependency) {
+        if (!(dependency instanceof DefaultSelfResolvingDependency)) {
+            return false;
+        }
+        DefaultSelfResolvingDependency selfResolvingDependency = (DefaultSelfResolvingDependency) dependency;
+        return source.equals(selfResolvingDependency.source);
+    }
+
+    public SelfResolvingDependency copy() {
+        return new DefaultSelfResolvingDependency(source);
+    }
+
+    public String getGroup() {
+        return null;
+    }
+
+    public String getName() {
+        return "unspecified";
+    }
+
+    public String getVersion() {
+        return null;
+    }
+
+    @Override
+    public void resolve(DependencyResolveContext context) {
+        context.add(source);
+    }
+
+    public Set<File> resolve() {
+        return source.getFiles();
+    }
+
+    public Set<File> resolve(boolean transitive) {
+        return source.getFiles();
+    }
+
+    public TaskDependency getBuildDependencies() {
+        return source.getBuildDependencies();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/AbstractScriptTransformer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/AbstractScriptTransformer.java
new file mode 100644
index 0000000..ec2f593
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/AbstractScriptTransformer.java
@@ -0,0 +1,60 @@
+/*
+ * 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) {
+        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/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/BuildScriptClasspathScriptTransformer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/BuildScriptClasspathScriptTransformer.java
new file mode 100644
index 0000000..d9c7885
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/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.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/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/BuildScriptTransformer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/BuildScriptTransformer.java
new file mode 100644
index 0000000..f4a3031
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/BuildScriptTransformer.java
@@ -0,0 +1,40 @@
+/*
+ * 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/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ClasspathScriptTransformer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ClasspathScriptTransformer.java
new file mode 100644
index 0000000..06a9d23
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/ClasspathScriptTransformer.java
@@ -0,0 +1,172 @@
+/*
+ * 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.util.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.asUncheckedException(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);
+        Iterator<ClassNode> classes = source.getAST().getClasses().iterator();
+        while (classes.hasNext()) {
+            ClassNode classNode = classes.next();
+            if (classNode != scriptClass) {
+                classes.remove();
+            }
+        }
+
+        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/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandler.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandler.groovy
new file mode 100644
index 0000000..7780522
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandler.groovy
@@ -0,0 +1,66 @@
+/*
+ * 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.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.artifacts.dsl.ArtifactHandler
+import org.gradle.util.GUtil
+import org.gradle.util.ConfigureUtil
+
+/**
+ * @author Hans Dockter
+ */
+class DefaultArtifactHandler implements ArtifactHandler {
+
+    ConfigurationContainer configurationContainer
+    PublishArtifactFactory publishArtifactFactory
+
+    def DefaultArtifactHandler(ConfigurationContainer configurationContainer, PublishArtifactFactory publishArtifactFactory) {
+        this.configurationContainer = configurationContainer;
+        this.publishArtifactFactory = publishArtifactFactory;
+    }
+
+    private PublishArtifact pushArtifact(org.gradle.api.artifacts.Configuration configuration, Object notation, Closure configureClosure) {
+        PublishArtifact publishArtifact
+        if (notation instanceof PublishArtifact) {
+            publishArtifact = notation
+        } else {
+            publishArtifact = publishArtifactFactory.createArtifact(notation)
+        }
+        configuration.addArtifact(publishArtifact)
+        ConfigureUtil.configure(configureClosure, publishArtifact)
+        return publishArtifact
+    }
+
+    public def methodMissing(String name, args) {
+        Configuration configuration = configurationContainer.findByName(name)
+        if (configuration == null) {
+            if (!getMetaClass().respondsTo(this, name, args.size())) {
+                throw new MissingMethodException(name, this.getClass(), args);
+            }
+        }
+        Object[] normalizedArgs = GUtil.flatten(args as List, false)
+        if (normalizedArgs.length == 2 && normalizedArgs[1] instanceof Closure) {
+            return pushArtifact(configuration, normalizedArgs[0], (Closure) normalizedArgs[1])
+        } 
+        args.each {notation ->
+            pushArtifact(configuration, notation, null)
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactory.java
new file mode 100644
index 0000000..39bf287
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactory.java
@@ -0,0 +1,34 @@
+/*
+ * 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.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.tasks.bundling.AbstractArchiveTask;
+import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact;
+import org.gradle.api.InvalidUserDataException;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultPublishArtifactFactory implements PublishArtifactFactory {
+    public PublishArtifact createArtifact(Object notation) {
+        if (notation instanceof AbstractArchiveTask) {
+            return new ArchivePublishArtifact((AbstractArchiveTask) notation);
+        }
+        throw new InvalidUserDataException("Notation is invalid for an artifact! Passed notation=" + notation);
+    }                            
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java
new file mode 100644
index 0000000..e00cb7e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java
@@ -0,0 +1,165 @@
+/*
+ * 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 groovy.lang.Closure;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.FileSystemResolver;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.artifacts.maven.GroovyMavenDeployer;
+import org.gradle.api.artifacts.maven.MavenResolver;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.artifacts.DefaultResolverContainer;
+import org.gradle.api.internal.artifacts.ivyservice.ResolverFactory;
+import org.gradle.util.GUtil;
+import org.gradle.util.HashUtil;
+import org.gradle.util.WrapUtil;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultRepositoryHandler extends DefaultResolverContainer implements RepositoryHandler {
+    public DefaultRepositoryHandler(ResolverFactory resolverFactory, ClassGenerator classGenerator) {
+        super(resolverFactory, classGenerator);
+    }
+
+    public FileSystemResolver flatDir(Map args) {
+        Object[] rootDirPaths = getFlatDirRootDirs(args);
+        File[] rootDirs = new File[rootDirPaths.length];
+        for (int i = 0; i < rootDirPaths.length; i++) {
+            Object rootDirPath = rootDirPaths[i];
+            rootDirs[i] = new File(rootDirPath.toString());
+        }
+        FileSystemResolver resolver = getResolverFactory().createFlatDirResolver(
+                getNameFromMap(args, HashUtil.createHash(GUtil.join(rootDirPaths, ""))),
+                rootDirs);
+        return (FileSystemResolver) add(resolver);
+    }
+
+    private String getNameFromMap(Map args, String defaultName) {
+        Object name = args.get("name");
+        return name != null ? name.toString() : defaultName;
+    }
+
+    private Object[] getFlatDirRootDirs(Map args) {
+        List dirs = createStringifiedListFromMapArg(args, "dirs");
+        if (dirs == null) {
+            throw new InvalidUserDataException("You must specify dirs for the flat dir repository.");
+        }
+        return dirs.toArray();
+    }
+
+    private List<String> createStringifiedListFromMapArg(Map args, String argName) {
+        Object dirs = args.get(argName);
+        if (dirs == null) {
+            return null;
+        }
+        Iterable<Object> iterable;
+        if (dirs instanceof Iterable) {
+            iterable = (Iterable<Object>) dirs;
+        } else {
+            iterable = WrapUtil.toSet(dirs);
+        }
+        List<String> list = new ArrayList<String>();
+        for (Object o : iterable) {
+            list.add(o.toString());
+        }
+        return list;
+    }
+
+    public DependencyResolver mavenCentral() {
+        return mavenCentral(Collections.emptyMap());
+    }
+
+    public DependencyResolver mavenCentral(Map args) {
+        List<String> urls = createStringifiedListFromMapArg(args, "urls");
+        return add(getResolverFactory().createMavenRepoResolver(
+                getNameFromMap(args, DEFAULT_MAVEN_CENTRAL_REPO_NAME),
+                MAVEN_CENTRAL_URL,
+                urls == null ? new String[0] : urls.toArray(new String[urls.size()])));
+    }
+
+    public DependencyResolver mavenRepo(Map args) {
+        List<String> urls = createStringifiedListFromMapArg(args, "urls");
+        if (urls == null) {
+            throw new InvalidUserDataException("You must specify a urls for a Maven repo.");
+        }
+        List<String> extraUrls = urls.subList(1, urls.size());
+        return add(getResolverFactory().createMavenRepoResolver(
+                getNameFromMap(args, urls.get(0)),
+                urls.get(0),
+                urls.size() == 1 ? new String[0] : extraUrls.toArray(new String[extraUrls.size()])));
+    }
+
+    public GroovyMavenDeployer mavenDeployer(Map args) {
+        GroovyMavenDeployer mavenDeployer = createMavenDeployer(args);
+        return (GroovyMavenDeployer) add(mavenDeployer);
+    }
+
+    private GroovyMavenDeployer createMavenDeployer(Map args) {
+        GroovyMavenDeployer mavenDeployer = createMavenDeployer("dummyName");
+        String defaultName = RepositoryHandler.DEFAULT_MAVEN_DEPLOYER_NAME + "-" + System.identityHashCode(
+                mavenDeployer);
+        mavenDeployer.setName(getNameFromMap(args, defaultName));
+        return mavenDeployer;
+    }
+
+    public GroovyMavenDeployer mavenDeployer() {
+        return mavenDeployer(Collections.emptyMap());
+    }
+
+    public GroovyMavenDeployer mavenDeployer(Closure configureClosure) {
+        return mavenDeployer(Collections.emptyMap(), configureClosure);
+    }
+
+    public GroovyMavenDeployer mavenDeployer(Map args, Closure configureClosure) {
+        GroovyMavenDeployer mavenDeployer = createMavenDeployer(args);
+        return (GroovyMavenDeployer) add(mavenDeployer, configureClosure);
+    }
+
+    public MavenResolver mavenInstaller() {
+        return mavenInstaller(Collections.emptyMap());
+    }
+
+    public MavenResolver mavenInstaller(Closure configureClosure) {
+        return mavenInstaller(Collections.emptyMap(), configureClosure);
+    }
+
+    public MavenResolver mavenInstaller(Map args) {
+        MavenResolver mavenInstaller = createMavenInstaller(args);
+        return (MavenResolver) add(mavenInstaller);
+    }
+
+    public MavenResolver mavenInstaller(Map args, Closure configureClosure) {
+        MavenResolver mavenInstaller = createMavenInstaller(args);
+        return (MavenResolver) add(mavenInstaller, configureClosure);
+    }
+
+    private MavenResolver createMavenInstaller(Map args) {
+        MavenResolver mavenInstaller = createMavenInstaller("dummyName");
+        String defaultName = RepositoryHandler.DEFAULT_MAVEN_INSTALLER_NAME + "-" + System.identityHashCode(
+                mavenInstaller);
+        mavenInstaller.setName(getNameFromMap(args, defaultName));
+        return mavenInstaller;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerFactory.java
new file mode 100644
index 0000000..b151639
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerFactory.java
@@ -0,0 +1,42 @@
+/*
+ * 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.gradle.api.artifacts.dsl.RepositoryHandlerFactory;
+import org.gradle.api.internal.artifacts.ivyservice.ResolverFactory;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.IConventionAware;
+import org.gradle.api.plugins.Convention;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultRepositoryHandlerFactory implements RepositoryHandlerFactory {
+    private final ResolverFactory repositoryFactory;
+    private final ClassGenerator classGenerator;
+
+    public DefaultRepositoryHandlerFactory(ResolverFactory repositoryFactory, ClassGenerator classGenerator) {
+        this.repositoryFactory = repositoryFactory;
+        this.classGenerator = classGenerator;
+    }
+
+    public DefaultRepositoryHandler createRepositoryHandler(Convention convention) {
+        DefaultRepositoryHandler repositoryHandler = classGenerator.newInstance(DefaultRepositoryHandler.class,
+                repositoryFactory, classGenerator);
+        ((IConventionAware) repositoryHandler).getConventionMapping().setConvention(convention);
+        return repositoryHandler;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/FixMainScriptTransformer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/FixMainScriptTransformer.java
new file mode 100644
index 0000000..519c759
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/FixMainScriptTransformer.java
@@ -0,0 +1,48 @@
+/*
+ * 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);
+        for (MethodNode methodNode : scriptClass.getMethods()) {
+            if (methodNode.getName().equals("main")) {
+                removeMethod(scriptClass, methodNode);
+                break;
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/PublishArtifactFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/PublishArtifactFactory.java
new file mode 100644
index 0000000..da0ecfb
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/PublishArtifactFactory.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.api.internal.artifacts.dsl;
+
+import org.gradle.api.artifacts.PublishArtifact;
+
+/**
+ * @author Hans Dockter
+ */
+public interface PublishArtifactFactory {
+    PublishArtifact createArtifact(Object notation);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/TaskDefinitionScriptTransformer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/TaskDefinitionScriptTransformer.java
new file mode 100644
index 0000000..63d236e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/TaskDefinitionScriptTransformer.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.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/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ClassPathDependencyFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ClassPathDependencyFactory.java
new file mode 100644
index 0000000..6fcb667
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ClassPathDependencyFactory.java
@@ -0,0 +1,49 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.IllegalDependencyNotation;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency;
+import org.gradle.api.internal.file.FileResolver;
+
+public class ClassPathDependencyFactory implements IDependencyImplementationFactory {
+    private final ClassPathRegistry classPathRegistry;
+    private final ClassGenerator classGenerator;
+    private final FileResolver fileResolver;
+
+    public ClassPathDependencyFactory(ClassGenerator classGenerator, ClassPathRegistry classPathRegistry,
+                                      FileResolver fileResolver) {
+        this.classGenerator = classGenerator;
+        this.classPathRegistry = classPathRegistry;
+        this.fileResolver = fileResolver;
+    }
+
+    public <T extends Dependency> T createDependency(Class<T> type, Object userDependencyDescription)
+            throws IllegalDependencyNotation {
+        if (userDependencyDescription instanceof DependencyFactory.ClassPathNotation) {
+            DependencyFactory.ClassPathNotation classPathNotation
+                    = (DependencyFactory.ClassPathNotation) userDependencyDescription;
+            FileCollection files = fileResolver.resolveFiles(classPathRegistry.getClassPathFiles(classPathNotation.name()));
+            return type.cast(classGenerator.newInstance(DefaultSelfResolvingDependency.class, files));
+        }
+        
+        throw new IllegalDependencyNotation();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultClientModuleFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultClientModuleFactory.java
new file mode 100644
index 0000000..1c66426
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultClientModuleFactory.java
@@ -0,0 +1,55 @@
+/*
+ * 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.dependencies;
+
+import groovy.lang.GString;
+import org.gradle.api.IllegalDependencyNotation;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.artifacts.dependencies.DefaultClientModule;
+
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultClientModuleFactory implements IDependencyImplementationFactory {
+    private final MapModuleNotationParser mapNotationParser;
+    private final ClassGenerator classGenerator;
+
+    public DefaultClientModuleFactory(ClassGenerator classGenerator) {
+        this.classGenerator = classGenerator;
+        mapNotationParser = new MapModuleNotationParser(classGenerator);
+    }
+
+    public <T extends Dependency> T createDependency(Class<T> type, Object notation) throws IllegalDependencyNotation {
+        assert notation != null;
+        if (notation instanceof String || notation instanceof GString) {
+            return type.cast(createDependencyFromString(notation.toString()));
+        } else if (notation instanceof Map) {
+            return type.cast(mapNotationParser.createDependency(DefaultClientModule.class, notation));
+        }
+        throw new IllegalDependencyNotation();
+    }
+
+    private DefaultClientModule createDependencyFromString(String notation) {
+        ParsedModuleStringNotation parsedNotation = new ParsedModuleStringNotation(notation, null);
+        DefaultClientModule clientModule = classGenerator.newInstance(DefaultClientModule.class,
+                parsedNotation.getGroup(), parsedNotation.getName(), parsedNotation.getVersion());
+        ModuleFactoryHelper.addExplicitArtifactsIfDefined(clientModule, null, parsedNotation.getClassifier());
+        return clientModule;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyFactory.java
new file mode 100644
index 0000000..2111a1a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyFactory.java
@@ -0,0 +1,85 @@
+/*
+ * 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.api.internal.artifacts.dsl.dependencies;
+
+import groovy.lang.Closure;
+import org.gradle.api.GradleException;
+import org.gradle.api.IllegalDependencyNotation;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.ClientModule;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ProjectDependency;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultDependencyFactory implements DependencyFactory {
+    private Set<IDependencyImplementationFactory> dependencyFactories;
+    private IDependencyImplementationFactory clientModuleFactory;
+    private ProjectDependencyFactory projectDependencyFactory;
+
+    public DefaultDependencyFactory(Set<IDependencyImplementationFactory> dependencyFactories,
+                                    IDependencyImplementationFactory clientModuleFactory,
+                                    ProjectDependencyFactory projectDependencyFactory) {
+        this.dependencyFactories = dependencyFactories;
+        this.clientModuleFactory = clientModuleFactory;
+        this.projectDependencyFactory = projectDependencyFactory;
+    }
+
+    public Dependency createDependency(Object dependencyNotation) {
+        if (dependencyNotation instanceof Dependency) {
+            return (Dependency) dependencyNotation;
+        }
+        
+        Dependency dependency = null;
+        for (IDependencyImplementationFactory factory : dependencyFactories) {
+            try {
+                dependency = factory.createDependency(Dependency.class, dependencyNotation);
+                break;
+            }
+            catch (IllegalDependencyNotation e) {
+                // ignore
+            }
+            catch (Exception e) {
+                throw new GradleException(String.format("Could not create a dependency using notation: %s", dependencyNotation), e);
+            }
+        }
+
+        if (dependency == null) {
+            throw new InvalidUserDataException(String.format("The dependency notation: %s is invalid.",
+                    dependencyNotation));
+        }
+        return dependency;
+    }
+
+    public ClientModule createModule(Object dependencyNotation, Closure configureClosure) {
+        ClientModule clientModule = clientModuleFactory.createDependency(ClientModule.class, dependencyNotation);
+        ModuleFactoryDelegate moduleFactoryDelegate = new ModuleFactoryDelegate(clientModule, this);
+        moduleFactoryDelegate.prepareDelegation(configureClosure);
+        if (configureClosure != null) {
+            configureClosure.call();
+        }
+        return clientModule;
+    }
+
+    public ProjectDependency createProjectDependencyFromMap(ProjectFinder projectFinder, Map<? extends String, ? extends Object> map) {
+        return projectDependencyFactory.createProjectDependencyFromMap(projectFinder, map);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandler.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandler.groovy
new file mode 100644
index 0000000..4ca079e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandler.groovy
@@ -0,0 +1,96 @@
+/*
+ * 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.dependencies
+
+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.util.ConfigureUtil
+import org.gradle.util.GUtil
+
+/**
+ * @author Hans Dockter
+ */
+class DefaultDependencyHandler implements DependencyHandler {
+    ConfigurationContainer configurationContainer
+    DependencyFactory dependencyFactory
+    ProjectFinder projectFinder
+
+    def DefaultDependencyHandler(ConfigurationContainer configurationContainer, DependencyFactory dependencyFactory,
+                                 ProjectFinder projectFinder) {
+        this.configurationContainer = configurationContainer;
+        this.dependencyFactory = dependencyFactory;
+        this.projectFinder = projectFinder;
+    }
+
+    public Dependency add(String configurationName, Object dependencyNotation) {
+        pushDependency(configurationContainer[configurationName], dependencyNotation, null)
+    }
+
+    public Dependency add(String configurationName, Object dependencyNotation, Closure configureClosure) {
+        pushDependency(configurationContainer[configurationName], dependencyNotation, configureClosure)
+    }
+
+    private Dependency pushDependency(org.gradle.api.artifacts.Configuration configuration, Object notation, Closure configureClosure) {
+        Dependency dependency
+        dependency = dependencyFactory.createDependency(notation)
+        configuration.addDependency(dependency)
+        ConfigureUtil.configure(configureClosure, dependency)
+        dependency
+    }
+
+    public Dependency module(Object notation) {
+        module(notation, null)
+    }
+
+    public Dependency project(Map notation) {
+        return dependencyFactory.createProjectDependencyFromMap(projectFinder, notation)
+    }
+
+    public Dependency module(Object notation, Closure configureClosure) {
+        return dependencyFactory.createModule(notation, configureClosure)
+    }
+
+    public Dependency gradleApi() {
+        return dependencyFactory.createDependency(DependencyFactory.ClassPathNotation.GRADLE_API);
+    }
+
+    public Dependency localGroovy() {
+        return dependencyFactory.createDependency(DependencyFactory.ClassPathNotation.LOCAL_GROOVY);
+    }
+
+    public def methodMissing(String name, args) {
+        Configuration configuration = configurationContainer.findByName(name)
+        if (configuration == null) {
+            if (!getMetaClass().respondsTo(this, name, args.size())) {
+                throw new MissingMethodException(name, this.getClass(), args);
+            }
+        }
+
+        Object[] normalizedArgs = GUtil.flatten(args as List, false)
+        if (normalizedArgs.length == 2 && normalizedArgs[1] instanceof Closure) {
+            return pushDependency(configuration, normalizedArgs[0], (Closure) normalizedArgs[1])
+        } else if (normalizedArgs.length == 1) {
+            return pushDependency(configuration, normalizedArgs[0], (Closure) null)
+        }
+        normalizedArgs.each {notation ->
+            pushDependency(configuration, notation, null)
+        }
+        return null;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultProjectDependencyFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultProjectDependencyFactory.java
new file mode 100644
index 0000000..e76213f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultProjectDependencyFactory.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.api.internal.artifacts.dsl.dependencies;
+
+import org.gradle.api.IllegalDependencyNotation;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ProjectDependenciesBuildInstruction;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency;
+import org.gradle.util.ConfigureUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultProjectDependencyFactory implements ProjectDependencyFactory {
+    private final ProjectDependenciesBuildInstruction instruction;
+    private final ClassGenerator classGenerator;
+
+    public DefaultProjectDependencyFactory(ProjectDependenciesBuildInstruction instruction, ClassGenerator classGenerator) {
+        this.instruction = instruction;
+        this.classGenerator = classGenerator;
+    }
+
+    public <T extends Dependency> T createDependency(Class<T> type, Object userDependencyDescription) throws IllegalDependencyNotation {
+        if (userDependencyDescription instanceof Project) {
+            return type.cast(classGenerator.newInstance(DefaultProjectDependency.class, userDependencyDescription, instruction));
+        }
+        throw new IllegalDependencyNotation();
+    }
+    
+    public ProjectDependency createProjectDependencyFromMap(ProjectFinder projectFinder,
+                                                   Map<? extends String, ? extends Object> map) {
+        Map<String, Object> args = new HashMap<String, Object>(map);
+        String path = getAndRemove(args, "path");
+        String configuration = getAndRemove(args, "configuration");
+        ProjectDependency dependency = classGenerator.newInstance(DefaultProjectDependency.class, projectFinder.getProject(path), configuration, instruction);
+        ConfigureUtil.configureByMap(args, dependency);
+        return dependency;
+    }
+
+    private String getAndRemove(Map<String, Object> args, String key) {
+        Object value = args.get(key);
+        args.remove(key);
+        return value != null ? value.toString() : null;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DependencyFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DependencyFactory.java
new file mode 100644
index 0000000..277d0b8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DependencyFactory.java
@@ -0,0 +1,36 @@
+/*
+ * 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.dependencies;
+
+import groovy.lang.Closure;
+import org.gradle.api.artifacts.ClientModule;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ProjectDependency;
+
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public interface DependencyFactory {
+    enum ClassPathNotation {
+        GRADLE_API, LOCAL_GROOVY
+    }
+    
+    Dependency createDependency(Object dependencyNotation);
+    ClientModule createModule(Object dependencyNotation, Closure configureClosure);
+    ProjectDependency createProjectDependencyFromMap(ProjectFinder projectFinder, Map<? extends String, ? extends Object> map);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/IDependencyImplementationFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/IDependencyImplementationFactory.java
new file mode 100644
index 0000000..e1d7325
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/IDependencyImplementationFactory.java
@@ -0,0 +1,26 @@
+/*
+ * 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.dsl.dependencies;
+
+import org.gradle.api.IllegalDependencyNotation;
+import org.gradle.api.artifacts.Dependency;
+
+/**
+ * @author Hans Dockter
+ */
+public interface IDependencyImplementationFactory {
+    <T extends Dependency> T createDependency(Class<T> type, Object userDependencyDescription) throws IllegalDependencyNotation;
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/MapModuleNotationParser.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/MapModuleNotationParser.java
new file mode 100644
index 0000000..e0d08c6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/MapModuleNotationParser.java
@@ -0,0 +1,56 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.IllegalDependencyNotation;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ExternalDependency;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.util.ConfigureUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+class MapModuleNotationParser implements IDependencyImplementationFactory {
+    private final ClassGenerator classGenerator;
+
+    MapModuleNotationParser(ClassGenerator classGenerator) {
+        this.classGenerator = classGenerator;
+    }
+
+    public <T extends Dependency> T createDependency(Class<T> type, Object userDependencyDescription)
+            throws IllegalDependencyNotation {
+        Map<String, Object> args = new HashMap<String, Object>((Map<String, ?>) userDependencyDescription);
+        String group = getAndRemove(args, "group");
+        String name = getAndRemove(args, "name");
+        String version = getAndRemove(args, "version");
+        String configuration = getAndRemove(args, "configuration");
+        ExternalDependency dependency = (ExternalDependency) classGenerator.newInstance(type, group, name, version, configuration);
+        ModuleFactoryHelper.addExplicitArtifactsIfDefined(dependency, getAndRemove(args, "ext"), getAndRemove(args,
+                "classifier"));
+        ConfigureUtil.configureByMap(args, dependency);
+        return type.cast(dependency);
+    }
+
+    private String getAndRemove(Map<String, Object> args, String key) {
+        Object value = args.get(key);
+        args.remove(key);
+        return value != null ? value.toString() : null;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDependencyFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDependencyFactory.java
new file mode 100644
index 0000000..da6f4a7
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDependencyFactory.java
@@ -0,0 +1,74 @@
+/*
+ * 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.dependencies;
+
+import groovy.lang.GString;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.IllegalDependencyNotation;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class ModuleDependencyFactory implements IDependencyImplementationFactory {
+    private final MapModuleNotationParser mapNotationParser;
+    private final ClassGenerator classGenerator;
+
+    public ModuleDependencyFactory(ClassGenerator classGenerator) {
+        this.classGenerator = classGenerator;
+        mapNotationParser = new MapModuleNotationParser(classGenerator);
+    }
+
+    public <T extends Dependency> T createDependency(Class<T> type, Object notation) throws IllegalDependencyNotation {
+        assert notation != null;
+        if (notation instanceof String || notation instanceof GString) {
+            return type.cast(createDependencyFromString(notation.toString()));
+        } else if (notation instanceof Map) {
+            return type.cast(mapNotationParser.createDependency(DefaultExternalModuleDependency.class, notation));
+        }
+        throw new IllegalDependencyNotation();
+    }
+
+    private static final Pattern EXTENSION_SPLITTER = Pattern.compile("^(.+)\\@([^:]+$)");
+
+    private DefaultExternalModuleDependency createDependencyFromString(String notation) {
+        ParsedModuleStringNotation parsedNotation = splitDescriptionIntoModuleNotationAndArtifactType(notation);
+        DefaultExternalModuleDependency moduleDependency = classGenerator.newInstance(
+                DefaultExternalModuleDependency.class, parsedNotation.getGroup(), parsedNotation.getName(),
+                parsedNotation.getVersion());
+        ModuleFactoryHelper.addExplicitArtifactsIfDefined(moduleDependency, parsedNotation.getArtifactType(),
+                parsedNotation.getClassifier());
+        return moduleDependency;
+    }
+
+    private ParsedModuleStringNotation splitDescriptionIntoModuleNotationAndArtifactType(String notation) {
+        Matcher matcher = EXTENSION_SPLITTER.matcher(notation);
+        boolean hasArtifactType = matcher.matches();
+        if (hasArtifactType) {
+            if (matcher.groupCount() != 2) {
+                throw new InvalidUserDataException("The description " + notation + " is invalid");
+            }
+            return new ParsedModuleStringNotation(matcher.group(1), matcher.group(2));
+        }
+        return new ParsedModuleStringNotation(notation, null);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDescriptorDelegate.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDescriptorDelegate.groovy
new file mode 100644
index 0000000..4ea3563
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDescriptorDelegate.groovy
@@ -0,0 +1,65 @@
+/*
+ * 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.dependencies
+
+import org.gradle.api.artifacts.ClientModule
+import org.gradle.util.ConfigureUtil
+
+/**
+ * @author Hans Dockter
+ */
+class ModuleFactoryDelegate {
+  ClientModule clientModule
+  DependencyFactory dependencyFactory
+
+  def ModuleFactoryDelegate(ClientModule clientModule, DependencyFactory dependencyFactory) {
+    this.clientModule = clientModule
+    this.dependencyFactory = dependencyFactory
+  }
+
+  void prepareDelegation(Closure configureClosure) {
+    if (!configureClosure) {
+        return
+    }
+    Closure delegationClosure = {}
+    delegationClosure.delegate = clientModule
+    delegationClosure.resolveStrategy = Closure.DELEGATE_FIRST
+    configureClosure.delegate = delegationClosure
+    configureClosure.resolveStrategy = Closure.DELEGATE_FIRST
+  }
+
+  void dependency(Object dependencyNotation) {
+    dependency(dependencyNotation, null)
+  }
+
+  void dependency(Object dependencyNotation, Closure configureClosure) {
+    def dependency = dependencyFactory.createDependency(dependencyNotation)
+    clientModule.addDependency(dependency)
+    ConfigureUtil.configure(configureClosure, dependency)
+  }
+
+  void dependencies(Object[] dependencyNotations) {
+    dependencyNotations.each { notation ->
+      clientModule.addDependency(dependencyFactory.createDependency(notation))
+    }
+  }
+
+  void module(Object dependencyNotation, Closure configureClosure) {
+    clientModule.addDependency(dependencyFactory.createModule(dependencyNotation, configureClosure))
+  }
+}
+
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleFactoryHelper.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleFactoryHelper.java
new file mode 100644
index 0000000..f9ce356
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleFactoryHelper.java
@@ -0,0 +1,40 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.artifacts.ExternalDependency;
+import org.gradle.api.artifacts.DependencyArtifact;
+import org.gradle.api.internal.artifacts.dependencies.DefaultDependencyArtifact;
+
+/**
+ * @author Hans Dockter
+ */
+public class ModuleFactoryHelper {
+    public static void addExplicitArtifactsIfDefined(ExternalDependency moduleDependency, String artifactType, String classifier) {
+        String actualArtifactType = artifactType;
+        if (actualArtifactType == null) {
+            if (classifier != null) {
+                actualArtifactType = DependencyArtifact.DEFAULT_TYPE;
+            }
+        } else {
+            moduleDependency.setTransitive(false);
+        }
+        if (actualArtifactType != null) {
+            moduleDependency.addArtifact(new DefaultDependencyArtifact(moduleDependency.getName(),
+                    actualArtifactType, actualArtifactType, classifier, null));
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ParsedModuleStringNotation.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ParsedModuleStringNotation.java
new file mode 100644
index 0000000..850b1ff
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ParsedModuleStringNotation.java
@@ -0,0 +1,68 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.IllegalDependencyNotation;
+import org.gradle.util.GUtil;
+
+/**
+ * @author Hans Dockter
+ */
+class ParsedModuleStringNotation {
+    private String group;
+    private String name;
+    private String version;
+    private String classifier;
+    private String artifactType;
+
+    ParsedModuleStringNotation(String moduleNotation, String artifactType) {
+        assignValuesFromModuleNotation(moduleNotation);
+        this.artifactType = artifactType;
+    }
+
+    private void assignValuesFromModuleNotation(String moduleNotation) {
+        String[] moduleNotationParts = moduleNotation.split(":");
+        if (moduleNotationParts.length < 2 || moduleNotationParts.length > 4) {
+            throw new IllegalDependencyNotation("The description " + moduleNotation + " is invalid");
+        }
+        group = GUtil.elvis(moduleNotationParts[0], null);
+        name = moduleNotationParts[1];
+        version = moduleNotationParts.length == 2 ? null : moduleNotationParts[2];
+        if (moduleNotationParts.length == 4) {
+            classifier = moduleNotationParts[3];
+        }
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public String getClassifier() {
+        return classifier;
+    }
+
+    public String getArtifactType() {
+        return artifactType;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ProjectDependencyFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ProjectDependencyFactory.java
new file mode 100644
index 0000000..be2b09e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ProjectDependencyFactory.java
@@ -0,0 +1,28 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.artifacts.ProjectDependency;
+
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ProjectDependencyFactory extends IDependencyImplementationFactory {
+    public ProjectDependency createProjectDependencyFromMap(ProjectFinder projectFinder,
+                                                            Map<? extends String, ? extends Object> map);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ProjectFinder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ProjectFinder.java
new file mode 100644
index 0000000..65f8b80
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ProjectFinder.java
@@ -0,0 +1,30 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.Project;
+
+/**
+ * @author Hans Dockter
+*/
+public interface ProjectFinder {
+    /**
+     *
+     * @param path Can be relative or absolute
+     * @return The project belonging to the path
+     */
+    Project getProject(String path);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/SelfResolvingDependencyFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/SelfResolvingDependencyFactory.java
new file mode 100644
index 0000000..d18f5b7
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/SelfResolvingDependencyFactory.java
@@ -0,0 +1,40 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.IllegalDependencyNotation;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency;
+
+public class SelfResolvingDependencyFactory implements IDependencyImplementationFactory {
+    private final ClassGenerator classGenerator;
+
+    public SelfResolvingDependencyFactory(ClassGenerator classGenerator) {
+        this.classGenerator = classGenerator;
+    }
+
+    public <T extends Dependency> T createDependency(Class<T> type, Object userDependencyDescription)
+            throws IllegalDependencyNotation {
+        if (!(userDependencyDescription instanceof FileCollection)) {
+            throw new IllegalDependencyNotation();
+        }
+
+        FileCollection fileCollection = (FileCollection) userDependencyDescription;
+        return type.cast(classGenerator.newInstance(DefaultSelfResolvingDependency.class, fileCollection));
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ClientModuleResolver.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ClientModuleResolver.java
new file mode 100644
index 0000000..ea4db3a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ClientModuleResolver.java
@@ -0,0 +1,126 @@
+/*
+ * 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.ivyservice;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.cache.DefaultRepositoryCacheManager;
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.report.DownloadStatus;
+import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.plugins.lock.NoLockStrategy;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.resolver.BasicResolver;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.artifacts.ClientModule;
+import org.gradle.api.artifacts.ResolverContainer;
+import org.gradle.util.DeleteOnExit;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class ClientModuleResolver extends BasicResolver {
+    private Map moduleRegistry;
+    private DependencyResolver userResolver;
+
+    public ClientModuleResolver(String name, Map moduleRegistry, DependencyResolver userResolver) {
+        setName(name);
+        this.moduleRegistry = moduleRegistry;
+        this.userResolver = userResolver;
+        setRepositoryCacheManager(createUseOriginCacheManager(name));
+    }
+
+    public ResolvedModuleRevision getDependency(DependencyDescriptor dde, ResolveData data) {
+        if (dde.getExtraAttribute(ClientModule.CLIENT_MODULE_KEY) == null) {
+            return null;
+        }
+
+        IvyContext context = IvyContext.pushNewCopyContext();
+        try {
+            context.setDependencyDescriptor(dde);
+            context.setResolveData(data);
+            DefaultModuleDescriptor moduleDescriptor =
+                    (DefaultModuleDescriptor) moduleRegistry.get(dde.getExtraAttribute(ClientModule.CLIENT_MODULE_KEY));
+            MetadataArtifactDownloadReport downloadReport = new MetadataArtifactDownloadReport(moduleDescriptor.getMetadataArtifact());
+            downloadReport.setDownloadStatus(DownloadStatus.NO);
+            downloadReport.setSearched(false);
+            return new ResolvedModuleRevision(userResolver, userResolver, moduleDescriptor, downloadReport);
+        } finally {
+            IvyContext.popContext();
+        }
+    }
+
+    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
+        return null;
+    }
+
+    protected Collection findNames(Map tokenValues, String token) {
+        return null;
+    }
+
+    protected ResolvedResource findArtifactRef(Artifact artifact, Date date) {
+        return null;
+    }
+
+    protected long get(Resource resource, File dest) {
+        return resource.getContentLength();
+    }
+
+    protected Resource getResource(String s) {
+        return null;
+    }
+
+    public void publish(Artifact artifact, File src, boolean overwrite) {
+    }
+
+    private RepositoryCacheManager createUseOriginCacheManager(String name) {
+        File tmpIvyCache = createTmpDir();
+        DefaultRepositoryCacheManager cacheManager = new DefaultRepositoryCacheManager();
+        cacheManager.setBasedir(tmpIvyCache);
+        cacheManager.setName(name);
+        cacheManager.setUseOrigin(true);
+        cacheManager.setLockStrategy(new NoLockStrategy());
+        cacheManager.setIvyPattern(ResolverContainer.DEFAULT_CACHE_IVY_PATTERN);
+        cacheManager.setArtifactPattern(ResolverContainer.DEFAULT_CACHE_ARTIFACT_PATTERN);
+        return cacheManager;
+    }
+
+    private File createTmpDir() {
+        File tmpFile;
+        try {
+            tmpFile = File.createTempFile("gradle_ivy_cache_" + getName(), "");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        tmpFile.delete();
+        tmpFile.mkdir();
+        DeleteOnExit.addFile(tmpFile);
+        return tmpFile;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyConversionResult.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyConversionResult.java
new file mode 100644
index 0000000..8eec7f7
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyConversionResult.java
@@ -0,0 +1,50 @@
+/*
+ * 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.ivyservice;
+
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.artifacts.ResolvedDependency;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultIvyConversionResult implements IvyConversionResult {
+    private final ResolvedDependency root;
+    private final Map<Dependency, Set<ResolvedDependency>> firstLevelResolvedDependencies;
+    private final Set<ResolvedArtifact> resolvedArtifacts;
+
+    public DefaultIvyConversionResult(ResolvedDependency root, Map<Dependency, Set<ResolvedDependency>> firstLevelResolvedDependencies, Set<ResolvedArtifact> resolvedArtifacts) {
+        this.root = root;
+        this.firstLevelResolvedDependencies = firstLevelResolvedDependencies;
+        this.resolvedArtifacts = resolvedArtifacts;
+    }
+
+    public ResolvedDependency getRoot() {
+        return root;
+    }
+
+    public Map<Dependency, Set<ResolvedDependency>> getFirstLevelResolvedDependencies() {
+        return firstLevelResolvedDependencies;
+    }
+
+    public Set<ResolvedArtifact> getResolvedArtifacts() {
+        return resolvedArtifacts;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisher.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisher.java
new file mode 100644
index 0000000..7803f6c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisher.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.api.internal.artifacts.ivyservice;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.publish.PublishEngine;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.util.WrapUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @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));
+
+    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) {
+        try {
+            for (DependencyResolver resolver : publishResolvers) {
+                logger.info("Publishing to Resolver {}", resolver);
+                publishEngine.publish(moduleDescriptor, ARTIFACT_PATTERN, resolver,
+                        publishOptionsFactory.createPublishOptions(configurations, descriptorDestination));
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        } 
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyResolver.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyResolver.java
new file mode 100644
index 0000000..7e50744
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyResolver.java
@@ -0,0 +1,157 @@
+/*
+ * 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.ivyservice;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.report.ResolveReport;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.apache.ivy.util.Message;
+import org.gradle.api.artifacts.*;
+import org.gradle.api.internal.CachingDirectedGraphWalker;
+import org.gradle.api.internal.DirectedGraphWithEdgeValues;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.logging.IvyLoggingAdaper;
+import org.gradle.util.Clock;
+import org.gradle.util.WrapUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultIvyDependencyResolver implements IvyDependencyResolver {
+    private static Logger logger = LoggerFactory.getLogger(DefaultIvyDependencyResolver.class);
+
+    private IvyReportConverter ivyReportTranslator;
+
+    public DefaultIvyDependencyResolver(IvyReportConverter ivyReportTranslator) {
+        this.ivyReportTranslator = ivyReportTranslator;
+        Message.setDefaultLogger(new IvyLoggingAdaper());
+    }
+
+    public ResolvedConfiguration resolve(Configuration configuration, Ivy ivy, ModuleDescriptor moduleDescriptor) {
+        Clock clock = new Clock();
+        ResolveOptions resolveOptions = createResolveOptions(configuration);
+        ResolveReport resolveReport;
+        try {
+            resolveReport = ivy.resolve(moduleDescriptor, resolveOptions);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        logger.debug("Timing: Ivy resolve took {}", clock.getTime());
+        return new ResolvedConfigurationImpl(resolveReport, configuration);
+    }
+
+    private ResolveOptions createResolveOptions(Configuration configuration) {
+        ResolveOptions resolveOptions = new ResolveOptions();
+        resolveOptions.setDownload(false);
+        resolveOptions.setConfs(WrapUtil.toArray(configuration.getName()));
+        return resolveOptions;
+    }
+
+    class ResolvedConfigurationImpl implements ResolvedConfiguration {
+        private final Configuration configuration;
+        private boolean hasError;
+        private List<String> problemMessages;
+        private IvyConversionResult conversionResult;
+        private final CachingDirectedGraphWalker<ResolvedDependency, ResolvedArtifact> walker
+                = new CachingDirectedGraphWalker<ResolvedDependency, ResolvedArtifact>(new ResolvedDependencyArtifactsGraph());
+
+        public ResolvedConfigurationImpl(ResolveReport resolveReport, Configuration configuration) {
+            this.hasError = resolveReport.hasError();
+            if (this.hasError) {
+                this.problemMessages = resolveReport.getAllProblemMessages();
+            } else {
+                 this.conversionResult = ivyReportTranslator.convertReport(
+                    resolveReport,
+                    configuration);
+            }
+            this.configuration = configuration;
+        }
+
+        public boolean hasError() {
+            return hasError;
+        }
+
+        public void rethrowFailure() throws ResolveException {
+            if (hasError) {
+                Formatter formatter = new Formatter();
+                for (String msg : problemMessages) {
+                    formatter.format("    - %s%n", msg);
+                }
+                throw new ResolveException(configuration, formatter.toString());
+            }
+        }
+
+        public Set<File> getFiles(Spec<Dependency> dependencySpec) {
+            rethrowFailure();
+            Set<ModuleDependency> allDependencies = configuration.getAllDependencies(ModuleDependency.class);
+            Set<ModuleDependency> selectedDependencies = Specs.filterIterable(allDependencies, dependencySpec);
+
+            Set<ResolvedArtifact> artifacts = new LinkedHashSet<ResolvedArtifact>();
+
+            for (ModuleDependency moduleDependency : selectedDependencies) {
+                Set<ResolvedDependency> resolvedDependencies = conversionResult.getFirstLevelResolvedDependencies().get(moduleDependency);
+                for (ResolvedDependency resolvedDependency : resolvedDependencies) {
+                    artifacts.addAll(resolvedDependency.getParentArtifacts(conversionResult.getRoot()));
+                    walker.add(resolvedDependency);
+                }
+            }
+
+            artifacts.addAll(walker.findValues());
+
+            Set<File> files = new LinkedHashSet<File>();
+            for (ResolvedArtifact artifact : artifacts) {
+                File depFile = artifact.getFile();
+                if (depFile != null) {
+                    files.add(depFile);
+                } else {
+                    logger.debug(String.format("Resolved artifact %s contains a null value.", artifact));
+                }
+            }
+            return files;
+        }
+
+        public Set<ResolvedDependency> getFirstLevelModuleDependencies() {
+            rethrowFailure();
+            return conversionResult.getRoot().getChildren();
+        }
+
+        public Set<ResolvedArtifact> getResolvedArtifacts() {
+            rethrowFailure();
+            return conversionResult.getResolvedArtifacts();
+        }
+
+        private class ResolvedDependencyArtifactsGraph implements DirectedGraphWithEdgeValues<ResolvedDependency, ResolvedArtifact> {
+            public void getNodeValues(ResolvedDependency node, Collection<ResolvedArtifact> values,
+                                      Collection<ResolvedDependency> connectedNodes) {
+                values.addAll(node.getModuleArtifacts());
+                connectedNodes.addAll(node.getChildren());
+            }
+
+            public void getEdgeValues(ResolvedDependency from, ResolvedDependency to,
+                                      Collection<ResolvedArtifact> values) {
+                values.addAll(to.getParentArtifacts(from));
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactory.java
new file mode 100644
index 0000000..ddafc8b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactory.java
@@ -0,0 +1,28 @@
+/*
+ * 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.Ivy;
+import org.apache.ivy.core.settings.IvySettings;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultIvyFactory implements IvyFactory {
+    public Ivy createIvy(IvySettings ivySettings) {
+        return Ivy.newInstance(ivySettings);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyReportConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyReportConverter.java
new file mode 100644
index 0000000..e39534e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyReportConverter.java
@@ -0,0 +1,329 @@
+/*
+ * 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.ivyservice;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.ConfigurationResolveReport;
+import org.apache.ivy.core.report.ResolveReport;
+import org.apache.ivy.core.resolve.IvyNode;
+import org.apache.ivy.core.resolve.IvyNodeCallers;
+import org.gradle.api.artifacts.*;
+import org.gradle.api.internal.artifacts.DefaultResolvedArtifact;
+import org.gradle.api.internal.artifacts.DefaultResolvedDependency;
+import org.gradle.api.internal.artifacts.ResolvedConfigurationIdentifier;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependencyDescriptorFactory;
+import org.gradle.util.Clock;
+import org.gradle.util.WrapUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultIvyReportConverter implements IvyReportConverter {
+    private static Logger logger = LoggerFactory.getLogger(DefaultIvyReportConverter.class);
+
+    private DependencyDescriptorFactory dependencyDescriptorFactory;
+
+    public DefaultIvyReportConverter(DependencyDescriptorFactory dependencyDescriptorFactory) {
+        this.dependencyDescriptorFactory = dependencyDescriptorFactory;
+    }
+
+    public IvyConversionResult convertReport(ResolveReport resolveReport, Configuration configuration) {
+        Clock clock = new Clock();
+        ReportConversionContext context = new ReportConversionContext(resolveReport, configuration);
+        List<IvyNode> resolvedNodes = findResolvedNodes(resolveReport, context);
+        for (IvyNode node : resolvedNodes) {
+            constructConfigurationsForNode(node, context);
+        }
+        for (IvyNode node : resolvedNodes) {
+            attachToParents(node, context);
+        }
+
+        if (context.root == null) {
+            context.root = new DefaultResolvedDependency(resolveReport.getModuleDescriptor().getModuleRevisionId().getOrganisation(),
+                    resolveReport.getModuleDescriptor().getModuleRevisionId().getName(),
+                    resolveReport.getModuleDescriptor().getModuleRevisionId().getRevision(), configuration.getName(),
+                    Collections.EMPTY_SET);
+        }
+
+        logger.debug("Timing: Translating report for configuration {} took {}", configuration, clock.getTime());
+        return new DefaultIvyConversionResult(context.root, context.firstLevelResolvedDependencies, context.resolvedArtifacts);
+    }
+
+    private List<IvyNode> findResolvedNodes(ResolveReport resolveReport, ReportConversionContext context) {
+        List<IvyNode> nodes = resolveReport.getDependencies();
+        List<IvyNode> resolvedNodes = new ArrayList<IvyNode>();
+        for (IvyNode node : nodes) {
+            if (!isResolvedNode(node, context.conf)) {
+                continue;
+            }
+            resolvedNodes.add(node);
+        }
+        if (!resolvedNodes.isEmpty()) {
+            resolvedNodes.add(resolvedNodes.get(0).getRoot());
+        }
+        return resolvedNodes;
+    }
+
+    private boolean isResolvedNode(IvyNode node, String configuration) {
+        return node.isLoaded() && !node.isEvicted(configuration);
+    }
+
+    private void attachToParents(IvyNode ivyNode, ReportConversionContext context) {
+        Map<String, ConfigurationDetails> resolvedDependencies = context.handledNodes.get(ivyNode.getId());
+        for (IvyNodeCallers.Caller caller : ivyNode.getCallers(context.conf)) {
+            Set<String> dependencyConfigurationsForNode = getDependencyConfigurationsByCaller(ivyNode, caller);
+            IvyNode parentNode = isRootCaller(context.configurationResolveReport, caller) ? ivyNode.getRoot() : context.configurationResolveReport.getDependency(caller.getModuleRevisionId());
+            if (!isResolvedNode(parentNode, context.conf)) {
+                continue;
+            }
+            Map<String, ConfigurationDetails> parentResolvedDependencies = context.handledNodes.get(parentNode.getId());
+            if (parentResolvedDependencies == null) {
+                throw new IllegalStateException(String.format("Could not find caller node %s for node %s. Available nodes: %s",
+                        parentNode.getId(), ivyNode.getId(), context.handledNodes.keySet()));
+            }
+            createAssociationsBetweenChildAndParentResolvedDependencies(ivyNode, resolvedDependencies, context.resolvedArtifacts, parentNode, caller,
+                    dependencyConfigurationsForNode, parentResolvedDependencies.values());
+        }
+    }
+
+    private void constructConfigurationsForNode(IvyNode ivyNode, ReportConversionContext context) {
+        Map<String, ConfigurationDetails> resolvedDependencies = new LinkedHashMap<String, ConfigurationDetails>();
+        for (IvyNodeCallers.Caller caller : ivyNode.getCallers(context.conf)) {
+            Set<String> dependencyConfigurationsForNode = getDependencyConfigurationsByCaller(ivyNode, caller);
+            for (String dependencyConfiguration : dependencyConfigurationsForNode) {
+                if (!resolvedDependencies.containsKey(dependencyConfiguration)) {
+                    ConfigurationDetails configurationDetails = context.addConfiguration(ivyNode, dependencyConfiguration);
+                    context.resolvedArtifacts.addAll(configurationDetails.dependency.getModuleArtifacts());
+                    resolvedDependencies.put(dependencyConfiguration, configurationDetails);
+                }
+            }
+        }
+        if (ivyNode == ivyNode.getRoot()) {
+            ConfigurationDetails rootConfiguration = resolvedDependencies.get(context.conf);
+            if (rootConfiguration == null) {
+                rootConfiguration = context.addConfiguration(ivyNode, context.conf);
+                resolvedDependencies.put(context.conf, rootConfiguration);
+            }
+            context.root = rootConfiguration.dependency;
+        }
+        context.handledNodes.put(ivyNode.getId(), resolvedDependencies);
+    }
+
+    private void createAssociationsBetweenChildAndParentResolvedDependencies(IvyNode childNode, Map<String, ConfigurationDetails> childConfigurations,
+                                                                             Set<ResolvedArtifact> resolvedArtifacts,
+                                                                             IvyNode parentNode, IvyNodeCallers.Caller caller,
+                                                                             Set<String> childConfigurationsToAttach,
+                                                                             Collection<ConfigurationDetails> parentConfigurations) {
+        for (String dependencyConfiguration : childConfigurationsToAttach) {
+            Set<String> callerConfigurations = getCallerConfigurationsByDependencyConfiguration(caller, childNode, dependencyConfiguration);
+            Set<ConfigurationDetails> parentCallerConfigurations = selectParentConfigurations(parentConfigurations,
+                    callerConfigurations);
+            for (ConfigurationDetails parentConfiguration : parentCallerConfigurations) {
+                ConfigurationDetails childConfiguration = childConfigurations.get(dependencyConfiguration);
+                parentConfiguration.dependency.getChildren().add(childConfiguration.dependency);
+                childConfiguration.dependency.getParents().add(parentConfiguration.dependency);
+                Set<ResolvedArtifact> parentSpecificResolvedArtifacts = getParentSpecificArtifacts(childConfiguration.dependency, parentConfiguration.dependency.getConfiguration(),
+                        parentNode, caller, childNode);
+                childConfiguration.dependency.addParentSpecificArtifacts(parentConfiguration.dependency, parentSpecificResolvedArtifacts);
+                resolvedArtifacts.addAll(parentSpecificResolvedArtifacts);
+            }
+        }
+    }
+
+    private Set<ResolvedArtifact> getParentSpecificArtifacts(DefaultResolvedDependency resolvedDependency, String parentConfiguration, IvyNode callerNode,
+                                                             IvyNodeCallers.Caller caller, IvyNode childNode) {
+        Set<String> parentConfigurations = getConfigurationHierarchy(callerNode, parentConfiguration);
+        Set<DependencyArtifactDescriptor> parentArtifacts = new LinkedHashSet<DependencyArtifactDescriptor>();
+        for (String configuration : parentConfigurations) {
+            parentArtifacts.addAll(WrapUtil.toSet(caller.getDependencyDescriptor().getDependencyArtifacts(configuration)));
+        }
+
+        Artifact[] allArtifacts = childNode.getSelectedArtifacts(null);
+        Set<ResolvedArtifact> artifacts = new LinkedHashSet<ResolvedArtifact>();
+        for (Artifact artifact : allArtifacts) {
+            for (DependencyArtifactDescriptor parentArtifact : parentArtifacts) {
+                if (isEquals(parentArtifact, artifact)) {
+                    DefaultResolvedArtifact resolvedArtifact = createResolvedArtifact(artifact, childNode);
+                    resolvedArtifact.setResolvedDependency(resolvedDependency);
+                    artifacts.add(resolvedArtifact);
+                    break;
+                }
+            }
+        }
+        return artifacts;
+    }
+
+    private DefaultResolvedArtifact createResolvedArtifact(Artifact artifact, IvyNode ivyNode) {
+        return new DefaultResolvedArtifact(artifact, ivyNode.getData().getEngine());
+    }
+
+    private boolean isEquals(DependencyArtifactDescriptor parentArtifact, Artifact artifact) {
+        return parentArtifact.getName().equals(artifact.getName())
+                && parentArtifact.getExt().equals(artifact.getExt())
+                && parentArtifact.getType().equals(artifact.getType())
+                && parentArtifact.getQualifiedExtraAttributes().equals(artifact.getQualifiedExtraAttributes());
+    }
+
+    private boolean isRootCaller(ConfigurationResolveReport configurationResolveReport, IvyNodeCallers.Caller caller) {
+        return caller.getModuleDescriptor().equals(configurationResolveReport.getModuleDescriptor());
+    }
+
+    private Set<ConfigurationDetails> selectParentConfigurations(Collection<ConfigurationDetails> parentConfigurations,
+                                                                 Set<String> callerConfigurations) {
+        Set<ConfigurationDetails> matchingParentConfigurations = new LinkedHashSet<ConfigurationDetails>();
+        for (String callerConfiguration : callerConfigurations) {
+            for (ConfigurationDetails parentConfiguration : parentConfigurations) {
+                if (parentConfiguration.containsConfiguration(callerConfiguration)) {
+                    matchingParentConfigurations.add(parentConfiguration);
+                }
+            }
+        }
+        return matchingParentConfigurations;
+    }
+
+    private Set<String> getConfigurationHierarchy(IvyNode node, String configurationName) {
+        Set<String> configurations = new LinkedHashSet<String>();
+        configurations.add(configurationName);
+        org.apache.ivy.core.module.descriptor.Configuration configuration = node.getConfiguration(configurationName);
+        for (String extendedConfigurationNames : configuration.getExtends()) {
+            configurations.addAll(getConfigurationHierarchy(node, extendedConfigurationNames));
+        }
+        return configurations;
+    }
+
+    private Set<String> getCallerConfigurationsByDependencyConfiguration(IvyNodeCallers.Caller caller, IvyNode dependencyNode, String dependencyConfiguration) {
+        Map<String, Set<String>> dependency2CallerConfs = new LinkedHashMap<String, Set<String>>();
+        for (String callerConf : caller.getCallerConfigurations()) {
+            Set<String> dependencyConfs = getRealConfigurations(dependencyNode
+                    , caller.getDependencyDescriptor().getDependencyConfigurations(callerConf));
+            for (String dependencyConf : dependencyConfs) {
+                if (!dependency2CallerConfs.containsKey(dependencyConf)) {
+                    dependency2CallerConfs.put(dependencyConf, new LinkedHashSet<String>());
+                }
+                dependency2CallerConfs.get(dependencyConf).add(callerConf);
+            }
+        }
+        return dependency2CallerConfs.get(dependencyConfiguration);
+    }
+
+    private Set<String> getDependencyConfigurationsByCaller(IvyNode dependencyNode, IvyNodeCallers.Caller caller) {
+        String[] dependencyConfigurations = caller.getDependencyDescriptor().getDependencyConfigurations(caller.getCallerConfigurations());
+        return getRealConfigurations(dependencyNode, dependencyConfigurations);
+    }
+
+    private Set<String> getRealConfigurations(IvyNode dependencyNode, String[] dependencyConfigurations) {
+        Set<String> realDependencyConfigurations = new LinkedHashSet<String>();
+        for (String dependencyConfiguration : dependencyConfigurations) {
+            realDependencyConfigurations.addAll(WrapUtil.toSet(dependencyNode.getRealConfs(dependencyConfiguration)));
+        }
+        return realDependencyConfigurations;
+    }
+
+    private Set<ResolvedArtifact> getArtifacts(IvyNode dependencyNode) {
+        Set<ResolvedArtifact> resolvedArtifacts = new LinkedHashSet<ResolvedArtifact>();
+        Artifact[] artifacts = dependencyNode.getSelectedArtifacts(null);
+        for (Artifact artifact : artifacts) {
+            resolvedArtifacts.add(createResolvedArtifact(artifact, dependencyNode));
+        }
+        return resolvedArtifacts;
+    }
+
+    private class ReportConversionContext {
+        ResolvedDependency root;
+        final Map<Dependency, Set<ResolvedDependency>> firstLevelResolvedDependencies = new LinkedHashMap<Dependency, Set<ResolvedDependency>>();
+        final Map<ModuleRevisionId, Map<String, ConfigurationDetails>> handledNodes = new LinkedHashMap<ModuleRevisionId, Map<String, ConfigurationDetails>>();
+        final Set<ResolvedArtifact> resolvedArtifacts = new LinkedHashSet<ResolvedArtifact>();
+        final ConfigurationResolveReport configurationResolveReport;
+        final Map<ResolvedConfigurationIdentifier, ModuleDependency> firstLevelDependenciesModuleRevisionIds = new HashMap<ResolvedConfigurationIdentifier, ModuleDependency>();
+        final Map<ResolvedConfigurationIdentifier, ConfigurationDetails> configurations = new HashMap<ResolvedConfigurationIdentifier, ConfigurationDetails>();
+        final String conf;
+
+        public ReportConversionContext(ResolveReport resolveReport, Configuration configuration) {
+            configurationResolveReport = resolveReport.getConfigurationReport(configuration.getName());
+            createFirstLevelDependenciesModuleRevisionIds(configuration.getAllDependencies(ModuleDependency.class));
+            conf = configuration.getName();
+        }
+
+        public ConfigurationDetails addConfiguration(IvyNode ivyNode, String configuration) {
+            ModuleRevisionId actualId = ivyNode.getResolvedId();
+            Set<String> configurations = getConfigurationHierarchy(ivyNode, configuration);
+            DefaultResolvedDependency resolvedDependency;
+            if (actualId.getAttribute(DependencyDescriptorFactory.PROJECT_PATH_KEY) != null) {
+                resolvedDependency = new DefaultResolvedDependency(
+                        actualId.getAttribute(DependencyDescriptorFactory.PROJECT_PATH_KEY),
+                        actualId.getOrganisation(), actualId.getName(), actualId.getRevision(),
+                        configuration, getArtifacts(ivyNode));
+            } else {
+                resolvedDependency = new DefaultResolvedDependency(
+                        actualId.getOrganisation(), actualId.getName(), actualId.getRevision(),
+                        configuration, getArtifacts(ivyNode));
+            }
+            for (ResolvedArtifact resolvedArtifact : resolvedDependency.getModuleArtifacts()) {
+                ((DefaultResolvedArtifact) resolvedArtifact).setResolvedDependency(resolvedDependency);
+            }
+            ConfigurationDetails configurationDetails = new ConfigurationDetails(resolvedDependency, ivyNode,
+                    configurations);
+            this.configurations.put(resolvedDependency.getId(), configurationDetails);
+
+            // Collect top level dependencies
+            ResolvedConfigurationIdentifier originalId = new ResolvedConfigurationIdentifier(ivyNode.getId(),
+                    configuration);
+            if (firstLevelDependenciesModuleRevisionIds.containsKey(originalId)) {
+                ModuleDependency firstLevelNode = firstLevelDependenciesModuleRevisionIds.get(originalId);
+                firstLevelResolvedDependencies.get(firstLevelNode).add(resolvedDependency);
+            }
+
+            return configurationDetails;
+        }
+
+        private void createFirstLevelDependenciesModuleRevisionIds(Set<ModuleDependency> firstLevelDependencies) {
+            for (ModuleDependency firstLevelDependency : firstLevelDependencies) {
+                ResolvedConfigurationIdentifier id = new ResolvedConfigurationIdentifier(dependencyDescriptorFactory.createModuleRevisionId(firstLevelDependency), firstLevelDependency.getConfiguration());
+                firstLevelDependenciesModuleRevisionIds.put(id, firstLevelDependency);
+                firstLevelResolvedDependencies.put(firstLevelDependency, new LinkedHashSet<ResolvedDependency>());
+            }
+        }
+    }
+
+    private static class ConfigurationDetails {
+        final DefaultResolvedDependency dependency;
+        final IvyNode node;
+        final Set<String> configurationHierarchy;
+
+        private ConfigurationDetails(DefaultResolvedDependency dependency, IvyNode node,
+                                     Set<String> configurationHierarchy) {
+            this.dependency = dependency;
+            this.node = node;
+            this.configurationHierarchy = configurationHierarchy;
+        }
+
+        public boolean containsConfiguration(String configuration) {
+            return configurationHierarchy.contains(configuration);
+        }
+
+        @Override
+        public String toString() {
+            return dependency.toString();
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyService.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyService.java
new file mode 100644
index 0000000..0f8b04a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyService.java
@@ -0,0 +1,191 @@
+/*
+ * 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.Ivy;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ResolvedConfiguration;
+import org.gradle.api.internal.artifacts.IvyService;
+import org.gradle.api.internal.artifacts.configurations.Configurations;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultIvyService implements IvyService {
+    private SettingsConverter settingsConverter;
+    private ModuleDescriptorConverter resolveModuleDescriptorConverter;
+    private ModuleDescriptorConverter publishModuleDescriptorConverter;
+    private ModuleDescriptorConverter fileModuleDescriptorConverter;
+    private IvyFactory ivyFactory;
+    private IvyDependencyResolver dependencyResolver;
+    private IvyDependencyPublisher dependencyPublisher;
+    private final DependencyMetaDataProvider metaDataProvider;
+    private final ResolverProvider resolverProvider;
+    private Map clientModuleRegistry;
+
+    public DefaultIvyService(DependencyMetaDataProvider metaDataProvider, ResolverProvider resolverProvider,
+                             SettingsConverter settingsConverter,
+                             ModuleDescriptorConverter resolveModuleDescriptorConverter,
+                             ModuleDescriptorConverter publishModuleDescriptorConverter,
+                             ModuleDescriptorConverter fileModuleDescriptorConverter,
+                             IvyFactory ivyFactory,
+                             IvyDependencyResolver dependencyResolver,
+                             IvyDependencyPublisher dependencyPublisher,
+                             Map clientModuleRegistry) {
+        this.metaDataProvider = metaDataProvider;
+        this.resolverProvider = resolverProvider;
+        this.settingsConverter = settingsConverter;
+        this.resolveModuleDescriptorConverter = resolveModuleDescriptorConverter;
+        this.publishModuleDescriptorConverter = publishModuleDescriptorConverter;
+        this.fileModuleDescriptorConverter = fileModuleDescriptorConverter;
+        this.ivyFactory = ivyFactory;
+        this.dependencyResolver = dependencyResolver;
+        this.dependencyPublisher = dependencyPublisher;
+        this.clientModuleRegistry = clientModuleRegistry;
+    }
+
+    private Ivy ivyForResolve(List<DependencyResolver> dependencyResolvers, File cacheParentDir,
+                   Map<String, ModuleDescriptor> clientModuleRegistry) {
+        return ivyFactory.createIvy(
+                settingsConverter.convertForResolve(
+                        dependencyResolvers,
+                        cacheParentDir,
+                        metaDataProvider.getInternalRepository(),
+                        clientModuleRegistry
+                )
+        );
+    }
+
+    private Ivy ivyForPublish(List<DependencyResolver> publishResolvers, File cacheParentDir) {
+        return ivyFactory.createIvy(
+                settingsConverter.convertForPublish(
+                        publishResolvers,
+                        cacheParentDir,
+                        metaDataProvider.getInternalRepository()
+                )
+        );
+    }
+
+    public SettingsConverter getSettingsConverter() {
+        return settingsConverter;
+    }
+
+    public ModuleDescriptorConverter getResolveModuleDescriptorConverter() {
+        return resolveModuleDescriptorConverter;
+    }
+
+    public ModuleDescriptorConverter getPublishModuleDescriptorConverter() {
+        return publishModuleDescriptorConverter;
+    }
+
+    public ModuleDescriptorConverter getFileModuleDescriptorConverter() {
+        return fileModuleDescriptorConverter;
+    }
+
+    public IvyFactory getIvyFactory() {
+        return ivyFactory;
+    }
+
+    public DependencyMetaDataProvider getMetaDataProvider() {
+        return metaDataProvider;
+    }
+
+    public ResolverProvider getResolverProvider() {
+        return resolverProvider;
+    }
+
+    public IvyDependencyResolver getDependencyResolver() {
+        return dependencyResolver;
+    }
+
+    public IvyDependencyPublisher getDependencyPublisher() {
+        return dependencyPublisher;
+    }
+
+    public ResolvedConfiguration resolve(final Configuration configuration) {
+        Ivy ivy = ivyForResolve(resolverProvider.getResolvers(), metaDataProvider.getGradleUserHomeDir(),
+                clientModuleRegistry);
+        ModuleDescriptor moduleDescriptor = resolveModuleDescriptorConverter.convert(configuration.getAll(),
+                metaDataProvider.getModule(), ivy.getSettings());
+        return dependencyResolver.resolve(configuration, ivy, moduleDescriptor);
+    }
+
+    public void publish(Set<Configuration> configurationsToPublish, File descriptorDestination,
+                        List<DependencyResolver> publishResolvers) {
+        Ivy ivy = ivyForPublish(publishResolvers, metaDataProvider.getGradleUserHomeDir());
+        Set<String> confs = Configurations.getNames(configurationsToPublish, false);
+        writeDescriptorFile(descriptorDestination, configurationsToPublish, ivy.getSettings());
+        dependencyPublisher.publish(
+                confs,
+                publishResolvers,
+                publishModuleDescriptorConverter.convert(configurationsToPublish, metaDataProvider.getModule(), ivy.getSettings()),
+                descriptorDestination,
+                ivy.getPublishEngine());
+    }
+
+    private void writeDescriptorFile(File descriptorDestination, Set<Configuration> configurationsToPublish, IvySettings ivySettings) {
+        if (descriptorDestination == null) {
+            return;
+        }
+        assert configurationsToPublish.size() > 0;
+        Set<Configuration> allConfigurations = configurationsToPublish.iterator().next().getAll();
+        ModuleDescriptor moduleDescriptor = fileModuleDescriptorConverter.convert(allConfigurations, metaDataProvider.getModule(), ivySettings);
+        try {
+            moduleDescriptor.toIvyFile(descriptorDestination);
+        } catch (ParseException e) {
+            throw new RuntimeException(e);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void setSettingsConverter(SettingsConverter settingsConverter) {
+        this.settingsConverter = settingsConverter;
+    }
+
+    public void setIvyFactory(IvyFactory ivyFactory) {
+        this.ivyFactory = ivyFactory;
+    }
+
+    public void setDependencyResolver(IvyDependencyResolver dependencyResolver) {
+        this.dependencyResolver = dependencyResolver;
+    }
+
+    public void setDependencyPublisher(IvyDependencyPublisher dependencyPublisher) {
+        this.dependencyPublisher = dependencyPublisher;
+    }
+
+    public Map getClientModuleRegistry() {
+        return clientModuleRegistry;
+    }
+
+    public void setClientModuleRegistry(Map clientModuleRegistry) {
+        this.clientModuleRegistry = clientModuleRegistry;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultPublishOptionsFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultPublishOptionsFactory.java
new file mode 100644
index 0000000..e4d2fdb
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultPublishOptionsFactory.java
@@ -0,0 +1,41 @@
+/*
+ * 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/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultResolverFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultResolverFactory.java
new file mode 100644
index 0000000..2d2238e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultResolverFactory.java
@@ -0,0 +1,175 @@
+/*
+ * 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.api.internal.artifacts.ivyservice;
+
+import org.apache.ivy.core.cache.DefaultRepositoryCacheManager;
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.plugins.lock.NoLockStrategy;
+import org.apache.ivy.plugins.resolver.*;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.ResolverContainer;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.artifacts.maven.GroovyMavenDeployer;
+import org.gradle.api.artifacts.maven.MavenResolver;
+import org.gradle.api.internal.artifacts.publish.maven.DefaultArtifactPomFactory;
+import org.gradle.api.internal.artifacts.publish.maven.DefaultMavenPomFactory;
+import org.gradle.api.internal.artifacts.publish.maven.MavenPomMetaInfoProvider;
+import org.gradle.api.internal.artifacts.publish.maven.dependencies.DefaultExcludeRuleConverter;
+import org.gradle.api.internal.artifacts.publish.maven.dependencies.DefaultPomDependenciesConverter;
+import org.gradle.api.internal.artifacts.publish.maven.deploy.BaseMavenInstaller;
+import org.gradle.api.internal.artifacts.publish.maven.deploy.DefaultArtifactPomContainer;
+import org.gradle.api.internal.artifacts.publish.maven.deploy.groovy.DefaultGroovyMavenDeployer;
+import org.gradle.api.internal.artifacts.publish.maven.deploy.groovy.DefaultGroovyPomFilterContainer;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.logging.LoggingManagerFactory;
+import org.gradle.util.DeleteOnExit;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultResolverFactory implements ResolverFactory {
+    private final LoggingManagerFactory loggingManagerFactory;
+
+    public DefaultResolverFactory(LoggingManagerFactory loggingManagerFactory) {
+        this.loggingManagerFactory = loggingManagerFactory;
+    }
+
+    public DependencyResolver createResolver(Object userDescription) {
+        DependencyResolver result;
+        if (userDescription instanceof String) {
+            result = createMavenRepoResolver((String) userDescription, (String) userDescription);
+        } else if (userDescription instanceof Map) {
+            Map<String, String> userDescriptionMap = (Map<String, String>) userDescription;
+            result = createMavenRepoResolver(userDescriptionMap.get(ResolverContainer.RESOLVER_NAME),
+                    userDescriptionMap.get(ResolverContainer.RESOLVER_URL));
+        } else if (userDescription instanceof DependencyResolver) {
+            result = (DependencyResolver) userDescription;
+        } else {
+            throw new InvalidUserDataException("Illegal Resolver type");
+        }
+        return result;
+    }
+
+    public FileSystemResolver createFlatDirResolver(String name, File... roots) {
+        FileSystemResolver resolver = new FileSystemResolver();
+        resolver.setName(name);
+        for (File root : roots) {
+            String pattern = root.getAbsolutePath() + "/" + ResolverContainer.FLAT_DIR_RESOLVER_PATTERN;
+            resolver.addArtifactPattern(pattern);
+        }
+        resolver.setValidate(false);
+        resolver.setRepositoryCacheManager(createUseOriginCacheManager(name));
+        return resolver;
+    }
+
+    private RepositoryCacheManager createUseOriginCacheManager(String name) {
+        File tmpIvyCache = createTmpDir();
+        DefaultRepositoryCacheManager cacheManager = new DefaultRepositoryCacheManager();
+        cacheManager.setBasedir(tmpIvyCache);
+        cacheManager.setName(name);
+        cacheManager.setUseOrigin(true);
+        cacheManager.setLockStrategy(new NoLockStrategy());
+        cacheManager.setIvyPattern(ResolverContainer.DEFAULT_CACHE_IVY_PATTERN);
+        cacheManager.setArtifactPattern(ResolverContainer.DEFAULT_CACHE_ARTIFACT_PATTERN);
+        return cacheManager;
+    }
+
+    private File createTmpDir() {
+        File tmpFile;
+        try {
+            tmpFile = File.createTempFile("gradle_ivy_cache", "");
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        tmpFile.delete();
+        tmpFile.mkdir();
+        DeleteOnExit.addFile(tmpFile);
+        return tmpFile;
+    }
+
+    public AbstractResolver createMavenRepoResolver(String name, String root, String... jarRepoUrls) {
+        GradleIBiblioResolver iBiblioResolver = createIBiblioResolver(name, root);
+        if (jarRepoUrls.length == 0) {
+            iBiblioResolver.setDescriptor(IBiblioResolver.DESCRIPTOR_OPTIONAL);
+            return iBiblioResolver;
+        }
+        iBiblioResolver.setName(iBiblioResolver.getName() + "_poms");
+        URLResolver urlResolver = createUrlResolver(name, root, jarRepoUrls);
+        return createDualResolver(name, iBiblioResolver, urlResolver);
+    }
+
+    private GradleIBiblioResolver createIBiblioResolver(String name, String root) {
+        GradleIBiblioResolver iBiblioResolver = new GradleIBiblioResolver();
+        iBiblioResolver.setUsepoms(true);
+        iBiblioResolver.setName(name);
+        iBiblioResolver.setRoot(root);
+        iBiblioResolver.setPattern(ResolverContainer.MAVEN_REPO_PATTERN);
+        iBiblioResolver.setM2compatible(true);
+        iBiblioResolver.setUseMavenMetadata(true);
+        return iBiblioResolver;
+    }
+
+    private URLResolver createUrlResolver(String name, String root, String... jarRepoUrls) {
+        URLResolver urlResolver = new URLResolver();
+        urlResolver.setName(name + "_jars");
+        urlResolver.setM2compatible(true);
+        urlResolver.addArtifactPattern(root + '/' + ResolverContainer.MAVEN_REPO_PATTERN);
+        for (String jarRepoUrl : jarRepoUrls) {
+            urlResolver.addArtifactPattern(jarRepoUrl + '/' + ResolverContainer.MAVEN_REPO_PATTERN);
+        }
+        return urlResolver;
+    }
+
+    private DualResolver createDualResolver(String name, GradleIBiblioResolver iBiblioResolver, URLResolver urlResolver) {
+        DualResolver dualResolver = new DualResolver();
+        dualResolver.setName(name);
+        dualResolver.setIvyResolver(iBiblioResolver);
+        dualResolver.setArtifactResolver(urlResolver);
+        dualResolver.setDescriptor(DualResolver.DESCRIPTOR_OPTIONAL);
+        return dualResolver;
+    }
+
+    // todo use MavenPluginConvention pom factory after modularization is done
+
+    public GroovyMavenDeployer createMavenDeployer(String name, MavenPomMetaInfoProvider pomMetaInfoProvider,
+                                                   ConfigurationContainer configurationContainer,
+                                                   Conf2ScopeMappingContainer scopeMapping, FileResolver fileResolver) {
+        DefaultGroovyPomFilterContainer pomFilterContainer = new DefaultGroovyPomFilterContainer(
+                new DefaultMavenPomFactory(configurationContainer, scopeMapping, new DefaultPomDependenciesConverter(
+                        new DefaultExcludeRuleConverter()), fileResolver));
+        return new DefaultGroovyMavenDeployer(name, pomFilterContainer, new DefaultArtifactPomContainer(
+                pomMetaInfoProvider, pomFilterContainer, new DefaultArtifactPomFactory()), loggingManagerFactory.create());
+    }
+
+    // todo use MavenPluginConvention pom factory after modularization is done
+
+    public MavenResolver createMavenInstaller(String name, MavenPomMetaInfoProvider pomMetaInfoProvider,
+                                              ConfigurationContainer configurationContainer,
+                                              Conf2ScopeMappingContainer scopeMapping, FileResolver fileResolver) {
+        DefaultGroovyPomFilterContainer pomFilterContainer = new DefaultGroovyPomFilterContainer(
+                new DefaultMavenPomFactory(configurationContainer, scopeMapping, new DefaultPomDependenciesConverter(
+                        new DefaultExcludeRuleConverter()), fileResolver));
+        return new BaseMavenInstaller(name, pomFilterContainer, new DefaultArtifactPomContainer(pomMetaInfoProvider,
+                pomFilterContainer, new DefaultArtifactPomFactory()), loggingManagerFactory.create());
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverter.java
new file mode 100644
index 0000000..5112dd8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverter.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.api.internal.artifacts.ivyservice;
+
+import org.apache.ivy.core.cache.DefaultRepositoryCacheManager;
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.apache.ivy.plugins.repository.Repository;
+import org.apache.ivy.plugins.repository.TransferEvent;
+import org.apache.ivy.plugins.repository.TransferListener;
+import org.apache.ivy.plugins.resolver.ChainResolver;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.RepositoryResolver;
+import org.gradle.api.artifacts.ResolverContainer;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.logging.ProgressLogger;
+import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.util.Clock;
+import org.gradle.util.WrapUtil;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultSettingsConverter implements SettingsConverter {
+    private static Logger logger = Logging.getLogger(DefaultSettingsConverter.class);
+
+    private RepositoryCacheManager repositoryCacheManager;
+    private IvySettings ivySettings;
+    private final ProgressLoggerFactory progressLoggerFactory;
+    private final TransferListener transferListener = new ProgressLoggingTransferListener();
+
+    public DefaultSettingsConverter(ProgressLoggerFactory progressLoggerFactory) {
+        this.progressLoggerFactory = progressLoggerFactory;
+    }
+
+    private static String getLengthText(TransferEvent evt) {
+        return getLengthText(evt.isTotalLengthSet() ? evt.getTotalLength() : null);
+    }
+
+    private static String getLengthText(Long bytes) {
+        if (bytes == null) {
+            return "unknown size";
+        }
+        if (bytes < 1024) {
+            return bytes + " B";
+        } else if (bytes < 1048576) {
+            return (bytes / 1024) + " KB";
+        } else {
+            return String.format("%.2f MB", bytes / 1048576.0);
+        }
+    }
+
+    public IvySettings convertForPublish(List<DependencyResolver> publishResolvers, File gradleUserHome, DependencyResolver internalRepository) {
+        if (ivySettings != null) {
+            return ivySettings;
+        }
+        Clock clock = new Clock();
+        ChainResolver userResolverChain = createUserResolverChain(Collections.<DependencyResolver>emptyList(), internalRepository);
+        ClientModuleResolver clientModuleResolver = createClientModuleResolver(new HashMap(), userResolverChain);
+        ChainResolver outerChain = createOuterChain(userResolverChain, clientModuleResolver);
+
+        IvySettings ivySettings = createIvySettings(gradleUserHome);
+        initializeResolvers(ivySettings, getAllResolvers(Collections.<DependencyResolver>emptyList(), publishResolvers, internalRepository, userResolverChain, clientModuleResolver, outerChain));
+        ivySettings.setDefaultResolver(CLIENT_MODULE_CHAIN_NAME);
+        logger.debug("Timing: Ivy convert for publish took {}", clock.getTime());
+        return ivySettings;
+    }
+
+    public IvySettings convertForResolve(List<DependencyResolver> dependencyResolvers,
+                               File gradleUserHome, DependencyResolver internalRepository, Map clientModuleRegistry) {
+        if (ivySettings != null) {
+            return ivySettings;
+        }
+        Clock clock = new Clock();
+        ChainResolver userResolverChain = createUserResolverChain(dependencyResolvers, internalRepository);
+        ClientModuleResolver clientModuleResolver = createClientModuleResolver(clientModuleRegistry, userResolverChain);
+        ChainResolver outerChain = createOuterChain(userResolverChain, clientModuleResolver);
+
+        IvySettings ivySettings = createIvySettings(gradleUserHome);
+        initializeResolvers(ivySettings, getAllResolvers(dependencyResolvers, Collections.<DependencyResolver>emptyList(), internalRepository, userResolverChain, clientModuleResolver, outerChain));
+        ivySettings.setDefaultResolver(CLIENT_MODULE_CHAIN_NAME);
+        logger.debug("Timing: Ivy convert for resolve took {}", clock.getTime());
+        return ivySettings;
+    }
+
+    private List<DependencyResolver> getAllResolvers(List<DependencyResolver> classpathResolvers,
+                                                     List<DependencyResolver> otherResolvers, DependencyResolver internalRepository, 
+                                                     ChainResolver userResolverChain, ClientModuleResolver clientModuleResolver,
+                                                     ChainResolver outerChain) {
+        List<DependencyResolver> allResolvers = new ArrayList<DependencyResolver>(otherResolvers);
+        allResolvers.addAll(classpathResolvers);
+        allResolvers.addAll(WrapUtil.toList(internalRepository, outerChain, clientModuleResolver, userResolverChain));
+        return allResolvers;
+    }
+
+    private ChainResolver createOuterChain(ChainResolver userResolverChain, ClientModuleResolver clientModuleResolver) {
+        ChainResolver clientModuleChain = new ChainResolver();
+        clientModuleChain.setName(CLIENT_MODULE_CHAIN_NAME);
+        clientModuleChain.setReturnFirst(true);
+        clientModuleChain.add(clientModuleResolver);
+        clientModuleChain.add(userResolverChain);
+        return clientModuleChain;
+    }
+
+    private ClientModuleResolver createClientModuleResolver(Map clientModuleRegistry, ChainResolver userResolverChain) {
+        return new ClientModuleResolver(CLIENT_MODULE_NAME, clientModuleRegistry, userResolverChain);
+    }
+
+    private ChainResolver createUserResolverChain(List<DependencyResolver> classpathResolvers, DependencyResolver internalRepository) {
+        ChainResolver chainResolver = new ChainResolver();
+        chainResolver.setName(CHAIN_RESOLVER_NAME);
+        chainResolver.add(internalRepository);
+        // todo Figure out why Ivy thinks this is necessary. The IBiblio resolver has already this pattern which should be good enough. By doing this we let Maven semantics seep into our whole system.
+        chainResolver.setChangingPattern(".*-SNAPSHOT");
+        chainResolver.setChangingMatcher(PatternMatcher.REGEXP);
+        chainResolver.setReturnFirst(true);
+        for (Object classpathResolver : classpathResolvers) {
+            chainResolver.add((DependencyResolver) classpathResolver);
+        }
+        return chainResolver;
+    }
+
+    private IvySettings createIvySettings(File gradleUserHome) {
+        IvySettings ivySettings = new IvySettings();
+        ivySettings.setDefaultCache(new File(gradleUserHome, ResolverContainer.DEFAULT_CACHE_DIR_NAME));
+        ivySettings.setDefaultCacheIvyPattern(ResolverContainer.DEFAULT_CACHE_IVY_PATTERN);
+        ivySettings.setDefaultCacheArtifactPattern(ResolverContainer.DEFAULT_CACHE_ARTIFACT_PATTERN);
+        ivySettings.setVariable("ivy.log.modules.in.use", "false");
+        setRepositoryCacheManager(ivySettings);
+        return ivySettings;
+    }
+
+    private void setRepositoryCacheManager(IvySettings ivySettings) {
+        if (repositoryCacheManager == null) {
+            repositoryCacheManager = ivySettings.getDefaultRepositoryCacheManager();
+        } else {
+            ivySettings.setDefaultRepositoryCacheManager(repositoryCacheManager);
+        }
+    }
+
+    private void initializeResolvers(IvySettings ivySettings, List<DependencyResolver> allResolvers) {
+        for (DependencyResolver dependencyResolver : allResolvers) {
+            ivySettings.addResolver(dependencyResolver);
+            ((DefaultRepositoryCacheManager) dependencyResolver.getRepositoryCacheManager()).setSettings(ivySettings);
+            if (dependencyResolver instanceof RepositoryResolver) {
+                Repository repository = ((RepositoryResolver) dependencyResolver).getRepository();
+                if (!repository.hasTransferListener(transferListener)) {
+                    repository.addTransferListener(transferListener);
+                }
+            }
+        }
+    }
+
+    public IvySettings getIvySettings() {
+        return ivySettings;
+    }
+
+    public void setIvySettings(IvySettings ivySettings) {
+        this.ivySettings = ivySettings;
+    }
+
+    private class ProgressLoggingTransferListener implements TransferListener {
+        private ProgressLogger logger;
+        private long total;
+
+        public void transferProgress(TransferEvent evt) {
+            if (evt.getResource().isLocal()) {
+                return;
+            }
+            if (evt.getEventType() == TransferEvent.TRANSFER_STARTED) {
+                total = 0;
+                DefaultSettingsConverter.this.logger.lifecycle("Download " + evt.getResource().getName());
+                logger = progressLoggerFactory.start();
+            }
+            if (evt.getEventType() == TransferEvent.TRANSFER_PROGRESS) {
+                total += evt.getLength();
+                logger.progress(String.format("%s/%s downloaded", getLengthText(total), getLengthText(evt)));
+            }
+            if (evt.getEventType() == TransferEvent.TRANSFER_COMPLETED
+                    || evt.getEventType() == TransferEvent.TRANSFER_ERROR) {
+                logger.completed();
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingIvyService.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingIvyService.java
new file mode 100644
index 0000000..e1f7852
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingIvyService.java
@@ -0,0 +1,142 @@
+/*
+ * 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.ivyservice;
+
+import org.gradle.api.internal.artifacts.IvyService;
+import org.gradle.api.artifacts.*;
+import org.gradle.api.GradleException;
+import org.gradle.api.specs.Spec;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+import java.util.Set;
+import java.util.List;
+import java.io.File;
+
+public class ErrorHandlingIvyService implements IvyService {
+    private final IvyService ivyService;
+
+    public ErrorHandlingIvyService(IvyService ivyService) {
+        this.ivyService = ivyService;
+    }
+
+    public IvyService getIvyService() {
+        return ivyService;
+    }
+
+    public void publish(Set<Configuration> configurationsToPublish, File descriptorDestination,
+                        List<DependencyResolver> publishResolvers) {
+        try {
+            ivyService.publish(configurationsToPublish, descriptorDestination, publishResolvers);
+        } catch (Throwable e) {
+            throw new GradleException(String.format("Could not publish configurations %s.", configurationsToPublish),
+                    e);
+        }
+    }
+
+    public ResolvedConfiguration resolve(final Configuration configuration) {
+        final ResolvedConfiguration resolvedConfiguration;
+        try {
+            resolvedConfiguration = ivyService.resolve(configuration);
+        } catch (final Throwable e) {
+            return new BrokenResolvedConfiguration(e, configuration);
+        }
+        return new ErrorHandlingResolvedConfiguration(resolvedConfiguration, configuration);
+    }
+
+    private static ResolveException wrapException(Throwable e, Configuration configuration) {
+        if (e instanceof ResolveException) {
+            return (ResolveException) e;
+        }
+        return new ResolveException(configuration, e);
+    }
+
+    private static class ErrorHandlingResolvedConfiguration implements ResolvedConfiguration {
+        private final ResolvedConfiguration resolvedConfiguration;
+        private final Configuration configuration;
+
+        public ErrorHandlingResolvedConfiguration(ResolvedConfiguration resolvedConfiguration,
+                                                  Configuration configuration) {
+            this.resolvedConfiguration = resolvedConfiguration;
+            this.configuration = configuration;
+        }
+
+        public boolean hasError() {
+            return resolvedConfiguration.hasError();
+        }
+
+        public void rethrowFailure() throws ResolveException {
+            try {
+                resolvedConfiguration.rethrowFailure();
+            } catch (Throwable e) {
+                throw wrapException(e, configuration);
+            }
+        }
+
+        public Set<File> getFiles(Spec<Dependency> dependencySpec) throws ResolveException {
+            try {
+                return resolvedConfiguration.getFiles(dependencySpec);
+            } catch (Throwable e) {
+                throw wrapException(e, configuration);
+            }
+        }
+
+        public Set<ResolvedDependency> getFirstLevelModuleDependencies() throws ResolveException {
+            try {
+                return resolvedConfiguration.getFirstLevelModuleDependencies();
+            } catch (Throwable e) {
+                throw wrapException(e, configuration);
+            }
+        }
+
+        public Set<ResolvedArtifact> getResolvedArtifacts() throws ResolveException {
+            try {
+                return resolvedConfiguration.getResolvedArtifacts();
+            } catch (Throwable e) {
+                throw wrapException(e, configuration);
+            }
+        }
+    }
+
+    private static class BrokenResolvedConfiguration implements ResolvedConfiguration {
+        private final Throwable e;
+        private final Configuration configuration;
+
+        public BrokenResolvedConfiguration(Throwable e, Configuration configuration) {
+            this.e = e;
+            this.configuration = configuration;
+        }
+
+        public boolean hasError() {
+            return true;
+        }
+
+        public void rethrowFailure() throws ResolveException {
+            throw wrapException(e, configuration);
+        }
+
+        public Set<File> getFiles(Spec<Dependency> dependencySpec) throws ResolveException {
+            throw wrapException(e, configuration);
+        }
+
+        public Set<ResolvedDependency> getFirstLevelModuleDependencies() throws ResolveException {
+            throw wrapException(e, configuration);
+        }
+
+        public Set<ResolvedArtifact> getResolvedArtifacts() throws ResolveException {
+            throw wrapException(e, configuration);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/GradleIBiblioResolver.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/GradleIBiblioResolver.java
new file mode 100644
index 0000000..00d0091
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/GradleIBiblioResolver.java
@@ -0,0 +1,145 @@
+/*
+ * 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.ivyservice;
+
+import org.apache.ivy.core.IvyPatternHelper;
+import org.apache.ivy.core.cache.DefaultRepositoryCacheManager;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.plugins.resolver.IBiblioResolver;
+import org.apache.ivy.util.PropertiesFile;
+
+import java.io.File;
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ * @author Hans Dockter
+ */
+public class GradleIBiblioResolver extends IBiblioResolver {
+    public static final CacheTimeoutStrategy NEVER = new CacheTimeoutStrategy() {
+        public boolean isCacheTimedOut(long lastResolvedTime) {
+            return false;
+        }
+    };
+
+    public static final CacheTimeoutStrategy ALWAYS = new CacheTimeoutStrategy() {
+        public boolean isCacheTimedOut(long lastResolvedTime) {
+            return true;
+        }
+    };
+
+    public static final CacheTimeoutStrategy DAILY = new CacheTimeoutStrategy() {
+        public boolean isCacheTimedOut(long lastResolvedTime) {
+            Calendar calendarCurrent = Calendar.getInstance();
+            calendarCurrent.setTime(new Date());
+            int dayOfYear = calendarCurrent.get(Calendar.DAY_OF_YEAR);
+            int year = calendarCurrent.get(Calendar.YEAR);
+
+            Calendar calendarLastResolved = Calendar.getInstance();
+            calendarLastResolved.setTime(new Date(lastResolvedTime));
+            if (calendarLastResolved.get(Calendar.YEAR) == year &&
+                    calendarLastResolved.get(Calendar.DAY_OF_YEAR) == dayOfYear) {
+                return false;
+            }
+            return true;
+        }
+    };
+
+    private CacheTimeoutStrategy snapshotTimeout = DAILY;
+
+    /**
+     * Returns the timeout strategy for a Maven Snapshot in the cache
+     */
+    public CacheTimeoutStrategy getSnapshotTimeout() {
+        return snapshotTimeout;
+    }
+
+    /**
+     * Sets the time in ms a Maven Snapshot in the cache is not checked for a newer version
+     *
+     * @param snapshotLifetime The lifetime in ms
+     */
+    public void setSnapshotTimeout(long snapshotLifetime) {
+        this.snapshotTimeout = new Interval(snapshotLifetime);
+    }
+
+    /**
+     * Sets a timeout strategy for a Maven Snapshot in the cache
+     *
+     * @param cacheTimeoutStrategy The strategy
+     */
+    public void setSnapshotTimeout(CacheTimeoutStrategy cacheTimeoutStrategy) {
+        this.snapshotTimeout = cacheTimeoutStrategy;
+    }
+
+    @Override
+    protected ResolvedModuleRevision findModuleInCache(DependencyDescriptor dd, ResolveData data) {
+        setChangingPattern(null);
+        ResolvedModuleRevision moduleRevision = super.findModuleInCache(dd, data);
+        if (moduleRevision == null) {
+            setChangingPattern(".*-SNAPSHOT");
+            return null;
+        }
+        PropertiesFile cacheProperties = getCacheProperties(dd, moduleRevision);
+        Long lastResolvedTime = getLastResolvedTime(cacheProperties);
+        updateCachePropertiesToCurrentTime(cacheProperties);
+        if (snapshotTimeout.isCacheTimedOut(lastResolvedTime)) {
+            setChangingPattern(".*-SNAPSHOT");
+            return null;
+        } else {
+            return moduleRevision;
+        }
+    }
+
+    private void updateCachePropertiesToCurrentTime(PropertiesFile cacheProperties) {
+        cacheProperties.setProperty("resolved.time", "" + System.currentTimeMillis());
+        cacheProperties.save();
+    }
+
+    private Long getLastResolvedTime(PropertiesFile cacheProperties) {
+        String lastResolvedProp = cacheProperties.getProperty("resolved.time");
+        Long lastResolvedTime = lastResolvedProp != null ? Long.parseLong(lastResolvedProp) : 0;
+        return lastResolvedTime;
+    }
+
+    private PropertiesFile getCacheProperties(DependencyDescriptor dd, ResolvedModuleRevision moduleRevision) {
+        DefaultRepositoryCacheManager cacheManager = (DefaultRepositoryCacheManager) getRepositoryCacheManager();
+        PropertiesFile props = new PropertiesFile(new File(cacheManager.getRepositoryCacheRoot(),
+                IvyPatternHelper.substitute(
+                        cacheManager.getDataFilePattern(), moduleRevision.getId())), "ivy cached data file for " + dd.getDependencyRevisionId());
+        return props;
+    }
+
+    public interface CacheTimeoutStrategy {
+        boolean isCacheTimedOut(long lastResolvedTime);
+    }
+
+    public static class Interval implements CacheTimeoutStrategy {
+        private long interval;
+
+        public Interval(long interval) {
+            this.interval = interval;
+        }
+
+        public boolean isCacheTimedOut(long lastResolvedTime) {
+            return System.currentTimeMillis() - lastResolvedTime > interval;
+        }
+    }
+}
+
+
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyConversionResult.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyConversionResult.java
new file mode 100644
index 0000000..765b8be
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyConversionResult.java
@@ -0,0 +1,34 @@
+/*
+ * 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.ivyservice;
+
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.artifacts.ResolvedDependency;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public interface IvyConversionResult {
+    Map<Dependency, Set<ResolvedDependency>> getFirstLevelResolvedDependencies();
+
+    Set<ResolvedArtifact> getResolvedArtifacts();
+
+    ResolvedDependency getRoot();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyPublisher.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyPublisher.java
new file mode 100644
index 0000000..5fae794
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyPublisher.java
@@ -0,0 +1,34 @@
+/*
+ * 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.plugins.resolver.DependencyResolver;
+
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public interface IvyDependencyPublisher {
+    void publish(Set<String> configurations,
+                 List<DependencyResolver> publishResolvers,
+                 ModuleDescriptor moduleDescriptor,
+                 File descriptorDestination, PublishEngine publishEngine);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyResolver.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyResolver.java
new file mode 100644
index 0000000..c702792
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyDependencyResolver.java
@@ -0,0 +1,28 @@
+/*
+ * 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.Ivy;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ResolvedConfiguration;
+
+/**
+ * @author Hans Dockter
+ */
+public interface IvyDependencyResolver {
+    ResolvedConfiguration resolve(Configuration configuration, Ivy ivy, ModuleDescriptor moduleDescriptor);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyFactory.java
new file mode 100644
index 0000000..4dfc079
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyFactory.java
@@ -0,0 +1,26 @@
+/*
+ * 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.Ivy;
+import org.apache.ivy.core.settings.IvySettings;
+
+/**
+ * @author Hans Dockter
+ */
+public interface IvyFactory {
+    public Ivy createIvy(IvySettings ivySettings);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyReportConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyReportConverter.java
new file mode 100644
index 0000000..bf2bc53
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyReportConverter.java
@@ -0,0 +1,26 @@
+/*
+ * 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.ivyservice;
+
+import org.apache.ivy.core.report.ResolveReport;
+import org.gradle.api.artifacts.Configuration;
+
+/**
+ * @author Hans Dockter
+ */
+public interface IvyReportConverter {
+    IvyConversionResult convertReport(ResolveReport resolveReport, Configuration configuration);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtil.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtil.java
new file mode 100644
index 0000000..ec85f7a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtil.java
@@ -0,0 +1,54 @@
+/*
+ * 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.ivyservice;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.Module;
+import org.gradle.util.GUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class IvyUtil {
+
+    public static ModuleRevisionId createModuleRevisionId(Module module) {
+        return createModuleRevisionId(module, new HashMap());
+    }
+
+    public static ModuleRevisionId createModuleRevisionId(Module module, Map<String, String> extraAttributes) {
+        return createModuleRevisionId(module.getGroup(), module.getName(), module.getVersion(), extraAttributes);
+    }
+
+    public static ModuleRevisionId createModuleRevisionId(Dependency dependency) {
+        return createModuleRevisionId(dependency, new HashMap<String, String>());
+    }
+
+    public static ModuleRevisionId createModuleRevisionId(Dependency dependency, Map<String, String> extraAttributes) {
+        return createModuleRevisionId(dependency.getGroup(), dependency.getName(), dependency.getVersion(), extraAttributes);
+    }
+
+    public static ModuleRevisionId createModuleRevisionId(String group, String name, String version, Map<String, String> extraAttributes) {
+        return ModuleRevisionId.newInstance(emptyStringIfNull(group), name, emptyStringIfNull(version), extraAttributes);
+    }
+
+    private static String emptyStringIfNull(String value) {
+        return GUtil.elvis(value, "");
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java
new file mode 100644
index 0000000..84be07c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ModuleDescriptorConverter.java
@@ -0,0 +1,30 @@
+/*
+ * 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.settings.IvySettings;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ModuleDescriptorConverter {
+    ModuleDescriptor convert(Set<Configuration> configurations, Module module, IvySettings settings);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/PublishOptionsFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/PublishOptionsFactory.java
new file mode 100644
index 0000000..8d1310f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/PublishOptionsFactory.java
@@ -0,0 +1,28 @@
+/*
+ * 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/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolverFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolverFactory.java
new file mode 100644
index 0000000..44677f5
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ResolverFactory.java
@@ -0,0 +1,45 @@
+/*
+ * 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.plugins.resolver.AbstractResolver;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.apache.ivy.plugins.resolver.FileSystemResolver;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.artifacts.maven.GroovyMavenDeployer;
+import org.gradle.api.artifacts.maven.MavenResolver;
+import org.gradle.api.internal.artifacts.publish.maven.MavenPomMetaInfoProvider;
+import org.gradle.api.internal.file.FileResolver;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ResolverFactory {
+    DependencyResolver createResolver(Object userDescription);
+
+    FileSystemResolver createFlatDirResolver(String name, File... roots);
+
+    AbstractResolver createMavenRepoResolver(String name, String root, String... jarRepoUrls);
+
+    GroovyMavenDeployer createMavenDeployer(String name, MavenPomMetaInfoProvider pomMetaInfoProvider, ConfigurationContainer configurationContainer,
+                                           Conf2ScopeMappingContainer scopeMapping, FileResolver fileResolver);
+
+    MavenResolver createMavenInstaller(String name, MavenPomMetaInfoProvider pomMetaInfoProvider, ConfigurationContainer configurationContainer,
+                                       Conf2ScopeMappingContainer scopeMapping, FileResolver fileResolver);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolver.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolver.java
new file mode 100644
index 0000000..557c4f3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolver.java
@@ -0,0 +1,78 @@
+/*
+ * 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.ivyservice;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.GradleException;
+import org.gradle.api.artifacts.*;
+import org.gradle.api.internal.artifacts.CachingDependencyResolveContext;
+import org.gradle.api.internal.artifacts.DependencyInternal;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+
+import java.io.File;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class SelfResolvingDependencyResolver implements IvyDependencyResolver {
+    private final IvyDependencyResolver resolver;
+
+    public SelfResolvingDependencyResolver(IvyDependencyResolver resolver) {
+        this.resolver = resolver;
+    }
+
+    public IvyDependencyResolver getResolver() {
+        return resolver;
+    }
+
+    public ResolvedConfiguration resolve(final Configuration configuration, Ivy ivy, ModuleDescriptor moduleDescriptor) {
+        final ResolvedConfiguration resolvedConfiguration = resolver.resolve(configuration, ivy, moduleDescriptor);
+        final Set<DependencyInternal> dependencies = configuration.getAllDependencies(DependencyInternal.class);
+
+        return new ResolvedConfiguration() {
+            private final CachingDependencyResolveContext resolveContext = new CachingDependencyResolveContext(configuration.isTransitive());
+
+            public Set<File> getFiles(Spec<Dependency> dependencySpec) {
+                Set<File> files = new LinkedHashSet<File>();
+
+                Set<DependencyInternal> selectedDependencies = Specs.filterIterable(dependencies, dependencySpec);
+                for (DependencyInternal dependency : selectedDependencies) {
+                    resolveContext.add(dependency);
+                }
+                files.addAll(resolveContext.resolve().getFiles());
+                files.addAll(resolvedConfiguration.getFiles(dependencySpec));
+                return files;
+            }
+
+            public Set<ResolvedArtifact> getResolvedArtifacts() {
+                return resolvedConfiguration.getResolvedArtifacts();
+            }
+
+            public Set<ResolvedDependency> getFirstLevelModuleDependencies() {
+                return resolvedConfiguration.getFirstLevelModuleDependencies();
+            }
+
+            public boolean hasError() {
+                return resolvedConfiguration.hasError();
+            }
+
+            public void rethrowFailure() throws GradleException {
+                resolvedConfiguration.rethrowFailure();
+            }
+        };
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SettingsConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SettingsConverter.java
new file mode 100644
index 0000000..fe39f48
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SettingsConverter.java
@@ -0,0 +1,37 @@
+/*
+ * 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.settings.IvySettings;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public interface SettingsConverter {
+    String CHAIN_RESOLVER_NAME = "chain";
+    String CLIENT_MODULE_CHAIN_NAME = "clientModuleChain";
+    String CLIENT_MODULE_NAME = "clientModule";
+
+    IvySettings convertForPublish(List<DependencyResolver> publishResolvers, File gradleUserHome, DependencyResolver internalRepository);
+
+    IvySettings convertForResolve(List<DependencyResolver> classpathResolvers, File gradleUserHome, DependencyResolver internalRepository,
+                        Map clientModuleRegistry);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsIvyService.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsIvyService.java
new file mode 100644
index 0000000..eb6cc8c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsIvyService.java
@@ -0,0 +1,70 @@
+/*
+ * 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.ivyservice;
+
+import org.gradle.api.internal.artifacts.IvyService;
+import org.gradle.api.artifacts.*;
+import org.gradle.api.specs.Spec;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+
+import java.util.Set;
+import java.util.List;
+import java.util.Collections;
+import java.io.File;
+
+public class ShortcircuitEmptyConfigsIvyService implements IvyService {
+    private final ResolvedConfiguration emptyConfig = new ResolvedConfiguration() {
+        public boolean hasError() {
+            return false;
+        }
+
+        public void rethrowFailure() throws ResolveException {
+        }
+
+        public Set<File> getFiles(Spec<Dependency> dependencySpec) {
+            return Collections.emptySet();
+        }
+
+        public Set<ResolvedDependency> getFirstLevelModuleDependencies() {
+            return Collections.emptySet();
+        }
+
+        public Set<ResolvedArtifact> getResolvedArtifacts() {
+            return Collections.emptySet();
+        }
+    };
+    private final IvyService ivyService;
+
+    public ShortcircuitEmptyConfigsIvyService(IvyService ivyService) {
+        this.ivyService = ivyService;
+    }
+
+    public IvyService getIvyService() {
+        return ivyService;
+    }
+
+    public void publish(Set<Configuration> configurationsToPublish, File descriptorDestination,
+                        List<DependencyResolver> publishResolvers) {
+        ivyService.publish(configurationsToPublish, descriptorDestination, publishResolvers);
+    }
+
+    public ResolvedConfiguration resolve(Configuration configuration) {
+        if (configuration.getAllDependencies().isEmpty()) {
+            return emptyConfig;
+        }
+        return ivyService.resolve(configuration);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SnapshotVersionMatcher.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SnapshotVersionMatcher.java
new file mode 100644
index 0000000..5e23471
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/SnapshotVersionMatcher.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.api.internal.artifacts.ivyservice;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.plugins.version.AbstractVersionMatcher;
+
+/**
+ * @author Hans Dockter
+ */
+public class SnapshotVersionMatcher extends AbstractVersionMatcher {
+    public SnapshotVersionMatcher() {
+        super("snapshot");
+    }
+
+    public boolean isDynamic(ModuleRevisionId askedMrid) {
+        return askedMrid.getRevision().endsWith("-SNAPSHOT");
+    }
+
+    public boolean accept(ModuleRevisionId askedMrid, ModuleRevisionId foundMrid) {
+        return askedMrid.getRevision().equals(foundMrid.getRevision());
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/AbstractModuleDescriptorConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/AbstractModuleDescriptorConverter.java
new file mode 100644
index 0000000..966b81a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/AbstractModuleDescriptorConverter.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.api.internal.artifacts.ivyservice.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.settings.IvySettings;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependenciesToModuleDescriptorConverter;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractModuleDescriptorConverter implements ModuleDescriptorConverter {
+    private ModuleDescriptorFactory moduleDescriptorFactory;
+
+    private ConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter;
+    private DependenciesToModuleDescriptorConverter dependenciesToModuleDescriptorConverter;
+
+    public AbstractModuleDescriptorConverter(ModuleDescriptorFactory moduleDescriptorFactory,
+                                            ConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter,
+                                            DependenciesToModuleDescriptorConverter dependenciesToModuleDescriptorConverter) {
+        this.moduleDescriptorFactory = moduleDescriptorFactory;
+        this.configurationsToModuleDescriptorConverter = configurationsToModuleDescriptorConverter;
+        this.dependenciesToModuleDescriptorConverter = dependenciesToModuleDescriptorConverter;
+    }
+
+    protected DefaultModuleDescriptor createCommonModuleDescriptor(Module module, Set<Configuration> configurations, IvySettings ivySettings) {
+        DefaultModuleDescriptor moduleDescriptor = moduleDescriptorFactory.createModuleDescriptor(module);
+        configurationsToModuleDescriptorConverter.addConfigurations(moduleDescriptor, configurations);
+        dependenciesToModuleDescriptorConverter.addDependencyDescriptors(moduleDescriptor, configurations, ivySettings);
+        return moduleDescriptor;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsExtraAttributesStrategy.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsExtraAttributesStrategy.java
new file mode 100644
index 0000000..4828e51
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsExtraAttributesStrategy.java
@@ -0,0 +1,27 @@
+/*
+ * 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.ivyservice.moduleconverter;
+
+import org.gradle.api.artifacts.PublishArtifact;
+
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ArtifactsExtraAttributesStrategy {
+    Map<String, String> createExtraAttributes(PublishArtifact publishArtifact);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsToModuleDescriptorConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsToModuleDescriptorConverter.java
new file mode 100644
index 0000000..4c620c1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ArtifactsToModuleDescriptorConverter.java
@@ -0,0 +1,28 @@
+/*
+ * 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.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.gradle.api.artifacts.Configuration;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ArtifactsToModuleDescriptorConverter {
+    void addArtifacts(DefaultModuleDescriptor moduleDescriptor, Set<Configuration> configurations);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ConfigurationsToModuleDescriptorConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ConfigurationsToModuleDescriptorConverter.java
new file mode 100644
index 0000000..b971b28
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ConfigurationsToModuleDescriptorConverter.java
@@ -0,0 +1,28 @@
+/*
+ * 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.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.gradle.api.artifacts.Configuration;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ConfigurationsToModuleDescriptorConverter {
+    void addConfigurations(DefaultModuleDescriptor moduleDescriptor, Set<Configuration> configurations);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverter.java
new file mode 100644
index 0000000..067aec4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverter.java
@@ -0,0 +1,77 @@
+/*
+ * 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.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.internal.artifacts.ivyservice.DefaultIvyDependencyPublisher;
+import org.gradle.util.GUtil;
+import org.gradle.util.WrapUtil;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.HashMap;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultArtifactsToModuleDescriptorConverter implements ArtifactsToModuleDescriptorConverter {
+    public final static ArtifactsExtraAttributesStrategy IVY_FILE_STRATEGY = new ArtifactsExtraAttributesStrategy() {
+        public Map<String, String> createExtraAttributes(PublishArtifact publishArtifact) {
+            return new HashMap<String, String>();
+        }
+    };
+
+    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());
+        }
+    };
+
+    private ArtifactsExtraAttributesStrategy artifactsExtraAttributesStrategy;
+
+    public DefaultArtifactsToModuleDescriptorConverter(ArtifactsExtraAttributesStrategy artifactsExtraAttributesStrategy) {
+        this.artifactsExtraAttributesStrategy = artifactsExtraAttributesStrategy;
+    }
+
+    public void addArtifacts(DefaultModuleDescriptor moduleDescriptor, Set<Configuration> configurations) {
+        for (Configuration configuration : configurations) {
+            for (PublishArtifact publishArtifact : configuration.getArtifacts()) {
+                Artifact ivyArtifact = createIvyArtifact(publishArtifact, moduleDescriptor.getModuleRevisionId());
+                moduleDescriptor.addArtifact(configuration.getName(), ivyArtifact);
+            }
+        }
+    }
+
+    public Artifact createIvyArtifact(PublishArtifact publishArtifact, ModuleRevisionId moduleRevisionId) {
+        Map extraAttributes = artifactsExtraAttributesStrategy.createExtraAttributes(publishArtifact);
+        if (GUtil.isTrue(publishArtifact.getClassifier())) {
+            extraAttributes.put(Dependency.CLASSIFIER, publishArtifact.getClassifier());
+        }
+        return new DefaultArtifact(
+                moduleRevisionId,
+                publishArtifact.getDate(),
+                publishArtifact.getName(),
+                publishArtifact.getType(),
+                publishArtifact.getExtension(),
+                extraAttributes);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverter.java
new file mode 100644
index 0000000..fb238f6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverter.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.api.internal.artifacts.ivyservice.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.internal.artifacts.configurations.Configurations;
+
+import java.util.Arrays;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultConfigurationsToModuleDescriptorConverter implements ConfigurationsToModuleDescriptorConverter {
+    public void addConfigurations(DefaultModuleDescriptor moduleDescriptor, Set<Configuration> configurations) {
+        for (Configuration configuration : configurations) {
+            moduleDescriptor.addConfiguration(getIvyConfiguration(configuration));
+        }
+    }
+
+    public org.apache.ivy.core.module.descriptor.Configuration getIvyConfiguration(Configuration configuration) {
+        String[] superConfigs = Configurations.getNames(configuration.getExtendsFrom(), false).toArray(new String[configuration.getExtendsFrom().size()]);
+        Arrays.sort(superConfigs);
+        return new org.apache.ivy.core.module.descriptor.Configuration(
+                configuration.getName(),
+                configuration.isVisible() ? org.apache.ivy.core.module.descriptor.Configuration.Visibility.PUBLIC : org.apache.ivy.core.module.descriptor.Configuration.Visibility.PRIVATE,
+                configuration.getDescription(),
+                superConfigs,
+                configuration.isTransitive(),
+                null);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverter.java
new file mode 100644
index 0000000..99f8a9d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverter.java
@@ -0,0 +1,41 @@
+/*
+ * 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.ivyservice.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultExcludeRule;
+import org.apache.ivy.core.module.id.ArtifactId;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.util.GUtil;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultExcludeRuleConverter implements ExcludeRuleConverter {
+    public DefaultExcludeRule createExcludeRule(String configurationName, ExcludeRule excludeRule) {
+        String org = GUtil.elvis(excludeRule.getExcludeArgs().get(ExcludeRule.GROUP_KEY), PatternMatcher.ANY_EXPRESSION);
+        String module = GUtil.elvis(excludeRule.getExcludeArgs().get(ExcludeRule.MODULE_KEY), PatternMatcher.ANY_EXPRESSION);
+        DefaultExcludeRule ivyExcludeRule = new DefaultExcludeRule(new ArtifactId(
+                new ModuleId(org, module), PatternMatcher.ANY_EXPRESSION,
+                PatternMatcher.ANY_EXPRESSION,
+                PatternMatcher.ANY_EXPRESSION),
+                ExactPatternMatcher.INSTANCE, null);
+        ivyExcludeRule.addConfiguration(configurationName);
+        return ivyExcludeRule;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactory.java
new file mode 100644
index 0000000..9aa9809
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactory.java
@@ -0,0 +1,30 @@
+/*
+ * 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.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultModuleDescriptorFactory implements ModuleDescriptorFactory {
+    public DefaultModuleDescriptor createModuleDescriptor(Module module) {
+        return new DefaultModuleDescriptor(IvyUtil.createModuleRevisionId(module),
+                module.getStatus(), null);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ExcludeRuleConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ExcludeRuleConverter.java
new file mode 100644
index 0000000..4ed4bc5
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ExcludeRuleConverter.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.api.internal.artifacts.ivyservice.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.ExcludeRule;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ExcludeRuleConverter {
+    ExcludeRule createExcludeRule(String configuration, org.gradle.api.artifacts.ExcludeRule excludeRule);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ModuleDescriptorFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ModuleDescriptorFactory.java
new file mode 100644
index 0000000..1f271a9
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ModuleDescriptorFactory.java
@@ -0,0 +1,26 @@
+/*
+ * 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.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.gradle.api.artifacts.Module;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ModuleDescriptorFactory {
+    DefaultModuleDescriptor createModuleDescriptor(Module module);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverter.java
new file mode 100644
index 0000000..9cc4020
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverter.java
@@ -0,0 +1,64 @@
+/*
+ * 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.api.internal.artifacts.ivyservice.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.settings.IvySettings;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
+import org.gradle.util.Clock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class PublishModuleDescriptorConverter implements ModuleDescriptorConverter {
+    static final String IVY_MAVEN_NAMESPACE = "http://ant.apache.org/ivy/maven";
+    static final String IVY_MAVEN_NAMESPACE_PREFIX = "m";
+
+    private static Logger logger = LoggerFactory.getLogger(PublishModuleDescriptorConverter.class);
+    private ModuleDescriptorConverter resolveModuleDescriptorConverter;
+    private ArtifactsToModuleDescriptorConverter artifactsToModuleDescriptorConverter;
+
+    public PublishModuleDescriptorConverter(ModuleDescriptorConverter resolveModuleDescriptorConverter,
+                                            ArtifactsToModuleDescriptorConverter artifactsToModuleDescriptorConverter) {
+        this.resolveModuleDescriptorConverter = resolveModuleDescriptorConverter;
+        this.artifactsToModuleDescriptorConverter = artifactsToModuleDescriptorConverter;
+    }
+
+    public ModuleDescriptor convert(Set<Configuration> configurations, Module module, IvySettings settings) {
+        Clock clock = new Clock();
+        DefaultModuleDescriptor moduleDescriptor = (DefaultModuleDescriptor) resolveModuleDescriptorConverter.convert(configurations, module, settings);
+        moduleDescriptor.addExtraAttributeNamespace(IVY_MAVEN_NAMESPACE_PREFIX, IVY_MAVEN_NAMESPACE);
+        artifactsToModuleDescriptorConverter.addArtifacts((DefaultModuleDescriptor) moduleDescriptor, configurations);
+        logger.debug("Timing: Ivy convert for publish took {}", clock.getTime());
+        return moduleDescriptor;
+    }
+
+    public ArtifactsToModuleDescriptorConverter getArtifactsToModuleDescriptorConverter() {
+        return artifactsToModuleDescriptorConverter;
+    }
+
+    public void setArtifactsToModuleDescriptorConverter(ArtifactsToModuleDescriptorConverter artifactsToModuleDescriptorConverter) {
+        this.artifactsToModuleDescriptorConverter = artifactsToModuleDescriptorConverter;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverter.java
new file mode 100644
index 0000000..6e64ec0
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverter.java
@@ -0,0 +1,51 @@
+/*
+ * 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.api.internal.artifacts.ivyservice.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.settings.IvySettings;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependenciesToModuleDescriptorConverter;
+import org.gradle.util.Clock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class ResolveModuleDescriptorConverter extends AbstractModuleDescriptorConverter implements ModuleDescriptorConverter {
+    private static Logger logger = LoggerFactory.getLogger(ResolveModuleDescriptorConverter.class);
+
+    public ResolveModuleDescriptorConverter(ModuleDescriptorFactory moduleDescriptorFactory,
+                                            ConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter,
+                                            DependenciesToModuleDescriptorConverter dependenciesToModuleDescriptorConverter) {
+        super(moduleDescriptorFactory, configurationsToModuleDescriptorConverter, dependenciesToModuleDescriptorConverter);
+    }
+
+    public ModuleDescriptor convert(Set<Configuration> configurations, Module module, IvySettings settings) {
+        assert configurations.size() > 0;
+        Clock clock = new Clock();
+        DefaultModuleDescriptor moduleDescriptor = createCommonModuleDescriptor(module, configurations, settings);
+        logger.debug("Timing: Ivy convert for resolve took {}", clock.getTime());
+        return moduleDescriptor;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternal.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternal.java
new file mode 100644
index 0000000..2d4287b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternal.java
@@ -0,0 +1,123 @@
+/*
+ * 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.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.*;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.DependencyArtifact;
+import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
+import org.gradle.util.WrapUtil;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractDependencyDescriptorFactoryInternal implements DependencyDescriptorFactoryInternal {
+    private ExcludeRuleConverter excludeRuleConverter;
+
+    public AbstractDependencyDescriptorFactoryInternal(ExcludeRuleConverter excludeRuleConverter) {
+        this.excludeRuleConverter = excludeRuleConverter;
+    }
+
+    public void addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor, ModuleDependency dependency) {
+        ModuleRevisionId moduleRevisionId = createModuleRevisionId(dependency);
+        DependencyDescriptor newDescriptor = createDependencyDescriptor(dependency, configuration, moduleDescriptor, moduleRevisionId);
+        DefaultDependencyDescriptor existingDependencyDescriptor = findExistingDescriptor(moduleDescriptor, newDescriptor);
+
+        if (existingDependencyDescriptor == null) {
+            moduleDescriptor.addDependency(newDescriptor);
+        } else {
+            existingDependencyDescriptor.addDependencyConfiguration(configuration, dependency.getConfiguration());
+        }
+    }
+
+    protected abstract DependencyDescriptor createDependencyDescriptor(ModuleDependency dependency, String configuration,
+                                                            ModuleDescriptor moduleDescriptor, ModuleRevisionId moduleRevisionId);
+
+    private DefaultDependencyDescriptor findExistingDescriptor(DefaultModuleDescriptor moduleDescriptor, DependencyDescriptor targetDescriptor) {
+        for (DependencyDescriptor dependencyDescriptor : moduleDescriptor.getDependencies()) {
+
+            if (dependencyDescriptor.getDependencyRevisionId().equals(targetDescriptor.getDependencyRevisionId())) {
+                HashSet<DependencyArtifactDescriptor> nextDependencies =
+                        new HashSet<DependencyArtifactDescriptor>(Arrays.asList(dependencyDescriptor.getAllDependencyArtifacts()));
+                HashSet<DependencyArtifactDescriptor> targetDependencies =
+                        new HashSet<DependencyArtifactDescriptor>(Arrays.asList(targetDescriptor.getAllDependencyArtifacts()));
+
+                if (nextDependencies.equals(targetDependencies)) {
+                    return (DefaultDependencyDescriptor) dependencyDescriptor;
+                }
+            }
+        }
+        return null;
+    }
+
+    protected void addExcludesArtifactsAndDependencies(String configuration, ModuleDependency dependency,
+                                                     DefaultDependencyDescriptor dependencyDescriptor) {
+        addArtifacts(configuration, dependency.getArtifacts(), dependencyDescriptor);
+        addExcludes(configuration, dependency.getExcludeRules(), dependencyDescriptor);
+        addDependencyConfiguration(configuration, dependency, dependencyDescriptor);
+    }
+
+    private void addArtifacts(String configuration, Set<DependencyArtifact> artifacts,
+                              DefaultDependencyDescriptor dependencyDescriptor) {
+        for (DependencyArtifact artifact : artifacts) {
+            DefaultDependencyArtifactDescriptor artifactDescriptor;
+            try {
+                artifactDescriptor = new DefaultDependencyArtifactDescriptor(dependencyDescriptor, artifact.getName(),
+                        artifact.getType(),
+                        artifact.getExtension() != null ? artifact.getExtension() : artifact.getType(),
+                        artifact.getUrl() != null ? new URL(artifact.getUrl()) : null,
+                        artifact.getClassifier() != null ? WrapUtil.toMap(Dependency.CLASSIFIER,
+                                artifact.getClassifier()) : null);
+            } catch (MalformedURLException e) {
+                throw new InvalidUserDataException("URL for artifact can't be parsed: " + artifact.getUrl(), e);
+            }
+            dependencyDescriptor.addDependencyArtifact(configuration, artifactDescriptor);
+        }
+    }
+
+    private void addDependencyConfiguration(String configuration, ModuleDependency dependency,
+                                            DefaultDependencyDescriptor dependencyDescriptor) {
+        dependencyDescriptor.addDependencyConfiguration(configuration, dependency.getConfiguration());
+    }
+
+    private void addExcludes(String configuration, Set<ExcludeRule> excludeRules,
+                             DefaultDependencyDescriptor dependencyDescriptor) {
+        for (ExcludeRule excludeRule : excludeRules) {
+            dependencyDescriptor.addExcludeRule(configuration, excludeRuleConverter.createExcludeRule(configuration,
+                    excludeRule));
+        }
+    }
+
+    public ExcludeRuleConverter getExcludeRuleConverter() {
+        return excludeRuleConverter;
+    }
+
+    public void setExcludeRuleConverter(ExcludeRuleConverter excludeRuleConverter) {
+        this.excludeRuleConverter = excludeRuleConverter;
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactory.java
new file mode 100644
index 0000000..dfe1063
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactory.java
@@ -0,0 +1,69 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ClientModule;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.util.WrapUtil;
+
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+*/
+public class ClientModuleDependencyDescriptorFactory extends AbstractDependencyDescriptorFactoryInternal {
+    private ModuleDescriptorFactoryForClientModule moduleDescriptorFactoryForClientModule;
+    private Map<String, ModuleDescriptor> clientModuleRegistry;
+
+    public ClientModuleDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter, ModuleDescriptorFactoryForClientModule moduleDescriptorFactoryForClientModule, Map<String, ModuleDescriptor> clientModuleRegistry) {
+        super(excludeRuleConverter);
+        this.moduleDescriptorFactoryForClientModule = moduleDescriptorFactoryForClientModule;
+        this.clientModuleRegistry = clientModuleRegistry;
+    }
+
+    public ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
+        return IvyUtil.createModuleRevisionId(dependency,
+                WrapUtil.toMap(getClientModule(dependency).CLIENT_MODULE_KEY, getClientModule(dependency).getId()));
+    }
+
+    public DependencyDescriptor createDependencyDescriptor(ModuleDependency dependency, String configuration, ModuleDescriptor parent,
+                                                           ModuleRevisionId moduleRevisionId) {
+        DefaultDependencyDescriptor dependencyDescriptor = new DefaultDependencyDescriptor(parent,
+                moduleRevisionId, getClientModule(dependency).isForce(),
+                false, getClientModule(dependency).isTransitive());
+        addExcludesArtifactsAndDependencies(configuration, getClientModule(dependency), dependencyDescriptor);
+
+        ModuleDescriptor moduleDescriptor = moduleDescriptorFactoryForClientModule.createModuleDescriptor(
+                dependencyDescriptor.getDependencyRevisionId(), getClientModule(dependency).getDependencies());
+        clientModuleRegistry.put(getClientModule(dependency).getId(), moduleDescriptor);
+
+        return dependencyDescriptor;
+    }
+
+    private ClientModule getClientModule(ModuleDependency dependency) {
+        return (ClientModule) dependency;
+    }
+
+    public boolean canConvert(ModuleDependency dependency) {
+        return dependency instanceof ClientModule;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java
new file mode 100644
index 0000000..8a330a0
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverter.java
@@ -0,0 +1,93 @@
+/*
+ * 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.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.conflict.LatestConflictManager;
+import org.apache.ivy.plugins.latest.LatestRevisionStrategy;
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultDependenciesToModuleDescriptorConverter implements DependenciesToModuleDescriptorConverter {
+    private DependencyDescriptorFactory dependencyDescriptorFactory;
+    private ExcludeRuleConverter excludeRuleConverter;
+
+    public DefaultDependenciesToModuleDescriptorConverter(DependencyDescriptorFactory dependencyDescriptorFactory,
+                                                          ExcludeRuleConverter excludeRuleConverter) {
+        this.dependencyDescriptorFactory = dependencyDescriptorFactory;
+        this.excludeRuleConverter = excludeRuleConverter;
+    }
+
+    public void addDependencyDescriptors(DefaultModuleDescriptor moduleDescriptor, Set<Configuration> configurations,
+                                         IvySettings ivySettings) {
+        assert !configurations.isEmpty();
+        addDependencies(moduleDescriptor, configurations);
+        addExcludeRules(moduleDescriptor, configurations);
+        addConflictManager(moduleDescriptor, ivySettings);
+    }
+
+    private void addDependencies(DefaultModuleDescriptor moduleDescriptor, Set<Configuration> configurations) {
+        for (Configuration configuration : configurations) {
+            for (ModuleDependency dependency : configuration.getDependencies(ModuleDependency.class)) {
+                dependencyDescriptorFactory.addDependencyDescriptor(configuration.getName(), moduleDescriptor, dependency);
+            }
+        }
+    }
+
+    private void addExcludeRules(DefaultModuleDescriptor moduleDescriptor, Set<Configuration> configurations) {
+        for (Configuration configuration : configurations) {
+            for (ExcludeRule excludeRule : configuration.getExcludeRules()) {
+                org.apache.ivy.core.module.descriptor.ExcludeRule rule = excludeRuleConverter.createExcludeRule(
+                        configuration.getName(), excludeRule);
+                moduleDescriptor.addExcludeRule(rule);
+            }
+        }
+    }
+
+    private void addConflictManager(DefaultModuleDescriptor moduleDescriptor, IvySettings ivySettings) {
+        LatestConflictManager conflictManager = new LatestConflictManager(new LatestRevisionStrategy());
+        conflictManager.setSettings(ivySettings);
+        moduleDescriptor.addConflictManager(new ModuleId(ExactPatternMatcher.ANY_EXPRESSION,
+                ExactPatternMatcher.ANY_EXPRESSION), ExactPatternMatcher.INSTANCE,
+                conflictManager);
+    }
+
+    public DependencyDescriptorFactory getDependencyDescriptorFactory() {
+        return dependencyDescriptorFactory;
+    }
+
+    public void setDependencyDescriptorFactory(DependencyDescriptorFactory dependencyDescriptorFactory) {
+        this.dependencyDescriptorFactory = dependencyDescriptorFactory;
+    }
+
+    public ExcludeRuleConverter getExcludeRuleConverter() {
+        return excludeRuleConverter;
+    }
+
+    public void setExcludeRuleConverter(ExcludeRuleConverter excludeRuleConverter) {
+        this.excludeRuleConverter = excludeRuleConverter;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModule.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModule.java
new file mode 100644
index 0000000..f55f989
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModule.java
@@ -0,0 +1,60 @@
+/*
+ * 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.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.Configuration;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ModuleDependency;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultModuleDescriptorFactoryForClientModule implements ModuleDescriptorFactoryForClientModule {
+    // Because of bi directioal dependencies we need setter injection
+    private DependencyDescriptorFactory dependencyDescriptorFactory;
+
+    public ModuleDescriptor createModuleDescriptor(ModuleRevisionId moduleRevisionId, Set<ModuleDependency> dependencies) {
+        DefaultModuleDescriptor moduleDescriptor = new DefaultModuleDescriptor(moduleRevisionId,
+                "release", null);
+        moduleDescriptor.addConfiguration(new Configuration(Dependency.DEFAULT_CONFIGURATION));
+        addDependencyDescriptors(moduleDescriptor, dependencies, dependencyDescriptorFactory);
+        moduleDescriptor.addArtifact(Dependency.DEFAULT_CONFIGURATION,
+                new DefaultArtifact(moduleRevisionId, null, moduleRevisionId.getName(), "jar", "jar"));
+        return moduleDescriptor;
+    }
+
+    private void addDependencyDescriptors(DefaultModuleDescriptor moduleDescriptor, Set<ModuleDependency> dependencies,
+                                          DependencyDescriptorFactory dependencyDescriptorFactory) {
+        for (ModuleDependency dependency : dependencies) {
+            dependencyDescriptorFactory.addDependencyDescriptor(Dependency.DEFAULT_CONFIGURATION, moduleDescriptor,
+                    dependency);
+        }
+    }
+
+    public DependencyDescriptorFactory getDependencyDescriptorFactory() {
+        return dependencyDescriptorFactory;
+    }
+
+    public void setDependencyDescriptorFactory(DependencyDescriptorFactory dependencyDescriptorFactory) {
+        this.dependencyDescriptorFactory = dependencyDescriptorFactory;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependenciesToModuleDescriptorConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependenciesToModuleDescriptorConverter.java
new file mode 100644
index 0000000..07ca8cb
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependenciesToModuleDescriptorConverter.java
@@ -0,0 +1,29 @@
+/*
+ * 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.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.settings.IvySettings;
+import org.gradle.api.artifacts.Configuration;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public interface DependenciesToModuleDescriptorConverter {
+    void addDependencyDescriptors(DefaultModuleDescriptor moduleDescriptor, Set<Configuration> configurations, IvySettings ivySettings);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java
new file mode 100644
index 0000000..5fd574f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactory.java
@@ -0,0 +1,32 @@
+/*
+ * 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.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ModuleDependency;
+
+/**
+ * @author Hans Dockter
+ */
+public interface DependencyDescriptorFactory {
+    String PROJECT_PATH_KEY = "org.gradle.projectPath";
+
+    void addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor,
+                                 ModuleDependency dependency);
+
+    ModuleRevisionId createModuleRevisionId(ModuleDependency dependency);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegate.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegate.java
new file mode 100644
index 0000000..f0f6818
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegate.java
@@ -0,0 +1,56 @@
+/*
+ * 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.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.util.WrapUtil;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DependencyDescriptorFactoryDelegate implements DependencyDescriptorFactory {
+    private Set<DependencyDescriptorFactoryInternal> dependencyDescriptorFactories;
+
+    public DependencyDescriptorFactoryDelegate(DependencyDescriptorFactoryInternal... dependencyDescriptorFactories) {
+        this.dependencyDescriptorFactories = WrapUtil.toSet(dependencyDescriptorFactories);
+    }
+
+    public void addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor,
+                                        ModuleDependency dependency) {
+        DependencyDescriptorFactoryInternal factoryInternal = findFactoryForDependency(dependency);
+        factoryInternal.addDependencyDescriptor(configuration, moduleDescriptor, dependency);
+    }
+
+    public ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
+        DependencyDescriptorFactoryInternal factoryInternal = findFactoryForDependency(dependency);
+        return factoryInternal.createModuleRevisionId(dependency);
+    }
+
+    private DependencyDescriptorFactoryInternal findFactoryForDependency(ModuleDependency dependency) {
+        for (DependencyDescriptorFactoryInternal dependencyDescriptorFactoryInternal : dependencyDescriptorFactories) {
+            if (dependencyDescriptorFactoryInternal.canConvert(dependency)) {
+                return dependencyDescriptorFactoryInternal;
+            }
+        }
+        throw new InvalidUserDataException("Can't map dependency of type: " + dependency.getClass());
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryInternal.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryInternal.java
new file mode 100644
index 0000000..0f1b531
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryInternal.java
@@ -0,0 +1,25 @@
+/*
+ * 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.moduleconverter.dependencies;
+
+import org.gradle.api.artifacts.ModuleDependency;
+
+/**
+ * @author Hans Dockter
+ */
+public interface DependencyDescriptorFactoryInternal extends DependencyDescriptorFactory {
+    boolean canConvert(ModuleDependency dependency);
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactory.java
new file mode 100644
index 0000000..49ddf60
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactory.java
@@ -0,0 +1,55 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ExternalModuleDependency;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+
+/**
+ * @author Hans Dockter
+*/
+public class ExternalModuleDependencyDescriptorFactory extends AbstractDependencyDescriptorFactoryInternal {
+    public ExternalModuleDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter) {
+        super(excludeRuleConverter);
+    }
+
+    public ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
+        return IvyUtil.createModuleRevisionId(dependency);
+    }
+
+    public DependencyDescriptor createDependencyDescriptor(ModuleDependency dependency, String configuration, ModuleDescriptor parent,
+                                                           ModuleRevisionId moduleRevisionId) {
+        DefaultDependencyDescriptor dependencyDescriptor = new DefaultDependencyDescriptor(parent,
+                moduleRevisionId, getExternalModuleDependency(dependency).isForce(),
+                getExternalModuleDependency(dependency).isChanging(), getExternalModuleDependency(dependency).isTransitive());
+        addExcludesArtifactsAndDependencies(configuration, getExternalModuleDependency(dependency), dependencyDescriptor);
+        return dependencyDescriptor;
+    }
+
+    private ExternalModuleDependency getExternalModuleDependency(ModuleDependency dependency) {
+        return (ExternalModuleDependency) dependency;
+    }
+
+    public boolean canConvert(ModuleDependency dependency) {
+        return dependency instanceof ExternalModuleDependency;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ModuleDescriptorFactoryForClientModule.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ModuleDescriptorFactoryForClientModule.java
new file mode 100644
index 0000000..760cf15
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ModuleDescriptorFactoryForClientModule.java
@@ -0,0 +1,29 @@
+/*
+ * 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.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ModuleDependency;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ModuleDescriptorFactoryForClientModule {
+    ModuleDescriptor createModuleDescriptor(ModuleRevisionId moduleRevisionId, Set<ModuleDependency> dependencies);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactory.java
new file mode 100644
index 0000000..e2727f9
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactory.java
@@ -0,0 +1,79 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.util.WrapUtil;
+
+/**
+ * @author Hans Dockter
+ */
+public class ProjectDependencyDescriptorFactory extends AbstractDependencyDescriptorFactoryInternal {
+    public final static ProjectDependencyDescriptorStrategy IVY_FILE_DESCRIPTOR_STRATEGY =
+            new ProjectDependencyDescriptorStrategy() {
+                public ModuleRevisionId createModuleRevisionId(ProjectDependency dependency) {
+                    Module module = ((ProjectInternal) dependency.getDependencyProject()).getModule();
+                    return IvyUtil.createModuleRevisionId(module);
+                }
+                public boolean isChanging() {
+                    return false;
+                }
+            };
+
+    public final static ProjectDependencyDescriptorStrategy RESOLVE_DESCRIPTOR_STRATEGY =
+            new ProjectDependencyDescriptorStrategy() {
+                public ModuleRevisionId createModuleRevisionId(ProjectDependency dependency) {
+                    Module module = ((ProjectInternal) dependency.getDependencyProject()).getModule();
+                    return IvyUtil.createModuleRevisionId(module, WrapUtil.toMap(DependencyDescriptorFactory.PROJECT_PATH_KEY,
+                            dependency.getDependencyProject().getPath()));
+                }
+                public boolean isChanging() {
+                    return true;
+                }
+            };
+
+    private ProjectDependencyDescriptorStrategy projectDependencyDescriptorStrategy;
+
+    public ProjectDependencyDescriptorFactory(ExcludeRuleConverter excludeRuleConverter, ProjectDependencyDescriptorStrategy projectDependencyDescriptorStrategy) {
+        super(excludeRuleConverter);
+        this.projectDependencyDescriptorStrategy = projectDependencyDescriptorStrategy;
+    }
+
+    public DependencyDescriptor createDependencyDescriptor(ModuleDependency dependency, String configuration, ModuleDescriptor parent,
+                                                           ModuleRevisionId moduleRevisionId) {
+        DefaultDependencyDescriptor dependencyDescriptor = new DefaultDependencyDescriptor(parent,
+                moduleRevisionId, false, projectDependencyDescriptorStrategy.isChanging(), dependency.isTransitive());
+        addExcludesArtifactsAndDependencies(configuration, dependency, dependencyDescriptor);
+        return dependencyDescriptor;
+    }
+
+    public boolean canConvert(ModuleDependency dependency) {
+        return dependency instanceof ProjectDependency;
+    }
+
+    public ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
+        return projectDependencyDescriptorStrategy.createModuleRevisionId((ProjectDependency) dependency);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorStrategy.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorStrategy.java
new file mode 100644
index 0000000..3c7ac80
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorStrategy.java
@@ -0,0 +1,27 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ProjectDependency;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ProjectDependencyDescriptorStrategy {
+    ModuleRevisionId createModuleRevisionId(ProjectDependency dependency);
+    boolean isChanging();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifact.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifact.java
new file mode 100644
index 0000000..15846f8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifact.java
@@ -0,0 +1,40 @@
+/*
+ * 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.publish;
+
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.internal.tasks.DefaultTaskDependency;
+import org.gradle.api.tasks.TaskDependency;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractPublishArtifact implements PublishArtifact {
+    private TaskDependency taskDependency;
+
+    public AbstractPublishArtifact(Object... tasks) {
+        taskDependency = new DefaultTaskDependency();
+        ((DefaultTaskDependency) taskDependency).add(tasks);
+    }
+
+    public TaskDependency getBuildDependencies() {
+        return taskDependency;
+    }
+
+    public void setTaskDependency(TaskDependency taskDependency) {
+        this.taskDependency = taskDependency;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifact.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifact.java
new file mode 100644
index 0000000..e8d960e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifact.java
@@ -0,0 +1,97 @@
+/*
+ * 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.publish;
+
+import org.gradle.api.tasks.bundling.AbstractArchiveTask;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.util.Date;
+
+/**
+ * @author Hans Dockter
+ */
+public class ArchivePublishArtifact extends AbstractPublishArtifact {
+    private String name;
+    private String extension;
+    private String type;
+    private String classifier;
+    private Date date;
+    private File file;
+    
+    private AbstractArchiveTask archiveTask;
+
+    public ArchivePublishArtifact(AbstractArchiveTask archiveTask) {
+        super(archiveTask);
+        this.archiveTask = archiveTask;
+    }
+
+    public String getName() {
+        return GUtil.elvis(name, archiveTask.getBaseName() + (GUtil.isTrue(archiveTask.getAppendix()) ? "-" + archiveTask.getAppendix() : ""));
+    }
+
+    public String getExtension() {
+        return GUtil.elvis(extension, archiveTask.getExtension());
+    }
+
+    public String getType() {
+        return GUtil.elvis(type, archiveTask.getExtension());
+    }
+
+    public String getClassifier() {
+        return GUtil.elvis(classifier, archiveTask.getClassifier());
+    }
+
+    public File getFile() {
+        return GUtil.elvis(file, archiveTask.getArchivePath());
+    }
+
+    public Date getDate() {
+        return GUtil.elvis(date, new Date(archiveTask.getArchivePath().lastModified()));
+    }
+
+    public String toString() {
+        return String.format("ArchivePublishArtifact $s:%s:%s:%s", getName(), getType(), getExtension(), getClassifier());
+    }
+
+    public AbstractArchiveTask getArchiveTask() {
+        return archiveTask;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public void setExtension(String extension) {
+        this.extension = extension;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public void setClassifier(String classifier) {
+        this.classifier = classifier;
+    }
+
+    public void setDate(Date date) {
+        this.date = date;
+    }
+
+    public void setFile(File file) {
+        this.file = file;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultArtifactContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultArtifactContainer.java
new file mode 100644
index 0000000..4318c91
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultArtifactContainer.java
@@ -0,0 +1,47 @@
+/*
+ * 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.publish;
+
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.internal.artifacts.ArtifactContainer;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultArtifactContainer implements ArtifactContainer {
+    private Set<PublishArtifact> artifacts = new HashSet<PublishArtifact>();
+
+    public DefaultArtifactContainer() {
+    }
+    
+    public void addArtifacts(PublishArtifact... publishArtifacts) {
+        artifacts.addAll(Arrays.asList(publishArtifacts));
+    }
+
+    public Set<PublishArtifact> getArtifacts() {
+        return artifacts;
+    }
+
+    public Set<PublishArtifact> getArtifacts(Spec<PublishArtifact> spec) {
+        return new HashSet<PublishArtifact>(Specs.filterIterable(getArtifacts(), spec));
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifact.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifact.java
new file mode 100644
index 0000000..237ee3e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifact.java
@@ -0,0 +1,95 @@
+/*
+ * 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.api.internal.artifacts.publish;
+
+import java.io.File;
+import java.util.Date;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultPublishArtifact extends AbstractPublishArtifact {
+    private String name;
+    private String extension;
+    private String type;
+    private String classifier;
+    private Date date;
+    private File file;
+
+    public DefaultPublishArtifact(String name, String extension, String type,
+                                  String classifier, Date date, File file, Object... tasks) {
+        super(tasks);
+        this.name = name;
+        this.extension = extension;
+        this.type = type;
+        this.date = date;
+        this.classifier = classifier;
+        this.file = file;
+    }
+
+    public String toString() {
+        return String.format("DefaultPublishArtifact %s:%s:%s:%s", name, type, extension, classifier);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getExtension() {
+        return extension;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getClassifier() {
+        return classifier;
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public Date getDate() {
+        return date;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public void setExtension(String extension) {
+        this.extension = extension;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public void setClassifier(String classifier) {
+        this.classifier = classifier;
+    }
+
+    public void setDate(Date date) {
+        this.date = date;
+    }
+
+    public void setFile(File file) {
+        this.file = file;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultArtifactPomFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultArtifactPomFactory.java
new file mode 100644
index 0000000..9e28f4f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultArtifactPomFactory.java
@@ -0,0 +1,30 @@
+/*
+ * 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.publish.maven;
+
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.internal.artifacts.publish.maven.deploy.ArtifactPom;
+import org.gradle.api.internal.artifacts.publish.maven.deploy.ArtifactPomFactory;
+import org.gradle.api.internal.artifacts.publish.maven.deploy.DefaultArtifactPom;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultArtifactPomFactory implements ArtifactPomFactory {
+    public ArtifactPom createArtifactPom(MavenPom pom) {
+        return new DefaultArtifactPom(pom);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPom.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPom.java
new file mode 100644
index 0000000..98bf094
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPom.java
@@ -0,0 +1,240 @@
+/*
+ * 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.publish.maven;
+
+import groovy.lang.Closure;
+import org.apache.commons.io.IOUtils;
+import org.apache.maven.model.Dependency;
+import org.apache.maven.model.Model;
+import org.apache.maven.project.MavenProject;
+import org.codehaus.groovy.runtime.InvokerHelper;
+import org.gradle.api.Action;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.artifacts.maven.XmlProvider;
+import org.gradle.api.internal.artifacts.publish.maven.dependencies.PomDependenciesConverter;
+import org.gradle.api.internal.artifacts.publish.maven.pombuilder.CustomModelBuilder;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.listener.ListenerBroadcast;
+
+import java.io.*;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultMavenPom implements MavenPom {
+    private PomDependenciesConverter pomDependenciesConverter;
+    private FileResolver fileResolver;
+    private MavenProject mavenProject = new MavenProject();
+    private Conf2ScopeMappingContainer scopeMappings;
+    private ListenerBroadcast<Action> whenConfiguredActions = new ListenerBroadcast<Action>(Action.class);
+    private ListenerBroadcast<Action> withXmlActions = new ListenerBroadcast<Action>(Action.class);
+    private ConfigurationContainer configurations;
+
+    public DefaultMavenPom(ConfigurationContainer configurationContainer, Conf2ScopeMappingContainer scopeMappings, PomDependenciesConverter pomDependenciesConverter,
+                           FileResolver fileResolver) {
+        this.configurations = configurationContainer;
+        this.scopeMappings = scopeMappings;
+        this.pomDependenciesConverter = pomDependenciesConverter;
+        this.fileResolver = fileResolver;
+        mavenProject.setModelVersion("4.0.0");
+    }
+
+    public Conf2ScopeMappingContainer getScopeMappings() {
+        return scopeMappings;
+    }
+
+    public ConfigurationContainer getConfigurations() {
+        return configurations;
+    }
+
+    public DefaultMavenPom setConfigurations(ConfigurationContainer configurations) {
+        this.configurations = configurations;
+        return this;
+    }
+    
+    public DefaultMavenPom setGroupId(String groupId) {
+        getModel().setGroupId(groupId);
+        return this;
+    }
+
+    public String getGroupId() {
+        return getModel().getGroupId();
+    }
+
+    public DefaultMavenPom setArtifactId(String artifactId) {
+        getModel().setArtifactId(artifactId);
+        return this;
+    }
+
+    public String getArtifactId() {
+        return getModel().getArtifactId();
+    }
+
+    public DefaultMavenPom setDependencies(List<Dependency> dependencies) {
+        getModel().setDependencies(dependencies);
+        return this;
+    }
+
+    public List<Dependency> getDependencies() {
+        return getModel().getDependencies();
+    }
+
+    public DefaultMavenPom setName(String name) {
+        getModel().setName(name);
+        return this;
+    }
+
+    public String getName() {
+        return getModel().getName();
+    }
+
+    public DefaultMavenPom setVersion(String version) {
+        getModel().setVersion(version);
+        return this;
+    }
+
+    public String getVersion() {
+        return getModel().getVersion();
+    }
+
+    public String getPackaging() {
+        return getModel().getPackaging();
+    }
+
+    public DefaultMavenPom setPackaging(String packaging) {
+        getModel().setPackaging(packaging);
+        return this;
+    }
+
+    public DefaultMavenPom project(Closure cl) {
+        CustomModelBuilder pomBuilder = new CustomModelBuilder(getModel());
+        InvokerHelper.invokeMethod(pomBuilder, "project", cl);
+        return this;
+    }
+
+    public Model getModel() {
+        return mavenProject.getModel();
+    }
+
+    public DefaultMavenPom setModel(Model model) {
+        this.mavenProject = new MavenProject(model);
+        return this;
+    }
+
+    public MavenProject getMavenProject() {
+        return mavenProject;
+    }
+
+    public DefaultMavenPom setMavenProject(MavenProject mavenProject) {
+        this.mavenProject = mavenProject;
+        return this;
+    }
+
+    public List<Dependency> getGeneratedDependencies() {
+        if (configurations == null) {
+            return Collections.emptyList();
+        }
+        return pomDependenciesConverter.convert(getScopeMappings(), configurations.getAll());
+    }
+
+    public DefaultMavenPom getEffectivePom() {
+        DefaultMavenPom effectivePom = new DefaultMavenPom(null, this.scopeMappings, pomDependenciesConverter, fileResolver);
+        try {
+            effectivePom.setMavenProject((MavenProject) mavenProject.clone());
+        } catch (CloneNotSupportedException e) {
+            throw new RuntimeException(e);
+        }
+        effectivePom.getDependencies().addAll(getGeneratedDependencies());
+        effectivePom.withXmlActions = withXmlActions;
+        whenConfiguredActions.getSource().execute(effectivePom);
+        return effectivePom;
+    }
+
+    public PomDependenciesConverter getPomDependenciesConverter() {
+        return pomDependenciesConverter;
+    }
+
+    public FileResolver getFileResolver() {
+        return fileResolver;
+    }
+
+    public DefaultMavenPom setFileResolver(FileResolver fileResolver) {
+        this.fileResolver = fileResolver;
+        return this;
+    }
+
+    public DefaultMavenPom writeTo(final Writer pomWriter) {
+        getEffectivePom().writeNonEffectivePom(pomWriter);
+        return this;
+    }
+
+    public DefaultMavenPom writeTo(Object path) {
+        try {
+            File file = fileResolver.resolve(path);
+            if (file.getParentFile() != null) {
+                file.getParentFile().mkdirs();
+            }
+            return writeTo(new FileWriter(file));
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    private void writeNonEffectivePom(final Writer pomWriter) {
+        try {
+            final StringWriter stringWriter = new StringWriter();
+            mavenProject.writeModel(stringWriter);
+            final StringBuilder stringBuilder = new StringBuilder(stringWriter.toString());
+            withXmlActions.getSource().execute(new XmlProvider() {
+                public StringBuilder asString() {
+                    return stringBuilder;
+                }
+            });
+            IOUtils.write(stringBuilder.toString(), pomWriter);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        } finally {
+            IOUtils.closeQuietly(pomWriter);
+        }
+    }
+
+    public DefaultMavenPom whenConfigured(final Closure closure) {
+        whenConfiguredActions.add("execute", closure);
+        return this;
+    }
+
+    public DefaultMavenPom whenConfigured(final Action<MavenPom> action) {
+        whenConfiguredActions.add(action);
+        return this;
+    }
+
+    public DefaultMavenPom withXml(final Closure closure) {
+        withXmlActions.add("execute", closure);
+        return this;
+    }
+
+    public DefaultMavenPom withXml(final Action<XmlProvider> action) {
+        withXmlActions.add(action);
+        return this;
+    }
+
+
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPomFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPomFactory.java
new file mode 100644
index 0000000..d074740
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPomFactory.java
@@ -0,0 +1,47 @@
+/*
+ * 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.publish.maven;
+
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.internal.artifacts.publish.maven.dependencies.DefaultConf2ScopeMappingContainer;
+import org.gradle.api.internal.artifacts.publish.maven.dependencies.PomDependenciesConverter;
+import org.gradle.api.internal.file.FileResolver;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultMavenPomFactory implements MavenPomFactory {
+    private ConfigurationContainer configurationContainer;
+    private Conf2ScopeMappingContainer conf2ScopeMappingContainer;
+    private PomDependenciesConverter pomDependenciesConverter;
+    private FileResolver fileResolver;
+
+
+    public DefaultMavenPomFactory(ConfigurationContainer configurationContainer, Conf2ScopeMappingContainer conf2ScopeMappingContainer, PomDependenciesConverter pomDependenciesConverter,
+                                  FileResolver fileResolver) {
+        this.configurationContainer = configurationContainer;
+        this.conf2ScopeMappingContainer = conf2ScopeMappingContainer;
+        this.pomDependenciesConverter = pomDependenciesConverter;
+        this.fileResolver = fileResolver;
+    }
+
+    public MavenPom createMavenPom() {
+        return new DefaultMavenPom(configurationContainer,
+                new DefaultConf2ScopeMappingContainer(conf2ScopeMappingContainer.getMappings()), pomDependenciesConverter, fileResolver);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/MavenPomFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/MavenPomFactory.java
new file mode 100644
index 0000000..f53b20a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/MavenPomFactory.java
@@ -0,0 +1,25 @@
+/*
+ * 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.publish.maven;
+
+import org.gradle.api.artifacts.maven.MavenPom;
+
+/**
+ * @author Hans Dockter
+ */
+public interface MavenPomFactory {
+    MavenPom createMavenPom();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/MavenPomMetaInfoProvider.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/MavenPomMetaInfoProvider.java
new file mode 100644
index 0000000..961ec24
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/MavenPomMetaInfoProvider.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.api.internal.artifacts.publish.maven;
+
+import java.io.File;
+
+public interface MavenPomMetaInfoProvider {
+    File getMavenPomDir();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultConf2ScopeMappingContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultConf2ScopeMappingContainer.java
new file mode 100644
index 0000000..8464b10
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultConf2ScopeMappingContainer.java
@@ -0,0 +1,115 @@
+/*
+ * 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.publish.maven.dependencies;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.maven.Conf2ScopeMapping;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.util.WrapUtil;
+
+import java.util.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultConf2ScopeMappingContainer implements Conf2ScopeMappingContainer {
+    private Map<Configuration, Conf2ScopeMapping> mappings = new HashMap<Configuration, Conf2ScopeMapping>();
+
+    private boolean skipUnmappedConfs = true;
+
+    public DefaultConf2ScopeMappingContainer() {
+    }
+
+    public DefaultConf2ScopeMappingContainer(Map<Configuration, Conf2ScopeMapping> mappings) {
+        this.mappings.putAll(mappings);
+    }
+
+    public Conf2ScopeMapping getMapping(Collection<Configuration> configurations) {
+        Set<Conf2ScopeMapping> result = getMappingsWithHighestPriority(configurations);
+        if (result.size() > 1) {
+            throw new InvalidUserDataException(
+                    "The configuration to scope mapping is not unique. The following configurations "
+                            + "have the same priority: " + result);
+        }
+        return result.size() == 0 ? null : result.iterator().next();
+    }
+
+    private Set<Conf2ScopeMapping> getMappingsWithHighestPriority(Collection<Configuration> configurations) {
+        Integer lastPriority = null;
+        Set<Conf2ScopeMapping> result = new HashSet<Conf2ScopeMapping>();
+        for (Conf2ScopeMapping conf2ScopeMapping : getMappingsForConfigurations(configurations)) {
+            Integer thisPriority = conf2ScopeMapping.getPriority();
+            if (lastPriority != null && lastPriority.equals(thisPriority)) {
+                result.add(conf2ScopeMapping);
+            } else if (lastPriority == null || (thisPriority != null && lastPriority < thisPriority)) {
+                lastPriority = thisPriority;
+                result = WrapUtil.toSet(conf2ScopeMapping);
+            }
+        }
+        return result;
+    }
+
+    private List<Conf2ScopeMapping> getMappingsForConfigurations(Collection<Configuration> configurations) {
+        List<Conf2ScopeMapping> existingMappings = new ArrayList<Conf2ScopeMapping>();
+        for (Configuration configuration : configurations) {
+            if (mappings.get(configuration) != null) {
+                existingMappings.add(mappings.get(configuration));
+            } else {
+                existingMappings.add(new Conf2ScopeMapping(null, configuration, null));
+            }
+        }
+        return existingMappings;
+    }
+
+    public Conf2ScopeMappingContainer addMapping(int priority, Configuration configuration, String scope) {
+        mappings.put(configuration, new Conf2ScopeMapping(priority, configuration, scope));
+        return this;
+    }
+
+    public Map<Configuration, Conf2ScopeMapping> getMappings() {
+        return mappings;
+    }
+
+    public boolean isSkipUnmappedConfs() {
+        return skipUnmappedConfs;
+    }
+
+    public void setSkipUnmappedConfs(boolean skipUnmappedConfs) {
+        this.skipUnmappedConfs = skipUnmappedConfs;
+    }
+
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultConf2ScopeMappingContainer that = (DefaultConf2ScopeMappingContainer) o;
+
+        if (!mappings.equals(that.mappings)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    public int hashCode() {
+        return mappings.hashCode();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultExcludeRuleConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultExcludeRuleConverter.java
new file mode 100644
index 0000000..c3704bb
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultExcludeRuleConverter.java
@@ -0,0 +1,39 @@
+/*
+ * 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.publish.maven.dependencies;
+
+import org.apache.maven.model.Exclusion;
+import org.gradle.api.artifacts.ExcludeRule;
+
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultExcludeRuleConverter implements ExcludeRuleConverter {
+    public Exclusion convert(ExcludeRule excludeRule) {
+        if (isConvertable(excludeRule)) {
+            Exclusion exclusion = new Exclusion();
+            exclusion.setGroupId(excludeRule.getExcludeArgs().get(ExcludeRule.GROUP_KEY));
+            exclusion.setArtifactId(excludeRule.getExcludeArgs().get(ExcludeRule.MODULE_KEY));
+            return exclusion;
+        }
+        return null;
+    }
+
+    private boolean isConvertable(ExcludeRule excludeRule) {
+        return excludeRule.getExcludeArgs().containsKey(ExcludeRule.GROUP_KEY) && excludeRule.getExcludeArgs().containsKey(ExcludeRule.MODULE_KEY);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultPomDependenciesConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultPomDependenciesConverter.java
new file mode 100644
index 0000000..29e2347
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultPomDependenciesConverter.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.api.internal.artifacts.publish.maven.dependencies;
+
+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.maven.Conf2ScopeMapping;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+
+import java.util.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultPomDependenciesConverter implements PomDependenciesConverter {
+    private ExcludeRuleConverter excludeRuleConverter;
+
+    public DefaultPomDependenciesConverter(ExcludeRuleConverter excludeRuleConverter) {
+        this.excludeRuleConverter = excludeRuleConverter;
+    }
+
+    public List<org.apache.maven.model.Dependency> convert(Conf2ScopeMappingContainer conf2ScopeMappingContainer, Set<Configuration> configurations) {
+        Map<ModuleDependency, String> dependenciesMap = createDependencyToScopeMap(conf2ScopeMappingContainer, configurations);
+        List<org.apache.maven.model.Dependency> mavenDependencies = new ArrayList<org.apache.maven.model.Dependency>();
+        for (ModuleDependency dependency : dependenciesMap.keySet()) {
+            if (dependency.getArtifacts().size() == 0) {
+                addFromDependencyDescriptor(mavenDependencies, dependency, dependenciesMap.get(dependency));
+            } else {
+                addFromArtifactDescriptor(mavenDependencies, dependency, dependenciesMap.get(dependency));
+            }
+        }
+        return mavenDependencies;
+    }
+
+    private Map<ModuleDependency, String> createDependencyToScopeMap(Conf2ScopeMappingContainer conf2ScopeMappingContainer, Set<Configuration> configurations) {
+        Map<ModuleDependency, Set<Configuration>> dependencyToConfigurations = createDependencyToConfigurationsMap(configurations);
+        Map<ModuleDependency, String> dependencyToScope = new HashMap<ModuleDependency, String>();
+        for (ModuleDependency dependency : dependencyToConfigurations.keySet()) {
+            Conf2ScopeMapping conf2ScopeDependencyMapping = conf2ScopeMappingContainer.getMapping(dependencyToConfigurations.get(dependency));
+            if (!useScope(conf2ScopeMappingContainer, conf2ScopeDependencyMapping)) {
+                continue;
+            }
+            dependencyToScope.put(findDependency(dependency, conf2ScopeDependencyMapping.getConfiguration()),
+                    conf2ScopeDependencyMapping.getScope());
+        }
+        return dependencyToScope;
+    }
+
+    private ModuleDependency findDependency(ModuleDependency dependency, Configuration configuration) {
+        for (ModuleDependency configurationDependency : configuration.getDependencies(ModuleDependency.class)) {
+            if (dependency.equals(configurationDependency)) {
+                return configurationDependency;
+            }
+        }
+        throw new GradleException("Dependency could not be found. We should never get here!");
+    }
+
+    private boolean useScope(Conf2ScopeMappingContainer conf2ScopeMappingContainer, Conf2ScopeMapping conf2ScopeMapping) {
+        return conf2ScopeMapping.getScope() != null || !conf2ScopeMappingContainer.isSkipUnmappedConfs();
+    }
+
+    private Map<ModuleDependency, Set<Configuration>> createDependencyToConfigurationsMap(Set<Configuration> configurations) {
+        Map<ModuleDependency, Set<Configuration>> dependencySetMap = new HashMap<ModuleDependency, Set<Configuration>>();
+        for (Configuration configuration : configurations) {
+            for (ModuleDependency dependency : configuration.getDependencies(ModuleDependency.class)) {
+                if (dependencySetMap.get(dependency) == null) {
+                    dependencySetMap.put(dependency, new HashSet<Configuration>());
+                }
+                dependencySetMap.get(dependency).add(configuration);
+            }
+        }
+        return dependencySetMap;
+    }
+
+    private void addFromArtifactDescriptor(List<Dependency> mavenDependencies, ModuleDependency dependency, String scope) {
+        for (DependencyArtifact artifact : dependency.getArtifacts()) {
+            mavenDependencies.add(createMavenDependencyFromArtifactDescriptor(dependency, artifact, scope));
+        }
+    }
+
+    private void addFromDependencyDescriptor(List<Dependency> mavenDependencies, ModuleDependency dependency, String scope) {
+        mavenDependencies.add(createMavenDependencyFromDependencyDescriptor(dependency, scope));
+    }
+
+    private Dependency createMavenDependencyFromArtifactDescriptor(ModuleDependency dependency, DependencyArtifact artifact, String scope) {
+        return createMavenDependency(dependency, artifact.getName(), artifact.getType(), scope, artifact.getClassifier());
+    }
+
+    private Dependency createMavenDependencyFromDependencyDescriptor(ModuleDependency dependency, String scope) {
+        return createMavenDependency(dependency, dependency.getName(), null, scope, null);
+    }
+
+    private Dependency createMavenDependency(ModuleDependency dependency, String name, String type, String scope, String classifier) {
+        Dependency mavenDependency =  new Dependency();
+        mavenDependency.setGroupId(dependency.getGroup());
+        mavenDependency.setArtifactId(name);
+        mavenDependency.setVersion(dependency.getVersion());
+        mavenDependency.setType(type);
+        mavenDependency.setScope(scope);
+        mavenDependency.setOptional(false);
+        mavenDependency.setClassifier(classifier);
+        mavenDependency.setExclusions(getExclusions(dependency));
+        return mavenDependency;
+    }
+
+    private List<Exclusion> getExclusions(ModuleDependency dependency) {
+        List<Exclusion> mavenExclusions = new ArrayList<Exclusion>();
+        for (ExcludeRule excludeRule : dependency.getExcludeRules()) {
+            Exclusion mavenExclusion = excludeRuleConverter.convert(excludeRule);
+            if (mavenExclusion != null) {
+                mavenExclusions.add(mavenExclusion);
+            }
+        }
+        return mavenExclusions;
+    }
+
+    public ExcludeRuleConverter getExcludeRuleConverter() {
+        return excludeRuleConverter;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/ExcludeRuleConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/ExcludeRuleConverter.java
new file mode 100644
index 0000000..78c247b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/ExcludeRuleConverter.java
@@ -0,0 +1,27 @@
+/*
+ * 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.publish.maven.dependencies;
+
+import org.apache.maven.model.Exclusion;
+import org.gradle.api.artifacts.ExcludeRule;
+
+
+/**
+ * @author Hans Dockter
+ */
+public interface ExcludeRuleConverter {
+    Exclusion convert(ExcludeRule excludeRule);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/PomDependenciesConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/PomDependenciesConverter.java
new file mode 100644
index 0000000..43cb885
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/PomDependenciesConverter.java
@@ -0,0 +1,29 @@
+/*
+ * 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.publish.maven.dependencies;
+
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public interface PomDependenciesConverter {
+    public List<org.apache.maven.model.Dependency> convert(Conf2ScopeMappingContainer conf2ScopeMappingContainer, Set<Configuration> configurations);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/PomDependenciesWriter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/PomDependenciesWriter.java
new file mode 100644
index 0000000..da126ac
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/PomDependenciesWriter.java
@@ -0,0 +1,31 @@
+/*
+ * 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.publish.maven.dependencies;
+
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.maven.MavenPom;
+
+import java.io.PrintWriter;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public interface PomDependenciesWriter {
+    String DEPENDENCIES = "dependencies";
+
+    void convert(MavenPom pom, Set<Configuration> configurations, PrintWriter printWriter);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/AbstractMavenResolver.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/AbstractMavenResolver.java
new file mode 100644
index 0000000..faa37f4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/AbstractMavenResolver.java
@@ -0,0 +1,274 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.cache.DefaultRepositoryCacheManager;
+import org.apache.ivy.core.cache.RepositoryCacheManager;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadReport;
+import org.apache.ivy.core.resolve.DownloadOptions;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.core.search.ModuleEntry;
+import org.apache.ivy.core.search.OrganisationEntry;
+import org.apache.ivy.core.search.RevisionEntry;
+import org.apache.ivy.plugins.namespace.Namespace;
+import org.apache.ivy.plugins.resolver.ResolverSettings;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.apache.maven.artifact.ant.AttachedArtifact;
+import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
+import org.apache.maven.artifact.ant.Pom;
+import org.apache.maven.settings.Settings;
+import org.apache.tools.ant.Project;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.artifacts.maven.MavenResolver;
+import org.gradle.api.artifacts.maven.PomFilterContainer;
+import org.gradle.api.artifacts.maven.PublishFilter;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.util.AntUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractMavenResolver implements MavenResolver {
+    protected final static String SETTINGS_XML = "<settings/>"; 
+
+    private String name;
+    
+    private ArtifactPomContainer artifactPomContainer;
+
+    private PomFilterContainer pomFilterContainer;
+
+    private Settings settings;
+
+    private LoggingManagerInternal loggingManager;
+
+    public AbstractMavenResolver(String name, PomFilterContainer pomFilterContainer, ArtifactPomContainer artifactPomContainer, LoggingManagerInternal loggingManager) {
+        this.name = name;
+        this.pomFilterContainer = pomFilterContainer;
+        this.artifactPomContainer = artifactPomContainer;
+        this.loggingManager = loggingManager;
+    }
+
+    protected abstract InstallDeployTaskSupport createPreConfiguredTask(Project project);
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+    
+    public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data) throws ParseException {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public ArtifactDownloadReport download(ArtifactOrigin artifact, DownloadOptions options) {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public boolean exists(Artifact artifact) {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public ArtifactOrigin locate(Artifact artifact) {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public void reportFailure() {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public void reportFailure(Artifact art) {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public String[] listTokenValues(String token, Map otherTokenValues) {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public Map[] listTokenValues(String[] tokens, Map criteria) {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public OrganisationEntry[] listOrganisations() {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public ModuleEntry[] listModules(OrganisationEntry org) {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public RevisionEntry[] listRevisions(ModuleEntry module) {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public Namespace getNamespace() {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+    public void dumpSettings() {
+        throw new UnsupportedOperationException("A MavenPublishOnlyResolver can only publish artifacts.");
+    }
+
+
+    public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
+        if (isIgnorable(artifact)) {
+            return;
+        }
+        getArtifactPomContainer().addArtifact(artifact, src);
+    }
+
+    private boolean isIgnorable(Artifact artifact) {
+        return artifact.getType().equals("ivy");
+    }
+
+    public void beginPublishTransaction(ModuleRevisionId module, boolean overwrite) throws IOException {
+        // do nothing
+    }
+
+    public void abortPublishTransaction() throws IOException {
+        // do nothing
+    }
+
+    public void commitPublishTransaction() throws IOException {
+        InstallDeployTaskSupport installDeployTaskSupport = createPreConfiguredTask(AntUtil.createProject());
+        Set<DeployableFilesInfo> deployableFilesInfos = getArtifactPomContainer().createDeployableFilesInfos();
+        File emptySettingsXml = createEmptyMavenSettingsXml();
+        installDeployTaskSupport.setSettingsFile(emptySettingsXml);
+        for (DeployableFilesInfo deployableFilesInfo : deployableFilesInfos) {
+            addPomAndArtifact(installDeployTaskSupport, deployableFilesInfo);
+            execute(installDeployTaskSupport);
+        }
+        emptySettingsXml.delete();
+        settings = ((CustomInstallDeployTaskSupport) installDeployTaskSupport).getSettings();
+    }
+
+    private void execute(InstallDeployTaskSupport deployTask) {
+        loggingManager.captureStandardOutput(LogLevel.INFO).start();
+        try {
+            deployTask.execute();
+        } finally {
+            loggingManager.stop();
+        }
+    }
+
+    private void addPomAndArtifact(InstallDeployTaskSupport installOrDeployTask, DeployableFilesInfo deployableFilesInfo) {
+        Pom pom = new Pom();
+        pom.setProject(installOrDeployTask.getProject());
+        pom.setFile(deployableFilesInfo.getPomFile());
+        installOrDeployTask.addPom(pom);
+        installOrDeployTask.setFile(deployableFilesInfo.getArtifactFile());
+        for (ClassifierArtifact classifierArtifact : deployableFilesInfo.getClassifierArtifacts()) {
+            AttachedArtifact attachedArtifact = installOrDeployTask.createAttach();
+            attachedArtifact.setClassifier(classifierArtifact.getClassifier());
+            attachedArtifact.setFile(classifierArtifact.getFile());
+            attachedArtifact.setType(classifierArtifact.getType());
+        }
+    }
+
+    private File createEmptyMavenSettingsXml() {
+        try {
+            File settingsXml = File.createTempFile("gradle_empty_settings", ".xml");
+            FileUtils.writeStringToFile(settingsXml, SETTINGS_XML);
+            return settingsXml;
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void setSettings(ResolverSettings settings) {
+        // do nothing
+    }
+
+    public RepositoryCacheManager getRepositoryCacheManager() {
+        return new DefaultRepositoryCacheManager();
+    }
+
+    public ArtifactPomContainer getArtifactPomContainer() {
+        return artifactPomContainer;
+    }
+
+    public void setArtifactPomContainer(ArtifactPomContainer artifactPomContainer) {
+        this.artifactPomContainer = artifactPomContainer;
+    }
+
+    public Settings getSettings() {
+        return settings;
+    }
+
+    public PublishFilter getFilter() {
+        return pomFilterContainer.getFilter();
+    }
+
+    public void setFilter(PublishFilter defaultFilter) {
+        pomFilterContainer.setFilter(defaultFilter);
+    }
+
+    public MavenPom getPom() {
+        return pomFilterContainer.getPom();
+    }
+
+    public void setPom(MavenPom defaultPom) {
+        pomFilterContainer.setPom(defaultPom);
+    }
+
+    public MavenPom addFilter(String name, PublishFilter publishFilter) {
+        return pomFilterContainer.addFilter(name, publishFilter);
+    }
+
+    public PublishFilter filter(String name) {
+        return pomFilterContainer.filter(name);
+    }
+
+    public MavenPom pom(String name) {
+        return pomFilterContainer.pom(name);
+    }
+
+    public Iterable<PomFilter> getActivePomFilters() {
+        return pomFilterContainer.getActivePomFilters();
+    }
+
+    public PomFilterContainer getPomFilterContainer() {
+        return pomFilterContainer;
+    }
+
+    public void setPomFilterContainer(PomFilterContainer pomFilterContainer) {
+        this.pomFilterContainer = pomFilterContainer;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ArtifactPom.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ArtifactPom.java
new file mode 100644
index 0000000..48abb5e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ArtifactPom.java
@@ -0,0 +1,39 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.artifacts.maven.MavenPom;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ArtifactPom {
+    Artifact getArtifact();
+
+    File getArtifactFile();
+
+    MavenPom getPom();
+
+    void addArtifact(Artifact artifact, File src);
+
+    Set<ClassifierArtifact> getClassifiers();
+
+    void writePom(File pomFile);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ArtifactPomContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ArtifactPomContainer.java
new file mode 100644
index 0000000..cea01cc
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ArtifactPomContainer.java
@@ -0,0 +1,30 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ArtifactPomContainer {
+    void addArtifact(Artifact artifact, File src);
+
+    Set<DeployableFilesInfo> createDeployableFilesInfos();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ArtifactPomFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ArtifactPomFactory.java
new file mode 100644
index 0000000..4efdc58
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ArtifactPomFactory.java
@@ -0,0 +1,25 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.gradle.api.artifacts.maven.MavenPom;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ArtifactPomFactory {
+    ArtifactPom createArtifactPom(MavenPom pom);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenDeployer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenDeployer.java
new file mode 100644
index 0000000..3f706b7
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenDeployer.java
@@ -0,0 +1,127 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.apache.maven.artifact.ant.DeployTask;
+import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
+import org.apache.maven.artifact.ant.RemoteRepository;
+import org.apache.tools.ant.Project;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.PlexusContainerException;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.maven.MavenDeployer;
+import org.gradle.api.artifacts.maven.PomFilterContainer;
+import org.gradle.logging.LoggingManagerInternal;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public class BaseMavenDeployer extends AbstractMavenResolver implements MavenDeployer {
+    private RemoteRepository remoteRepository;
+
+    private RemoteRepository remoteSnapshotRepository;
+
+    private DeployTaskFactory deployTaskFactory = new DefaultDeployTaskFactory();
+
+    private Configuration configuration;
+
+    // todo remove this property once configuration can handle normal file system dependencies
+    private List<File> protocolProviderJars = new ArrayList<File>();
+
+    private boolean uniqueVersion = true;
+
+    public BaseMavenDeployer(String name, PomFilterContainer pomFilterContainer, ArtifactPomContainer artifactPomContainer, LoggingManagerInternal loggingManager) {
+        super(name, pomFilterContainer, artifactPomContainer, loggingManager);
+    }
+
+    protected InstallDeployTaskSupport createPreConfiguredTask(Project project) {
+        CustomDeployTask deployTask = deployTaskFactory.createDeployTask();
+        deployTask.setProject(project);
+        deployTask.setUniqueVersion(isUniqueVersion());
+        addProtocolProvider(deployTask);
+        addRemoteRepositories(deployTask);
+        return deployTask;
+    }
+
+    private void addProtocolProvider(CustomDeployTask deployTask) {
+        PlexusContainer plexusContainer = deployTask.getContainer();
+        for (File wagonProviderJar : getJars()) {
+            try {
+                plexusContainer.addJarResource(wagonProviderJar);
+            } catch (PlexusContainerException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    private List<File> getJars() {
+        return configuration != null ? new ArrayList(configuration.resolve()) : protocolProviderJars;
+    }
+
+    private void addRemoteRepositories(DeployTask deployTask) {
+        deployTask.addRemoteRepository(remoteRepository);
+        deployTask.addRemoteSnapshotRepository(remoteSnapshotRepository);
+    }
+
+    public RemoteRepository getRepository() {
+        return remoteRepository;
+    }
+
+    public void setRepository(RemoteRepository remoteRepository) {
+        this.remoteRepository = remoteRepository;
+    }
+
+    public RemoteRepository getSnapshotRepository() {
+        return remoteSnapshotRepository;
+    }
+
+    public void setSnapshotRepository(RemoteRepository remoteSnapshotRepository) {
+        this.remoteSnapshotRepository = remoteSnapshotRepository;
+    }
+
+    public DeployTaskFactory getDeployTaskFactory() {
+        return deployTaskFactory;
+    }
+
+    public void setDeployTaskFactory(DeployTaskFactory deployTaskFactory) {
+        this.deployTaskFactory = deployTaskFactory;
+    }
+
+    public void addProtocolProviderJars(Collection<File> jars) {
+        protocolProviderJars.addAll(jars);
+    }
+
+    public Configuration getConfiguration() {
+        return configuration;
+    }
+
+    public void setConfiguration(Configuration configuration) {
+        this.configuration = configuration;
+    }
+
+    public boolean isUniqueVersion() {
+        return uniqueVersion;
+    }
+
+    public void setUniqueVersion(boolean uniqueVersion) {
+        this.uniqueVersion = uniqueVersion;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenInstaller.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenInstaller.java
new file mode 100644
index 0000000..53f2df0
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenInstaller.java
@@ -0,0 +1,47 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
+import org.apache.maven.artifact.ant.InstallTask;
+import org.apache.tools.ant.Project;
+import org.gradle.api.artifacts.maven.PomFilterContainer;
+import org.gradle.logging.LoggingManagerInternal;
+
+/**
+ * @author Hans Dockter
+ */
+public class BaseMavenInstaller extends AbstractMavenResolver {
+    private InstallTaskFactory installTaskFactory = new DefaultInstallTaskFactory();
+
+    public BaseMavenInstaller(String name, PomFilterContainer pomFilterContainer, ArtifactPomContainer artifactPomContainer, LoggingManagerInternal loggingManager) {
+        super(name, pomFilterContainer, artifactPomContainer, loggingManager);
+    }
+
+    protected InstallDeployTaskSupport createPreConfiguredTask(Project project) {
+        InstallTask installTask = installTaskFactory.createInstallTask();
+        installTask.setProject(project);
+        return installTask;
+    }
+
+    public InstallTaskFactory getInstallTaskFactory() {
+        return installTaskFactory;
+    }
+
+    public void setInstallTaskFactory(InstallTaskFactory installTaskFactory) {
+        this.installTaskFactory = installTaskFactory;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BasePomFilterContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BasePomFilterContainer.java
new file mode 100644
index 0000000..bb770c2
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BasePomFilterContainer.java
@@ -0,0 +1,114 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.artifacts.maven.PomFilterContainer;
+import org.gradle.api.artifacts.maven.PublishFilter;
+import org.gradle.api.internal.artifacts.publish.maven.MavenPomFactory;
+import org.gradle.util.WrapUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class BasePomFilterContainer implements PomFilterContainer {
+    private Map<String, PomFilter> pomFilters = new HashMap<String, PomFilter>();
+
+    private PomFilter defaultPomFilter;
+
+    private MavenPomFactory mavenPomFactory;
+
+    public BasePomFilterContainer(MavenPomFactory mavenPomFactory) {
+        this.mavenPomFactory = mavenPomFactory;
+    }
+
+    public PublishFilter getFilter() {
+        return getDefaultPomFilter().getFilter();
+    }
+
+    public void setFilter(PublishFilter defaultFilter) {
+        getDefaultPomFilter().setFilter(defaultFilter);
+    }
+
+    public MavenPom getPom() {
+        return getDefaultPomFilter().getPomTemplate();
+    }
+
+    public void setPom(MavenPom defaultPom) {
+        getDefaultPomFilter().setPomTemplate(defaultPom);
+    }
+
+    public MavenPom addFilter(String name, PublishFilter publishFilter) {
+        if (name == null || publishFilter == null) {
+            throw new InvalidUserDataException("Name and Filter must not be null.");
+        }
+        MavenPom pom = mavenPomFactory.createMavenPom();
+        pomFilters.put(name, new DefaultPomFilter(name, pom, publishFilter));
+        return pom;
+    }
+
+    public PublishFilter filter(String name) {
+        if (name == null) {
+            throw new InvalidUserDataException("Name must not be null.");
+        }
+        return pomFilters.get(name).getFilter();
+    }
+
+    public MavenPom pom(String name) {
+        if (name == null) {
+            throw new InvalidUserDataException("Name must not be null.");
+        }
+        return pomFilters.get(name).getPomTemplate();
+    }
+
+    public Iterable<PomFilter> getActivePomFilters() {
+        Iterable<PomFilter> activeArtifactPoms;
+        if (pomFilters.size() == 0 && getDefaultPomFilter() != null) {
+            activeArtifactPoms = WrapUtil.toSet(getDefaultPomFilter());
+        } else {
+            activeArtifactPoms = pomFilters.values();
+        }
+        return activeArtifactPoms;
+    }
+
+    public MavenPomFactory getMavenPomFactory() {
+        return mavenPomFactory;
+    }
+
+    public PomFilter getDefaultPomFilter() {
+        if (defaultPomFilter == null) {
+            defaultPomFilter = new DefaultPomFilter(PomFilterContainer.DEFAULT_ARTIFACT_POM_NAME, mavenPomFactory.createMavenPom(),
+                PublishFilter.ALWAYS_ACCEPT);
+        }
+        return defaultPomFilter;
+    }
+
+    public void setDefaultPomFilter(PomFilter defaultPomFilter) {
+        this.defaultPomFilter = defaultPomFilter;
+    }
+
+    public Map<String, PomFilter> getPomFilters() {
+        return pomFilters;
+    }
+
+    protected BasePomFilterContainer newInstance() {
+        return new BasePomFilterContainer(mavenPomFactory);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ClassifierArtifact.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ClassifierArtifact.java
new file mode 100644
index 0000000..1d14f46
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/ClassifierArtifact.java
@@ -0,0 +1,71 @@
+/*
+ * 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.publish.maven.deploy;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class ClassifierArtifact {
+    private String classifier;
+    private String type;
+    private File file;
+
+    public ClassifierArtifact(String classifier, String type, File file) {
+        this.classifier = classifier;
+        this.type = type;
+        this.file = file;
+    }
+
+    public String getClassifier() {
+        return classifier;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        ClassifierArtifact that = (ClassifierArtifact) o;
+
+        if (classifier != null ? !classifier.equals(that.classifier) : that.classifier != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = classifier != null ? classifier.hashCode() : 0;
+        result = 31 * result + (type != null ? type.hashCode() : 0);
+        result = 31 * result + (file != null ? file.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/CustomDeployTask.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/CustomDeployTask.java
new file mode 100644
index 0000000..76cc474
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/CustomDeployTask.java
@@ -0,0 +1,44 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.apache.maven.artifact.ant.DeployTask;
+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.
+ *
+ * @author Hans Dockter
+ */
+public class CustomDeployTask extends DeployTask implements CustomInstallDeployTaskSupport {
+    @Override
+    public synchronized Settings getSettings() {
+        return super.getSettings();
+    }
+    
+    @Override
+    public synchronized PlexusContainer getContainer() {
+        return super.getContainer();
+    }
+
+    @Override
+    public void doExecute() {
+        LoggingHelper.injectLogger(getContainer(), getProject());
+        super.doExecute();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/CustomInstallDeployTaskSupport.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/CustomInstallDeployTaskSupport.java
new file mode 100644
index 0000000..8351a0e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/CustomInstallDeployTaskSupport.java
@@ -0,0 +1,29 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.apache.maven.settings.Settings;
+import org.apache.maven.artifact.ant.AttachedArtifact;
+import org.apache.tools.ant.Project;
+
+/**
+ * @author Hans Dockter
+ */
+public interface CustomInstallDeployTaskSupport {
+    Settings getSettings();
+    Project getProject();
+    AttachedArtifact createAttach();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/CustomInstallTask.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/CustomInstallTask.java
new file mode 100644
index 0000000..4ff001a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/CustomInstallTask.java
@@ -0,0 +1,37 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.apache.maven.artifact.ant.InstallTask;
+import org.apache.maven.settings.Settings;
+
+/**
+ * @author Hans Dockter
+ */
+public class CustomInstallTask extends InstallTask implements CustomInstallDeployTaskSupport {
+    @Override
+    public synchronized Settings getSettings() {
+        return super.getSettings();   
+    }
+
+    @Override
+    public void doExecute() {
+        LoggingHelper.injectLogger(getContainer(), getProject());
+        super.doExecute();
+    }
+
+
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPom.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPom.java
new file mode 100644
index 0000000..cdbee69
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPom.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.api.internal.artifacts.publish.maven.deploy;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.maven.project.MavenProject;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.maven.MavenPom;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultArtifactPom implements ArtifactPom {
+    private final MavenPom pom;
+
+    private Artifact artifact;
+
+    private File artifactFile;
+
+    private final Set<ClassifierArtifact> classifiers = new HashSet<ClassifierArtifact>();
+
+    public DefaultArtifactPom(MavenPom pom) {
+        this.pom = pom;
+    }
+
+    public MavenPom getPom() {
+        return pom;
+    }
+
+    public File getArtifactFile() {
+        return artifactFile;
+    }
+
+    public Artifact getArtifact() {
+        return artifact;
+    }
+
+    public Set<ClassifierArtifact> getClassifiers() {
+        return Collections.unmodifiableSet(classifiers);
+    }
+
+    public void writePom(File pomFile) {
+        try {
+            pomFile.getParentFile().mkdirs();
+            FileWriter writer = new FileWriter(pomFile);
+            try {
+                getPom().writeTo(writer);
+            } finally {
+                writer.close();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void addArtifact(Artifact artifact, File src) {
+        throwExceptionIfArtifactOrSrcIsNull(artifact, src);
+        if (hasClassifier(artifact)) {
+            addClassifierArtifact(artifact, src);
+            assignArtifactValuesToPom(artifact, pom, false);
+            return;
+        }
+        if (this.artifact != null) {
+            throw new InvalidUserDataException("A pom can't have multiple main artifacts. " +
+                    "Already assigned artifact: " + this.artifact + " Artifact trying to assign: " + artifact);
+        }
+        this.artifact = artifact;
+        this.artifactFile = src;
+        assignArtifactValuesToPom(artifact, pom, true);
+    }
+
+    private void addClassifierArtifact(Artifact artifact, File artifactFile) {
+        String classifier = getClassifier(artifact);
+        ClassifierArtifact classifierArtifact = new ClassifierArtifact(classifier,
+                artifact.getType(), artifactFile);
+        if (classifiers.contains(classifierArtifact)) {
+            throw new InvalidUserDataException("A pom can't have multiple artifacts for the same classifier=" + classifier +
+                    " Artifact trying to assign: " + artifact);
+        }
+        classifiers.add(classifierArtifact);
+    }
+
+    private boolean hasClassifier(Artifact artifact) {
+        return getClassifier(artifact) != null;
+    }
+
+    private String getClassifier(Artifact artifact) {
+        return artifact.getExtraAttribute(Dependency.CLASSIFIER);
+    }
+
+    private void assignArtifactValuesToPom(Artifact artifact, MavenPom pom, boolean setType) {
+        if (pom.getGroupId().equals(MavenProject.EMPTY_PROJECT_GROUP_ID)) {
+            pom.setGroupId(artifact.getModuleRevisionId().getOrganisation());
+        }
+        if (pom.getArtifactId().equals(MavenProject.EMPTY_PROJECT_ARTIFACT_ID)) {
+            pom.setArtifactId(artifact.getName());
+        }
+        if (pom.getVersion().equals(MavenProject.EMPTY_PROJECT_VERSION)) {
+            pom.setVersion(artifact.getModuleRevisionId().getRevision());
+        }
+        if (setType) {
+            pom.setPackaging(artifact.getType());
+        }
+    }
+
+    private void throwExceptionIfArtifactOrSrcIsNull(Artifact artifact, File src) {
+        if (artifact == null) {
+            throw new InvalidUserDataException("Artifact must not be null.");
+        }
+        if (src == null) {
+            throw new InvalidUserDataException("Src file must not be null.");
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainer.java
new file mode 100644
index 0000000..35e9dee
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainer.java
@@ -0,0 +1,78 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.maven.PomFilterContainer;
+import org.gradle.api.internal.artifacts.publish.maven.MavenPomMetaInfoProvider;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultArtifactPomContainer implements ArtifactPomContainer {
+    private Map<String, ArtifactPom> artifactPoms = new HashMap<String, ArtifactPom>();
+    private final MavenPomMetaInfoProvider pomMetaInfoProvider;
+    private PomFilterContainer pomFilterContainer;
+    private ArtifactPomFactory artifactPomFactory;
+
+    public DefaultArtifactPomContainer(MavenPomMetaInfoProvider pomMetaInfoProvider, PomFilterContainer pomFilterContainer,
+                                       ArtifactPomFactory artifactPomFactory) {
+        this.pomMetaInfoProvider = pomMetaInfoProvider;
+        this.pomFilterContainer = pomFilterContainer;
+        this.artifactPomFactory = artifactPomFactory;
+    }
+
+    public void addArtifact(Artifact artifact, File src) {
+        if (artifact == null || src == null) {
+            throw new InvalidUserDataException("Artifact or source file must not be null!");
+        }
+        for (PomFilter activePomFilter : pomFilterContainer.getActivePomFilters()) {
+            if (activePomFilter.getFilter().accept(artifact, src)) {
+                if (artifactPoms.get(activePomFilter.getName()) == null) {
+                    artifactPoms.put(activePomFilter.getName(), artifactPomFactory.createArtifactPom(activePomFilter.getPomTemplate()));
+                }
+                artifactPoms.get(activePomFilter.getName()).addArtifact(artifact, src); 
+            }
+        }
+    }
+
+    public Set<DeployableFilesInfo> createDeployableFilesInfos() {
+        Set<DeployableFilesInfo> deployableFilesInfos = new HashSet<DeployableFilesInfo>();
+        for (String activeArtifactPomName : artifactPoms.keySet()) {
+            ArtifactPom activeArtifactPom = artifactPoms.get(activeArtifactPomName);
+            File pomFile = createPomFile(activeArtifactPomName);
+            activeArtifactPom.writePom(pomFile);
+            deployableFilesInfos.add(new DeployableFilesInfo(pomFile, activeArtifactPom.getArtifactFile(), activeArtifactPom.getClassifiers()));
+        }
+        return deployableFilesInfos;
+    }
+
+    private File createPomFile(String artifactPomName) {
+        return new File(pomMetaInfoProvider.getMavenPomDir(), "pom-" + artifactPomName + ".xml");
+    }
+
+    public Map<String, ArtifactPom> getArtifactPoms() {
+        return artifactPoms;
+    }
+
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultDeployTaskFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultDeployTaskFactory.java
new file mode 100644
index 0000000..3266ce0
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultDeployTaskFactory.java
@@ -0,0 +1,25 @@
+/*
+ * 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.publish.maven.deploy;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultDeployTaskFactory implements DeployTaskFactory {
+    public CustomDeployTask createDeployTask() {
+        return new CustomDeployTask();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultInstallTaskFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultInstallTaskFactory.java
new file mode 100644
index 0000000..ec16fdc
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultInstallTaskFactory.java
@@ -0,0 +1,25 @@
+/*
+ * 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.publish.maven.deploy;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultInstallTaskFactory implements InstallTaskFactory {
+    public CustomInstallTask createInstallTask() {
+        return new CustomInstallTask();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultPomFilter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultPomFilter.java
new file mode 100644
index 0000000..9158b3e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultPomFilter.java
@@ -0,0 +1,56 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.artifacts.maven.PublishFilter;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultPomFilter implements PomFilter {
+    private String name;
+
+    private MavenPom pom;
+
+    private PublishFilter filter;
+
+    public DefaultPomFilter(String name, MavenPom pom, PublishFilter filter) {
+        this.name = name;
+        this.pom = pom;
+        this.filter = filter;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public MavenPom getPomTemplate() {
+        return pom;
+    }
+
+    public void setPomTemplate(MavenPom pom) {
+        this.pom = pom;
+    }
+
+    public PublishFilter getFilter() {
+        return filter;
+    }
+
+    public void setFilter(PublishFilter filter) {
+        this.filter = filter;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DeployTaskFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DeployTaskFactory.java
new file mode 100644
index 0000000..e598e45
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DeployTaskFactory.java
@@ -0,0 +1,23 @@
+/*
+ * 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.publish.maven.deploy;
+
+/**
+ * @author Hans Dockter
+ */
+public interface DeployTaskFactory {
+    CustomDeployTask createDeployTask();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DeployableFilesInfo.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DeployableFilesInfo.java
new file mode 100644
index 0000000..088f45b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DeployableFilesInfo.java
@@ -0,0 +1,46 @@
+/*
+ * 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.publish.maven.deploy;
+
+import java.io.File;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+*/
+public class DeployableFilesInfo {
+    private File pomFile;
+    private File artifactFile;
+    private Set<ClassifierArtifact> classifierArtifacts;
+
+    public DeployableFilesInfo(File pomFile, File artifactFile, Set<ClassifierArtifact> classifierArtifacts) {
+        this.pomFile = pomFile;
+        this.artifactFile = artifactFile;
+        this.classifierArtifacts = classifierArtifacts;
+    }
+
+    public File getPomFile() {
+        return pomFile;
+    }
+
+    public File getArtifactFile() {
+        return artifactFile;
+    }
+
+    public Set<ClassifierArtifact> getClassifierArtifacts() {
+        return classifierArtifacts;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/InstallTaskFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/InstallTaskFactory.java
new file mode 100644
index 0000000..1e366e9
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/InstallTaskFactory.java
@@ -0,0 +1,23 @@
+/*
+ * 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.publish.maven.deploy;
+
+/**
+ * @author Hans Dockter
+ */
+public interface InstallTaskFactory {
+    CustomInstallTask createInstallTask();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/LoggingHelper.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/LoggingHelper.java
new file mode 100644
index 0000000..4af2dff
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/LoggingHelper.java
@@ -0,0 +1,46 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.apache.maven.artifact.ant.AntDownloadMonitor;
+import org.apache.maven.artifact.manager.DefaultWagonManager;
+import org.apache.maven.artifact.manager.WagonManager;
+import org.apache.tools.ant.Project;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
+
+import java.lang.reflect.Field;
+
+/**
+ * @author Hans Dockter
+ */
+public class LoggingHelper {
+    public static void injectLogger(PlexusContainer container, Project project) {
+        try {
+            WagonManager wagonManager = (WagonManager) container.lookup(WagonManager.ROLE);
+            Field field = DefaultWagonManager.class.getDeclaredField("downloadMonitor");
+            field.setAccessible(true);
+            AntDownloadMonitor antDownloadMonitor = (AntDownloadMonitor) field.get(wagonManager);
+            antDownloadMonitor.setProject(project);
+        } catch (ComponentLookupException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/PomFilter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/PomFilter.java
new file mode 100644
index 0000000..5ead72e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/PomFilter.java
@@ -0,0 +1,34 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.artifacts.maven.PublishFilter;
+
+/**
+ * @author Hans Dockter
+ */
+public interface PomFilter {
+    String getName();
+
+    PublishFilter getFilter();
+
+    void setFilter(PublishFilter filter);
+
+    MavenPom getPomTemplate();
+
+    void setPomTemplate(MavenPom pom);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenDeployer.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenDeployer.groovy
new file mode 100644
index 0000000..4b14a73
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenDeployer.groovy
@@ -0,0 +1,69 @@
+/*
+ * 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.publish.maven.deploy.groovy
+
+import org.codehaus.groovy.runtime.InvokerHelper
+import org.gradle.api.artifacts.maven.GroovyMavenDeployer
+import org.gradle.api.artifacts.maven.GroovyPomFilterContainer
+import org.gradle.api.artifacts.maven.MavenPom
+import org.gradle.api.internal.artifacts.publish.maven.deploy.ArtifactPomContainer
+import org.gradle.api.internal.artifacts.publish.maven.deploy.BaseMavenDeployer
+import org.gradle.logging.LoggingManagerInternal
+
+/**
+ * @author Hans Dockter
+ */
+class DefaultGroovyMavenDeployer extends BaseMavenDeployer implements GroovyMavenDeployer, GroovyPomFilterContainer {
+    public static final String REPOSITORY_BUILDER = "repository"
+    public static final String SNAPSHOT_REPOSITORY_BUILDER = 'snapshotRepository'
+    
+    private RepositoryBuilder repositoryBuilder = new RepositoryBuilder()
+
+    DefaultGroovyMavenDeployer(String name, GroovyPomFilterContainer pomFilterContainer, ArtifactPomContainer artifactPomContainer, LoggingManagerInternal loggingManager) {
+        super(name, pomFilterContainer, artifactPomContainer, loggingManager)
+    }
+    
+    def methodMissing(String name, args) {
+        if (name == REPOSITORY_BUILDER || name == SNAPSHOT_REPOSITORY_BUILDER) {
+            Object repository = InvokerHelper.invokeMethod(repositoryBuilder, REPOSITORY_BUILDER, args)
+            if (name == REPOSITORY_BUILDER) {
+                setRepository(repository)
+            } else {
+                setSnapshotRepository(repository)
+            }
+            return repository;
+        } else {
+            throw new MissingMethodException(name, this.class, args)
+        }
+    }
+
+    void filter(Closure filter) {
+        getPomFilterContainer().filter(filter)
+    }
+
+    MavenPom addFilter(String name, Closure filter) {
+        getPomFilterContainer().addFilter(name, filter)
+    }
+
+    MavenPom pom(Closure configureClosure) {
+        getPomFilterContainer().pom(configureClosure)
+    }
+
+    MavenPom pom(String name, Closure configureClosure) {
+        getPomFilterContainer().pom(name, configureClosure)
+    }
+
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenInstaller.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenInstaller.groovy
new file mode 100644
index 0000000..625ef82
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenInstaller.groovy
@@ -0,0 +1,48 @@
+/*
+ * 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.publish.maven.deploy.groovy
+
+import org.gradle.api.artifacts.maven.GroovyPomFilterContainer
+import org.gradle.api.artifacts.maven.MavenPom
+import org.gradle.api.internal.artifacts.publish.maven.deploy.ArtifactPomContainer
+import org.gradle.api.internal.artifacts.publish.maven.deploy.BaseMavenInstaller
+import org.gradle.api.logging.LoggingManager
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultGroovyMavenInstaller extends BaseMavenInstaller implements GroovyPomFilterContainer {
+    DefaultGroovyMavenInstaller(String name, GroovyPomFilterContainer pomFilterContainer, ArtifactPomContainer artifactPomContainer, LoggingManager loggingManager) {
+        super(name, pomFilterContainer, artifactPomContainer, loggingManager)
+    }
+
+    void filter(Closure filter) {
+        getPomFilterContainer().filter(filter)
+    }
+
+    MavenPom addFilter(String name, Closure filter) {
+        getPomFilterContainer().addFilter(name, filter)
+    }
+
+    MavenPom pom(Closure configureClosure) {
+        getPomFilterContainer().pom(configureClosure)
+    }
+
+    MavenPom pom(String name, Closure configureClosure) {
+        getPomFilterContainer().pom(name, configureClosure)
+    }
+
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyPomFilterContainer.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyPomFilterContainer.groovy
new file mode 100644
index 0000000..506e679
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyPomFilterContainer.groovy
@@ -0,0 +1,52 @@
+/*
+ * 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.publish.maven.deploy.groovy
+
+import org.gradle.api.artifacts.maven.MavenPom
+import org.gradle.api.artifacts.maven.PublishFilter
+import org.gradle.api.internal.artifacts.publish.maven.MavenPomFactory
+import org.gradle.api.internal.artifacts.publish.maven.deploy.BasePomFilterContainer
+import org.gradle.util.ConfigureUtil
+import org.gradle.api.artifacts.maven.GroovyPomFilterContainer;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultGroovyPomFilterContainer extends BasePomFilterContainer implements GroovyPomFilterContainer {
+    DefaultGroovyPomFilterContainer(MavenPomFactory mavenPomFactory) {
+        super(mavenPomFactory);
+    }
+
+    void filter(Closure filter) {
+        this.filter = filter as PublishFilter
+    }
+
+    MavenPom addFilter(String name, Closure filter) {
+        addFilter(name, filter as PublishFilter)
+    }
+
+    MavenPom pom(Closure configureClosure) {
+        ConfigureUtil.configure(configureClosure, pom)
+    }
+
+    MavenPom pom(String name, Closure configureClosure) {
+        ConfigureUtil.configure(configureClosure, pom(name))
+    }
+
+    protected BasePomFilterContainer newInstance() {
+        return new DefaultGroovyPomFilterContainer(mavenPomFactory);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/RepositoryBuilder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/RepositoryBuilder.java
new file mode 100644
index 0000000..0d24946
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/RepositoryBuilder.java
@@ -0,0 +1,35 @@
+/*
+ * 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.publish.maven.deploy.groovy;
+
+import groovy.util.FactoryBuilderSupport;
+import org.apache.maven.artifact.ant.Authentication;
+import org.apache.maven.artifact.ant.Proxy;
+import org.apache.maven.artifact.ant.RemoteRepository;
+import org.apache.maven.artifact.ant.RepositoryPolicy;
+
+/**
+ * @author Hans Dockter
+ */
+public class RepositoryBuilder extends FactoryBuilderSupport {
+    public RepositoryBuilder() {
+        registerFactory("repository", new RepositoryFactory(RemoteRepository.class));
+        registerBeanFactory("authentication", Authentication.class);
+        registerBeanFactory("proxy", Proxy.class);
+        registerBeanFactory("snapshots", RepositoryPolicy.class);
+        registerBeanFactory("releases", RepositoryPolicy.class);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/RepositoryFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/RepositoryFactory.java
new file mode 100644
index 0000000..c5efe20
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/RepositoryFactory.java
@@ -0,0 +1,54 @@
+/*
+ * 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.publish.maven.deploy.groovy;
+
+import groovy.swing.factory.BeanFactory;
+import groovy.util.FactoryBuilderSupport;
+import org.apache.maven.artifact.ant.Authentication;
+import org.apache.maven.artifact.ant.Proxy;
+import org.apache.maven.artifact.ant.RemoteRepository;
+import org.apache.maven.artifact.ant.RepositoryPolicy;
+
+/**
+ * @author Hans Dockter
+ */
+public class RepositoryFactory extends BeanFactory {
+    public RepositoryFactory(Class klass) {
+        super(klass);
+    }
+
+    public RepositoryFactory(Class klass, boolean leaf) {
+        super(klass, leaf);
+    }
+
+    public void setChild(FactoryBuilderSupport builder, Object parent, Object child) {
+        if (child instanceof Authentication) {
+            getRepository(parent).addAuthentication((Authentication) child);
+        } else if (child instanceof Proxy) {
+            getRepository(parent).addProxy((Proxy) child);
+        } else if (child instanceof RepositoryPolicy) {
+            if (builder.getCurrentName().equals("snapshots")) {
+                getRepository(parent).addSnapshots((RepositoryPolicy) child);
+            } else {
+                getRepository(parent).addReleases((RepositoryPolicy) child);
+            }
+        }
+    }
+
+    private RemoteRepository getRepository(Object parent) {
+        return (RemoteRepository) parent;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/pombuilder/CustomModelBuilder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/pombuilder/CustomModelBuilder.java
new file mode 100644
index 0000000..d7df344
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/pombuilder/CustomModelBuilder.java
@@ -0,0 +1,83 @@
+/*
+ * 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.publish.maven.pombuilder;
+
+import groovy.util.FactoryBuilderSupport;
+import org.apache.maven.model.Model;
+import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
+import org.slf4j.LoggerFactory;
+import org.sonatype.maven.polyglot.execute.ExecuteManager;
+import org.sonatype.maven.polyglot.execute.ExecuteManagerImpl;
+import org.sonatype.maven.polyglot.groovy.builder.ModelBuilder;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+
+/**
+ * This is a slightly modified version as shipped with polyglot Maven.
+ */
+public class CustomModelBuilder extends ModelBuilder {
+    private Model model;
+
+    public CustomModelBuilder(Model model) {
+        this.model = model;
+        ExecuteManager executeManager = new ExecuteManagerImpl();
+        setProp(executeManager.getClass(), executeManager, "log",
+                new PlexusLoggerAdapter(LoggerFactory.getLogger(ExecuteManagerImpl.class)));
+        setProp(ModelBuilder.class, this, "executeManager", executeManager);
+        setProp(ModelBuilder.class, this, "log",
+                new PlexusLoggerAdapter(LoggerFactory.getLogger(ModelBuilder.class)));
+        try {
+            initialize();
+        } catch (InitializationException e) {
+            throw new RuntimeException(e);
+        }
+        Map factories = (Map) getProp(FactoryBuilderSupport.class, this, "factories");
+        factories.remove("project");
+        ModelFactory modelFactory = new ModelFactory(model);
+        registerFactory(modelFactory.getName(), null, modelFactory);
+    }
+
+    public static void setProp(Class c, Object obj, String fieldName, Object value) {
+        try {
+            Field f = c.getDeclaredField(fieldName);
+            f.setAccessible(true); // solution
+            f.set(obj, value); // IllegalAccessException
+            // production code should handle these exceptions more gracefully
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalArgumentException e) {
+           throw new RuntimeException(e);
+        } catch (IllegalAccessException e) {
+           throw new RuntimeException(e);
+        }
+    }
+
+    public static Object getProp(Class c, Object obj, String fieldName) {
+        try {
+            Field f = c.getDeclaredField(fieldName);
+            f.setAccessible(true); // solution
+            return f.get(obj); // IllegalAccessException
+            // production code should handle these exceptions more gracefully
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalArgumentException e) {
+            throw new RuntimeException(e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/pombuilder/ModelFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/pombuilder/ModelFactory.java
new file mode 100644
index 0000000..49b0f10
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/pombuilder/ModelFactory.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.api.internal.artifacts.publish.maven.pombuilder;
+
+import groovy.util.FactoryBuilderSupport;
+import org.apache.maven.model.Model;
+import org.sonatype.maven.polyglot.groovy.builder.factory.NamedFactory;
+
+import java.util.Map;
+
+/**
+ * This is a slightly modified version as shipped with polyglot Maven.
+ */
+public class ModelFactory extends NamedFactory {
+    private Model model;
+
+    public ModelFactory(Model model) {
+        super("project");
+        this.model = model;
+    }
+
+    public Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map attrs) throws InstantiationException, IllegalAccessException {
+        return model;
+    }
+
+    @Override
+    public void onNodeCompleted(FactoryBuilderSupport builder, Object parent, Object node) {
+        Model model = (Model)node;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/pombuilder/PlexusLoggerAdapter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/pombuilder/PlexusLoggerAdapter.java
new file mode 100644
index 0000000..26dbb71
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/publish/maven/pombuilder/PlexusLoggerAdapter.java
@@ -0,0 +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.api.internal.artifacts.publish.maven.pombuilder;
+
+import org.codehaus.plexus.logging.Logger;
+
+/**
+ * @author Hans Dockter
+ */
+public class PlexusLoggerAdapter implements Logger {
+    org.slf4j.Logger logger;
+
+    public PlexusLoggerAdapter(org.slf4j.Logger logger) {
+        this.logger = logger;
+    }
+
+    public void debug(String s) {
+        logger.debug(s);
+    }
+
+    public void debug(String s, Throwable throwable) {
+        logger.debug(s, throwable);
+    }
+
+    public boolean isDebugEnabled() {
+        return logger.isDebugEnabled();
+    }
+
+    public void info(String s) {
+        logger.info(s);
+    }
+
+    public void info(String s, Throwable throwable) {
+        logger.info(s, throwable);
+    }
+
+    public boolean isInfoEnabled() {
+        return logger.isInfoEnabled();
+    }
+
+    public void warn(String s) {
+        logger.warn(s);
+    }
+
+    public void warn(String s, Throwable throwable) {
+        logger.warn(s, throwable);
+    }
+
+    public boolean isWarnEnabled() {
+        return logger.isWarnEnabled();
+    }
+
+    public void error(String s) {
+        logger.error(s);
+    }
+
+    public void error(String s, Throwable throwable) {
+        logger.error(s, throwable);
+    }
+
+    public boolean isErrorEnabled() {
+        return logger.isErrorEnabled();
+    }
+
+    public void fatalError(String s) {
+        logger.error(s);
+    }
+
+    public void fatalError(String s, Throwable throwable) {
+        logger.error(s, throwable);
+    }
+
+    public boolean isFatalErrorEnabled() {
+        return logger.isErrorEnabled();
+    }
+
+    public Logger getChildLogger(String s) {
+        throw new UnsupportedOperationException();
+    }
+
+    public int getThreshold() {
+        throw new UnsupportedOperationException();
+    }
+
+    public String getName() {
+        return logger.getName();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultInternalRepository.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultInternalRepository.java
new file mode 100644
index 0000000..7c908b8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/DefaultInternalRepository.java
@@ -0,0 +1,159 @@
+/*
+ * 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.repositories;
+
+import org.apache.ivy.core.IvyContext;
+import org.apache.ivy.core.cache.ArtifactOrigin;
+import org.apache.ivy.core.module.descriptor.*;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.report.DownloadReport;
+import org.apache.ivy.core.report.DownloadStatus;
+import org.apache.ivy.core.report.MetadataArtifactDownloadReport;
+import org.apache.ivy.core.resolve.DownloadOptions;
+import org.apache.ivy.core.resolve.ResolveData;
+import org.apache.ivy.core.resolve.ResolvedModuleRevision;
+import org.apache.ivy.plugins.repository.Resource;
+import org.apache.ivy.plugins.repository.file.FileRepository;
+import org.apache.ivy.plugins.repository.file.FileResource;
+import org.apache.ivy.plugins.resolver.BasicResolver;
+import org.apache.ivy.plugins.resolver.util.ResolvedResource;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.ResolverContainer;
+import org.gradle.api.artifacts.repositories.InternalRepository;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.ivyservice.DefaultIvyDependencyPublisher;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependencyDescriptorFactory;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.util.ReflectionUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultInternalRepository extends BasicResolver implements InternalRepository {
+    private final ModuleDescriptorConverter moduleDescriptorConverter;
+    private final Gradle gradle;
+
+    public DefaultInternalRepository(Gradle gradle, ModuleDescriptorConverter moduleDescriptorConverter) {
+        this.gradle = gradle;
+        this.moduleDescriptorConverter = moduleDescriptorConverter;
+        setName(ResolverContainer.INTERNAL_REPOSITORY_NAME);
+    }
+
+    @Override
+    public ResolvedModuleRevision getDependency(DependencyDescriptor dd, ResolveData data) throws ParseException {
+        ModuleDescriptor moduleDescriptor = findProject(dd);
+        if (moduleDescriptor == null) {
+            return data.getCurrentResolvedModuleRevision();
+        }
+
+        IvyContext context = IvyContext.pushNewCopyContext();
+        try {
+            context.setDependencyDescriptor(dd);
+            context.setResolveData(data);
+            MetadataArtifactDownloadReport downloadReport = new MetadataArtifactDownloadReport(moduleDescriptor.getMetadataArtifact());
+            downloadReport.setDownloadStatus(DownloadStatus.NO);
+            downloadReport.setSearched(false);
+            return new ResolvedModuleRevision(this, this, moduleDescriptor, downloadReport);
+        } finally {
+            IvyContext.popContext();
+        }
+    }
+
+    private ModuleDescriptor findProject(DependencyDescriptor descriptor) {
+        String projectPathValue = descriptor.getAttribute(DependencyDescriptorFactory.PROJECT_PATH_KEY);
+        if (projectPathValue == null) {
+            return null;
+        }
+        Project project = gradle.getRootProject().project(projectPathValue);
+        DependencyMetaDataProvider dependencyMetaDataProvider = ((ProjectInternal) project).getServiceRegistryFactory().get(DependencyMetaDataProvider.class);
+        ModuleDescriptor projectDescriptor = moduleDescriptorConverter.convert(project.getConfigurations().getAll(),
+                dependencyMetaDataProvider.getModule(), IvyContext.getContext().getIvy().getSettings());
+
+        for (DependencyArtifactDescriptor artifactDescriptor : descriptor.getAllDependencyArtifacts()) {
+            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);
+                    ReflectionUtil.invoke(artifactDescriptor, "setExtraAttribute",
+                            new Object[]{DefaultIvyDependencyPublisher.FILE_PATH_EXTRA_ATTRIBUTE, path});
+                }
+            }
+        }
+
+        return projectDescriptor;
+    }
+
+    @Override
+    protected ResolvedResource findArtifactRef(Artifact artifact, Date date) {
+        String path = artifact.getExtraAttribute(DefaultIvyDependencyPublisher.FILE_PATH_EXTRA_ATTRIBUTE);
+        if (path == null) {            
+            return null;
+        }
+        File file = new File(path);
+        return new ResolvedResource(new FileResource(new FileRepository(), file), artifact.getId().getRevision());
+    }
+
+    @Override
+    public DownloadReport download(Artifact[] artifacts, DownloadOptions options) {
+        DownloadReport dr = new DownloadReport();
+        for (Artifact artifact : artifacts) {
+            ArtifactDownloadReport artifactDownloadReport = new ArtifactDownloadReport(artifact);
+            String path = artifact.getExtraAttribute(DefaultIvyDependencyPublisher.FILE_PATH_EXTRA_ATTRIBUTE);
+            if (path == null) {
+                artifactDownloadReport.setDownloadStatus(DownloadStatus.FAILED);
+            } else {
+                File file = new File(path);
+                artifactDownloadReport.setDownloadStatus(DownloadStatus.SUCCESSFUL);
+                artifactDownloadReport.setArtifactOrigin(new ArtifactOrigin(artifact, true, getName()));
+                artifactDownloadReport.setLocalFile(file);
+                artifactDownloadReport.setSize(file.length());
+            }
+            dr.addArtifactReport(artifactDownloadReport);
+        }
+        return dr;
+    }
+
+    @Override
+    protected Resource getResource(String source) throws IOException {
+        return null;
+    }
+
+    @Override
+    protected Collection findNames(Map tokenValues, String token) {
+        return null;
+    }
+
+    @Override
+    protected long get(Resource resource, File dest) throws IOException {
+        return 0;
+    }
+
+    public ResolvedResource findIvyFileRef(DependencyDescriptor dd, ResolveData data) {
+        return null;
+    }
+
+    public void publish(Artifact artifact, File src, boolean overwrite) throws IOException {
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/WebdavRepository.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/WebdavRepository.java
new file mode 100644
index 0000000..d395a96
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/artifacts/repositories/WebdavRepository.java
@@ -0,0 +1,80 @@
+/*
+ * 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.repositories;
+
+import org.apache.commons.httpclient.HttpsURL;
+import org.apache.ivy.plugins.repository.url.URLRepository;
+import org.apache.webdav.lib.WebdavResource;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author Hans Dockter
+ */
+public class WebdavRepository extends URLRepository {
+    private String userPassword;
+
+    private String user;
+
+    public String getUserPassword() {
+        return userPassword;
+    }
+
+    public void setUserPassword(String userPassword) {
+        this.userPassword = userPassword;
+    }
+
+    public String getUser() {
+        return user;
+    }
+
+    public void setUser(String user) {
+        this.user = user;
+    }
+
+    public void put(File source, String destination, boolean overwrite) throws IOException {
+        int fileNameStart = destination.lastIndexOf('/');
+        String baseUrl = destination.substring(0, fileNameStart + 1);
+        String destinationFileName =  destination.substring(fileNameStart + 1);
+        HttpsURL hrl = new HttpsURL(baseUrl);
+        hrl.setUserinfo(user, userPassword);
+        WebdavResource wdr = new WebdavResource(hrl);
+        wdr.putMethod(wdr.getPath() + '/' + destinationFileName, source);
+        wdr.close();
+    }
+
+    //    Alternative implementation with httpclient only. Unfortunately this is slower.
+//
+//    public void put(File source, String destination, boolean overwrite) throws IOException {
+//        HttpClient client = new HttpClient();
+//        HttpState state = client.getState();
+//        PutMethod putMethod = new PutMethod(destination);
+//        Credentials credentials = new UsernamePasswordCredentials("hans_d", "magus96");
+//        state.setCredentials(null, null, credentials);
+//        logger.info("Publishing: " + source.getAbsolutePath());
+////        putMethod.setRequestEntity(new InputStreamRequestEntity(new FileInputStream(source)));
+//        putMethod.setRequestEntity(new FileRequestEntity(source, "application/binary"));
+//        try {
+//            // execute the GET
+//            int status = client.executeMethod(putMethod);
+//            // evaluate status
+//        } finally {
+//            // release any connection resources used by the method
+//            putMethod.releaseConnection();
+//        }
+//    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/CachingHasher.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/CachingHasher.java
new file mode 100644
index 0000000..5a0d9ca
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/CachingHasher.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.api.internal.changedetection;
+
+import org.gradle.cache.CacheRepository;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.cache.Serializer;
+
+import java.io.*;
+
+public class CachingHasher implements Hasher {
+    private final PersistentIndexedCache<File, FileInfo> cache;
+    private final Hasher hasher;
+    private long timestamp;
+
+    public CachingHasher(Hasher hasher, CacheRepository cacheRepository) {
+        this.hasher = hasher;
+        cache = cacheRepository.cache("fileHashes").open().openIndexedCache(new FileInfoSerializer());
+    }
+
+    public byte[] hash(File file) {
+        FileInfo info = cache.get(file);
+
+        long length = file.length();
+        timestamp = file.lastModified();
+        if (info != null && length == info.length && timestamp == info.timestamp) {
+            return info.hash;
+        }
+
+        byte[] hash = hasher.hash(file);
+        cache.put(file, new FileInfo(hash, length, timestamp));
+        return hash;
+    }
+
+    public static class FileInfo implements Serializable {
+        private final byte[] hash;
+        private final long timestamp;
+        private final long length;
+
+        public FileInfo(byte[] hash, long length, long timestamp) {
+            this.hash = hash;
+            this.length = length;
+            this.timestamp = timestamp;
+        }
+    }
+
+    private static class FileInfoSerializer implements Serializer<FileInfo> {
+        public FileInfo read(InputStream instr) throws Exception {
+            DataInputStream input = new DataInputStream(instr);
+            int hashLength = input.readInt();
+            byte[] hash = new byte[hashLength];
+            input.readFully(hash);
+            long timestamp = input.readLong();
+            long length = input.readLong();
+            return new FileInfo(hash, length, timestamp);
+        }
+
+        public void write(OutputStream outstr, FileInfo value) throws Exception {
+            DataOutputStream output = new DataOutputStream(outstr);
+            output.writeInt(value.hash.length);
+            output.write(value.hash);
+            output.writeLong(value.timestamp);
+            output.writeLong(value.length);
+            output.flush();
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultFileSnapshotter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultFileSnapshotter.java
new file mode 100644
index 0000000..cc4d72b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultFileSnapshotter.java
@@ -0,0 +1,158 @@
+/*
+ * 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.changedetection;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.SimpleFileCollection;
+import org.gradle.util.ChangeListener;
+import org.gradle.util.NoOpChangeListener;
+
+import java.io.File;
+import java.io.Serializable;
+import java.math.BigInteger;
+import java.util.*;
+
+public class DefaultFileSnapshotter implements FileSnapshotter {
+    private final Hasher hasher;
+
+    public DefaultFileSnapshotter(Hasher hasher) {
+        this.hasher = hasher;
+    }
+
+    public FileCollectionSnapshot snapshot() {
+        return new FileCollectionSnapshotImpl(new HashMap<String, FileSnapshot>());
+    }
+
+    public FileCollectionSnapshot snapshot(FileCollection sourceFiles) {
+        Map<String, FileSnapshot> snapshots = new HashMap<String, FileSnapshot>();
+        for (File file : sourceFiles.getAsFileTree()) {
+            if (file.isFile()) {
+                snapshots.put(file.getAbsolutePath(), new FileHashSnapshot(hasher.hash(file)));
+            } else if (file.isDirectory()) {
+                snapshots.put(file.getAbsolutePath(), new DirSnapshot());
+            } else {
+                snapshots.put(file.getAbsolutePath(), new MissingFileSnapshot());
+            }
+        }
+        return new FileCollectionSnapshotImpl(snapshots);
+    }
+
+    private interface FileSnapshot extends Serializable {
+        boolean isUpToDate(FileSnapshot snapshot);
+    }
+
+    private static class FileHashSnapshot implements FileSnapshot {
+        private final byte[] hash;
+
+        public FileHashSnapshot(byte[] hash) {
+            this.hash = hash;
+        }
+
+        public boolean isUpToDate(FileSnapshot snapshot) {
+            if (!(snapshot instanceof FileHashSnapshot)) {
+                return false;
+            }
+
+            FileHashSnapshot other = (FileHashSnapshot) snapshot;
+            return Arrays.equals(hash, other.hash);
+        }
+
+        @Override
+        public String toString() {
+            return new BigInteger(1, hash).toString(16);
+        }
+    }
+
+    private static class DirSnapshot implements FileSnapshot {
+        public boolean isUpToDate(FileSnapshot snapshot) {
+            return snapshot instanceof DirSnapshot;
+        }
+    }
+
+    private static class MissingFileSnapshot implements FileSnapshot {
+        public boolean isUpToDate(FileSnapshot snapshot) {
+            return snapshot instanceof MissingFileSnapshot;
+        }
+    }
+
+    private static class FileCollectionSnapshotImpl implements FileCollectionSnapshot {
+        private final Map<String, FileSnapshot> snapshots;
+
+        public FileCollectionSnapshotImpl(Map<String, FileSnapshot> snapshots) {
+            this.snapshots = snapshots;
+        }
+
+        public FileCollection getFiles() {
+            List<File> files = new ArrayList<File>();
+            for (Map.Entry<String, FileSnapshot> entry : snapshots.entrySet()) {
+                if (entry.getValue() instanceof FileHashSnapshot) {
+                    files.add(new File(entry.getKey()));
+                }
+            }
+            return new SimpleFileCollection(files);
+        }
+
+        public void changesSince(FileCollectionSnapshot oldSnapshot, final ChangeListener<File> listener) {
+            FileCollectionSnapshotImpl other = (FileCollectionSnapshotImpl) oldSnapshot;
+            diff(snapshots, other.snapshots, new ChangeListener<Map.Entry<String, FileSnapshot>>() {
+                public void added(Map.Entry<String, FileSnapshot> element) {
+                    listener.added(new File(element.getKey()));
+                }
+
+                public void removed(Map.Entry<String, FileSnapshot> element) {
+                    listener.removed(new File(element.getKey()));
+                }
+
+                public void changed(Map.Entry<String, FileSnapshot> element) {
+                    listener.changed(new File(element.getKey()));
+                }
+            });
+        }
+
+        private void diff(Map<String, FileSnapshot> snapshots, Map<String, FileSnapshot> oldSnapshots,
+                          ChangeListener<Map.Entry<String, FileSnapshot>> listener) {
+            Map<String, FileSnapshot> otherSnapshots = new HashMap<String, FileSnapshot>(oldSnapshots);
+            for (Map.Entry<String, FileSnapshot> entry : snapshots.entrySet()) {
+                FileSnapshot otherFile = otherSnapshots.remove(entry.getKey());
+                if (otherFile == null) {
+                    listener.added(entry);
+                } else if (!entry.getValue().isUpToDate(otherFile)) {
+                    listener.changed(entry);
+                }
+            }
+            for (Map.Entry<String, FileSnapshot> entry : otherSnapshots.entrySet()) {
+                listener.removed(entry);
+            }
+        }
+
+        public Diff changesSince(final FileCollectionSnapshot oldSnapshot) {
+            final FileCollectionSnapshotImpl other = (FileCollectionSnapshotImpl) oldSnapshot;
+            return new Diff() {
+                public FileCollectionSnapshot applyTo(FileCollectionSnapshot snapshot) {
+                    return applyTo(snapshot, new NoOpChangeListener<Merge>());
+                }
+
+                public FileCollectionSnapshot applyTo(FileCollectionSnapshot snapshot, final ChangeListener<Merge> listener) {
+                    FileCollectionSnapshotImpl target = (FileCollectionSnapshotImpl) snapshot;
+                    final Map<String, FileSnapshot> newSnapshots = new HashMap<String, FileSnapshot>(target.snapshots);
+                    diff(snapshots, other.snapshots, new MapMergeChangeListener<String, FileSnapshot>(listener, newSnapshots));
+                    return new FileCollectionSnapshotImpl(newSnapshots);
+                }
+            };
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultHasher.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultHasher.java
new file mode 100644
index 0000000..59a6b2a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultHasher.java
@@ -0,0 +1,26 @@
+/*
+ * 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.changedetection;
+
+import org.gradle.util.HashUtil;
+
+import java.io.File;
+
+public class DefaultHasher implements Hasher {
+    public byte[] hash(File file) {
+        return HashUtil.createHash(file);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepository.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepository.java
new file mode 100644
index 0000000..f3ca339
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepository.java
@@ -0,0 +1,355 @@
+/*
+ * 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.changedetection;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.file.SimpleFileCollection;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.cache.CacheRepository;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.util.ChangeListener;
+import org.gradle.util.DiffUtil;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.*;
+
+import static java.util.Collections.*;
+
+public class DefaultTaskArtifactStateRepository implements TaskArtifactStateRepository {
+    private static final int MAX_OUT_OF_DATE_MESSAGES = 10;
+    private static final Logger LOGGER = Logging.getLogger(DefaultTaskArtifactStateRepository.class);
+    private final CacheRepository repository;
+    private final FileSnapshotter inputFilesSnapshotter;
+    private final FileSnapshotter outputFilesSnapshotter;
+    private PersistentIndexedCache<String, TaskHistory> taskHistoryCache;
+
+    public DefaultTaskArtifactStateRepository(CacheRepository repository, FileSnapshotter inputFilesSnapshotter, FileSnapshotter outputFilesSnapshotter) {
+        this.repository = repository;
+        this.inputFilesSnapshotter = inputFilesSnapshotter;
+        this.outputFilesSnapshotter = outputFilesSnapshotter;
+    }
+
+    public TaskArtifactState getStateFor(final TaskInternal task) {
+        if (taskHistoryCache == null) {
+            loadTasks(task);
+        }
+
+        return new TaskArtifactStateImpl(task);
+    }
+
+    private void loadTasks(TaskInternal task) {
+        taskHistoryCache = repository.cache("taskArtifacts").forObject(task.getProject().getGradle()).open().openIndexedCache();
+    }
+
+    private static Set<String> outputFiles(TaskInternal task) {
+        Set<String> outputFiles = new HashSet<String>();
+        for (File file : task.getOutputs().getFiles()) {
+            outputFiles.add(file.getAbsolutePath());
+        }
+        return outputFiles;
+    }
+
+    private interface TaskExecution {
+        List<String> isUpToDate();
+
+        boolean snapshot();
+
+        FileCollection getPreviousOutputFiles();
+    }
+
+    private static class TaskHistory implements Serializable {
+        private static final int MAX_HISTORY_ENTRIES = 3;
+        private final List<TaskConfiguration> configurations = new ArrayList<TaskConfiguration>();
+
+        public void addConfiguration(TaskConfiguration configuration) {
+            configurations.add(0, configuration);
+            // Only keep a few of the most recent configurations
+            while (configurations.size() > MAX_HISTORY_ENTRIES) {
+                configurations.remove(MAX_HISTORY_ENTRIES);
+            }
+        }
+    }
+
+    private static class NoDeclaredArtifactsExecution implements TaskExecution {
+        private final TaskInternal task;
+
+        private NoDeclaredArtifactsExecution(TaskInternal task) {
+            this.task = task;
+        }
+
+        public List<String> isUpToDate() {
+            List<String> messages = new ArrayList<String>();
+            if (!task.getOutputs().getHasOutput()) {
+                messages.add(String.format("%s has not declared any outputs.", StringUtils.capitalize(task.toString())));
+            }
+            return messages;
+        }
+
+        public boolean snapshot() {
+            return false;
+        }
+
+        public FileCollection getPreviousOutputFiles() {
+            return new SimpleFileCollection();
+        }
+    }
+
+    private static class HistoricExecution implements TaskExecution {
+        private final TaskHistory history;
+        private final TaskInternal task;
+        private final TaskConfiguration lastExecution;
+        private final FileSnapshotter inputFilesSnapshotter;
+        private final FileSnapshotter outputFilesSnapshotter;
+        private boolean upToDate;
+        private TaskConfiguration thisExecution;
+        private FileCollectionSnapshot outputFilesBefore;
+
+        public HistoricExecution(TaskHistory history, TaskInternal task, TaskConfiguration lastExecution,
+                                 FileSnapshotter inputFilesSnapshotter, FileSnapshotter outputFilesSnapshotter) {
+            this.history = history;
+            this.task = task;
+            this.lastExecution = lastExecution;
+            this.inputFilesSnapshotter = inputFilesSnapshotter;
+            this.outputFilesSnapshotter = outputFilesSnapshotter;
+        }
+
+        private void calcCurrentState() {
+            if (thisExecution != null) {
+                return;
+            }
+
+            // Calculate current state - note this is potentially expensive
+            FileCollectionSnapshot inputFilesSnapshot = inputFilesSnapshotter.snapshot(task.getInputs().getFiles());
+            thisExecution = new TaskConfiguration(task, inputFilesSnapshot);
+            outputFilesBefore = outputFilesSnapshotter.snapshot(task.getOutputs().getFiles());
+        }
+
+        public FileCollection getPreviousOutputFiles() {
+            return lastExecution != null ? lastExecution.outputFilesSnapshot.getFiles() : new SimpleFileCollection();
+        }
+
+        public List<String> isUpToDate() {
+            calcCurrentState();
+
+            // Now determine if we're out of date
+            if (lastExecution == null) {
+                return singletonList(String.format("No history is available for %s.", task));
+            }
+
+            if (!task.getClass().getName().equals(lastExecution.taskClass)) {
+                return singletonList(String.format("%s has changed type from '%s' to '%s'.", StringUtils.capitalize(
+                        task.toString()), lastExecution.taskClass, task.getClass().getName()));
+            }
+
+            List<String> messages = new ArrayList<String>();
+            checkInputProperties(messages);
+            if (!messages.isEmpty()) {
+                return messages;
+            }
+
+            checkInputs(messages);
+            if (!messages.isEmpty()) {
+                return messages;
+            }
+
+            checkOutputs(messages);
+            if (!messages.isEmpty()) {
+                return messages;
+            }
+
+            upToDate = true;
+            return emptyList();
+        }
+
+        private void checkOutputs(final Collection<String> messages) {
+            outputFilesBefore.changesSince(lastExecution.outputFilesSnapshot, new ChangeListener<File>() {
+                public void added(File element) {
+                    messages.add(String.format("Output file '%s' has been added for %s.", element, task));
+                }
+
+                public void removed(File element) {
+                    messages.add(String.format("Output file %s has been removed for %s.", element.getAbsolutePath(), task));
+                }
+
+                public void changed(File element) {
+                    messages.add(String.format("Output file %s for %s has changed.", element.getAbsolutePath(), task));
+                }
+            });
+        }
+
+        private void checkInputs(final Collection<String> messages) {
+            thisExecution.inputFilesSnapshot.changesSince(lastExecution.inputFilesSnapshot, new ChangeListener<File>() {
+                public void added(File file) {
+                    messages.add(String.format("Input file %s for %s added.", file, task));
+                }
+
+                public void removed(File file) {
+                    messages.add(String.format("Input file %s for %s removed.", file, task));
+                }
+
+                public void changed(File file) {
+                    messages.add(String.format("Input file %s for %s has changed.", file, task));
+                }
+            });
+        }
+
+        private void checkInputProperties(final Collection<String> messages) {
+            DiffUtil.diff(thisExecution.inputProperties, lastExecution.inputProperties, new ChangeListener<Map.Entry<String, Object>>() {
+                public void added(Map.Entry<String, Object> element) {
+                    messages.add(String.format("Input property '%s' has been added for %s", element.getKey(), task));
+                }
+
+                public void removed(Map.Entry<String, Object> element) {
+                    messages.add(String.format("Input property '%s' has been removed for %s", element.getKey(), task));
+                }
+
+                public void changed(Map.Entry<String, Object> element) {
+                    messages.add(String.format("Value of input property '%s' has changed for %s", element.getKey(), task));
+                }
+            });
+        }
+
+        public boolean snapshot() {
+            calcCurrentState();
+            
+            if (upToDate) {
+                return false;
+            }
+
+            FileCollectionSnapshot lastExecutionOutputFiles = lastExecution == null ? outputFilesSnapshotter.snapshot()
+                    : lastExecution.outputFilesSnapshot;
+            FileCollectionSnapshot newOutputFiles = outputFilesBefore.changesSince(lastExecutionOutputFiles).applyTo(
+                    lastExecutionOutputFiles, new ChangeListener<FileCollectionSnapshot.Merge>() {
+                        public void added(FileCollectionSnapshot.Merge element) {
+                            // Ignore added files
+                            element.ignore();
+                        }
+
+                        public void removed(FileCollectionSnapshot.Merge element) {
+                            // Discard any files removed since the task was last executed
+                        }
+
+                        public void changed(FileCollectionSnapshot.Merge element) {
+                            // Update any files which were change since the task was last executed
+                        }
+                    });
+            FileCollectionSnapshot outputFilesAfter = outputFilesSnapshotter.snapshot(task.getOutputs().getFiles());
+            thisExecution.outputFilesSnapshot = outputFilesAfter.changesSince(outputFilesBefore).applyTo(
+                    newOutputFiles);
+            history.addConfiguration(thisExecution);
+            return true;
+        }
+    }
+
+    private static class TaskConfiguration implements Serializable {
+        private final String taskClass;
+        private Set<String> outputFiles;
+        private Map<String, Object> inputProperties;
+        private FileCollectionSnapshot inputFilesSnapshot;
+        private FileCollectionSnapshot outputFilesSnapshot;
+
+        private TaskConfiguration(TaskInternal task, FileCollectionSnapshot inputFilesSnapshot) {
+            this.taskClass = task.getClass().getName();
+            this.outputFiles = outputFiles(task);
+            this.inputProperties = new HashMap<String, Object>(task.getInputs().getProperties());
+            this.inputFilesSnapshot = inputFilesSnapshot;
+        }
+    }
+
+    private class TaskArtifactStateImpl implements TaskArtifactState {
+        private final TaskInternal task;
+        private final TaskHistory history;
+        private final TaskExecution execution;
+
+        public TaskArtifactStateImpl(TaskInternal task) {
+            this.task = task;
+            history = getHistory();
+            execution = getExecution();
+        }
+
+        public boolean isUpToDate() {
+            List<String> messages = execution.isUpToDate();
+            if (messages == null || messages.isEmpty()) {
+                LOGGER.info("Skipping {} as it is up-to-date.", task);
+                return true;
+            }
+            if (LOGGER.isInfoEnabled()) {
+                Formatter formatter = new Formatter();
+                formatter.format("Executing %s due to:", task);
+                for (int i = 0; i < messages.size() && i < MAX_OUT_OF_DATE_MESSAGES; i++) {
+                    String message = messages.get(i);
+                    formatter.format("%n%s", message);
+                }
+                if (messages.size() > MAX_OUT_OF_DATE_MESSAGES) {
+                    formatter.format("%d more ...", messages.size() - MAX_OUT_OF_DATE_MESSAGES);
+                }
+                LOGGER.info(formatter.toString());
+            }
+            return false;
+        }
+
+        public FileCollection getOutputFiles() {
+            return execution.getPreviousOutputFiles();
+        }
+
+        private TaskHistory getHistory() {
+            TaskHistory history = taskHistoryCache.get(task.getPath());
+            return history == null ? new TaskHistory() : history;
+        }
+
+        public TaskExecution getExecution() {
+            if (!task.getOutputs().getHasOutput()) {
+                return new NoDeclaredArtifactsExecution(task);
+            }
+            Set<String> outputFiles = outputFiles(task);
+            TaskConfiguration bestMatch = null;
+            int bestMatchOverlap = 0;
+            for (TaskConfiguration configuration : history.configurations) {
+                if (outputFiles.size() == 0) {
+                    if (configuration.outputFiles.size() == 0) {
+                        bestMatch = configuration;
+                        break;
+                    }
+                }
+
+                Set<String> intersection = new HashSet<String>(outputFiles);
+                intersection.retainAll(configuration.outputFiles);
+                if (intersection.size() > bestMatchOverlap) {
+                    bestMatch = configuration;
+                    bestMatchOverlap = intersection.size();
+                }
+                if (bestMatchOverlap == outputFiles.size()) {
+                    break;
+                }
+            }
+            if (bestMatch == null) {
+                return new HistoricExecution(history, task, null, inputFilesSnapshotter, outputFilesSnapshotter);
+            }
+            return new HistoricExecution(history, task, bestMatch, inputFilesSnapshotter, outputFilesSnapshotter);
+        }
+
+        public void update() {
+            if (execution.snapshot()) {
+                taskHistoryCache.put(task.getPath(), history);
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/FileCollectionSnapshot.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/FileCollectionSnapshot.java
new file mode 100644
index 0000000..b3f0cac
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/FileCollectionSnapshot.java
@@ -0,0 +1,59 @@
+/*
+ * 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.changedetection;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.util.ChangeListener;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * An immutable snapshot of the contents of a collection of files.
+ */
+public interface FileCollectionSnapshot extends Serializable {
+    void changesSince(FileCollectionSnapshot oldSnapshot, ChangeListener<File> listener);
+
+    Diff changesSince(FileCollectionSnapshot oldSnapshot);
+
+    FileCollection getFiles();
+
+    public interface Diff {
+        /**
+         * Applies this diff to the given snapshot. Adds any added or changed files in this diff to the given snapshot.
+         * Removes any removed files in this diff from the given snapshot.
+         *
+         * @param snapshot the snapshot to apply the changes to.
+         * @param listener the listener to notify of changes. The listener can veto a particular change.
+         * @return an updated copy of the provided snapshot
+         */
+        FileCollectionSnapshot applyTo(FileCollectionSnapshot snapshot, ChangeListener<Merge> listener);
+
+        /**
+         * Applies this diff to the given snapshot. Adds any added or changed files in this diff to the given snapshot.
+         * Removes any removed files in this diff from the given snapshot.
+         *
+         * @param snapshot the snapshot to apply the changes to.
+         * @return an updated copy of the provided snapshot
+         */
+        FileCollectionSnapshot applyTo(FileCollectionSnapshot snapshot);
+    }
+
+    public interface Merge {
+        void ignore();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/FileSnapshotter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/FileSnapshotter.java
new file mode 100644
index 0000000..3a3eb3b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/FileSnapshotter.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.api.internal.changedetection;
+
+import org.gradle.api.file.FileCollection;
+
+public interface FileSnapshotter {
+    /**
+     * Creates an empty snapshot, which changes can be later merged into.
+     *
+     * @return The snapshot.
+     */
+    FileCollectionSnapshot snapshot();
+
+    /**
+     * Creates a snapshot of the contents of the given collection
+     *
+     * @param files The files to snapshot
+     * @return The snapshot.
+     */
+    FileCollectionSnapshot snapshot(FileCollection files);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/Hasher.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/Hasher.java
new file mode 100644
index 0000000..db4ac84
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/Hasher.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.api.internal.changedetection;
+
+import java.io.File;
+
+public interface Hasher {
+    byte[] hash(File file);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/MapMergeChangeListener.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/MapMergeChangeListener.java
new file mode 100644
index 0000000..4c1e52e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/MapMergeChangeListener.java
@@ -0,0 +1,67 @@
+/*
+ * 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.changedetection;
+
+import org.gradle.util.ChangeListener;
+
+import java.util.Map;
+
+class MapMergeChangeListener<K, V> implements ChangeListener<Map.Entry<K, V>> {
+    private final ChangeListener<FileCollectionSnapshot.Merge> listener;
+    private final Map<K, V> newSnapshots;
+
+    public MapMergeChangeListener(ChangeListener<FileCollectionSnapshot.Merge> listener, Map<K, V> targetMap) {
+        this.listener = listener;
+        this.newSnapshots = targetMap;
+    }
+
+    public void added(Map.Entry<K, V> element) {
+        DefaultMerge merge = new DefaultMerge();
+        listener.added(merge);
+        if (!merge.isIgnore()) {
+            newSnapshots.put(element.getKey(), element.getValue());
+        }
+    }
+
+    public void removed(Map.Entry<K, V> element) {
+        DefaultMerge merge = new DefaultMerge();
+        listener.removed(merge);
+        if (!merge.isIgnore()) {
+            newSnapshots.remove(element.getKey());
+        }
+    }
+
+    public void changed(Map.Entry<K, V> element) {
+        DefaultMerge merge = new DefaultMerge();
+        listener.changed(merge);
+        if (!merge.isIgnore()) {
+            newSnapshots.put(element.getKey(), element.getValue());
+        }
+    }
+
+    private static class DefaultMerge implements FileCollectionSnapshot.Merge {
+        private boolean ignore;
+
+        public boolean isIgnore() {
+            return ignore;
+        }
+
+        public void ignore() {
+            ignore = true;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/OutputFilesSnapshotter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/OutputFilesSnapshotter.java
new file mode 100644
index 0000000..d9bcca1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/OutputFilesSnapshotter.java
@@ -0,0 +1,155 @@
+/*
+ * 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.changedetection;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.cache.CacheRepository;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.util.ChangeListener;
+import org.gradle.util.DiffUtil;
+import org.gradle.util.IdGenerator;
+import org.gradle.util.NoOpChangeListener;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Takes a snapshot of the output files of a task. 2 parts to the algorithm:
+ *
+ * <ul>
+ * <li>Collect the unique id for each output file and directory. The unique id is generated when we notice that
+ * a file/directory has been created. The id is regenerated when the file/directory is deleted.</li>
+ *
+ * <li>Collect the hash of each output file and each file in each output directory.</li>
+ * </ul>
+ *
+ */
+public class OutputFilesSnapshotter implements FileSnapshotter {
+    private final FileSnapshotter snapshotter;
+    private final IdGenerator<Long> idGenerator;
+    private final PersistentIndexedCache<String, Long> dirIdentiferCache;
+
+    public OutputFilesSnapshotter(FileSnapshotter snapshotter, IdGenerator<Long> idGenerator,
+                                  CacheRepository cacheRepository) {
+        this.snapshotter = snapshotter;
+        this.idGenerator = idGenerator;
+        dirIdentiferCache = cacheRepository.cache("outputFileStates").open().openIndexedCache();
+    }
+
+    public FileCollectionSnapshot snapshot() {
+        return new OutputFilesSnapshot(new HashMap<String, Long>(), snapshotter.snapshot());
+    }
+
+    public FileCollectionSnapshot snapshot(FileCollection files) {
+        Map<String, Long> snapshotDirIds = new HashMap<String, Long>();
+        for (File file : files) {
+            Long dirId;
+            if (file.exists()) {
+                dirId = dirIdentiferCache.get(file.getAbsolutePath());
+                if (dirId == null) {
+                    dirId = idGenerator.generateId();
+                    dirIdentiferCache.put(file.getAbsolutePath(), dirId);
+                }
+            } else {
+                dirIdentiferCache.remove(file.getAbsolutePath());
+                dirId = null;
+            }
+            snapshotDirIds.put(file.getAbsolutePath(), dirId);
+        }
+        return new OutputFilesSnapshot(snapshotDirIds, snapshotter.snapshot(files));
+    }
+
+    private static class OutputFilesSnapshot implements FileCollectionSnapshot {
+        private final Map<String, Long> rootFileIds;
+        private final FileCollectionSnapshot filesSnapshot;
+
+        public OutputFilesSnapshot(Map<String, Long> rootFileIds, FileCollectionSnapshot filesSnapshot) {
+            this.rootFileIds = rootFileIds;
+            this.filesSnapshot = filesSnapshot;
+        }
+
+        public FileCollection getFiles() {
+            return filesSnapshot.getFiles();
+        }
+
+        public Diff changesSince(final FileCollectionSnapshot oldSnapshot) {
+            OutputFilesSnapshot other = (OutputFilesSnapshot) oldSnapshot;
+            return new OutputFilesDiff(rootFileIds, other.rootFileIds, filesSnapshot.changesSince(other.filesSnapshot));
+        }
+
+        public void changesSince(FileCollectionSnapshot oldSnapshot, final ChangeListener<File> listener) {
+            final OutputFilesSnapshot other = (OutputFilesSnapshot) oldSnapshot;
+            DiffUtil.diff(rootFileIds, other.rootFileIds, new ChangeListener<Map.Entry<String, Long>>() {
+                public void added(Map.Entry<String, Long> element) {
+                    listener.added(new File(element.getKey()));
+                }
+
+                public void removed(Map.Entry<String, Long> element) {
+                    listener.removed(new File(element.getKey()));
+                }
+
+                public void changed(Map.Entry<String, Long> element) {
+                    if (other.rootFileIds.get(element.getKey()) == null) {
+                        // Dir used to not exist, now does. Don't care
+                        return;
+                    }
+                    listener.changed(new File(element.getKey()));
+                }
+            });
+            filesSnapshot.changesSince(other.filesSnapshot, new ChangeListener<File>() {
+                public void added(File element) {
+                    // Ignore files added to output dirs which have been added since last time task executed
+                }
+
+                public void removed(File element) {
+                    listener.removed(element);
+                }
+
+                public void changed(File element) {
+                    listener.changed(element);
+                }
+            });
+        }
+    }
+
+    private static class OutputFilesDiff implements FileCollectionSnapshot.Diff {
+        private final Map<String, Long> newFileIds;
+        private final Map<String, Long> oldFileIds;
+        private final FileCollectionSnapshot.Diff filesDiff;
+
+        public OutputFilesDiff(Map<String, Long> newFileIds, Map<String, Long> oldFileIds,
+                               FileCollectionSnapshot.Diff filesDiff) {
+            this.newFileIds = newFileIds;
+            this.oldFileIds = oldFileIds;
+            this.filesDiff = filesDiff;
+        }
+
+        public FileCollectionSnapshot applyTo(FileCollectionSnapshot snapshot,
+                                              ChangeListener<FileCollectionSnapshot.Merge> listener) {
+            OutputFilesSnapshot other = (OutputFilesSnapshot) snapshot;
+            Map<String, Long> dirIds = new HashMap<String, Long>(other.rootFileIds);
+            DiffUtil.diff(newFileIds, oldFileIds, new MapMergeChangeListener<String, Long>(
+                    new NoOpChangeListener<FileCollectionSnapshot.Merge>(), dirIds));
+            return new OutputFilesSnapshot(newFileIds, filesDiff.applyTo(other.filesSnapshot, listener));
+        }
+
+        public FileCollectionSnapshot applyTo(FileCollectionSnapshot snapshot) {
+            return applyTo(snapshot, new NoOpChangeListener<FileCollectionSnapshot.Merge>());
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepository.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepository.java
new file mode 100644
index 0000000..7bd7639
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepository.java
@@ -0,0 +1,47 @@
+/*
+ * 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.changedetection;
+
+import org.gradle.StartParameter;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.TaskInternal;
+
+public class ShortCircuitTaskArtifactStateRepository implements TaskArtifactStateRepository {
+    private final StartParameter startParameter;
+    private final TaskArtifactStateRepository repository;
+
+    public ShortCircuitTaskArtifactStateRepository(StartParameter startParameter, TaskArtifactStateRepository repository) {
+        this.startParameter = startParameter;
+        this.repository = repository;
+    }
+
+    public TaskArtifactState getStateFor(final TaskInternal task) {
+        final TaskArtifactState state = repository.getStateFor(task);
+        return new TaskArtifactState() {
+            public boolean isUpToDate() {
+                return !startParameter.isNoOpt() && task.getOutputs().getUpToDateSpec().isSatisfiedBy(task) && state.isUpToDate();
+            }
+
+            public FileCollection getOutputFiles() {
+                return state.getOutputFiles();
+            }
+
+            public void update() {
+                state.update();
+            }
+        };
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/TaskArtifactState.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/TaskArtifactState.java
new file mode 100644
index 0000000..ec055a4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/TaskArtifactState.java
@@ -0,0 +1,33 @@
+/*
+ * 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.changedetection;
+
+import org.gradle.api.internal.TaskExecutionHistory;
+
+/**
+ * Encapsulates the state of the task when its outputs were last generated.
+ */
+public interface TaskArtifactState extends TaskExecutionHistory {
+    /**
+     * Returns true if the task outputs were generated using the given task inputs.
+     */
+    boolean isUpToDate();
+
+    /**
+     * Marks current state as valid.
+     */
+    void update();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/TaskArtifactStateRepository.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/TaskArtifactStateRepository.java
new file mode 100644
index 0000000..6201ad6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/changedetection/TaskArtifactStateRepository.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.api.internal.changedetection;
+
+import org.gradle.api.internal.TaskInternal;
+
+public interface TaskArtifactStateRepository {
+    TaskArtifactState getStateFor(TaskInternal task);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AbstractFileCollection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AbstractFileCollection.java
new file mode 100644
index 0000000..931bbea
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AbstractFileCollection.java
@@ -0,0 +1,232 @@
+/*
+ * 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 groovy.lang.Closure;
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.internal.tasks.DefaultTaskDependency;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.api.tasks.StopExecutionException;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.util.*;
+
+public abstract class AbstractFileCollection implements FileCollection {
+    /**
+     * Returns the display name of this file collection. Used in log and error messages.
+     *
+     * @return the display name
+     */
+    public abstract String getDisplayName();
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+
+    public File getSingleFile() throws IllegalStateException {
+        Collection<File> files = getFiles();
+        if (files.isEmpty()) {
+            throw new IllegalStateException(String.format(
+                    "Expected %s to contain exactly one file, however, it contains no files.", getDisplayName()));
+        }
+        if (files.size() != 1) {
+            throw new IllegalStateException(String.format(
+                    "Expected %s to contain exactly one file, however, it contains %d files.", getDisplayName(),
+                    files.size()));
+        }
+        return files.iterator().next();
+    }
+
+    public Iterator<File> iterator() {
+        return getFiles().iterator();
+    }
+
+    public String getAsPath() {
+        return GUtil.join(getFiles(), File.pathSeparator);
+    }
+
+    public boolean contains(File file) {
+        return getFiles().contains(file);
+    }
+
+    public FileCollection plus(FileCollection collection) {
+        return new UnionFileCollection(this, collection);
+    }
+
+    public FileCollection minus(final FileCollection collection) {
+        return new AbstractFileCollection() {
+            @Override
+            public String getDisplayName() {
+                return AbstractFileCollection.this.getDisplayName();
+            }
+
+            @Override
+            public TaskDependency getBuildDependencies() {
+                return AbstractFileCollection.this.getBuildDependencies();
+            }
+
+            public Set<File> getFiles() {
+                Set<File> files = new LinkedHashSet<File>(AbstractFileCollection.this.getFiles());
+                files.removeAll(collection.getFiles());
+                return files;
+            }
+        };
+    }
+
+    public FileCollection add(FileCollection collection) throws UnsupportedOperationException {
+        throw new UnsupportedOperationException(String.format("%s does not allow modification.", getCapDisplayName()));
+    }
+
+    public void addToAntBuilder(Object builder, String nodeName, AntType type) {
+        if (type == AntType.ResourceCollection) {
+            addAsResourceCollection(builder, nodeName);
+        }
+        else if (type == AntType.FileSet) {
+            addAsFileSet(builder, nodeName);
+        }
+        else {
+            addAsMatchingTask(builder, nodeName);
+        }
+    }
+
+    protected void addAsMatchingTask(Object builder, String nodeName) {
+        new AntFileCollectionMatchingTaskBuilder(getAsFileTrees()).addToAntBuilder(builder, nodeName);
+    }
+
+    protected void addAsFileSet(Object builder, String nodeName) {
+        for (DefaultConfigurableFileTree fileTree : getAsFileTrees()) {
+            fileTree.addToAntBuilder(builder, nodeName, AntType.FileSet);
+        }
+    }
+
+    protected void addAsResourceCollection(Object builder, String nodeName) {
+        new AntFileCollectionBuilder(this).addToAntBuilder(builder, nodeName);
+    }
+
+    /**
+     * Returns this collection as a set of {@link DefaultConfigurableFileTree} instances.
+     */
+    protected Collection<DefaultConfigurableFileTree> getAsFileTrees() {
+        List<DefaultConfigurableFileTree> fileTrees = new ArrayList<DefaultConfigurableFileTree>();
+        for (File file : getFiles()) {
+            if (file.isFile()) {
+                DefaultConfigurableFileTree fileTree = new DefaultConfigurableFileTree(file.getParentFile(), null, null);
+                fileTree.include(new String[]{file.getName()});
+                fileTrees.add(fileTree);
+            }
+        }
+        return fileTrees;
+    }
+
+    public Object addToAntBuilder(Object node, String childNodeName) {
+        addToAntBuilder(node, childNodeName, AntType.ResourceCollection);
+        return this;
+    }
+
+    public boolean isEmpty() {
+        return getFiles().isEmpty();
+    }
+
+    public FileCollection stopExecutionIfEmpty() {
+        if (isEmpty()) {
+            throw new StopExecutionException(String.format("%s does not contain any files.", getCapDisplayName()));
+        }
+        return this;
+    }
+
+    public Object asType(Class<?> type) throws UnsupportedOperationException {
+        if (type.isAssignableFrom(Set.class)) {
+            return getFiles();
+        }
+        if (type.isAssignableFrom(List.class)) {
+            return new ArrayList<File>(getFiles());
+        }
+        if (type.isAssignableFrom(File[].class)) {
+            Set<File> files = getFiles();
+            return files.toArray(new File[files.size()]);
+        }
+        if (type.isAssignableFrom(File.class)) {
+            return getSingleFile();
+        }
+        if (type.isAssignableFrom(FileCollection.class)) {
+            return this;
+        }
+        if (type.isAssignableFrom(FileTree.class)) {
+            return getAsFileTree();
+        }
+        throw new UnsupportedOperationException(String.format(
+                "Cannot convert %s to type %s, as this type is not supported.", getDisplayName(),
+                type.getSimpleName()));
+    }
+
+    public TaskDependency getBuildDependencies() {
+        return new DefaultTaskDependency();
+    }
+
+    public FileTree getAsFileTree() {
+        return new CompositeFileTree() {
+            @Override
+            public TaskDependency getBuildDependencies() {
+                return AbstractFileCollection.this.getBuildDependencies();
+            }
+
+            @Override
+            protected void addSourceCollections(Collection<FileCollection> sources) {
+                TaskDependency taskDependency = AbstractFileCollection.this.getBuildDependencies();
+                for (File file : AbstractFileCollection.this.getFiles()) {
+                    sources.add(new SingletonFileTree(file, taskDependency));
+                }
+            }
+
+            @Override
+            public String getDisplayName() {
+                return AbstractFileCollection.this.getDisplayName();
+            }
+        };
+    }
+
+    public FileCollection filter(Closure filterClosure) {
+        return filter(Specs.convertClosureToSpec(filterClosure));
+    }
+
+    public FileCollection filter(final Spec<? super File> filterSpec) {
+        return new AbstractFileCollection() {
+            @Override
+            public String getDisplayName() {
+                return AbstractFileCollection.this.getDisplayName();
+            }
+
+            @Override
+            public TaskDependency getBuildDependencies() {
+                return AbstractFileCollection.this.getBuildDependencies();
+            }
+
+            public Set<File> getFiles() {
+                return Specs.filterIterable(AbstractFileCollection.this, filterSpec);
+            }
+        };
+    }
+
+    protected String getCapDisplayName() {
+        return StringUtils.capitalize(getDisplayName());
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResolver.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResolver.java
new file mode 100644
index 0000000..81cf729
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AbstractFileResolver.java
@@ -0,0 +1,209 @@
+/*
+ * 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 groovy.lang.Closure;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.PathValidation;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.OperatingSystem;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.concurrent.Callable;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public abstract class AbstractFileResolver implements FileResolver {
+    private static final Pattern URI_SCHEME = Pattern.compile("[a-zA-Z][a-zA-Z0-9+-\\.]*:.+");
+    private static final Pattern ENCODED_URI = Pattern.compile("%([0-9a-fA-F]{2})");
+
+    public FileResolver withBaseDir(Object path) {
+        return new BaseDirConverter(resolve(path));
+    }
+
+    public File resolve(Object path) {
+        return resolve(path, PathValidation.NONE);
+    }
+
+    public File resolve(Object path, PathValidation validation) {
+        File file = doResolve(path);
+        file = GFileUtils.canonicalise(file);
+        validate(file, validation);
+        return file;
+    }
+
+    public FileSource resolveLater(final Object path) {
+        return new FileSource() {
+            public File get() {
+                return resolve(path);
+            }
+        };
+    }
+
+    public URI resolveUri(Object path) {
+        return convertObjectToURI(path);
+    }
+
+    protected abstract File doResolve(Object path);
+
+    protected URI convertObjectToURI(Object path) {
+        Object object = unpack(path);
+        Object converted = convertToFileOrUri(object);
+        if (converted instanceof File) {
+            return resolve(converted).toURI();
+        }
+        return (URI) converted;
+    }
+
+    protected File convertObjectToFile(Object path) {
+        Object object = unpack(path);
+        if (object == null) {
+            return null;
+        }
+        Object converted = convertToFileOrUri(object);
+        if (converted instanceof File) {
+            return (File) converted;
+        }
+        throw new InvalidUserDataException(String.format("Cannot convert URL '%s' to a file.", converted));
+    }
+
+    private Object convertToFileOrUri(Object path) {
+        if (path instanceof File) {
+            return path;
+        }
+
+        if (path instanceof URL) {
+            try {
+                path = ((URL) path).toURI();
+            } catch (URISyntaxException e) {
+                throw new UncheckedIOException(e);
+            }
+        }
+
+        if (path instanceof URI) {
+            URI uri = (URI) path;
+            if (uri.getScheme().equals("file")) {
+                return new File(uri.getPath());
+            }
+            return uri;
+        }
+
+        String str = path.toString();
+        if (str.startsWith("file:")) {
+            return new File(uriDecode(str.substring(5)));
+        }
+
+        for (File file : File.listRoots()) {
+            String rootPath = file.getAbsolutePath();
+            String normalisedStr = str;
+            if (!OperatingSystem.current().isCaseSensitiveFileSystem()) {
+                rootPath = rootPath.toLowerCase();
+                normalisedStr = normalisedStr.toLowerCase();
+            }
+            if (normalisedStr.startsWith(rootPath) || normalisedStr.startsWith(rootPath.replace(File.separatorChar,
+                    '/'))) {
+                return new File(str);
+            }
+        }
+
+        // Check if string starts with a URI scheme
+        if (URI_SCHEME.matcher(str).matches()) {
+            try {
+                return new URI(str);
+            } catch (URISyntaxException e) {
+                throw new UncheckedIOException(e);
+            }
+        }
+
+        return new File(str);
+    }
+
+    private String uriDecode(String path) {
+        StringBuffer builder = new StringBuffer();
+        Matcher matcher = ENCODED_URI.matcher(path);
+        while (matcher.find()) {
+            String val = matcher.group(1);
+            matcher.appendReplacement(builder, String.valueOf((char) (Integer.parseInt(val, 16))));
+        }
+        matcher.appendTail(builder);
+        return builder.toString();
+    }
+
+    private Object unpack(Object path) {
+        Object current = path;
+        while (current != null) {
+            if (current instanceof Closure) {
+                current = ((Closure) current).call();
+            } else if (current instanceof Callable) {
+                try {
+                    current = ((Callable) current).call();
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+            } else if (current instanceof FileSource) {
+                return ((FileSource)current).get();
+            } else {
+                return current;
+            }
+        }
+        return null;
+    }
+
+    protected void validate(File file, PathValidation validation) {
+        switch (validation) {
+            case NONE:
+                break;
+            case EXISTS:
+                if (!file.exists()) {
+                    throw new InvalidUserDataException(String.format("File '%s' does not exist.", file));
+                }
+                break;
+            case FILE:
+                if (!file.exists()) {
+                    throw new InvalidUserDataException(String.format("File '%s' does not exist.", file));
+                }
+                if (!file.isFile()) {
+                    throw new InvalidUserDataException(String.format("File '%s' is not a file.", file));
+                }
+                break;
+            case DIRECTORY:
+                if (!file.exists()) {
+                    throw new InvalidUserDataException(String.format("Directory '%s' does not exist.", file));
+                }
+                if (!file.isDirectory()) {
+                    throw new InvalidUserDataException(String.format("Directory '%s' is not a directory.", file));
+                }
+                break;
+        }
+    }
+
+    public FileCollection resolveFiles(Object... paths) {
+        if (paths.length == 1 && paths[0] instanceof FileCollection) {
+            return (FileCollection) paths[0];
+        }
+        return new PathResolvingFileCollection(this, null, paths);
+    }
+
+    public FileTree resolveFilesAsTree(Object... paths) {
+        return resolveFiles(paths).getAsFileTree();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AbstractFileTree.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AbstractFileTree.java
new file mode 100644
index 0000000..1e49cd8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AbstractFileTree.java
@@ -0,0 +1,143 @@
+/*
+ * 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 groovy.lang.Closure;
+import org.codehaus.groovy.runtime.DefaultGroovyMethods;
+import org.gradle.api.file.*;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.util.PatternFilterable;
+import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.util.ConfigureUtil;
+
+import java.io.File;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public abstract class AbstractFileTree extends AbstractFileCollection implements FileTree {
+    public Set<File> getFiles() {
+        final Set<File> files = new LinkedHashSet<File>();
+        visit(new EmptyFileVisitor() {
+            public void visitFile(FileVisitDetails fileDetails) {
+                files.add(fileDetails.getFile());
+            }
+        });
+        return files;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        final AtomicBoolean found = new AtomicBoolean();
+        visit(new EmptyFileVisitor() {
+            public void visitFile(FileVisitDetails fileDetails) {
+                found.set(true);
+                fileDetails.stopVisiting();
+            }
+        });
+        return !found.get();
+    }
+
+    public FileTree matching(Closure filterConfigClosure) {
+        PatternSet patternSet = new PatternSet();
+        ConfigureUtil.configure(filterConfigClosure, patternSet);
+        return matching(patternSet);
+    }
+
+    public FileTree matching(PatternFilterable patterns) {
+        PatternSet patternSet = new PatternSet();
+        patternSet.copyFrom(patterns);
+        return new FilteredFileTree(this, patternSet.getAsSpec());
+    }
+
+    public Map<String, File> getAsMap() {
+        final Map<String, File> map = new LinkedHashMap<String, File>();
+        visit(new EmptyFileVisitor() {
+            public void visitFile(FileVisitDetails fileDetails) {
+                map.put(fileDetails.getRelativePath().getPathString(), fileDetails.getFile());
+            }
+        });
+        return map;
+    }
+
+    @Override
+    protected void addAsResourceCollection(Object builder, String nodeName) {
+        new AntFileTreeBuilder(getAsMap()).addToAntBuilder(builder, nodeName);
+    }
+
+    /**
+     * Visits all the files of this tree.
+     */
+    protected void visitAll() {
+        visit(new FileVisitor() {
+            public void visitDir(FileVisitDetails dirDetails) {
+                dirDetails.getFile();
+            }
+
+            public void visitFile(FileVisitDetails fileDetails) {
+                fileDetails.getFile();
+            }
+        });
+    }
+
+    @Override
+    public FileTree getAsFileTree() {
+        return this;
+    }
+
+    public FileTree plus(FileTree fileTree) {
+        return new UnionFileTree(this, fileTree);
+    }
+
+    public FileTree visit(Closure closure) {
+        return visit((FileVisitor) DefaultGroovyMethods.asType(closure, FileVisitor.class));
+    }
+
+    private static class FilteredFileTree extends AbstractFileTree {
+        private final AbstractFileTree fileTree;
+        private final Spec<FileTreeElement> spec;
+
+        public FilteredFileTree(AbstractFileTree fileTree, Spec<FileTreeElement> spec) {
+            this.fileTree = fileTree;
+            this.spec = spec;
+        }
+
+        @Override
+        public String getDisplayName() {
+            return fileTree.getDisplayName();
+        }
+
+        public FileTree visit(final FileVisitor visitor) {
+            fileTree.visit(new FileVisitor() {
+                public void visitDir(FileVisitDetails dirDetails) {
+                    if (spec.isSatisfiedBy(dirDetails)) {
+                        visitor.visitDir(dirDetails);
+                    }
+                }
+
+                public void visitFile(FileVisitDetails fileDetails) {
+                    if (spec.isSatisfiedBy(fileDetails)) {
+                        visitor.visitFile(fileDetails);
+                    }
+                }
+            });
+            return this;
+        }
+    }
+
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AbstractFileTreeElement.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AbstractFileTreeElement.java
new file mode 100644
index 0000000..d152a53
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AbstractFileTreeElement.java
@@ -0,0 +1,92 @@
+/*
+ * 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.file;
+
+import org.gradle.api.file.FileTreeElement;
+import org.gradle.api.GradleException;
+import org.gradle.api.UncheckedIOException;
+import org.apache.commons.io.IOUtils;
+
+import java.io.*;
+
+public abstract class AbstractFileTreeElement implements FileTreeElement {
+    public abstract String getDisplayName();
+
+    @Override
+    public String toString() {
+        return getDisplayName();
+    }
+
+    public String getName() {
+        return getRelativePath().getLastName();
+    }
+
+    public String getPath() {
+        return getRelativePath().getPathString();
+    }
+
+    public void copyTo(OutputStream outstr) {
+        try {
+            InputStream inputStream = open();
+            try {
+                IOUtils.copyLarge(inputStream, outstr);
+            } finally {
+                inputStream.close();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public boolean copyTo(File target) {
+        try {
+            if (!needsCopy(target)) {
+                return false;
+            }
+
+            target.getParentFile().mkdirs();
+
+            if (isDirectory()) {
+                target.mkdirs();
+            } else {
+                copyFile(target);
+            }
+            target.setLastModified(getLastModified());
+            return true;
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not copy %s to '%s'.", getDisplayName(), target), e);
+        }
+    }
+
+    private void copyFile(File target) throws IOException {
+        FileOutputStream outputStream = new FileOutputStream(target);
+        try {
+            copyTo(outputStream);
+        } finally {
+            outputStream.close();
+        }
+    }
+
+    boolean needsCopy(File dest) {
+        if (dest.exists()) {
+            if (getLastModified() == dest.lastModified()) {
+                return false;
+            }
+            // possibly add option to check file size too
+        }
+        return true;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AntFileCollectionBuilder.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AntFileCollectionBuilder.groovy
new file mode 100644
index 0000000..f7d47cf
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AntFileCollectionBuilder.groovy
@@ -0,0 +1,39 @@
+/*
+ * 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.api.internal.file
+
+import org.gradle.api.tasks.AntBuilderAware
+import org.gradle.api.file.FileCollection
+
+/**
+ * @author Hans Dockter
+ */
+class AntFileCollectionBuilder implements AntBuilderAware {
+    private final FileCollection files
+
+    AntFileCollectionBuilder(FileCollection files) {
+        this.files = files
+    }
+
+    def addToAntBuilder(node, String childNodeName = null) {
+        node."${childNodeName ?: 'resources'}"() {
+            files.each { File file ->
+                delegate.file(file: file.absolutePath)   
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AntFileCollectionMatchingTaskBuilder.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AntFileCollectionMatchingTaskBuilder.groovy
new file mode 100644
index 0000000..4bcbd54
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AntFileCollectionMatchingTaskBuilder.groovy
@@ -0,0 +1,40 @@
+/*
+ * 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.file
+
+import org.gradle.api.tasks.AntBuilderAware
+
+class AntFileCollectionMatchingTaskBuilder implements AntBuilderAware {
+    private final Iterable<DefaultConfigurableFileTree> fileTrees
+
+    def AntFileCollectionMatchingTaskBuilder(Iterable<DefaultConfigurableFileTree> fileTrees) {
+        this.fileTrees = fileTrees
+    }
+
+    def addToAntBuilder(Object node, String childNodeName) {
+        fileTrees.each {DefaultConfigurableFileTree fileTree ->
+            node."$childNodeName"(location: fileTree.dir)
+        }
+        node.or {
+            fileTrees.each {DefaultConfigurableFileTree fileTree ->
+                and {
+                    gradleBaseDirSelector(baseDir: fileTree.dir)
+                    fileTree.patternSet.addToAntBuilder(node, null)
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AntFileTreeBuilder.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AntFileTreeBuilder.groovy
new file mode 100644
index 0000000..ab0b326
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/AntFileTreeBuilder.groovy
@@ -0,0 +1,35 @@
+/*
+ * 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.api.internal.file
+
+import org.gradle.api.tasks.AntBuilderAware
+
+class AntFileTreeBuilder implements AntBuilderAware {
+    private final Map<String, File> files
+
+    def AntFileTreeBuilder(Map<String, File> files) {
+        this.files = files
+    }
+
+    def addToAntBuilder(node, String childNodeName = null) {
+        node."${childNodeName ?: 'resources'}"() {
+            files.each { String name, File file ->
+                gradleFileResource(file: file.absolutePath, name: name)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/BaseDirConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/BaseDirConverter.java
new file mode 100644
index 0000000..0496ebe
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/BaseDirConverter.java
@@ -0,0 +1,75 @@
+/*
+ * 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 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.List;
+
+/**
+ * @author Hans Dockter
+ */
+public class BaseDirConverter extends AbstractFileResolver {
+    private final File baseDir;
+
+    public BaseDirConverter(File baseDir) {
+        this.baseDir = baseDir;
+    }
+
+    public String resolveAsRelativePath(Object path) {
+        List<String> basePath = Arrays.asList(StringUtils.split(baseDir.getAbsolutePath(), "/" + File.separator));
+        File targetFile = resolve(path);
+        List<String> targetPath = new ArrayList<String>(Arrays.asList(StringUtils.split(targetFile.getAbsolutePath(),
+                "/" + File.separator)));
+
+        // Find and remove common prefix
+        int maxDepth = Math.min(basePath.size(), targetPath.size());
+        int prefixLen = 0;
+        while (prefixLen < maxDepth && basePath.get(prefixLen).equals(targetPath.get(prefixLen))) {
+            prefixLen++;
+        }
+        basePath = basePath.subList(prefixLen, basePath.size());
+        targetPath = targetPath.subList(prefixLen, targetPath.size());
+
+        for (int i = 0; i < basePath.size(); i++) {
+            targetPath.add(0, "..");
+        }
+        if (targetPath.isEmpty()) {
+            return ".";
+        }
+        return GUtil.join(targetPath, File.separator);
+    }
+
+    @Override
+    protected File doResolve(Object path) {
+        if (!GUtil.isTrue(path) || !GUtil.isTrue(baseDir)) {
+            throw new IllegalArgumentException(String.format(
+                    "Neither path nor baseDir may be null or empty string. path='%s' basedir='%s'", path, baseDir));
+        }
+
+        File file = convertObjectToFile(path);
+        if (!file.isAbsolute()) {
+            file = new File(baseDir, file.getPath());
+        }
+
+        return file;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/CompositeFileCollection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/CompositeFileCollection.java
new file mode 100644
index 0000000..3e48568
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/CompositeFileCollection.java
@@ -0,0 +1,149 @@
+/*
+ * 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 org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.internal.tasks.AbstractTaskDependency;
+import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.TaskDependency;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * A {@link org.gradle.api.file.FileCollection} which contains the union of the given source collections. Maintains file
+ * ordering.
+ */
+public abstract class CompositeFileCollection extends AbstractFileCollection {
+    public Set<File> getFiles() {
+        Set<File> files = new LinkedHashSet<File>();
+        for (FileCollection collection : getSourceCollections()) {
+            files.addAll(collection.getFiles());
+        }
+        return files;
+    }
+
+    @Override
+    public boolean contains(File file) {
+        for (FileCollection collection : getSourceCollections()) {
+            if (collection.contains(file)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        for (FileCollection collection : getSourceCollections()) {
+            if (!collection.isEmpty()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    protected void addAsResourceCollection(Object builder, String nodeName) {
+        for (FileCollection fileCollection : getSourceCollections()) {
+            fileCollection.addToAntBuilder(builder, nodeName, AntType.ResourceCollection);
+        }
+    }
+
+    @Override
+    protected Collection<DefaultConfigurableFileTree> getAsFileTrees() {
+        List<DefaultConfigurableFileTree> fileTree = new ArrayList<DefaultConfigurableFileTree>();
+        for (FileCollection source : getSourceCollections()) {
+            AbstractFileCollection collection = (AbstractFileCollection) source;
+            fileTree.addAll(collection.getAsFileTrees());
+        }
+        return fileTree;
+    }
+
+    @Override
+    public FileTree getAsFileTree() {
+        return new CompositeFileTree() {
+            @Override
+            protected void addSourceCollections(Collection<FileCollection> sources) {
+                for (FileCollection collection : CompositeFileCollection.this.getSourceCollections()) {
+                    sources.add(collection.getAsFileTree());
+                }
+            }
+
+            @Override
+            public String getDisplayName() {
+                return CompositeFileCollection.this.getDisplayName();
+            }
+
+            @Override
+            public TaskDependency getBuildDependencies() {
+                return CompositeFileCollection.this.getBuildDependencies();
+            }
+        };
+    }
+
+    @Override
+    public FileCollection filter(final Spec<? super File> filterSpec) {
+        return new CompositeFileCollection() {
+            @Override
+            protected void addSourceCollections(Collection<FileCollection> sources) {
+                for (FileCollection collection : CompositeFileCollection.this.getSourceCollections()) {
+                    sources.add(collection.filter(filterSpec));
+                }
+            }
+
+            @Override
+            public String getDisplayName() {
+                return CompositeFileCollection.this.getDisplayName();
+            }
+
+            @Override
+            public TaskDependency getBuildDependencies() {
+                return CompositeFileCollection.this.getBuildDependencies();
+            }
+        };
+    }
+
+    @Override
+    public TaskDependency getBuildDependencies() {
+        return new AbstractTaskDependency() {
+            public void resolve(TaskDependencyResolveContext context) {
+                addDependencies(context);
+            }
+        };
+    }
+
+    /**
+     * Allows subclasses to add additional dependencies
+     * @param context The context to add dependencies to.
+     */
+    protected void addDependencies(TaskDependencyResolveContext context) {
+        for (FileCollection collection : getSourceCollections()) {
+            context.add(collection);
+        }
+    }
+
+    protected List<? extends FileCollection> getSourceCollections() {
+        List<FileCollection> collections = new ArrayList<FileCollection>();
+        addSourceCollections(collections);
+        return collections;
+    }
+
+    protected abstract void addSourceCollections(Collection<FileCollection> sources);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/CompositeFileTree.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/CompositeFileTree.java
new file mode 100644
index 0000000..c2f33a1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/CompositeFileTree.java
@@ -0,0 +1,99 @@
+/*
+ * 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.file;
+
+import groovy.lang.Closure;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.FileVisitor;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.api.tasks.util.PatternFilterable;
+
+import java.util.Collection;
+import java.util.List;
+
+public abstract class CompositeFileTree extends CompositeFileCollection implements FileTree {
+    protected List<FileTree> getSourceCollections() {
+        return (List) super.getSourceCollections();
+    }
+
+    public FileTree plus(FileTree fileTree) {
+        return new UnionFileTree(this, fileTree);
+    }
+
+    public FileTree matching(Closure filterConfigClosure) {
+        return new FilteredFileTree(filterConfigClosure);
+    }
+
+    public FileTree matching(PatternFilterable patterns) {
+        return new FilteredFileTree(patterns);
+    }
+
+    public FileTree visit(Closure visitor) {
+        for (FileTree tree : getSourceCollections()) {
+            tree.visit(visitor);
+        }
+        return this;
+    }
+
+    public FileTree visit(FileVisitor visitor) {
+        for (FileTree tree : getSourceCollections()) {
+            tree.visit(visitor);
+        }
+        return this;
+    }
+
+    @Override
+    public FileTree getAsFileTree() {
+        return this;
+    }
+
+    private class FilteredFileTree extends CompositeFileTree {
+        private final Closure closure;
+        private final PatternFilterable patterns;
+
+        public FilteredFileTree(Closure closure) {
+            this.closure = closure;
+            patterns = null;
+        }
+
+        public FilteredFileTree(PatternFilterable patterns) {
+            this.patterns = patterns;
+            closure = null;
+        }
+
+        @Override
+        public String getDisplayName() {
+            return CompositeFileTree.this.getDisplayName();
+        }
+
+        @Override
+        public TaskDependency getBuildDependencies() {
+            return CompositeFileTree.this.getBuildDependencies();
+        }
+
+        @Override
+        protected void addSourceCollections(Collection<FileCollection> sources) {
+            for (FileTree set : CompositeFileTree.this.getSourceCollections()) {
+                if (closure != null) {
+                    sources.add(set.matching(closure));
+                } else {
+                    sources.add(set.matching(patterns));
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DefaultConfigurableFileTree.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DefaultConfigurableFileTree.java
new file mode 100644
index 0000000..22ca21d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DefaultConfigurableFileTree.java
@@ -0,0 +1,221 @@
+/*
+ * 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.file;
+
+import groovy.lang.Closure;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.file.*;
+import org.gradle.api.internal.file.copy.CopyActionImpl;
+import org.gradle.api.internal.file.copy.FileCopyActionImpl;
+import org.gradle.api.internal.file.copy.FileCopySpecVisitor;
+import org.gradle.api.internal.tasks.DefaultTaskDependency;
+import org.gradle.api.internal.tasks.TaskResolver;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.api.tasks.util.PatternFilterable;
+import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.util.ConfigureUtil;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultConfigurableFileTree extends AbstractFileTree implements ConfigurableFileTree {
+    private PatternSet patternSet = new PatternSet();
+    private Object dir;
+    private final FileResolver resolver;
+    private final DefaultTaskDependency buildDependency;
+    private TaskResolver taskResolver;
+
+    public DefaultConfigurableFileTree(Object dir, FileResolver resolver, TaskResolver taskResolver) {
+        this(Collections.singletonMap("dir", dir), resolver, taskResolver);
+    }
+
+    public DefaultConfigurableFileTree(Map<String, ?> args, FileResolver resolver, TaskResolver taskResolver) {
+        this.resolver = resolver != null ? resolver : new IdentityFileResolver();
+        ConfigureUtil.configureByMap(args, this);
+        buildDependency = new DefaultTaskDependency(taskResolver);
+    }
+
+    public PatternSet getPatternSet() {
+        return patternSet;
+    }
+
+    public void setPatternSet(PatternSet patternSet) {
+        this.patternSet = patternSet;
+    }
+
+    public DefaultConfigurableFileTree setDir(Object dir) {
+        from(dir);
+        return this;
+    }
+
+    public File getDir() {
+        if (dir == null) {
+            throw new InvalidUserDataException("A base directory must be specified in the task or via a method argument!");
+        }
+        return resolver.resolve(dir);
+    }
+
+    public DefaultConfigurableFileTree from(Object dir) {
+        this.dir = dir;
+        return this;
+    }
+
+    public String getDisplayName() {
+        return String.format("file set '%s'", dir);
+    }
+
+    public FileTree matching(PatternFilterable patterns) {
+        PatternSet patternSet = this.patternSet.intersect();
+        patternSet.copyFrom(patterns);
+        DefaultConfigurableFileTree filtered = new DefaultConfigurableFileTree(getDir(), resolver, taskResolver);
+        filtered.setPatternSet(patternSet);
+        return filtered;
+    }
+
+    public DefaultConfigurableFileTree visit(FileVisitor visitor) {
+        DefaultDirectoryWalker walker = new DefaultDirectoryWalker(visitor);
+        walker.match(patternSet).start(getDir());
+        return this;
+    }
+
+    public WorkResult copy(Closure closure) {
+        CopyActionImpl action = new FileCopyActionImpl(resolver, new FileCopySpecVisitor());
+        action.from(this);
+        ConfigureUtil.configure(closure, action);
+        action.execute();
+        return action;
+    }
+
+    public Set<String> getIncludes() {
+        return patternSet.getIncludes();
+    }
+
+    public DefaultConfigurableFileTree setIncludes(Iterable<String> includes) {
+        patternSet.setIncludes(includes);
+        return this;
+    }
+
+    public Set<String> getExcludes() {
+        return patternSet.getExcludes();
+    }
+
+    public DefaultConfigurableFileTree setExcludes(Iterable<String> excludes) {
+        patternSet.setExcludes(excludes);
+        return this;
+    }
+
+    public DefaultConfigurableFileTree include(String ... includes) {
+        patternSet.include(includes);
+        return this;
+    }
+
+    public DefaultConfigurableFileTree include(Iterable<String> includes) {
+        patternSet.include(includes);
+        return this;
+    }
+
+    public DefaultConfigurableFileTree include(Closure includeSpec) {
+        patternSet.include(includeSpec);
+        return this;
+    }
+
+    public DefaultConfigurableFileTree include(Spec<FileTreeElement> includeSpec) {
+        patternSet.include(includeSpec);
+        return this;
+    }
+
+    public DefaultConfigurableFileTree exclude(String ... excludes) {
+        patternSet.exclude(excludes);
+        return this;
+    }
+
+    public DefaultConfigurableFileTree exclude(Iterable<String> excludes) {
+        patternSet.exclude(excludes);
+        return this;
+    }
+
+    public DefaultConfigurableFileTree exclude(Spec<FileTreeElement> excludeSpec) {
+        patternSet.exclude(excludeSpec);
+        return this;
+    }
+
+    public DefaultConfigurableFileTree exclude(Closure excludeSpec) {
+        patternSet.exclude(excludeSpec);
+        return this;
+    }
+
+    public boolean contains(File file) {
+        String prefix = getDir().getAbsolutePath() + File.separator;
+        if (!file.getAbsolutePath().startsWith(prefix)) {
+            return false;
+        }
+        if (!file.isFile()) {
+            return false;
+        }
+        RelativePath path = new RelativePath(true, file.getAbsolutePath().substring(prefix.length()).split(
+                Pattern.quote(File.separator)));
+        return patternSet.getAsSpec().isSatisfiedBy(new DefaultFileTreeElement(file, path));
+    }
+
+    protected void addAsFileSet(Object builder, String nodeName) {
+        File dir = getDir();
+        if (!dir.exists()) {
+            return;
+        }
+        doAddFileSet(builder, dir, nodeName);
+    }
+
+    protected void addAsResourceCollection(Object builder, String nodeName) {
+        addAsFileSet(builder, nodeName);
+    }
+
+    protected Collection<DefaultConfigurableFileTree> getAsFileTrees() {
+        return getDir().exists() ? Collections.singletonList(this) : Collections.<DefaultConfigurableFileTree>emptyList();
+    }
+
+    protected Object doAddFileSet(Object builder, File dir, String nodeName) {
+        new FileSetHelper().addToAntBuilder(builder, dir, patternSet, nodeName);
+        return this;
+    }
+
+    public ConfigurableFileTree builtBy(Object... tasks) {
+        buildDependency.add(tasks);
+        return this;
+    }
+
+    public Set<Object> getBuiltBy() {
+        return buildDependency.getValues();
+    }
+
+    public ConfigurableFileTree setBuiltBy(Iterable<?> tasks) {
+        buildDependency.setValues(tasks);
+        return this;
+    }
+
+    @Override
+    public TaskDependency getBuildDependencies() {
+        return buildDependency;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DefaultDirectoryWalker.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DefaultDirectoryWalker.java
new file mode 100644
index 0000000..ed21488
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DefaultDirectoryWalker.java
@@ -0,0 +1,149 @@
+/*
+ * 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 org.gradle.api.GradleException;
+import org.gradle.api.file.FileTreeElement;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.FileVisitor;
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.util.GFileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Directory walker supporting {@link Spec}s for includes and excludes.
+ * The file system is traversed breadth first - all files in a directory will be
+ * visited before any child directory is visited.
+ *
+ * A file or directory will only be visited if it matches all includes and no
+ * excludes.
+ *
+ * @author Steve Appling
+ */
+public class DefaultDirectoryWalker implements DirectoryWalker {
+    private static Logger logger = LoggerFactory.getLogger(DefaultDirectoryWalker.class);
+
+    private FileVisitor visitor;
+    private Spec<FileTreeElement> spec;
+    private boolean depthFirst;
+
+    public DefaultDirectoryWalker(FileVisitor visitor) {
+        spec = Specs.satisfyAll();
+        this.visitor = visitor;
+    }
+
+    public DefaultDirectoryWalker match(PatternSet patternSet) {
+        spec = patternSet.getAsSpec();
+        return this;
+    }
+
+    /**
+     * Process the specified file or directory.  Note that the startFile parameter
+     * may be either a directory or a file.  If it is a directory, then it's contents
+     * (but not the directory itself) will be checked with isAllowed and notified to
+     * the listener.  If it is a file, the file will be checked and notified.
+     */
+    public void start(File startFile) {
+        File root = GFileUtils.canonicalise(startFile);
+        AtomicBoolean stopFlag = new AtomicBoolean();
+        if (root.exists()) {
+            if (root.isFile()) {
+                processSingleFile(root, stopFlag);
+            } else {
+               walkDir(root, new RelativePath(false), stopFlag);
+            }
+        } else {
+            logger.info("file or directory '"+startFile.toString()+"', not found");
+        }
+    }
+
+    private void processSingleFile(File file, AtomicBoolean stopFlag) {
+        RelativePath path = new RelativePath(true, file.getName());
+        FileVisitDetailsImpl details = new FileVisitDetailsImpl(file, path, stopFlag);
+        if (isAllowed(details)) {
+            visitor.visitFile(details);
+        }
+    }
+
+    private void walkDir(File file, RelativePath path, AtomicBoolean stopFlag) {
+        File[] children = file.listFiles();
+        if (children == null) {
+            if (file.isDirectory() && !file.canRead()) {
+                throw new GradleException(String.format("Could not list contents of directory '%s' as it is not readable.", file));
+            }
+            // else, might be a link which points to nothing, or has been removed while we're visiting, or ...
+            throw new GradleException(String.format("Could not list contents of '%s'.", file));
+        }
+        List<FileVisitDetailsImpl> dirs = new ArrayList<FileVisitDetailsImpl>();
+        for (int i = 0; !stopFlag.get() && i < children.length; i++) {
+            File child = children[i];
+            boolean isFile = child.isFile();
+            RelativePath childPath = path.append(isFile, child.getName());
+            FileVisitDetailsImpl details = new FileVisitDetailsImpl(child, childPath, stopFlag);
+            if (isAllowed(details)) {
+                if (isFile) {
+                    visitor.visitFile(details);
+                } else {
+                    dirs.add(details);
+                }
+            }
+        }
+
+        // now handle dirs
+        for (int i = 0; !stopFlag.get() && i < dirs.size(); i++) {
+            FileVisitDetailsImpl dir = dirs.get(i);
+            if (depthFirst) {
+                walkDir(dir.getFile(), dir.getRelativePath(), stopFlag);
+                visitor.visitDir(dir);
+            } else {
+                visitor.visitDir(dir);
+                walkDir(dir.getFile(), dir.getRelativePath(), stopFlag);
+            }
+        }
+    }
+
+    boolean isAllowed(FileTreeElement element) {
+        return spec.isSatisfiedBy(element);
+    }
+
+    public DirectoryWalker depthFirst() {
+        depthFirst = true;
+        return this;
+    }
+
+    private static class FileVisitDetailsImpl extends DefaultFileTreeElement implements FileVisitDetails {
+        private final AtomicBoolean stop;
+
+        private FileVisitDetailsImpl(File file, RelativePath relativePath, AtomicBoolean stop) {
+            super(file, relativePath);
+            this.stop = stop;
+        }
+
+        public void stopVisiting() {
+            stop.set(true);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DefaultFileOperations.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DefaultFileOperations.java
new file mode 100644
index 0000000..ea37c1e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DefaultFileOperations.java
@@ -0,0 +1,146 @@
+/*
+ * 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 groovy.lang.Closure;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.PathValidation;
+import org.gradle.api.file.*;
+import org.gradle.api.internal.file.archive.TarFileTree;
+import org.gradle.api.internal.file.archive.ZipFileTree;
+import org.gradle.api.internal.file.copy.*;
+import org.gradle.api.internal.tasks.TaskResolver;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.process.ExecResult;
+import org.gradle.process.internal.DefaultExecAction;
+import org.gradle.process.internal.DefaultJavaExecAction;
+import org.gradle.process.internal.ExecAction;
+import org.gradle.process.internal.JavaExecAction;
+import org.gradle.util.ConfigureUtil;
+
+import java.io.File;
+import java.net.URI;
+import java.util.Collections;
+import java.util.Map;
+
+import static org.gradle.util.ConfigureUtil.configure;
+
+public class DefaultFileOperations implements FileOperations {
+    private final FileResolver fileResolver;
+    private final TaskResolver taskResolver;
+    private final TemporaryFileProvider temporaryFileProvider;
+    private DeleteAction deleteAction;
+
+    public DefaultFileOperations(FileResolver fileResolver, TaskResolver taskResolver, TemporaryFileProvider temporaryFileProvider) {
+        this.fileResolver = fileResolver;
+        this.taskResolver = taskResolver;
+        this.temporaryFileProvider = temporaryFileProvider;
+        this.deleteAction = new DeleteActionImpl(fileResolver);
+    }
+
+    public File file(Object path) {
+        return fileResolver.resolve(path);
+    }
+
+    public File file(Object path, PathValidation validation) {
+        return fileResolver.resolve(path, validation);
+    }
+
+    public URI uri(Object path) {
+        return fileResolver.resolveUri(path);
+    }
+    
+    public ConfigurableFileCollection files(Object... paths) {
+        return new PathResolvingFileCollection(fileResolver, taskResolver, paths);
+    }
+
+    public ConfigurableFileCollection files(Object paths, Closure configureClosure) {
+        return configure(configureClosure, files(paths));
+    }
+
+    public ConfigurableFileTree fileTree(Object baseDir) {
+        return new DefaultConfigurableFileTree(baseDir, fileResolver, taskResolver);
+    }
+
+    public DefaultConfigurableFileTree fileTree(Map<String, ?> args) {
+        return new DefaultConfigurableFileTree(args, fileResolver, taskResolver);
+    }
+
+    public DefaultConfigurableFileTree fileTree(Closure closure) {
+        return configure(closure, new DefaultConfigurableFileTree(Collections.emptyMap(), fileResolver, taskResolver));
+    }
+
+    public FileTree zipTree(Object zipPath) {
+        return new ZipFileTree(file(zipPath), getExpandDir());
+    }
+
+    public FileTree tarTree(Object tarPath) {
+        return new TarFileTree(file(tarPath), getExpandDir());
+    }
+
+    private File getExpandDir() {
+        return temporaryFileProvider.newTemporaryFile("expandedArchives");
+    }
+
+    public String relativePath(Object path) {
+        return fileResolver.resolveAsRelativePath(path);
+    }
+
+    public File mkdir(Object path) {
+        File dir = fileResolver.resolve(path);
+        if (dir.isFile()) {
+            throw new InvalidUserDataException(String.format("Can't create directory. The path=%s points to an existing file.", path));
+        }
+        dir.mkdirs();
+        return dir;
+    }
+
+    public boolean delete(Object... paths) {
+        return deleteAction.delete(paths);
+    }
+
+    public WorkResult copy(Closure closure) {
+        CopyActionImpl action = configure(closure, new FileCopyActionImpl(fileResolver, new FileCopySpecVisitor()));
+        action.execute();
+        return action;
+    }
+
+    public CopySpec copySpec(Closure closure) {
+        return configure(closure, new CopySpecImpl(fileResolver));
+    }
+
+    public FileResolver getFileResolver() {
+        return fileResolver;
+    }
+
+    public DeleteAction getDeleteAction() {
+        return deleteAction;
+    }
+
+    public void setDeleteAction(DeleteAction deleteAction) {
+        this.deleteAction = deleteAction;
+    }
+
+    public ExecResult javaexec(Closure cl) {
+        JavaExecAction javaExecAction = ConfigureUtil.configure(cl, new DefaultJavaExecAction(fileResolver));
+        return javaExecAction.execute();
+    }
+
+    public ExecResult exec(Closure cl) {
+        ExecAction execAction = ConfigureUtil.configure(cl, new DefaultExecAction(fileResolver));
+        return execAction.execute();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DefaultFileTreeElement.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DefaultFileTreeElement.java
new file mode 100644
index 0000000..c9a9522
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DefaultFileTreeElement.java
@@ -0,0 +1,60 @@
+/*
+ * 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.file;
+
+import org.gradle.api.file.RelativePath;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.io.InputStream;
+
+public class DefaultFileTreeElement extends AbstractFileTreeElement {
+    private final File file;
+    private final RelativePath relativePath;
+
+    public DefaultFileTreeElement(File file, RelativePath relativePath) {
+        this.file = file;
+        this.relativePath = relativePath;
+    }
+
+    public File getFile() {
+        return file;
+    }
+
+    public String getDisplayName() {
+        return String.format("file '%s'", file);
+    }
+
+    public long getLastModified() {
+        return file.lastModified();
+    }
+
+    public long getSize() {
+        return file.length();
+    }
+
+    public boolean isDirectory() {
+        return file.isDirectory();
+    }
+
+    public InputStream open() {
+        return GFileUtils.openInputStream(file);
+    }
+
+    public RelativePath getRelativePath() {
+        return relativePath;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DefaultSourceDirectorySet.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DefaultSourceDirectorySet.java
new file mode 100644
index 0000000..affde9e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DefaultSourceDirectorySet.java
@@ -0,0 +1,157 @@
+/*
+ * 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.file;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.file.FileTreeElement;
+import org.gradle.api.tasks.util.PatternFilterable;
+import org.gradle.api.tasks.util.PatternSet;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import groovy.lang.Closure;
+
+public class DefaultSourceDirectorySet extends CompositeFileTree implements SourceDirectorySet {
+    private final PathResolvingFileCollection srcDirs;
+    private final String displayName;
+    private final FileResolver resolver;
+    private final PatternFilterable patterns = new PatternSet();
+    private final PatternFilterable filter = new PatternSet();
+
+    public DefaultSourceDirectorySet(FileResolver fileResolver) {
+        this("source set", fileResolver);
+    }
+
+    public DefaultSourceDirectorySet(String displayName, FileResolver fileResolver) {
+        this.displayName = displayName;
+        this.resolver = fileResolver;
+        srcDirs = new PathResolvingFileCollection(fileResolver, null);
+    }
+
+    public Set<File> getSrcDirs() {
+        return srcDirs.getFiles();
+    }
+
+    public Set<String> getIncludes() {
+        return patterns.getIncludes();
+    }
+
+    public Set<String> getExcludes() {
+        return patterns.getExcludes();
+    }
+
+    public PatternFilterable setIncludes(Iterable<String> includes) {
+        patterns.setIncludes(includes);
+        return this;
+    }
+
+    public PatternFilterable setExcludes(Iterable<String> excludes) {
+        patterns.setExcludes(excludes);
+        return this;
+    }
+
+    public PatternFilterable include(String... includes) {
+        patterns.include(includes);
+        return this;
+    }
+
+    public PatternFilterable include(Iterable<String> includes) {
+        patterns.include(includes);
+        return this;
+    }
+
+    public PatternFilterable include(Spec<FileTreeElement> includeSpec) {
+        patterns.include(includeSpec);
+        return this;
+    }
+
+    public PatternFilterable include(Closure includeSpec) {
+        patterns.include(includeSpec);
+        return this;
+    }
+
+    public PatternFilterable exclude(Iterable<String> excludes) {
+        patterns.exclude(excludes);
+        return this;
+    }
+
+    public PatternFilterable exclude(String... excludes) {
+        patterns.exclude(excludes);
+        return this;
+    }
+
+    public PatternFilterable exclude(Spec<FileTreeElement> excludeSpec) {
+        patterns.exclude(excludeSpec);
+        return this;
+    }
+
+    public PatternFilterable exclude(Closure excludeSpec) {
+        patterns.exclude(excludeSpec);
+        return this;
+    }
+
+    public PatternFilterable getFilter() {
+        return filter;
+    }
+
+    @Override
+    protected void addSourceCollections(Collection<FileCollection> sources) {
+        for (File sourceDir : getExistingSourceDirs()) {
+            DefaultConfigurableFileTree fileset = new DefaultConfigurableFileTree(sourceDir, resolver, null);
+            fileset.getPatternSet().copyFrom(patterns);
+            sources.add(fileset.matching(filter));
+        }
+    }
+
+    @Override
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    protected Set<File> getExistingSourceDirs() {
+        Set<File> existingSourceDirs = new LinkedHashSet<File>();
+        for (File srcDir : srcDirs) {
+            if (srcDir.isDirectory()) {
+                existingSourceDirs.add(srcDir);
+            } else if (srcDir.exists()) {
+                throw new InvalidUserDataException(String.format("Source directory '%s' is not a directory.", srcDir));
+            }
+        }
+        return existingSourceDirs;
+    }
+
+    public SourceDirectorySet srcDir(Object srcDir) {
+        srcDirs.from(srcDir);
+        return this;
+    }
+
+    public SourceDirectorySet srcDirs(Object... srcDirs) {
+        this.srcDirs.from(srcDirs);
+        return this;
+    }
+
+    public SourceDirectorySet setSrcDirs(Iterable<Object> srcPaths) {
+        srcDirs.clear();
+        srcDirs.from(srcPaths);
+        return this;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProvider.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProvider.java
new file mode 100644
index 0000000..48d9a58
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProvider.java
@@ -0,0 +1,34 @@
+/*
+ * 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 org.gradle.util.GFileUtils;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+
+public class DefaultTemporaryFileProvider implements TemporaryFileProvider {
+    private final FileSource baseDir;
+
+    public DefaultTemporaryFileProvider(FileSource baseDir) {
+        this.baseDir = baseDir;
+    }
+
+    public File newTemporaryFile(String... path) {
+        return GFileUtils.canonicalise(new File(baseDir.get(), GUtil.join(path, "/")));
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DirectoryWalker.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DirectoryWalker.java
new file mode 100644
index 0000000..2b09b93
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/DirectoryWalker.java
@@ -0,0 +1,26 @@
+/*
+ * 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.file;
+
+import org.gradle.api.tasks.util.PatternSet;
+
+import java.io.File;
+
+public interface DirectoryWalker {
+    DirectoryWalker match(PatternSet patternSet);
+
+    void start(File baseDir);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/FileOperations.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/FileOperations.java
new file mode 100644
index 0000000..18cf477
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/FileOperations.java
@@ -0,0 +1,67 @@
+/*
+ * 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 groovy.lang.Closure;
+import org.gradle.api.PathValidation;
+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.tasks.WorkResult;
+import org.gradle.process.ExecResult;
+
+import java.io.File;
+import java.net.URI;
+import java.util.Map;
+
+public interface FileOperations {
+    File file(Object path);
+
+    File file(Object path, PathValidation validation);
+
+    URI uri(Object path);
+
+    FileResolver getFileResolver();
+
+    String relativePath(Object path);
+
+    ConfigurableFileCollection files(Object... paths);
+
+    ConfigurableFileCollection files(Object paths, Closure configureClosure);
+
+    ConfigurableFileTree fileTree(Object baseDir);
+
+    ConfigurableFileTree fileTree(Map<String, ?> args);
+
+    ConfigurableFileTree fileTree(Closure closure);
+
+    FileTree zipTree(Object zipPath);
+
+    FileTree tarTree(Object tarPath);
+
+    CopySpec copySpec(Closure closure);
+
+    WorkResult copy(Closure closure);
+
+    File mkdir(Object path);
+
+    boolean delete(Object... paths);
+
+    ExecResult javaexec(Closure cl);
+
+    ExecResult exec(Closure cl);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/FileResolver.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/FileResolver.java
new file mode 100644
index 0000000..6f75df1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/FileResolver.java
@@ -0,0 +1,45 @@
+/*
+ * 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 org.gradle.api.PathValidation;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+
+import java.io.File;
+import java.net.URI;
+
+public interface FileResolver {
+    File resolve(Object path);
+
+    File resolve(Object path, PathValidation validation);
+
+    FileSource resolveLater(Object path);
+    
+    FileCollection resolveFiles(Object... paths);
+
+    FileTree resolveFilesAsTree(Object... paths);
+
+    URI resolveUri(Object path);
+
+    String resolveAsRelativePath(Object path);
+
+    /**
+     * Creates a new resolver with the given base directory.
+     * @param path The path for the base directory. Resolved relative to the current base directory (if any).
+     */
+    FileResolver withBaseDir(Object path);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/FileSetHelper.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/FileSetHelper.groovy
new file mode 100644
index 0000000..487ea51
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/FileSetHelper.groovy
@@ -0,0 +1,26 @@
+/*
+ * 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.file
+
+import org.gradle.api.tasks.util.PatternSet
+
+class FileSetHelper {
+    def addToAntBuilder(def node, File dir, PatternSet patternSet, String nodeName) {
+        node."${nodeName ?: 'fileset'}"(dir: dir) {
+            patternSet.addToAntBuilder(node, null)
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/FileSource.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/FileSource.java
new file mode 100644
index 0000000..07ef62d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/FileSource.java
@@ -0,0 +1,23 @@
+/*
+ * 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/gradle-core/src/main/groovy/org/gradle/api/internal/file/IdentityFileResolver.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/IdentityFileResolver.java
new file mode 100644
index 0000000..ef3be8c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/IdentityFileResolver.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+/**
+ * FileResolver that uses the file provided to it or constructs one from the toString of the provided object. Used in
+ * cases where a FileResolver is needed by the infrastructure, but no base directory can be known.
+ *
+ * @author Steve Appling
+ */
+public class IdentityFileResolver extends AbstractFileResolver {
+    @Override
+    protected File doResolve(Object path) {
+        File file = convertObjectToFile(path);
+        if (!file.isAbsolute()) {
+            throw new UnsupportedOperationException(String.format("Cannot convert relative path %s to an absolute file.", path));
+        }
+        return file;
+    }
+
+    public String resolveAsRelativePath(Object path) {
+        throw new UnsupportedOperationException(String.format("Cannot convert path %s to a relative path.", path));
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/MapFileTree.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/MapFileTree.java
new file mode 100644
index 0000000..dbb59c3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/MapFileTree.java
@@ -0,0 +1,154 @@
+/*
+ * 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.file;
+
+import groovy.lang.Closure;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.FileVisitor;
+import org.gradle.api.file.RelativePath;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A {@link FileTree} which is composed using a mapping from relative path to file source.
+ */
+public class MapFileTree extends AbstractFileTree {
+    private final Map<RelativePath, Closure> elements = new LinkedHashMap<RelativePath, Closure>();
+    private final File tmpDir;
+
+    public MapFileTree(File tmpDir) {
+        this.tmpDir = tmpDir;
+    }
+
+    public String getDisplayName() {
+        return "file tree";
+    }
+
+    @Override
+    protected Collection<DefaultConfigurableFileTree> getAsFileTrees() {
+        visitAll();
+        return Collections.singleton(new DefaultConfigurableFileTree(tmpDir, null, null));
+    }
+
+    public FileTree visit(FileVisitor visitor) {
+        AtomicBoolean stopFlag = new AtomicBoolean();
+        Visit visit = new Visit(visitor, stopFlag);
+        for (Map.Entry<RelativePath, Closure> entry : elements.entrySet()) {
+            if (stopFlag.get()) {
+                break;
+            }
+            RelativePath path = entry.getKey();
+            Closure generator = entry.getValue();
+            visit.visit(path, generator);
+        }
+        return this;
+    }
+
+
+    /**
+     * Adds an element to this tree. The given closure is passed an OutputStream which it can use to write the content
+     * of the element to.
+     */
+    public void add(String path, Closure contentClosure) {
+        elements.put(RelativePath.parse(true, path), contentClosure);
+    }
+
+    private class Visit {
+        private final Set<RelativePath> visitedDirs = new LinkedHashSet<RelativePath>();
+        private final FileVisitor visitor;
+        private final AtomicBoolean stopFlag;
+
+        public Visit(FileVisitor visitor, AtomicBoolean stopFlag) {
+            this.visitor = visitor;
+            this.stopFlag = stopFlag;
+        }
+
+        private void visitDirs(RelativePath path, FileVisitor visitor) {
+            if (path == null || path.getParent() == null || !visitedDirs.add(path)) {
+                return;
+            }
+
+            visitDirs(path.getParent(), visitor);
+            visitor.visitDir(new FileVisitDetailsImpl(path, null, stopFlag));
+        }
+
+        public void visit(RelativePath path, Closure generator) {
+            visitDirs(path.getParent(), visitor);
+            visitor.visitFile(new FileVisitDetailsImpl(path, generator, stopFlag));
+        }
+    }
+
+    private class FileVisitDetailsImpl extends AbstractFileTreeElement implements FileVisitDetails {
+        private final RelativePath path;
+        private final Closure generator;
+        private final long lastModified;
+        private final AtomicBoolean stopFlag;
+        private File file;
+
+        public FileVisitDetailsImpl(RelativePath path, Closure generator, AtomicBoolean stopFlag) {
+            this.path = path;
+            this.generator = generator;
+            this.stopFlag = stopFlag;
+            // round to nearest second
+            lastModified = System.currentTimeMillis() / 1000 * 1000;
+        }
+
+        public String getDisplayName() {
+            return path.toString();
+        }
+
+        public void stopVisiting() {
+            stopFlag.set(true);
+        }
+
+        public File getFile() {
+            if (file == null) {
+                file = path.getFile(tmpDir);
+                copyTo(file);
+            }
+            return file;
+        }
+
+        public boolean isDirectory() {
+            return !path.isFile();
+        }
+
+        public long getLastModified() {
+            return lastModified;
+        }
+
+        public long getSize() {
+            return getFile().length();
+        }
+
+        public void copyTo(OutputStream outstr) {
+            generator.call(outstr);
+        }
+
+        public InputStream open() {
+            throw new UnsupportedOperationException();
+        }
+
+        public RelativePath getRelativePath() {
+            return path;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/PathResolvingFileCollection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/PathResolvingFileCollection.java
new file mode 100644
index 0000000..efe9dd7
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/PathResolvingFileCollection.java
@@ -0,0 +1,145 @@
+/*
+ * 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 groovy.lang.Closure;
+import org.gradle.api.file.ConfigurableFileCollection;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.tasks.DefaultTaskDependency;
+import org.gradle.api.internal.tasks.TaskDependencyResolveContext;
+import org.gradle.api.internal.tasks.TaskResolver;
+import org.gradle.util.UncheckedException;
+
+import java.io.File;
+import java.util.*;
+import java.util.concurrent.Callable;
+
+/**
+ * A {@link org.gradle.api.file.FileCollection} which resolves a set of paths relative to a {@link FileResolver}.
+ */
+public class PathResolvingFileCollection extends CompositeFileCollection implements ConfigurableFileCollection {
+    private final List<Object> files;
+    private final String displayName;
+    private final FileResolver resolver;
+    private final DefaultTaskDependency buildDependency;
+
+    public PathResolvingFileCollection(FileResolver fileResolver, TaskResolver taskResolver, Object... files) {
+        this("file collection", fileResolver, taskResolver, files);
+    }
+
+    public PathResolvingFileCollection(String displayName, FileResolver fileResolver, TaskResolver taskResolver, Object... files) {
+        this.displayName = displayName;
+        this.resolver = fileResolver;
+        this.files = new ArrayList<Object>(Arrays.asList(files));
+        buildDependency = new DefaultTaskDependency(taskResolver);
+    }
+
+    public PathResolvingFileCollection clear() {
+        files.clear();
+        return this;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public List<?> getSources() {
+        return files;
+    }
+
+    public ConfigurableFileCollection from(Object... paths) {
+        for (Object path : paths) {
+            files.add(path);
+        }
+        return this;
+    }
+
+    public ConfigurableFileCollection builtBy(Object... tasks) {
+        buildDependency.add(tasks);
+        return this;
+    }
+
+    public Set<Object> getBuiltBy() {
+        return buildDependency.getValues();
+    }
+
+    public ConfigurableFileCollection setBuiltBy(Iterable<?> tasks) {
+        buildDependency.setValues(tasks);
+        return this;
+    }
+
+    @Override
+    protected void addDependencies(TaskDependencyResolveContext context) {
+        super.addDependencies(context);
+        context.add(buildDependency);
+    }
+
+    @Override
+    protected void addSourceCollections(Collection<FileCollection> sources) {
+        for (Object element : resolveToFilesAndFileCollections()) {
+            if (element instanceof FileCollection) {
+                FileCollection collection = (FileCollection) element;
+                sources.add(collection);
+            } else {
+                File file = (File) element;
+                sources.add(new SingletonFileCollection(file, buildDependency));
+            }
+        }
+    }
+
+    /**
+     * Converts everything in this collection which is not a FileCollection to Files, but leave FileCollections
+     * unresolved.
+     */
+    private List<Object> resolveToFilesAndFileCollections() {
+        List<Object> result = new ArrayList<Object>();
+        LinkedList<Object> queue = new LinkedList<Object>();
+        queue.addAll(files);
+        while (!queue.isEmpty()) {
+            Object first = queue.removeFirst();
+            if (first instanceof FileCollection) {
+                result.add(first);
+            } else if (first instanceof Closure) {
+                Closure closure = (Closure) first;
+                Object closureResult = closure.call();
+                if (closureResult != null) {
+                    queue.addFirst(closureResult);
+                }
+            } else if (first instanceof Collection) {
+                Collection<?> collection = (Collection<?>) first;
+                queue.addAll(0, collection);
+            } else if (first instanceof Object[]) {
+                Object[] array = (Object[]) first;
+                queue.addAll(0, Arrays.asList(array));
+            } else if (first instanceof Callable) {
+                Callable callable = (Callable) first;
+                Object callableResult;
+                try {
+                    callableResult = callable.call();
+                } catch (Exception e) {
+                    throw UncheckedException.asUncheckedException(e);
+                }
+                if (callableResult != null) {
+                    queue.add(0, callableResult);
+                }
+            } else {
+                result.add(resolver.resolve(first));
+            }
+        }
+        return result;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/SimpleFileCollection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/SimpleFileCollection.java
new file mode 100644
index 0000000..d6ba01b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/SimpleFileCollection.java
@@ -0,0 +1,50 @@
+/*
+ * 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 org.gradle.util.GUtil;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class SimpleFileCollection extends AbstractFileCollection {
+    private final Set<File> files;
+
+    public SimpleFileCollection(File... files) {
+        if (files.length > 0) {
+            this.files = new LinkedHashSet<File>(Arrays.asList(files));
+        } else {
+            this.files = Collections.emptySet();
+        }
+    }
+
+    public SimpleFileCollection(Iterable<File> files) {
+        this.files = new LinkedHashSet<File>();
+        GUtil.addToCollection(this.files, files);
+    }
+
+    @Override
+    public String getDisplayName() {
+        return "file collection";
+    }
+
+    public Set<File> getFiles() {
+        return files;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/SingletonFileCollection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/SingletonFileCollection.java
new file mode 100644
index 0000000..8ab3b7c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/SingletonFileCollection.java
@@ -0,0 +1,52 @@
+/*
+ * 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.file;
+
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.api.file.FileTree;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Set;
+
+class SingletonFileCollection extends AbstractFileCollection {
+    private final File file;
+    private final TaskDependency builtBy;
+
+    public SingletonFileCollection(File file, TaskDependency builtBy) {
+        this.file = file;
+        this.builtBy = builtBy;
+    }
+
+    @Override
+    public TaskDependency getBuildDependencies() {
+        return builtBy;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return String.format("file '%s'", file);
+    }
+
+    public Set<File> getFiles() {
+        return Collections.singleton(file);
+    }
+
+    @Override
+    public FileTree getAsFileTree() {
+        return new SingletonFileTree(file, builtBy);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/SingletonFileTree.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/SingletonFileTree.java
new file mode 100644
index 0000000..0a9a5ad
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/SingletonFileTree.java
@@ -0,0 +1,71 @@
+/*
+ * 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.file;
+
+import org.gradle.api.file.*;
+import org.gradle.api.tasks.TaskDependency;
+
+import java.io.File;
+import java.util.Collection;
+
+class SingletonFileTree extends CompositeFileTree {
+    private final File file;
+    private final TaskDependency builtBy;
+
+    public SingletonFileTree(File file, TaskDependency builtBy) {
+        this.file = file;
+        this.builtBy = builtBy;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return String.format("file '%s'", file);
+    }
+
+    @Override
+    public TaskDependency getBuildDependencies() {
+        return builtBy;
+    }
+
+    protected void addSourceCollections(Collection<FileCollection> sources) {
+        if (file.isDirectory()) {
+            sources.add(new DefaultConfigurableFileTree(file, null, null));
+        }
+        else if (file.isFile()) {
+            sources.add(new FileFileTree());
+        }
+    }
+
+    private class FileVisitDetailsImpl extends DefaultFileTreeElement implements FileVisitDetails {
+        private FileVisitDetailsImpl() {
+            super(file, new RelativePath(true, file.getName()));
+        }
+
+        public void stopVisiting() {
+        }
+    }
+
+    private class FileFileTree extends AbstractFileTree {
+        public String getDisplayName() {
+            return SingletonFileTree.this.getDisplayName();
+        }
+
+        public FileTree visit(FileVisitor visitor) {
+            visitor.visitFile(new FileVisitDetailsImpl());
+            return this;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/TemporaryFileProvider.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/TemporaryFileProvider.java
new file mode 100644
index 0000000..15176c2
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/TemporaryFileProvider.java
@@ -0,0 +1,28 @@
+/*
+ * 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 TemporaryFileProvider {
+    /**
+     * Allocates a new temporary file. Does not create the file.
+     *
+     * @param path The tail path components for the file.
+     * @return The file
+     */
+    File newTemporaryFile(String... path);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/UnionFileCollection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/UnionFileCollection.java
new file mode 100644
index 0000000..aea2261
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/UnionFileCollection.java
@@ -0,0 +1,48 @@
+/*
+ * 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.file;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.util.GUtil;
+
+import java.util.*;
+
+public class UnionFileCollection extends CompositeFileCollection {
+    private final Set<FileCollection> sourceCollections;
+
+    public UnionFileCollection(FileCollection... sourceCollections) {
+        this(Arrays.asList(sourceCollections));
+    }
+
+    public UnionFileCollection(Iterable<? extends FileCollection> sourceCollections) {
+        this.sourceCollections = GUtil.addToCollection(new LinkedHashSet<FileCollection>(), sourceCollections);
+    }
+
+    public String getDisplayName() {
+        return "file collection";
+    }
+
+    @Override
+    public FileCollection add(FileCollection collection) throws UnsupportedOperationException {
+        sourceCollections.add(collection);
+        return this;
+    }
+
+    @Override
+    protected void addSourceCollections(Collection<FileCollection> sources) {
+        sources.addAll(sourceCollections);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/UnionFileTree.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/UnionFileTree.java
new file mode 100644
index 0000000..6d1d6a3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/UnionFileTree.java
@@ -0,0 +1,63 @@
+/*
+ * 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.file;
+
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.FileCollection;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class UnionFileTree extends CompositeFileTree {
+    private final Set<FileTree> sourceTrees;
+    private final String displayName;
+
+    public UnionFileTree(FileTree... sourceTrees) {
+        this("file tree", Arrays.asList(sourceTrees));
+    }
+
+    public UnionFileTree(String displayName, FileTree... sourceTrees) {
+        this(displayName, Arrays.asList(sourceTrees));
+    }
+
+    public UnionFileTree(String displayName, Collection<? extends FileTree> sourceTrees) {
+        this.displayName = displayName;
+        this.sourceTrees = new LinkedHashSet<FileTree>(sourceTrees);
+    }
+
+    @Override
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    @Override
+    protected void addSourceCollections(Collection<FileCollection> sources) {
+        sources.addAll(sourceTrees);
+    }
+
+    @Override
+    public UnionFileTree add(FileCollection source) {
+        if (!(source instanceof FileTree)) {
+            throw new UnsupportedOperationException(String.format("Can only add FileTree instances to %s.",
+                    getDisplayName()));
+        }
+        
+        sourceTrees.add((FileTree) source);
+        return this;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/ant/AntFileResource.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/ant/AntFileResource.java
new file mode 100644
index 0000000..02e3631
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/ant/AntFileResource.java
@@ -0,0 +1,35 @@
+/*
+ * 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.file.ant;
+
+import org.apache.tools.ant.types.resources.FileResource;
+
+/**
+ * Unjiggers the FileResource.getName() method.
+ */
+public class AntFileResource extends FileResource {
+    private String name;
+
+    @Override
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/ant/BaseDirSelector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/ant/BaseDirSelector.java
new file mode 100644
index 0000000..eb13c4f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/ant/BaseDirSelector.java
@@ -0,0 +1,37 @@
+/*
+ * 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.file.ant;
+
+import org.apache.tools.ant.types.selectors.FileSelector;
+
+import java.io.File;
+
+public class BaseDirSelector implements FileSelector {
+    private File baseDir;
+
+    public void setBaseDir(File baseDir) {
+        this.baseDir = baseDir;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("{basedir: %s}", baseDir);
+    }
+
+    public boolean isSelected(File basedir, String filename, File file) {
+        return basedir.equals(this.baseDir);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopyAction.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopyAction.java
new file mode 100644
index 0000000..5d9b69f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopyAction.java
@@ -0,0 +1,23 @@
+/*
+ * 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.file.archive;
+
+import org.gradle.api.internal.file.copy.ArchiveCopyAction;
+import org.gradle.api.tasks.bundling.Compression;
+
+public interface TarCopyAction extends ArchiveCopyAction {
+    Compression getCompression();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitor.java
new file mode 100644
index 0000000..3649207
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitor.java
@@ -0,0 +1,107 @@
+/*
+ * 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.file.archive;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.file.CopyAction;
+import org.gradle.api.file.FileVisitDetails;
+import org.apache.tools.tar.TarOutputStream;
+import org.apache.tools.tar.TarEntry;
+import org.apache.tools.bzip2.CBZip2OutputStream;
+import org.apache.tools.zip.UnixStat;
+import org.gradle.api.internal.file.copy.EmptyCopySpecVisitor;
+import org.gradle.api.internal.file.copy.ReadableCopySpec;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.zip.GZIPOutputStream;
+
+public class TarCopySpecVisitor extends EmptyCopySpecVisitor {
+    private TarOutputStream tarOutStr;
+    private File tarFile;
+    private ReadableCopySpec spec;
+
+    public void startVisit(CopyAction action) {
+        TarCopyAction archiveAction = (TarCopyAction) action;
+        try {
+            tarFile = archiveAction.getArchivePath();
+            OutputStream outStr = new FileOutputStream(tarFile);
+            switch (archiveAction.getCompression()) {
+                case GZIP:
+                    outStr = new GZIPOutputStream(outStr);
+                    break;
+                case BZIP2:
+                    outStr.write('B');
+                    outStr.write('Z');
+                    outStr = new CBZip2OutputStream(outStr);
+                    break;
+            }
+            tarOutStr = new TarOutputStream(outStr);
+            tarOutStr.setLongFileMode(TarOutputStream.LONGFILE_GNU);
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not create TAR '%s'.", tarFile), e);
+        }
+    }
+
+    public void endVisit() {
+        try {
+            tarOutStr.close();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        } finally {
+            tarOutStr = null;
+            spec = null;
+        }
+    }
+
+    public void visitSpec(ReadableCopySpec spec) {
+        this.spec = spec;
+    }
+
+    public void visitFile(FileVisitDetails fileDetails) {
+        try {
+            TarEntry archiveEntry = new TarEntry(fileDetails.getRelativePath().getPathString());
+            archiveEntry.setModTime(fileDetails.getLastModified());
+            archiveEntry.setSize(fileDetails.getSize());
+            archiveEntry.setMode(UnixStat.FILE_FLAG | spec.getFileMode());
+            tarOutStr.putNextEntry(archiveEntry);
+            fileDetails.copyTo(tarOutStr);
+            tarOutStr.closeEntry();
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not add %s to TAR '%s'.", fileDetails, tarFile), e);
+        }
+    }
+
+    public void visitDir(FileVisitDetails dirDetails) {
+        try {
+            // Trailing slash on name indicates entry is a directory
+            TarEntry archiveEntry = new TarEntry(dirDetails.getRelativePath().getPathString() + '/');
+            archiveEntry.setModTime(dirDetails.getLastModified());
+            archiveEntry.setMode(UnixStat.DIR_FLAG | spec.getDirMode());
+            tarOutStr.putNextEntry(archiveEntry);
+            tarOutStr.closeEntry();
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not add %s to TAR '%s'.", dirDetails, tarFile), e);
+        }
+    }
+
+    public boolean getDidWork() {
+        return true;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/archive/TarFileTree.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/archive/TarFileTree.java
new file mode 100644
index 0000000..1bb01f7
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/archive/TarFileTree.java
@@ -0,0 +1,162 @@
+/*
+ * 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.file.archive;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.FileVisitor;
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.internal.file.AbstractFileTree;
+import org.gradle.api.internal.file.AbstractFileTreeElement;
+import org.gradle.api.internal.file.DefaultConfigurableFileTree;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.HashUtil;
+import org.apache.tools.tar.TarEntry;
+import org.apache.tools.tar.TarInputStream;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class TarFileTree extends AbstractFileTree {
+    private final File tarFile;
+    private final File tmpDir;
+
+    public TarFileTree(File tarFile, File tmpDir) {
+        this.tarFile = tarFile;
+        String expandDirName = String.format("%s_%s", tarFile.getName(), HashUtil.createHash(tarFile.getAbsolutePath()));
+        this.tmpDir = new File(tmpDir, expandDirName);
+    }
+
+    public String getDisplayName() {
+        return String.format("TAR '%s'", tarFile);
+    }
+
+    @Override
+    protected Collection<DefaultConfigurableFileTree> getAsFileTrees() {
+        visitAll();
+        return tarFile.exists() ? Collections.singleton(new DefaultConfigurableFileTree(tmpDir, null, null)) : Collections.<DefaultConfigurableFileTree>emptyList();
+    }
+
+    public FileTree visit(FileVisitor visitor) {
+        if (!tarFile.exists()) {
+            return this;
+        }
+        if (!tarFile.isFile()) {
+            throw new InvalidUserDataException(String.format("Cannot expand %s as it is not a file.", this));
+        }
+
+        AtomicBoolean stopFlag = new AtomicBoolean();
+        try {
+            FileInputStream inputStream = new FileInputStream(tarFile);
+            try {
+                NoCloseTarInputStream tar = new NoCloseTarInputStream(inputStream);
+                TarEntry entry;
+                while (!stopFlag.get() && (entry = tar.getNextEntry()) != null) {
+                    if (entry.isDirectory()) {
+                        visitor.visitDir(new DetailsImpl(entry, tar, stopFlag));
+                    } else {
+                        visitor.visitFile(new DetailsImpl(entry, tar, stopFlag));
+                    }
+
+                }
+            } finally {
+                inputStream.close();
+            }
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not expand %s.", this), e);
+        }
+
+        return this;
+    }
+
+    private class DetailsImpl extends AbstractFileTreeElement implements FileVisitDetails {
+        private final TarEntry entry;
+        private final NoCloseTarInputStream tar;
+        private final AtomicBoolean stopFlag;
+        private File file;
+        private boolean read;
+
+        public DetailsImpl(TarEntry entry, NoCloseTarInputStream tar, AtomicBoolean stopFlag) {
+            this.entry = entry;
+            this.tar = tar;
+            this.stopFlag = stopFlag;
+        }
+
+        public String getDisplayName() {
+            return String.format("tar entry %s!%s", tarFile, entry.getName());
+        }
+
+        public void stopVisiting() {
+            stopFlag.set(true);
+        }
+
+        public File getFile() {
+            if (file == null) {
+                file = new File(tmpDir, entry.getName());
+                copyTo(file);
+            }
+            return file;
+        }
+
+        public long getLastModified() {
+            return entry.getModTime().getTime();
+        }
+
+        public boolean isDirectory() {
+            return entry.isDirectory();
+        }
+
+        public long getSize() {
+            return entry.getSize();
+        }
+
+        public InputStream open() {
+            if (read && file != null) {
+                return GFileUtils.openInputStream(file);
+            }
+            if (read || tar.getCurrent() != entry) {
+                throw new UnsupportedOperationException(String.format("The contents of %s has already been read.", this));
+            }
+            read = true;
+            return tar;
+        }
+
+        public RelativePath getRelativePath() {
+            return new RelativePath(!entry.isDirectory(), entry.getName().split("/"));
+        }
+    }
+
+    private static class NoCloseTarInputStream extends TarInputStream {
+        public NoCloseTarInputStream(InputStream is) {
+            super(is);
+        }
+
+        @Override
+        public void close() throws IOException {
+        }
+
+        public TarEntry getCurrent() {
+            return currEntry;
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitor.java
new file mode 100644
index 0000000..f3a2814
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitor.java
@@ -0,0 +1,90 @@
+/*
+ * 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.archive;
+
+import org.apache.tools.zip.*;
+import org.gradle.api.GradleException;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.file.CopyAction;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.internal.file.copy.ArchiveCopyAction;
+import org.gradle.api.internal.file.copy.EmptyCopySpecVisitor;
+import org.gradle.api.internal.file.copy.ReadableCopySpec;
+
+import java.io.File;
+import java.io.IOException;
+
+public class ZipCopySpecVisitor extends EmptyCopySpecVisitor {
+    private ZipOutputStream zipOutStr;
+    private File zipFile;
+    private ReadableCopySpec spec;
+
+    public void startVisit(CopyAction action) {
+        ArchiveCopyAction archiveAction = (ArchiveCopyAction) action;
+        zipFile = archiveAction.getArchivePath();
+        try {
+            zipOutStr = new ZipOutputStream(zipFile);
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not create ZIP '%s'.", zipFile), e);
+        }
+    }
+
+    public void endVisit() {
+        try {
+            zipOutStr.close();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        } finally {
+            spec = null;
+            zipOutStr = null;
+        }
+    }
+
+    public void visitSpec(ReadableCopySpec spec) {
+        this.spec = spec;
+    }
+
+    public void visitFile(FileVisitDetails fileDetails) {
+        try {
+            ZipEntry archiveEntry = new ZipEntry(fileDetails.getRelativePath().getPathString());
+            archiveEntry.setMethod(ZipEntry.DEFLATED);
+            archiveEntry.setTime(fileDetails.getLastModified());
+            archiveEntry.setUnixMode(UnixStat.FILE_FLAG | spec.getFileMode());
+            zipOutStr.putNextEntry(archiveEntry);
+            fileDetails.copyTo(zipOutStr);
+            zipOutStr.closeEntry();
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not add %s to ZIP '%s'.", fileDetails, zipFile), e);
+        }
+    }
+
+    public void visitDir(FileVisitDetails dirDetails) {
+        try {
+            // Trailing slash in name indicates that entry is a directory
+            ZipEntry archiveEntry = new ZipEntry(dirDetails.getRelativePath().getPathString() + '/');
+            archiveEntry.setTime(dirDetails.getLastModified());
+            archiveEntry.setUnixMode(UnixStat.DIR_FLAG | spec.getDirMode());
+            zipOutStr.putNextEntry(archiveEntry);
+            zipOutStr.closeEntry();
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not add %s to ZIP '%s'.", dirDetails, zipFile), e);
+        }
+    }
+
+    public boolean getDidWork() {
+        return true;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/archive/ZipFileTree.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/archive/ZipFileTree.java
new file mode 100644
index 0000000..4c88e0f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/archive/ZipFileTree.java
@@ -0,0 +1,150 @@
+/*
+ * 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.archive;
+
+import org.apache.tools.zip.ZipEntry;
+import org.apache.tools.zip.ZipFile;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.file.AbstractFileTree;
+import org.gradle.api.internal.file.AbstractFileTreeElement;
+import org.gradle.api.internal.file.DefaultConfigurableFileTree;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.FileVisitor;
+import org.gradle.api.file.RelativePath;
+import org.gradle.util.HashUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class ZipFileTree extends AbstractFileTree {
+    private final File zipFile;
+    private final File tmpDir;
+
+    public ZipFileTree(File zipFile, File tmpDir) {
+        this.zipFile = zipFile;
+        String expandDirName = String.format("%s_%s", zipFile.getName(), HashUtil.createHash(zipFile.getAbsolutePath()));
+        this.tmpDir = new File(tmpDir, expandDirName);
+    }
+
+    public String getDisplayName() {
+        return String.format("ZIP '%s'", zipFile);
+    }
+
+    @Override
+    protected Collection<DefaultConfigurableFileTree> getAsFileTrees() {
+        visitAll();
+        return zipFile.exists() ? Collections.singleton(new DefaultConfigurableFileTree(tmpDir, null, null)) : Collections.<DefaultConfigurableFileTree>emptyList();
+    }
+
+    public FileTree visit(FileVisitor visitor) {
+        if (!zipFile.exists()) {
+            return this;
+        }
+        if (!zipFile.isFile()) {
+            throw new InvalidUserDataException(String.format("Cannot expand %s as it is not a file.", this));
+        }
+
+        AtomicBoolean stopFlag = new AtomicBoolean();
+
+        try {
+            ZipFile zip = new ZipFile(zipFile);
+            try {
+                // The iteration order of zip.getEntries() is based on the hash of the zip entry. This isn't much use
+                // to us. So, collect the entries in a map and iterate over them in alphabetical order.
+                Map<String, ZipEntry> entriesByName = new TreeMap<String, ZipEntry>();
+                Enumeration entries = zip.getEntries();
+                while (entries.hasMoreElements()) {
+                    ZipEntry entry = (ZipEntry) entries.nextElement();
+                    entriesByName.put(entry.getName(), entry);
+                }
+                Iterator<ZipEntry> sortedEntries = entriesByName.values().iterator();
+                while (!stopFlag.get() && sortedEntries.hasNext()) {
+                    ZipEntry entry = sortedEntries.next();
+                    if (entry.isDirectory()) {
+                        visitor.visitDir(new DetailsImpl(entry, zip, stopFlag));
+                    } else {
+                        visitor.visitFile(new DetailsImpl(entry, zip, stopFlag));
+                    }
+                }
+            } finally {
+                zip.close();
+            }
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not expand %s.", this), e);
+        }
+
+        return this;
+    }
+
+    private class DetailsImpl extends AbstractFileTreeElement implements FileVisitDetails {
+        private final ZipEntry entry;
+        private final ZipFile zip;
+        private final AtomicBoolean stopFlag;
+        private File file;
+
+        public DetailsImpl(ZipEntry entry, ZipFile zip, AtomicBoolean stopFlag) {
+            this.entry = entry;
+            this.zip = zip;
+            this.stopFlag = stopFlag;
+        }
+
+        public String getDisplayName() {
+            return String.format("zip entry %s!%s", zipFile, entry.getName());
+        }
+
+        public void stopVisiting() {
+            stopFlag.set(true);
+        }
+
+        public File getFile() {
+            if (file == null) {
+                file = new File(tmpDir, entry.getName());
+                copyTo(file);
+            }
+            return file;
+        }
+
+        public long getLastModified() {
+            return entry.getTime();
+        }
+
+        public boolean isDirectory() {
+            return entry.isDirectory();
+        }
+
+        public long getSize() {
+            return entry.getSize();
+        }
+
+        public InputStream open()  {
+            try {
+                return zip.getInputStream(entry);
+            } catch (IOException e) {
+                throw new UncheckedIOException(e);
+            }
+        }
+
+        public RelativePath getRelativePath() {
+            return new RelativePath(!entry.isDirectory(), entry.getName().split("/"));
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/ArchiveCopyAction.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/ArchiveCopyAction.java
new file mode 100644
index 0000000..3c2c1b6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/ArchiveCopyAction.java
@@ -0,0 +1,24 @@
+/*
+ * 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.file.copy;
+
+import org.gradle.api.file.CopyAction;
+
+import java.io.File;
+
+public interface ArchiveCopyAction extends CopyAction {
+    File getArchivePath();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionImpl.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionImpl.java
new file mode 100644
index 0000000..9c09691
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/CopyActionImpl.java
@@ -0,0 +1,236 @@
+/*
+ * 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.copy;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.file.*;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.specs.Spec;
+
+import java.io.FilterReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * @author Steve Appling
+ */
+public class CopyActionImpl implements CopyAction, CopySpecSource {
+    private final CopySpecVisitor visitor;
+    private final CopySpecImpl root;
+    private final CopySpecImpl mainContent;
+    private final FileResolver resolver;
+
+    public CopyActionImpl(FileResolver resolver, CopySpecVisitor visitor) {
+        this.resolver = resolver;
+        root = new CopySpecImpl(resolver);
+        mainContent = root.addChild();
+        this.visitor = new MappingCopySpecVisitor(new NormalizingCopySpecVisitor(visitor));
+    }
+
+    public FileResolver getResolver() {
+        return resolver;
+    }
+
+    public CopySpecImpl getRootSpec() {
+        return root;
+    }
+
+    public CopySpecImpl getMainSpec() {
+        return mainContent;
+    }
+
+    public void execute() {
+        visitor.startVisit(this);
+        for (ReadableCopySpec spec : root.getAllSpecs()) {
+            visitor.visitSpec(spec);
+            spec.getSource().visit(visitor);
+        }
+        visitor.endVisit();
+    }
+
+    public boolean getDidWork() {
+        return visitor.getDidWork();
+    }
+
+    public FileTree getAllSource() {
+        List<FileTree> sources = new ArrayList<FileTree>();
+        for (ReadableCopySpec spec : root.getAllSpecs()) {
+            FileTree source = spec.getSource();
+            sources.add(source);
+        }
+        return resolver.resolveFilesAsTree(sources);
+    }
+
+    public boolean hasSource() {
+        return root.hasSource();
+    }
+
+    public CopySpec eachFile(Action<? super FileCopyDetails> action) {
+        mainContent.eachFile(action);
+        return this;
+    }
+
+    public CopySpec eachFile(Closure closure) {
+        mainContent.eachFile(closure);
+        return this;
+    }
+
+    public CopySpec exclude(Iterable<String> excludes) {
+        mainContent.exclude(excludes);
+        return this;
+    }
+
+    public CopySpec exclude(String... excludes) {
+        mainContent.exclude(excludes);
+        return this;
+    }
+
+    public CopySpec exclude(Closure excludeSpec) {
+        mainContent.exclude(excludeSpec);
+        return this;
+    }
+
+    public CopySpec exclude(Spec<FileTreeElement> excludeSpec) {
+        mainContent.exclude(excludeSpec);
+        return this;
+    }
+
+    public CopySpec expand(Map<String, ?> properties) {
+        mainContent.expand(properties);
+        return this;
+    }
+
+    public CopySpec filter(Closure closure) {
+        mainContent.filter(closure);
+        return this;
+    }
+
+    public CopySpec filter(Class<? extends FilterReader> filterType) {
+        mainContent.filter(filterType);
+        return this;
+    }
+
+    public CopySpec filter(Map<String, ?> properties, Class<? extends FilterReader> filterType) {
+        mainContent.filter(properties, filterType);
+        return this;
+    }
+
+    public CopySpec from(Object sourcePath, Closure c) {
+        return mainContent.from(sourcePath, c);
+    }
+
+    public CopySpec from(Object... sourcePaths) {
+        mainContent.from(sourcePaths);
+        return this;
+    }
+
+    public Set<String> getExcludes() {
+        return mainContent.getExcludes();
+    }
+
+    public Set<String> getIncludes() {
+        return mainContent.getIncludes();
+    }
+
+    public CopySpec include(Iterable<String> includes) {
+        mainContent.include(includes);
+        return this;
+    }
+
+    public CopySpec include(String... includes) {
+        mainContent.include(includes);
+        return this;
+    }
+
+    public CopySpec include(Closure includeSpec) {
+        mainContent.include(includeSpec);
+        return this;
+    }
+
+    public CopySpec include(Spec<FileTreeElement> includeSpec) {
+        mainContent.include(includeSpec);
+        return this;
+    }
+
+    public CopySpec into(Object destDir) {
+        mainContent.into(destDir);
+        return this;
+    }
+
+    public CopySpec into(Object destPath, Closure configureClosure) {
+        return mainContent.into(destPath, configureClosure);
+    }
+
+    public boolean isCaseSensitive() {
+        return mainContent.isCaseSensitive();
+    }
+
+    public CopySpec rename(Closure closure) {
+        mainContent.rename(closure);
+        return this;
+    }
+
+    public CopySpec rename(Pattern sourceRegEx, String replaceWith) {
+        mainContent.rename(sourceRegEx, replaceWith);
+        return this;
+    }
+
+    public CopySpec rename(String sourceRegEx, String replaceWith) {
+        mainContent.rename(sourceRegEx, replaceWith);
+        return this;
+    }
+
+    public void setCaseSensitive(boolean caseSensitive) {
+        mainContent.setCaseSensitive(caseSensitive);
+    }
+
+    public int getDirMode() {
+        return mainContent.getDirMode();
+    }
+
+    public CopyProcessingSpec setDirMode(int mode) {
+        mainContent.setDirMode(mode);
+        return this;
+    }
+
+    public CopySpec setExcludes(Iterable<String> excludes) {
+        mainContent.setExcludes(excludes);
+        return this;
+    }
+
+    public int getFileMode() {
+        return mainContent.getFileMode();
+    }
+
+    public CopyProcessingSpec setFileMode(int mode) {
+        mainContent.setFileMode(mode);
+        return this;
+    }
+
+    public CopySpec setIncludes(Iterable<String> includes) {
+        mainContent.setIncludes(includes);
+        return this;
+    }
+
+    public CopySpec with(CopySpec... copySpecs) {
+        mainContent.with(copySpecs);
+        return this;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecImpl.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecImpl.java
new file mode 100644
index 0000000..202c7d9
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecImpl.java
@@ -0,0 +1,436 @@
+/*
+ * 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.copy;
+
+import groovy.lang.Closure;
+import org.apache.tools.zip.UnixStat;
+import org.codehaus.groovy.runtime.DefaultGroovyMethods;
+import org.gradle.api.Action;
+import org.gradle.api.file.*;
+import org.gradle.api.internal.ChainingTransformer;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.util.ConfigureUtil;
+
+import java.io.File;
+import java.io.FilterReader;
+import java.util.*;
+import java.util.regex.Pattern;
+
+/**
+ * @author Steve Appling
+ */
+public class CopySpecImpl implements CopySpec, ReadableCopySpec {
+    private final FileResolver resolver;
+    private final Set<Object> sourcePaths;
+    private Object destDir;
+    private final PatternSet patternSet;
+    private final List<ReadableCopySpec> childSpecs;
+    private final CopySpecImpl parentSpec;
+    private final List<Action<? super FileCopyDetails>> actions = new ArrayList<Action<? super FileCopyDetails>>();
+    private Integer dirMode;
+    private Integer fileMode;
+    private Boolean caseSensitive;
+
+    private CopySpecImpl(FileResolver resolver, CopySpecImpl parentSpec) {
+        this.parentSpec = parentSpec;
+        this.resolver = resolver;
+        sourcePaths = new LinkedHashSet<Object>();
+        childSpecs = new ArrayList<ReadableCopySpec>();
+        patternSet = new PatternSet();
+    }
+
+    public CopySpecImpl(FileResolver resolver) {
+        this(resolver, null);
+    }
+
+    protected FileResolver getResolver() {
+        return resolver;
+    }
+
+    public CopySpec with(CopySpec... copySpecs) {
+        for (CopySpec copySpec : copySpecs) {
+            ReadableCopySpec readableCopySpec;
+            if (copySpec instanceof CopySpecSource) {
+                CopySpecSource copySpecSource = (CopySpecSource) copySpec;
+                readableCopySpec = copySpecSource.getRootSpec();
+            } else {
+                readableCopySpec = (ReadableCopySpec) copySpec;
+            }
+            childSpecs.add(new WrapperCopySpec(this, readableCopySpec));
+        }
+        return this;
+    }
+
+    public CopySpec from(Object... sourcePaths) {
+        for (Object sourcePath : sourcePaths) {
+            this.sourcePaths.add(sourcePath);
+        }
+        return this;
+    }
+
+    public CopySpec from(Object sourcePath, Closure c) {
+        if (c == null) {
+            from(sourcePath);
+            return this;
+        } else {
+            CopySpecImpl child = addChild();
+            child.from(sourcePath);
+            ConfigureUtil.configure(c, child);
+            return child;
+        }
+    }
+
+    public CopySpecImpl addFirst() {
+        CopySpecImpl child = new CopySpecImpl(resolver, this);
+        childSpecs.add(0, child);
+        return child;
+    }
+
+    public CopySpecImpl addChild() {
+        CopySpecImpl child = new CopySpecImpl(resolver, this);
+        childSpecs.add(child);
+        return child;
+    }
+
+    public Set<Object> getSourcePaths() {
+        return sourcePaths;
+    }
+
+    public FileTree getSource() {
+        return resolver.resolveFilesAsTree(sourcePaths).matching(getPatternSet());
+    }
+
+    public List<ReadableCopySpec> getAllSpecs() {
+        List<ReadableCopySpec> result = new ArrayList<ReadableCopySpec>();
+        result.add(this);
+        for (ReadableCopySpec childSpec : childSpecs) {
+            result.addAll(childSpec.getAllSpecs());
+        }
+        return result;
+    }
+
+    public CopySpecImpl into(Object destDir) {
+        this.destDir = destDir;
+        return this;
+    }
+
+    public CopySpecImpl into(Object destPath, Closure configureClosure) {
+        if (configureClosure == null) {
+            into(destPath);
+            return this;
+        } else {
+            CopySpecImpl child = addChild();
+            child.into(destPath);
+            ConfigureUtil.configure(configureClosure, child);
+            return child;
+        }
+    }
+
+    public RelativePath getDestPath() {
+        RelativePath parentPath;
+        if (parentSpec == null) {
+            parentPath = new RelativePath(false);
+        } else {
+            parentPath = parentSpec.getDestPath();
+        }
+        if (destDir == null) {
+            return parentPath;
+        }
+
+        String path = destDir.toString();
+        if (path.startsWith("/") || path.startsWith(File.separator)) {
+            return RelativePath.parse(false, path);
+        }
+
+        return RelativePath.parse(false, parentPath, path);
+    }
+
+    public PatternSet getPatternSet() {
+        PatternSet patterns = new PatternSet();
+        patterns.setCaseSensitive(isCaseSensitive());
+        patterns.include(getAllIncludes());
+        patterns.includeSpecs(getAllIncludeSpecs());
+        patterns.exclude(getAllExcludes());
+        patterns.excludeSpecs(getAllExcludeSpecs());
+        return patterns;
+    }
+
+    public boolean isCaseSensitive() {
+        if (caseSensitive != null) {
+            return caseSensitive;
+        } else if (parentSpec != null) {
+            return parentSpec.isCaseSensitive();
+        }
+        return true;
+    }
+
+    public void setCaseSensitive(boolean caseSensitive) {
+        this.caseSensitive = caseSensitive;
+    }
+
+    public CopySpec include(String... includes) {
+        patternSet.include(includes);
+        return this;
+    }
+
+    public CopySpec include(Iterable<String> includes) {
+        patternSet.include(includes);
+        return this;
+    }
+
+    public CopySpec include(Spec<FileTreeElement> includeSpec) {
+        patternSet.include(includeSpec);
+        return this;
+    }
+
+    public CopySpec include(Closure includeSpec) {
+        patternSet.include(includeSpec);
+        return this;
+    }
+
+    public Set<String> getIncludes() {
+        return patternSet.getIncludes();
+    }
+
+    public CopySpec setIncludes(Iterable<String> includes) {
+        patternSet.setIncludes(includes);
+        return this;
+    }
+
+    public List<String> getAllIncludes() {
+        List<String> result = new ArrayList<String>();
+        if (parentSpec != null) {
+            result.addAll(parentSpec.getAllIncludes());
+        }
+        result.addAll(getIncludes());
+        return result;
+    }
+
+    public List<Spec<FileTreeElement>> getAllIncludeSpecs() {
+        List<Spec<FileTreeElement>> result = new ArrayList<Spec<FileTreeElement>>();
+        if (parentSpec != null) {
+            result.addAll(parentSpec.getAllIncludeSpecs());
+        }
+        result.addAll(patternSet.getIncludeSpecs());
+        return result;
+    }
+
+    public CopySpec exclude(String... excludes) {
+        patternSet.exclude(excludes);
+        return this;
+    }
+
+    public CopySpec exclude(Iterable<String> excludes) {
+        patternSet.exclude(excludes);
+        return this;
+    }
+
+    public CopySpec exclude(Spec<FileTreeElement> excludeSpec) {
+        patternSet.exclude(excludeSpec);
+        return this;
+    }
+
+    public CopySpec exclude(Closure excludeSpec) {
+        patternSet.exclude(excludeSpec);
+        return this;
+    }
+
+    public Set<String> getExcludes() {
+        return patternSet.getExcludes();
+    }
+
+    public CopySpecImpl setExcludes(Iterable<String> excludes) {
+        patternSet.setExcludes(excludes);
+        return this;
+    }
+
+    public CopySpec rename(String sourceRegEx, String replaceWith) {
+        actions.add(new RenamingCopyAction(new RegExpNameMapper(sourceRegEx, replaceWith)));
+        return this;
+    }
+
+    public CopySpec rename(Pattern sourceRegEx, String replaceWith) {
+        actions.add(new RenamingCopyAction(new RegExpNameMapper(sourceRegEx, replaceWith)));
+        return this;
+    }
+
+    public CopySpec filter(final Class<? extends FilterReader> filterType) {
+        actions.add(new Action<FileCopyDetails>() {
+            public void execute(FileCopyDetails fileCopyDetails) {
+                fileCopyDetails.filter(filterType);
+            }
+        });
+        return this;
+    }
+
+    public CopySpec filter(final Closure closure) {
+        actions.add(new Action<FileCopyDetails>() {
+            public void execute(FileCopyDetails fileCopyDetails) {
+                fileCopyDetails.filter(closure);
+            }
+        });
+        return this;
+    }
+
+    public CopySpec filter(final Map<String, ?> properties, final Class<? extends FilterReader> filterType) {
+        actions.add(new Action<FileCopyDetails>() {
+            public void execute(FileCopyDetails fileCopyDetails) {
+                fileCopyDetails.filter(properties, filterType);
+            }
+        });
+        return this;
+    }
+
+    public CopySpec expand(final Map<String, ?> properties) {
+        actions.add(new Action<FileCopyDetails>() {
+            public void execute(FileCopyDetails fileCopyDetails) {
+                fileCopyDetails.expand(properties);
+            }
+        });
+        return this;
+    }
+
+    public CopySpec rename(Closure closure) {
+        ChainingTransformer<String> transformer = new ChainingTransformer<String>(String.class);
+        transformer.add(closure);
+        actions.add(new RenamingCopyAction(transformer));
+        return this;
+    }
+
+    public int getDirMode() {
+        if (dirMode != null) {
+            return dirMode;
+        }
+        if (parentSpec != null) {
+            return parentSpec.getDirMode();
+        }
+        return UnixStat.DEFAULT_DIR_PERM;
+    }
+
+    public int getFileMode() {
+        if (fileMode != null) {
+            return fileMode;
+        }
+        if (parentSpec != null) {
+            return parentSpec.getFileMode();
+        }
+        return UnixStat.DEFAULT_FILE_PERM;
+    }
+
+    public CopyProcessingSpec setDirMode(int mode) {
+        dirMode = mode;
+        return this;
+    }
+
+    public CopyProcessingSpec setFileMode(int mode) {
+        fileMode = mode;
+        return this;
+    }
+
+    public CopySpec eachFile(Action<? super FileCopyDetails> action) {
+        actions.add(action);
+        return this;
+    }
+
+    public CopySpec eachFile(Closure closure) {
+        actions.add((Action<? super FileCopyDetails>) DefaultGroovyMethods.asType(closure, Action.class));
+        return this;
+    }
+
+    public List<String> getAllExcludes() {
+        List<String> result = new ArrayList<String>();
+        if (parentSpec != null) {
+            result.addAll(parentSpec.getAllExcludes());
+        }
+        result.addAll(getExcludes());
+        return result;
+    }
+
+    public List<Spec<FileTreeElement>> getAllExcludeSpecs() {
+        List<Spec<FileTreeElement>> result = new ArrayList<Spec<FileTreeElement>>();
+        if (parentSpec != null) {
+            result.addAll(parentSpec.getAllExcludeSpecs());
+        }
+        result.addAll(patternSet.getExcludeSpecs());
+        return result;
+    }
+
+    public List<Action<? super FileCopyDetails>> getAllCopyActions() {
+        if (parentSpec == null) {
+            return actions;
+        }
+        List<Action<? super FileCopyDetails>> allActions = new ArrayList<Action<? super FileCopyDetails>>();
+        allActions.addAll(parentSpec.getAllCopyActions());
+        allActions.addAll(actions);
+        return allActions;
+    }
+
+    public boolean hasSource() {
+        if (!sourcePaths.isEmpty()) {
+            return true;
+        }
+        for (ReadableCopySpec spec : childSpecs) {
+            if (spec.hasSource()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static class WrapperCopySpec implements ReadableCopySpec {
+        private final ReadableCopySpec root;
+        private final ReadableCopySpec spec;
+
+        public WrapperCopySpec(ReadableCopySpec root, ReadableCopySpec spec) {
+            this.root = root;
+            this.spec = spec;
+        }
+
+        public RelativePath getDestPath() {
+            return root.getDestPath().append(spec.getDestPath());
+        }
+
+        public int getFileMode() {
+            return spec.getFileMode();
+        }
+
+        public int getDirMode() {
+            return spec.getDirMode();
+        }
+
+        public FileTree getSource() {
+            return spec.getSource();
+        }
+
+        public Collection<? extends ReadableCopySpec> getAllSpecs() {
+            List<WrapperCopySpec> specs = new ArrayList<WrapperCopySpec>();
+            for (ReadableCopySpec child : spec.getAllSpecs()) {
+                specs.add(new WrapperCopySpec(root, child));
+            }
+            return specs;
+        }
+
+        public boolean hasSource() {
+            return spec.hasSource();
+        }
+
+        public Collection<? extends Action<? super FileCopyDetails>> getAllCopyActions() {
+            return spec.getAllCopyActions();
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecSource.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecSource.java
new file mode 100644
index 0000000..38f27a2
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecSource.java
@@ -0,0 +1,20 @@
+/*
+ * 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.copy;
+
+public interface CopySpecSource {
+    ReadableCopySpec getRootSpec();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecVisitor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecVisitor.java
new file mode 100644
index 0000000..a93391e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/CopySpecVisitor.java
@@ -0,0 +1,37 @@
+/*
+ * 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.file.copy;
+
+import org.gradle.api.file.FileVisitor;
+import org.gradle.api.file.CopyAction;
+import org.gradle.api.tasks.WorkResult;
+
+public interface CopySpecVisitor extends FileVisitor, WorkResult {
+    /**
+     * Called at the start of the visit.
+     */
+    void startVisit(CopyAction action);
+
+    /**
+     * Called at the end of the visit.
+     */
+    void endVisit();
+
+    /**
+     * Visits a spec. Called before any of the files or directories of the spec are visited.
+     */
+    void visitSpec(ReadableCopySpec spec);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/DelegatingCopySpecVisitor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/DelegatingCopySpecVisitor.java
new file mode 100644
index 0000000..c63d56c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/DelegatingCopySpecVisitor.java
@@ -0,0 +1,55 @@
+/*
+ * 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.file.copy;
+
+import org.gradle.api.file.CopyAction;
+import org.gradle.api.file.FileVisitDetails;
+
+public class DelegatingCopySpecVisitor implements CopySpecVisitor {
+    private final CopySpecVisitor visitor;
+
+    public DelegatingCopySpecVisitor(CopySpecVisitor visitor) {
+        this.visitor = visitor;
+    }
+
+    protected CopySpecVisitor getVisitor() {
+        return visitor;
+    }
+
+    public void startVisit(CopyAction action) {
+        getVisitor().startVisit(action);
+    }
+
+    public void endVisit() {
+        getVisitor().endVisit();
+    }
+
+    public void visitSpec(ReadableCopySpec spec) {
+        getVisitor().visitSpec(spec);
+    }
+
+    public void visitDir(FileVisitDetails dirDetails) {
+        getVisitor().visitDir(dirDetails);
+    }
+
+    public void visitFile(FileVisitDetails fileDetails) {
+        getVisitor().visitFile(fileDetails);
+    }
+
+    public boolean getDidWork() {
+        return getVisitor().getDidWork();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/DeleteActionImpl.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/DeleteActionImpl.java
new file mode 100644
index 0000000..e927524
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/DeleteActionImpl.java
@@ -0,0 +1,55 @@
+/*
+ * 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.copy;
+
+import org.gradle.api.file.DeleteAction;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.util.GFileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class DeleteActionImpl implements DeleteAction {
+    private static Logger logger = LoggerFactory.getLogger(DeleteActionImpl.class);
+    
+    private FileResolver fileResolver;
+
+    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);
+            }
+        }
+        return didWork;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/EmptyCopySpecVisitor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/EmptyCopySpecVisitor.java
new file mode 100644
index 0000000..e1e23a2
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/EmptyCopySpecVisitor.java
@@ -0,0 +1,40 @@
+/*
+ * 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.file.copy;
+
+import org.gradle.api.file.CopyAction;
+import org.gradle.api.file.FileVisitDetails;
+
+public class EmptyCopySpecVisitor implements CopySpecVisitor {
+    public boolean getDidWork() {
+        return false;
+    }
+
+    public void startVisit(CopyAction action) {
+    }
+
+    public void visitDir(FileVisitDetails dirDetails) {
+    }
+
+    public void endVisit() {
+    }
+
+    public void visitFile(FileVisitDetails fileDetails) {
+    }
+
+    public void visitSpec(ReadableCopySpec spec) {
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopyAction.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopyAction.java
new file mode 100644
index 0000000..b9ba983
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopyAction.java
@@ -0,0 +1,24 @@
+/*
+ * 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.file.copy;
+
+import org.gradle.api.file.CopyAction;
+
+import java.io.File;
+
+public interface FileCopyAction extends CopyAction {
+    File getDestinationDir();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopyActionImpl.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopyActionImpl.java
new file mode 100644
index 0000000..1a35e8c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopyActionImpl.java
@@ -0,0 +1,39 @@
+/*
+ * 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.file.copy;
+
+import org.gradle.api.file.CopySpec;
+import org.gradle.api.internal.file.FileResolver;
+
+import java.io.File;
+
+public class FileCopyActionImpl extends CopyActionImpl implements FileCopyAction {
+    private Object destDir;
+
+    public FileCopyActionImpl(FileResolver resolver, CopySpecVisitor visitor) {
+        super(resolver, visitor);
+    }
+
+    @Override
+    public CopySpec into(Object destDir) {
+        this.destDir = destDir;
+        return this;
+    }
+
+    public File getDestinationDir() {
+        return destDir == null ? null : getResolver().resolve(destDir);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopySpecVisitor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopySpecVisitor.java
new file mode 100644
index 0000000..881b017
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/FileCopySpecVisitor.java
@@ -0,0 +1,54 @@
+/*
+ * 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.file.copy;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.file.CopyAction;
+import org.gradle.api.file.FileTreeElement;
+import org.gradle.api.file.FileVisitDetails;
+
+import java.io.File;
+
+/**
+ * @author Steve Appling
+ */
+public class FileCopySpecVisitor extends EmptyCopySpecVisitor {
+    private File baseDestDir;
+    private boolean didWork;
+
+    public void startVisit(CopyAction action) {
+        baseDestDir = ((FileCopyAction) action).getDestinationDir();
+        if (baseDestDir == null) {
+            throw new InvalidUserDataException("No copy destination directory has been specified, use 'into' to specify a target directory.");
+        }
+    }
+
+    public void visitFile(FileVisitDetails source) {
+        File target = source.getRelativePath().getFile(baseDestDir);
+        copyFile(source, target);
+    }
+
+    public boolean getDidWork() {
+        return didWork;
+    }
+
+    void copyFile(FileTreeElement srcFile, File destFile) {
+        boolean copied = srcFile.copyTo(destFile);
+        if (copied) {
+            didWork = true;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/FilterChain.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/FilterChain.java
new file mode 100644
index 0000000..98a5c3b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/FilterChain.java
@@ -0,0 +1,103 @@
+/*
+ * 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.file.copy;
+
+import groovy.lang.Closure;
+import groovy.text.SimpleTemplateEngine;
+import groovy.text.Template;
+import org.apache.tools.ant.util.ReaderInputStream;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Transformer;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.ChainingTransformer;
+import org.gradle.util.ConfigureUtil;
+
+import java.io.*;
+import java.lang.reflect.Constructor;
+import java.util.Map;
+
+public class FilterChain implements Transformer<InputStream> {
+    private final ChainingTransformer<Reader> transformers = new ChainingTransformer<Reader>(Reader.class);
+
+    /**
+     * Transforms the given Reader. The original Reader will be closed by the returned Reader.
+     */
+    public Reader transform(Reader original) {
+        return transformers.transform(original);
+    }
+
+    /**
+     * Transforms the given InputStream. The original InputStream will be closed by the returned InputStream.
+     */
+    public InputStream transform(InputStream original) {
+        return new ReaderInputStream(transform(new InputStreamReader(original)));
+    }
+
+    public boolean hasFilters() {
+        return transformers.hasTransformers();
+    }
+
+    public void add(Class<? extends FilterReader> filterType) {
+        add(filterType, null);
+    }
+
+    public void add(final Class<? extends FilterReader> filterType, final Map<String, ?> properties) {
+        transformers.add(new Transformer<Reader>() {
+            public Reader transform(Reader original) {
+                try {
+                    Constructor<? extends FilterReader> constructor = filterType.getConstructor(Reader.class);
+                    FilterReader result = constructor.newInstance(original);
+
+                    if (properties != null) {
+                        ConfigureUtil.configureByMap(properties, result);
+                    }
+                    return result;
+                } catch (Throwable th) {
+                    throw new InvalidUserDataException("Error - Invalid filter specification for " + filterType.getName());
+                }
+            }
+        });
+    }
+
+    public void add(final Closure closure) {
+        transformers.add(new Transformer<Reader>() {
+            public Reader transform(Reader original) {
+                return new LineFilter(original, closure);
+            }
+        });
+    }
+
+    public void expand(final Map<String, ?> properties) {
+        transformers.add(new Transformer<Reader>() {
+            public Reader transform(Reader original) {
+                try {
+                    Template template;
+                    try {
+                        SimpleTemplateEngine engine = new SimpleTemplateEngine();
+                        template = engine.createTemplate(original);
+                    } finally {
+                        original.close();
+                    }
+                    StringWriter writer = new StringWriter();
+                    template.make(properties).writeTo(writer);
+                    return new StringReader(writer.toString());
+                } catch (IOException e) {
+                    throw new UncheckedIOException(e);
+                }
+            }
+        });
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/LineFilter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/LineFilter.java
new file mode 100644
index 0000000..0f67959
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/LineFilter.java
@@ -0,0 +1,112 @@
+/*
+ * 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.copy;
+
+import groovy.lang.Closure;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+
+public class LineFilter extends Reader {
+    private final Closure closure;
+    private String transformedLine;
+    private int transformedIndex;
+    private final BufferedReader bufferedIn;
+    private final String lineTerminator;
+    private final Reader in;
+
+    /**
+     * Creates a new filtered reader.
+     *
+     * @param closure a Closure to filter each line
+     * @throws NullPointerException if <code>in</code> is <code>null</code>
+     */
+    public LineFilter(Reader in, Closure closure) {
+        this.in = in;
+        this.bufferedIn = new BufferedReader(in);
+        this.closure = closure;
+        lineTerminator = System.getProperty("line.separator");
+    }
+
+    private String getTransformedLine() throws IOException {
+        StringBuilder line = new StringBuilder();
+        boolean eol = false;
+        int ch;
+        while (!eol && (ch = bufferedIn.read()) >= 0) {
+            if (ch == '\n') {
+                eol = true;
+            }
+            else if (ch == '\r') {
+                eol = true;
+                bufferedIn.mark(1);
+                if (bufferedIn.read() != '\n') {
+                    bufferedIn.reset();
+                }
+            }
+            else {
+                line.append((char) ch);
+            }
+        }
+        if (line.length() == 0 && !eol) {
+            return null;
+        }
+
+        StringBuilder result = new StringBuilder();
+        result.append(closure.call(line.toString()).toString());
+        if (eol) {
+            result.append(lineTerminator);
+        }
+        return result.toString();
+    }
+
+    private void ensureData() throws IOException {
+        if (transformedLine == null || transformedIndex >= transformedLine.length()) {
+            transformedLine = getTransformedLine();
+            transformedIndex = 0;
+        }
+    }
+
+    @Override
+    public int read() throws IOException {
+        ensureData();
+        if (transformedLine == null) {
+            return -1;
+        }
+        return transformedLine.charAt(transformedIndex++);
+    }
+
+    @Override
+    public int read(char[] cbuf, int off, int len) throws IOException {
+        for (int i = 0; i < len; i++) {
+            final int c = read();
+            if (c == -1) {
+                if (i == 0) {
+                    return -1;
+                }
+                else {
+                    return i;
+                }
+            }
+            cbuf[off + i] = (char) c;
+        }
+        return len;
+    }
+
+    public void close() throws IOException {
+        in.close();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitor.java
new file mode 100644
index 0000000..07268ec
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitor.java
@@ -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.api.internal.file.copy;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.file.*;
+import org.gradle.api.internal.file.AbstractFileTreeElement;
+
+import java.io.*;
+import java.util.Map;
+
+public class MappingCopySpecVisitor extends DelegatingCopySpecVisitor {
+    private ReadableCopySpec spec;
+
+    public MappingCopySpecVisitor(CopySpecVisitor visitor) {
+        super(visitor);
+    }
+
+    public void visitSpec(ReadableCopySpec spec) {
+        this.spec = spec;
+        getVisitor().visitSpec(spec);
+    }
+
+    public void visitDir(FileVisitDetails dirDetails) {
+        getVisitor().visitDir(new FileVisitDetailsImpl(dirDetails, spec));
+    }
+
+    public void visitFile(final FileVisitDetails fileDetails) {
+        FileVisitDetailsImpl details = new FileVisitDetailsImpl(fileDetails, spec);
+        for (Action<? super FileCopyDetails> action : spec.getAllCopyActions()) {
+            action.execute(details);
+            if (details.excluded) {
+                return;
+            }
+        }
+        getVisitor().visitFile(details);
+    }
+
+    private static class FileVisitDetailsImpl extends AbstractFileTreeElement implements FileVisitDetails, FileCopyDetails {
+        private final FileVisitDetails fileDetails;
+        private final ReadableCopySpec spec;
+        private final FilterChain filterChain = new FilterChain();
+        private RelativePath relativePath;
+        private boolean excluded;
+
+        public FileVisitDetailsImpl(FileVisitDetails fileDetails, ReadableCopySpec spec) {
+            this.fileDetails = fileDetails;
+            this.spec = spec;
+        }
+
+        public String getDisplayName() {
+            return fileDetails.toString();
+        }
+
+        public void stopVisiting() {
+            fileDetails.stopVisiting();
+        }
+
+        public File getFile() {
+            if (filterChain.hasFilters()) {
+                throw new UnsupportedOperationException();
+            } else {
+                return fileDetails.getFile();
+            }
+        }
+
+        public boolean isDirectory() {
+            return fileDetails.isDirectory();
+        }
+
+        public long getLastModified() {
+            return fileDetails.getLastModified();
+        }
+
+        public long getSize() {
+            if (filterChain.hasFilters()) {
+                ByteCountingOutputStream outputStream = new ByteCountingOutputStream();
+                copyTo(outputStream);
+                return outputStream.size;
+            } else {
+                return fileDetails.getSize();
+            }
+        }
+
+        public InputStream open() {
+            if (filterChain.hasFilters()) {
+                return filterChain.transform(fileDetails.open());
+            } else {
+                return fileDetails.open();
+            }
+        }
+
+        public void copyTo(OutputStream outstr) {
+            if (filterChain.hasFilters()) {
+                super.copyTo(outstr);
+            } else {
+                fileDetails.copyTo(outstr);
+            }
+        }
+
+        public boolean copyTo(File target) {
+            if (filterChain.hasFilters()) {
+                return super.copyTo(target);
+            }
+            else {
+                return fileDetails.copyTo(target);
+            }
+        }
+
+        public RelativePath getRelativePath() {
+            if (relativePath == null) {
+                RelativePath path = fileDetails.getRelativePath();
+                relativePath = spec.getDestPath().append(path.isFile(), path.getSegments());
+            }
+            return relativePath;
+        }
+
+        public void setRelativePath(RelativePath path) {
+            this.relativePath = path;
+        }
+
+        public void setName(String name) {
+            relativePath = getRelativePath().replaceLastName(name);
+        }
+
+        public void setPath(String path) {
+            relativePath = RelativePath.parse(getRelativePath().isFile(), path);
+        }
+
+        public void exclude() {
+            excluded = true;
+        }
+
+        public ContentFilterable filter(Closure closure) {
+            filterChain.add(closure);
+            return this;
+        }
+
+        public ContentFilterable filter(Map<String, ?> properties, Class<? extends FilterReader> filterType) {
+            filterChain.add(filterType, properties);
+            return this;
+        }
+
+        public ContentFilterable filter(Class<? extends FilterReader> filterType) {
+            filterChain.add(filterType);
+            return this;
+        }
+
+        public ContentFilterable expand(Map<String, ?> properties) {
+            filterChain.expand(properties);
+            return this;
+        }
+    }
+
+    private static class ByteCountingOutputStream extends OutputStream {
+        long size;
+
+        @Override
+        public void write(int b) throws IOException {
+            size++;
+        }
+
+        @Override
+        public void write(byte[] b) throws IOException {
+            size += b.length;
+        }
+
+        @Override
+        public void write(byte[] b, int off, int len) throws IOException {
+            size += len;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/NormalizingCopySpecVisitor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/NormalizingCopySpecVisitor.java
new file mode 100644
index 0000000..3ea4912
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/NormalizingCopySpecVisitor.java
@@ -0,0 +1,112 @@
+/*
+ * 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.file.copy;
+
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.internal.file.AbstractFileTreeElement;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A {@link CopySpecVisitor} which cleans up the tree as it is visited. Removes duplicate and empty directories and
+ * adds in missing directories.
+ */
+public class NormalizingCopySpecVisitor extends DelegatingCopySpecVisitor {
+    private final Set<RelativePath> visitedDirs = new HashSet<RelativePath>();
+    private final Map<RelativePath, FileVisitDetails> pendingDirs = new HashMap<RelativePath, FileVisitDetails>();
+
+    public NormalizingCopySpecVisitor(CopySpecVisitor visitor) {
+        super(visitor);
+    }
+
+    public void endVisit() {
+        visitedDirs.clear();
+        pendingDirs.clear();
+        getVisitor().endVisit();
+    }
+
+    private void maybeVisit(RelativePath path) {
+        if (path == null || path.getParent() == null || !visitedDirs.add(path)) {
+            return;
+        }
+        maybeVisit(path.getParent());
+        FileVisitDetails dir = pendingDirs.remove(path);
+        if (dir == null) {
+            dir = new FileVisitDetailsImpl(path);
+        }
+        getVisitor().visitDir(dir);
+    }
+
+    public void visitFile(FileVisitDetails fileDetails) {
+        maybeVisit(fileDetails.getRelativePath().getParent());
+        getVisitor().visitFile(fileDetails);
+    }
+
+    public void visitDir(FileVisitDetails dirDetails) {
+        RelativePath path = dirDetails.getRelativePath();
+        if (!visitedDirs.contains(path)) {
+            pendingDirs.put(path, dirDetails);
+        }
+    }
+
+    private static class FileVisitDetailsImpl extends AbstractFileTreeElement implements FileVisitDetails {
+        private final RelativePath path;
+        private long lastModified = System.currentTimeMillis();
+
+        private FileVisitDetailsImpl(RelativePath path) {
+            this.path = path;
+        }
+
+        @Override
+        public String getDisplayName() {
+            return path.toString();
+        }
+
+        public void stopVisiting() {
+            throw new UnsupportedOperationException();
+        }
+
+        public File getFile() {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean isDirectory() {
+            throw new UnsupportedOperationException();
+        }
+
+        public long getLastModified() {
+            return lastModified;
+        }
+
+        public long getSize() {
+            throw new UnsupportedOperationException();
+        }
+
+        public InputStream open() {
+            throw new UnsupportedOperationException();
+        }
+
+        public RelativePath getRelativePath() {
+            return path;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/ReadableCopySpec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/ReadableCopySpec.java
new file mode 100644
index 0000000..0d58a8e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/ReadableCopySpec.java
@@ -0,0 +1,39 @@
+/*
+ * 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.file.copy;
+
+import org.gradle.api.Action;
+import org.gradle.api.file.FileCopyDetails;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.RelativePath;
+
+import java.util.Collection;
+
+public interface ReadableCopySpec {
+    RelativePath getDestPath();
+
+    int getFileMode();
+
+    int getDirMode();
+
+    FileTree getSource();
+
+    Collection<? extends ReadableCopySpec> getAllSpecs();
+
+    boolean hasSource();
+
+    Collection<? extends Action<? super FileCopyDetails>> getAllCopyActions();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/RegExpNameMapper.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/RegExpNameMapper.java
new file mode 100644
index 0000000..2fc7709
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/RegExpNameMapper.java
@@ -0,0 +1,47 @@
+/*
+ * 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.file.copy;
+
+import org.gradle.api.Transformer;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author Steve Appling
+ */
+public class RegExpNameMapper implements Transformer<String> {
+    private Matcher matcher;
+    private String replacement;
+
+    public RegExpNameMapper(String sourceRegEx, String replaceWith) {
+        this(Pattern.compile(sourceRegEx), replaceWith);
+    }
+
+    public RegExpNameMapper(Pattern sourceRegEx, String replaceWith) {
+        matcher = sourceRegEx.matcher("");
+        replacement = replaceWith;
+    }
+
+    public String transform(String source) {
+        String result = source;
+        matcher.reset(source);
+        if (matcher.find()) {
+            result = matcher.replaceFirst(replacement);
+        }
+        return result;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/RenamingCopyAction.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/RenamingCopyAction.java
new file mode 100644
index 0000000..c988a42
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/RenamingCopyAction.java
@@ -0,0 +1,35 @@
+/*
+ * 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.file.copy;
+
+import org.gradle.api.Action;
+import org.gradle.api.Transformer;
+import org.gradle.api.file.FileCopyDetails;
+import org.gradle.api.file.RelativePath;
+
+public class RenamingCopyAction implements Action<FileCopyDetails> {
+    private final Transformer<String> transformer;
+
+    public RenamingCopyAction(Transformer<String> transformer) {
+        this.transformer = transformer;
+    }
+
+    public void execute(FileCopyDetails fileCopyDetails) {
+        RelativePath path = fileCopyDetails.getRelativePath();
+        path = path.replaceLastName(transformer.transform(path.getLastName()));
+        fileCopyDetails.setRelativePath(path);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitor.java
new file mode 100644
index 0000000..42ca23d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitor.java
@@ -0,0 +1,91 @@
+/*
+ * 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.file.copy;
+
+import org.gradle.api.file.CopyAction;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.FileVisitor;
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.internal.file.DefaultDirectoryWalker;
+import org.gradle.api.internal.file.DirectoryWalker;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+
+public class SyncCopySpecVisitor extends DelegatingCopySpecVisitor {
+    private final Set<RelativePath> visited = new HashSet<RelativePath>();
+    private File baseDestDir;
+    private boolean didWork;
+
+    public SyncCopySpecVisitor(CopySpecVisitor visitor) {
+        super(visitor);
+    }
+
+    public void startVisit(CopyAction action) {
+        baseDestDir = ((FileCopyAction) action).getDestinationDir();
+        getVisitor().startVisit(action);
+    }
+
+    @Override
+    public void visitDir(FileVisitDetails dirDetails) {
+        visited.add(dirDetails.getRelativePath());
+        getVisitor().visitDir(dirDetails);
+    }
+
+    @Override
+    public void visitFile(FileVisitDetails fileDetails) {
+        visited.add(fileDetails.getRelativePath());
+        getVisitor().visitFile(fileDetails);
+    }
+
+    @Override
+    public void endVisit() {
+        FileVisitor visitor = new FileVisitor() {
+            public void visitDir(FileVisitDetails dirDetails) {
+                maybeDelete(dirDetails, true);
+            }
+
+            public void visitFile(FileVisitDetails fileDetails) {
+                maybeDelete(fileDetails, false);
+            }
+
+            private void maybeDelete(FileVisitDetails fileDetails, boolean isDir) {
+                RelativePath path = fileDetails.getRelativePath();
+                if (!visited.contains(path)) {
+                    if (isDir) {
+                        GFileUtils.deleteDirectory(fileDetails.getFile());
+                    } else {
+                        GFileUtils.deleteQuietly(fileDetails.getFile());
+                    }
+                    didWork = true;
+                }
+            }
+        };
+
+        DirectoryWalker walker = new DefaultDirectoryWalker(visitor).depthFirst();
+        walker.start(baseDestDir);
+        visited.clear();
+
+        getVisitor().endVisit();
+    }
+
+    @Override
+    public boolean getDidWork() {
+        return didWork || getVisitor().getDidWork();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/DefaultPatternMatcher.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/DefaultPatternMatcher.java
new file mode 100644
index 0000000..53dd653
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/DefaultPatternMatcher.java
@@ -0,0 +1,159 @@
+/*
+ * 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.file.pattern;
+
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.specs.Spec;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.ListIterator;
+
+/**
+ * @author Steve Appling
+ */
+public class DefaultPatternMatcher implements Spec<RelativePath> {
+    private List<PatternStep> steps;
+    private boolean partialMatchDirs;
+
+    public DefaultPatternMatcher(boolean partialMatchDirs, boolean caseSensitive, String... patternParts) {
+        this.partialMatchDirs = partialMatchDirs;
+        steps = new ArrayList<PatternStep>();
+        compile(caseSensitive, patternParts);
+    }
+
+    private void compile(boolean caseSensitive, String[] parts) {
+        if (parts.length > 0) {
+            for (int i = 0; i < parts.length; i++) {
+                steps.add(PatternStepFactory.getStep(parts[i], i == parts.length - 1, caseSensitive));
+            }
+        }
+    }
+
+    // segment -> path to test
+    // step -> pattern
+
+    public boolean isSatisfiedBy(RelativePath pathToTest) {
+        ListIterator<PatternStep> patternIt = steps.listIterator();
+        ListIterator<String> testIt = pathToTest.segmentIterator();
+        boolean seenGreedy = false;
+
+        PatternStep patternStep;
+
+        while (testIt.hasNext()) {
+            String nextToTest = testIt.next();
+
+            if (!patternIt.hasNext()) {
+                return false;
+            }
+            patternStep = patternIt.next();
+
+            if (patternStep.isGreedy()) {
+                seenGreedy = true;
+                advancePatternStepToNextNonGreedy(patternIt);
+                if (!patternIt.hasNext()) {
+                    return true;
+                }    // pattern ends in greedy
+                patternStep = patternIt.next();
+
+                // advance test until match
+                while (!(patternStep.matches(nextToTest, !testIt.hasNext() && pathToTest.isFile()) && (
+                        (patternIt.hasNext() == testIt.hasNext()) || nextPatternIsGreedy(patternIt)))) {
+                    if (!testIt.hasNext()) {
+                        return partialMatchDirs && !pathToTest
+                                .isFile(); //isTerminatingMatch(pathToTest, patternIt);  // didn't match, but no more segments to test
+                    }
+                    nextToTest = testIt.next();
+                }
+
+                // should have match at this point, can continue on around the loop
+            } else {
+                // not a greedy patternStep
+                if (!patternStep.matches(nextToTest, !testIt.hasNext() && pathToTest.isFile())) {
+                    // didn't match, check if we are after another greedy
+                    if (seenGreedy) {
+                        rewindPatternStepToPreviousGreedy(patternIt);  // rewind pattern to greedy
+                        testIt.previous(); // back up test by one
+                    } else {
+                        return false;  // haven't seen greedy, no match
+                    }
+                }
+            }
+        }
+        // ran out of stuff to test
+
+        if (!patternIt.hasNext()) {
+            return true;    // if out of pattern too, then it's a match
+        }
+
+        return isTerminatingMatch(pathToTest, patternIt);
+    }
+
+    private boolean nextPatternIsGreedy(ListIterator<PatternStep> patternIt) {
+        boolean result = false;
+        if (patternIt.hasNext()) {
+            PatternStep next = patternIt.next();
+            if (next.isGreedy() && !patternIt.hasNext()) {
+                result = true;
+            }
+            patternIt.previous();
+        }
+        return result;
+    }
+
+    private boolean isTerminatingMatch(RelativePath pathToTest, ListIterator<PatternStep> patternIt) {
+        PatternStep patternStep;
+
+        if (patternIt.hasNext()) {
+            patternStep = patternIt.next();
+            if (patternStep.isGreedy() && !patternIt.hasNext()) {
+                return true;    // if only a trailing greedy is left, then it matches
+            }
+        }
+
+        return !pathToTest.isFile() && partialMatchDirs;
+    }
+
+    private void advancePatternStepToNextNonGreedy(ListIterator<PatternStep> patternIt) {
+        PatternStep next = null;
+        while (patternIt.hasNext()) {
+            next = patternIt.next();
+            if (!next.isGreedy()) {
+                break;
+            }
+        }
+        // back up one
+        if (next != null && !next.isGreedy()) {
+            patternIt.previous();
+        }
+    }
+
+    private void rewindPatternStepToPreviousGreedy(ListIterator<PatternStep> patternIt) {
+        PatternStep result = null;
+        while (patternIt.hasPrevious()) {
+            result = patternIt.previous();
+            if (result.isGreedy()) {
+                //patternIt.next();
+                return;
+            }
+        }
+        throw new IllegalStateException("PatternStep list iterator in non-greedy state when rewindToLastGreedy");
+    }
+
+    List<PatternStep> getStepsForTest() {
+        return steps;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/GreedyPatternStep.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/GreedyPatternStep.java
new file mode 100644
index 0000000..7903443
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/GreedyPatternStep.java
@@ -0,0 +1,29 @@
+/*
+ * 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.file.pattern;
+
+/**
+ * @author Steve Appling
+ */
+public class GreedyPatternStep implements PatternStep{
+    public boolean matches(String candidate, boolean isFile) {
+        return true;
+    }
+
+    public boolean isGreedy() {
+        return true;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/NameOnlyPatternMatcher.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/NameOnlyPatternMatcher.java
new file mode 100644
index 0000000..5092754
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/NameOnlyPatternMatcher.java
@@ -0,0 +1,58 @@
+/*
+ * 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.file.pattern;
+
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.specs.Spec;
+
+/**
+ * PatternMatcher for handling the very common '**\name' pattern
+ * more efficiently than the DefaultPatternMatcher.
+ *
+ * This will only match against the last part of a relative path and this only if
+ * the RelativePath is a File.
+ * @author Steve Appling
+ */
+public class NameOnlyPatternMatcher implements Spec<RelativePath> {
+    private boolean partialMatchDirs;
+    private PatternStep nameStep;
+
+    /**
+     * CTOR
+     *
+     * Note that pattern must be a single pattern step - i.e. can't contain
+     * embedded '\' or '/'.  This is intended to match a file name.  It
+     * can contain '*', '?' as wildcards.
+     * @param partialMatchDirs
+     * @param caseSensitive
+     * @param pattern
+     */
+    public NameOnlyPatternMatcher(boolean partialMatchDirs, boolean caseSensitive, String pattern) {
+        this.partialMatchDirs = partialMatchDirs;
+        nameStep = PatternStepFactory.getStep(pattern, true, caseSensitive);
+    }
+
+    public boolean isSatisfiedBy(RelativePath path) {
+        if (!path.isFile()) {
+            return partialMatchDirs;
+        }
+        String lastName = path.getLastName();
+        if (lastName == null) {
+            return false;
+        }
+        return nameStep.matches(lastName, true);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternMatcherFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternMatcherFactory.java
new file mode 100644
index 0000000..aa66c7c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternMatcherFactory.java
@@ -0,0 +1,50 @@
+/*
+ * 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.file.pattern;
+
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.specs.Spec;
+
+/**
+ * @author Steve Appling
+ */
+public class PatternMatcherFactory {
+    public static Spec<RelativePath> getPatternMatcher(boolean partialMatchDirs, boolean caseSensitive, String pattern) {
+        // trailing / or \ assumes **
+        if (  pattern.endsWith("/") ||
+              pattern.endsWith("\\") ) {
+            pattern = pattern + "**";
+        }
+
+        if (pattern.length() == 0) {
+            return new DefaultPatternMatcher(partialMatchDirs, true);
+        } else {
+            String[] parts = pattern.split("\\\\|/");
+            if (parts.length == 2) {
+                if ("**".equals(parts[0])) {
+                    if ("**".equals(parts[1])) {
+                        // don't need second **
+                        return new DefaultPatternMatcher(partialMatchDirs, caseSensitive, "**");
+                    } else {
+                        // common name only case
+                        return new NameOnlyPatternMatcher(partialMatchDirs, caseSensitive, parts[1]);
+                    }
+                }
+            }
+            return new DefaultPatternMatcher(partialMatchDirs, caseSensitive, parts);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternStep.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternStep.java
new file mode 100644
index 0000000..ddc46af
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternStep.java
@@ -0,0 +1,24 @@
+/*
+ * 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.file.pattern;
+
+/**
+ * @author Steve Appling
+ */
+public interface PatternStep {
+    public boolean matches(String candidate, boolean isFile);
+    public boolean isGreedy();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternStepFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternStepFactory.java
new file mode 100644
index 0000000..87d02fc
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/PatternStepFactory.java
@@ -0,0 +1,26 @@
+/*
+ * 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.file.pattern;
+
+public class PatternStepFactory {
+    public static PatternStep getStep(String source, boolean isLast, boolean caseSensitive) {
+        if (source.equals("**")) {
+            return new GreedyPatternStep();
+        } else {
+            return new RegExpPatternStep(source, caseSensitive);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/RegExpPatternStep.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/RegExpPatternStep.java
new file mode 100644
index 0000000..ae80553
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/file/pattern/RegExpPatternStep.java
@@ -0,0 +1,56 @@
+/*
+ * 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.file.pattern;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author Steve Appling
+ */
+public class RegExpPatternStep implements PatternStep {
+    private static final String ESCAPE_CHARS = "\\[]^-&.{}()$+|<=!";
+    private static final String PATTERN_CHARS = "*?";
+
+    private final Pattern pattern;
+
+    public RegExpPatternStep(String pattern, boolean caseSensitive) {
+        this.pattern = Pattern.compile(getRegExPattern(pattern), caseSensitive?0:Pattern.CASE_INSENSITIVE);
+    }
+
+    protected static String getRegExPattern(String pattern) {
+        StringBuilder result = new StringBuilder();
+        for (int i=0; i<pattern.length(); i++) {
+            char next = pattern.charAt(i);
+            if (ESCAPE_CHARS.indexOf(next) >= 0) {
+                result.append('\\');
+            } else if (PATTERN_CHARS.indexOf(next) >= 0) {
+                result.append('.');
+            }
+            result.append(next);
+        }
+        return result.toString();
+    }
+
+    public boolean matches(String testString, boolean isFile) {
+        Matcher matcher = pattern.matcher(testString);
+        return matcher.matches();
+    }
+
+    public boolean isGreedy() {
+        return false;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/AbstractScriptHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/AbstractScriptHandler.java
new file mode 100644
index 0000000..82aa8a3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/AbstractScriptHandler.java
@@ -0,0 +1,84 @@
+/*
+ * 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.initialization;
+
+import groovy.lang.Closure;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.dsl.DependencyHandler;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.util.ConfigureUtil;
+import org.gradle.util.ObservableUrlClassLoader;
+
+import java.io.File;
+import java.net.URI;
+
+public abstract class AbstractScriptHandler implements ScriptHandlerInternal {
+    private final ScriptSource scriptSource;
+    private final RepositoryHandler repositoryHandler;
+    private final DependencyHandler dependencyHandler;
+    private final ConfigurationContainer configContainer;
+    private final ObservableUrlClassLoader classLoader;
+    private final Configuration classpathConfiguration;
+
+    public AbstractScriptHandler(ObservableUrlClassLoader classLoader, RepositoryHandler repositoryHandler,
+                                 DependencyHandler dependencyHandler, ScriptSource scriptSource,
+                                 ConfigurationContainer configContainer) {
+        this.classLoader = classLoader;
+        this.repositoryHandler = repositoryHandler;
+        this.dependencyHandler = dependencyHandler;
+        this.scriptSource = scriptSource;
+        this.configContainer = configContainer;
+        classpathConfiguration = configContainer.add(CLASSPATH_CONFIGURATION);
+    }
+
+    public void dependencies(Closure configureClosure) {
+        ConfigureUtil.configure(configureClosure, dependencyHandler);
+    }
+
+    protected Configuration getClasspathConfiguration() {
+        return classpathConfiguration;
+    }
+
+    public DependencyHandler getDependencies() {
+        return dependencyHandler;
+    }
+
+    public RepositoryHandler getRepositories() {
+        return repositoryHandler;
+    }
+
+    public void repositories(Closure configureClosure) {
+        ConfigureUtil.configure(configureClosure, repositoryHandler);
+    }
+
+    public ConfigurationContainer getConfigurations() {
+        return configContainer;
+    }
+
+    public ObservableUrlClassLoader getClassLoader() {
+        return classLoader;
+    }
+
+    public File getSourceFile() {
+        return scriptSource.getResource().getFile();
+    }
+
+    public URI getSourceURI() {
+        return scriptSource.getResource().getURI();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandler.java
new file mode 100644
index 0000000..652a9c5
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandler.java
@@ -0,0 +1,44 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.dsl.DependencyHandler;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.util.ObservableUrlClassLoader;
+
+import java.io.File;
+import java.net.MalformedURLException;
+
+public class DefaultScriptHandler extends AbstractScriptHandler {
+
+    public DefaultScriptHandler(ScriptSource scriptSource, RepositoryHandler repositoryHandler,
+                                DependencyHandler dependencyHandler, ConfigurationContainer configContainer,
+                                ObservableUrlClassLoader classLoader) {
+        super(classLoader, repositoryHandler, dependencyHandler, scriptSource, configContainer);
+    }
+
+    public void updateClassPath() {
+        for (File file : getClasspathConfiguration().getFiles()) {
+            try {
+                getClassLoader().addURL(file.toURI().toURL());
+            } catch (MalformedURLException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactory.java
new file mode 100644
index 0000000..5e34418
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactory.java
@@ -0,0 +1,90 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.Project;
+import org.gradle.api.UnknownProjectException;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.dsl.DependencyHandler;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.artifacts.dsl.RepositoryHandlerFactory;
+import org.gradle.api.internal.DomainObjectContext;
+import org.gradle.api.internal.artifacts.ConfigurationContainerFactory;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler;
+import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
+import org.gradle.api.internal.plugins.DefaultConvention;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.util.ObservableUrlClassLoader;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultScriptHandlerFactory implements ScriptHandlerFactory {
+    private final RepositoryHandlerFactory repositoryHandlerFactory;
+    private final ConfigurationContainerFactory configurationContainerFactory;
+    private final DependencyMetaDataProvider dependencyMetaDataProvider;
+    private final DependencyFactory dependencyFactory;
+    private final Map<Collection<Object>, ObservableUrlClassLoader> classLoaderCache = new HashMap<Collection<Object>, ObservableUrlClassLoader>();  
+    private final ProjectFinder projectFinder = new ProjectFinder() {
+        public Project getProject(String path) {
+            throw new UnknownProjectException("Cannot use project dependencies in a script classpath definition.");
+        }
+    };
+
+    public DefaultScriptHandlerFactory(RepositoryHandlerFactory repositoryHandlerFactory,
+                                       ConfigurationContainerFactory configurationContainerFactory,
+                                       DependencyMetaDataProvider dependencyMetaDataProvider,
+                                       DependencyFactory dependencyFactory) {
+        this.repositoryHandlerFactory = repositoryHandlerFactory;
+        this.configurationContainerFactory = configurationContainerFactory;
+        this.dependencyMetaDataProvider = dependencyMetaDataProvider;
+        this.dependencyFactory = dependencyFactory;
+    }
+
+    public ScriptHandlerInternal create(ScriptSource scriptSource, ClassLoader parentClassLoader) {
+        return create(scriptSource, parentClassLoader, new BasicDomainObjectContext());
+    }
+
+    public ScriptHandlerInternal create(ScriptSource scriptSource, ClassLoader parentClassLoader,
+                                        DomainObjectContext context) {
+        RepositoryHandler repositoryHandler = repositoryHandlerFactory.createRepositoryHandler(new DefaultConvention());
+        ConfigurationContainer configurationContainer = configurationContainerFactory.createConfigurationContainer(
+                repositoryHandler, dependencyMetaDataProvider, context);
+        DependencyHandler dependencyHandler = new DefaultDependencyHandler(configurationContainer, dependencyFactory,
+                projectFinder);
+        Collection<Object> key = Arrays.asList(scriptSource.getClassName(), parentClassLoader);
+        ObservableUrlClassLoader classLoader = classLoaderCache.get(key);
+        if (classLoader == null) {
+            classLoader = new ObservableUrlClassLoader(parentClassLoader);
+            classLoaderCache.put(key, classLoader);
+            return new DefaultScriptHandler(scriptSource, repositoryHandler, dependencyHandler, configurationContainer,
+                    classLoader);
+        }
+        
+        return new NoClassLoaderUpdateScriptHandler(classLoader, repositoryHandler, dependencyHandler, scriptSource, configurationContainer);
+    }
+
+    private static class BasicDomainObjectContext implements DomainObjectContext {
+        public String absolutePath(String name) {
+            return name;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/NoClassLoaderUpdateScriptHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/NoClassLoaderUpdateScriptHandler.java
new file mode 100644
index 0000000..c19f4db
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/NoClassLoaderUpdateScriptHandler.java
@@ -0,0 +1,33 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.dsl.DependencyHandler;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.util.ObservableUrlClassLoader;
+
+public class NoClassLoaderUpdateScriptHandler extends AbstractScriptHandler {
+    public NoClassLoaderUpdateScriptHandler(ObservableUrlClassLoader classLoader, RepositoryHandler repositoryHandler,
+                                            DependencyHandler dependencyHandler, ScriptSource scriptSource,
+                                            ConfigurationContainer configContainer) {
+        super(classLoader, repositoryHandler, dependencyHandler, scriptSource, configContainer);
+    }
+
+    public void updateClassPath() {
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/ScriptClassLoaderProvider.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/ScriptClassLoaderProvider.java
new file mode 100644
index 0000000..94c9bab
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/ScriptClassLoaderProvider.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.api.internal.initialization;
+
+public interface ScriptClassLoaderProvider {
+    ClassLoader getClassLoader();
+
+    void updateClassPath();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerFactory.java
new file mode 100644
index 0000000..46f5009
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerFactory.java
@@ -0,0 +1,26 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.internal.DomainObjectContext;
+import org.gradle.groovy.scripts.ScriptSource;
+
+public interface ScriptHandlerFactory {
+    ScriptHandlerInternal create(ScriptSource scriptSource, ClassLoader parentClassLoader);
+
+    ScriptHandlerInternal create(ScriptSource scriptSource, ClassLoader parentClassLoader, DomainObjectContext context);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerInternal.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerInternal.java
new file mode 100644
index 0000000..6851bc6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/initialization/ScriptHandlerInternal.java
@@ -0,0 +1,21 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.initialization.dsl.ScriptHandler;
+
+public interface ScriptHandlerInternal extends ScriptHandler, ScriptClassLoaderProvider {
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/AbstractConvention.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/AbstractConvention.java
new file mode 100644
index 0000000..4211376
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/AbstractConvention.java
@@ -0,0 +1,127 @@
+/*
+ * 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;
+
+import org.gradle.api.plugins.Convention;
+import org.gradle.api.internal.BeanDynamicObject;
+
+import java.util.*;
+
+import groovy.lang.MissingPropertyException;
+import groovy.lang.MissingMethodException;
+
+/**
+ * @author Hans Dockter
+ */
+public class AbstractConvention implements Convention {
+
+    private final Map<String, Object> plugins = new LinkedHashMap<String, Object>();
+
+    public Map<String, Object> getPlugins() {
+        return plugins;
+    }
+
+    public boolean hasProperty(String property) {
+        for (Object object : plugins.values()) {
+            if (new BeanDynamicObject(object).hasProperty(property)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public Map<String, Object> getProperties() {
+        Map<String, Object> properties = new HashMap<String, Object>();
+        List<Object> reverseOrder = new ArrayList<Object>(plugins.values());
+        Collections.reverse(reverseOrder);
+        for (Object object : reverseOrder) {
+            properties.putAll(new BeanDynamicObject(object).getProperties());
+        }
+        return properties;
+    }
+
+    public Object getProperty(String name) throws MissingPropertyException {
+        BeanDynamicObject dynamicObject = new BeanDynamicObject(this);
+        if (dynamicObject.hasProperty(name)) {
+            return dynamicObject.getProperty(name);
+        }
+        for (Object object : plugins.values()) {
+            dynamicObject = new BeanDynamicObject(object);
+            if (dynamicObject.hasProperty(name)) {
+                return dynamicObject.getProperty(name);
+            }
+        }
+        throw new MissingPropertyException(name, Convention.class);
+    }
+
+    public void setProperty(String property, Object value) {
+        for (Object object : plugins.values()) {
+            BeanDynamicObject dynamicObject = new BeanDynamicObject(object);
+            if (dynamicObject.hasProperty(property)) {
+                dynamicObject.setProperty(property, value);
+                return;
+            }
+        }
+        throw new MissingPropertyException(property, Convention.class);
+    }
+
+    public Object invokeMethod(String name, Object... arguments) {
+        for (Object object : plugins.values()) {
+            BeanDynamicObject dynamicObject = new BeanDynamicObject(object);
+            if (dynamicObject.hasMethod(name, arguments)) {
+                return dynamicObject.invokeMethod(name, arguments);
+            }
+        }
+        throw new MissingMethodException(name, Convention.class, arguments);
+    }
+
+    public boolean hasMethod(String method, Object... args) {
+        for (Object object : plugins.values()) {
+            BeanDynamicObject dynamicObject = new BeanDynamicObject(object);
+            if (dynamicObject.hasMethod(method, args)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public <T> T getPlugin(Class<T> type) {
+        T value = findPlugin(type);
+        if (value == null) {
+            throw new IllegalStateException(String.format("Could not find any convention object of type %s.",
+                    type.getSimpleName()));
+        }
+        return value;
+    }
+
+    public <T> T findPlugin(Class<T> type) throws IllegalStateException {
+        List<T> values = new ArrayList<T>();
+        for (Object object : plugins.values()) {
+            if (type.isInstance(object)) {
+                values.add(type.cast(object));
+            }
+        }
+        if (values.isEmpty()) {
+            return null;
+        }
+        if (values.size() > 1) {
+            throw new IllegalStateException(String.format("Found multiple convention objects of type %s.",
+                    type.getSimpleName()));
+        }
+        return values.get(0);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/DefaultConvention.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/DefaultConvention.groovy
new file mode 100644
index 0000000..76d2f67
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/DefaultConvention.groovy
@@ -0,0 +1,34 @@
+/*
+ * 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.plugins
+
+/**
+ * @author Hans Dockter
+ */
+class DefaultConvention extends AbstractConvention {
+    def propertyMissing(String property) {
+        super.getProperty(property)
+    }
+
+    void setProperty(String property, value) {
+        super.setProperty(property, value)
+    }
+
+    def methodMissing(String method, arguments) {
+        super.invokeMethod(method, arguments)
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationAction.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationAction.java
new file mode 100644
index 0000000..991f247
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationAction.java
@@ -0,0 +1,117 @@
+/*
+ * 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.plugins;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.plugins.ObjectConfigurationAction;
+import org.gradle.configuration.ScriptPlugin;
+import org.gradle.configuration.ScriptPluginFactory;
+import org.gradle.groovy.scripts.UriScriptSource;
+import org.gradle.util.GUtil;
+
+import java.net.URI;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class DefaultObjectConfigurationAction implements ObjectConfigurationAction {
+    private final FileResolver resolver;
+    private final ScriptPluginFactory configurerFactory;
+    private final Set<Object> targets = new LinkedHashSet<Object>();
+    private final Set<Runnable> actions = new LinkedHashSet<Runnable>();
+    private final Object[] defaultTargets;
+
+    public DefaultObjectConfigurationAction(FileResolver resolver, ScriptPluginFactory configurerFactory,
+                                            Object... defaultTargets) {
+        this.resolver = resolver;
+        this.configurerFactory = configurerFactory;
+        this.defaultTargets = defaultTargets;
+    }
+
+    public ObjectConfigurationAction to(Object... targets) {
+        GUtil.flatten(targets, this.targets);
+        return this;
+    }
+
+    public ObjectConfigurationAction from(final Object script) {
+        actions.add(new Runnable() {
+            public void run() {
+                applyScript(script);
+            }
+        });
+        return this;
+    }
+
+    public ObjectConfigurationAction plugin(final Class<? extends Plugin> pluginClass) {
+        actions.add(new Runnable() {
+            public void run() {
+                applyPlugin(pluginClass);
+            }
+        });
+        return this;
+    }
+
+    public ObjectConfigurationAction plugin(final String pluginId) {
+        actions.add(new Runnable() {
+            public void run() {
+                applyPlugin(pluginId);
+            }
+        });
+        return this;
+    }
+
+    private void applyScript(Object script) {
+        URI scriptUri = resolver.resolveUri(script);
+        ScriptPlugin configurer = configurerFactory.create(new UriScriptSource("script", scriptUri));
+        for (Object target : targets) {
+            configurer.apply(target);
+        }
+    }
+
+    private void applyPlugin(Class<? extends Plugin> pluginClass) {
+        for (Object target : targets) {
+            if (target instanceof Project) {
+                Project project = (Project) target;
+                project.getPlugins().apply(pluginClass);
+            } else {
+                throw new UnsupportedOperationException();
+            }
+        }
+    }
+
+    private void applyPlugin(String pluginId) {
+        for (Object target : targets) {
+            if (target instanceof Project) {
+                Project project = (Project) target;
+                project.getPlugins().apply(pluginId);
+            } else {
+                throw new UnsupportedOperationException();
+            }
+        }
+    }
+
+    public void execute() {
+        if (targets.isEmpty()) {
+            to(defaultTargets);
+        }
+
+        for (Runnable action : actions) {
+            action.run();
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/DefaultPluginCollection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/DefaultPluginCollection.java
new file mode 100644
index 0000000..ec061d4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/DefaultPluginCollection.java
@@ -0,0 +1,64 @@
+/*
+ * 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.plugins;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.Plugin;
+import org.gradle.api.internal.DefaultDomainObjectContainer;
+import org.gradle.api.plugins.PluginCollection;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+
+public class DefaultPluginCollection<T extends Plugin> extends DefaultDomainObjectContainer<T>
+        implements PluginCollection<T> {
+    public DefaultPluginCollection(Class<T> type) {
+        super(type);
+    }
+
+    protected DefaultPluginCollection(Class<T> type, ObjectStore<T> store) {
+        super(type, store);
+    }
+
+    public PluginCollection<T> matching(Spec<? super T> spec) {
+        return new DefaultPluginCollection<T>(getType(), storeWithSpec(spec));
+    }
+
+    @Override
+    public PluginCollection<T> matching(Closure spec) {
+        return matching(Specs.convertClosureToSpec(spec));
+    }
+
+    public <S extends T> PluginCollection<S> withType(Class<S> type) {
+        return new DefaultPluginCollection<S>(type, storeWithType(type));
+    }
+
+    public Action<? super T> whenPluginAdded(Action<? super T> action) {
+        return whenObjectAdded(action);
+    }
+
+    public void whenPluginAdded(Closure closure) {
+        whenObjectAdded(closure);
+    }
+
+    public void allPlugins(Action<? super T> action) {
+        allObjects(action);
+    }
+
+    public void allPlugins(Closure closure) {
+        allObjects(closure);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistry.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistry.java
new file mode 100644
index 0000000..d973901
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistry.java
@@ -0,0 +1,110 @@
+/*
+ * 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.plugins;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Plugin;
+import org.gradle.api.plugins.PluginInstantiationException;
+import org.gradle.api.plugins.UnknownPluginException;
+import org.gradle.util.GUtil;
+
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * @author Hans Dockter
+ */
+
+public class DefaultPluginRegistry implements PluginRegistry {
+    private final Map<String, Class<? extends Plugin>> idMappings = new HashMap<String, Class<? extends Plugin>>();
+    private final DefaultPluginRegistry parent;
+    private final ClassLoader classLoader;
+
+    public DefaultPluginRegistry(ClassLoader classLoader) {
+        this(null, classLoader);
+    }
+
+    private DefaultPluginRegistry(DefaultPluginRegistry parent, ClassLoader classLoader) {
+        this.parent = parent;
+        this.classLoader = classLoader;
+    }
+
+    public PluginRegistry createChild(ClassLoader childClassPath) {
+        return new DefaultPluginRegistry(this, childClassPath);
+    }
+
+    public <T extends Plugin> T loadPlugin(Class<T> pluginClass) {
+        if (parent != null) {
+            return parent.loadPlugin(pluginClass);
+        }
+
+        if (!Plugin.class.isAssignableFrom(pluginClass)) {
+            throw new InvalidUserDataException(String.format(
+                    "Cannot create plugin of type '%s' as it does not implement the Plugin interface.",
+                    pluginClass.getSimpleName()));
+        }
+        try {
+            return pluginClass.newInstance();
+        } catch (InstantiationException e) {
+            throw new PluginInstantiationException(String.format("Could not create plugin of type '%s'.",
+                    pluginClass.getSimpleName()), e.getCause());
+        } catch (Exception e) {
+            throw new PluginInstantiationException(String.format("Could not create plugin of type '%s'.",
+                    pluginClass.getSimpleName()), e);
+        }
+    }
+
+    public Class<? extends Plugin> getTypeForId(String pluginId) {
+        if (parent != null) {
+            try {
+                return parent.getTypeForId(pluginId);
+            } catch (UnknownPluginException e) {
+                // Ignore
+            }
+        }
+
+        Class<? extends Plugin> implClass = idMappings.get(pluginId);
+        if (implClass != null) {
+            return implClass;
+        }
+
+        URL resource = classLoader.getResource(String.format("META-INF/gradle-plugins/%s.properties", pluginId));
+        if (resource == null) {
+            throw new UnknownPluginException("Plugin with id '" + pluginId + "' not found.");
+        }
+
+        Properties properties = GUtil.loadProperties(resource);
+        String implClassName = properties.getProperty("implementation-class");
+        if (!GUtil.isTrue(implClassName)) {
+            throw new PluginInstantiationException(String.format(
+                    "No implementation class specified for plugin '%s' in %s.", pluginId, resource));
+        }
+
+        try {
+            implClass = classLoader.loadClass(implClassName).asSubclass(Plugin.class);
+        } catch (ClassNotFoundException e) {
+            throw new PluginInstantiationException(String.format(
+                    "Could not find implementation class '%s' for plugin '%s' specified in %s.", implClass, pluginId,
+                    resource), e);
+        }
+
+        idMappings.put(pluginId, implClass);
+        return implClass;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/DefaultProjectsPluginContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/DefaultProjectsPluginContainer.java
new file mode 100644
index 0000000..1630aa7
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/DefaultProjectsPluginContainer.java
@@ -0,0 +1,106 @@
+/*
+ * 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.plugins;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.plugins.PluginContainer;
+import org.gradle.api.plugins.UnknownPluginException;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultProjectsPluginContainer extends DefaultPluginCollection<Plugin> implements PluginContainer {
+    private PluginRegistry pluginRegistry;
+    private final Project project;
+
+    public DefaultProjectsPluginContainer(PluginRegistry pluginRegistry, Project project) {
+        super(Plugin.class);
+        this.pluginRegistry = pluginRegistry;
+        this.project = project;
+    }
+
+    public Plugin apply(String id) {
+        return addPluginInternal(getTypeForId(id));
+    }
+
+    public <T extends Plugin> T apply(Class<T> type) {
+        return addPluginInternal(type);
+    }
+
+    public boolean hasPlugin(String id) {
+        return findPlugin(id) != null;
+    }
+
+    public boolean hasPlugin(Class<? extends Plugin> type) {
+        return findPlugin(type) != null;
+    }
+
+    public Plugin findPlugin(String id) {
+        return findPlugin(getTypeForId(id));
+    }
+
+    public <T extends Plugin> T findPlugin(Class<T> type) {
+        for (Plugin plugin : getAll()) {
+            if (plugin.getClass().equals(type)) {
+                return type.cast(plugin);
+            }
+        }
+        return null;
+    }
+
+    private <T extends Plugin> T addPluginInternal(Class<T> type) {
+        if (findPlugin(type) == null) {
+            Plugin plugin = providePlugin(type);
+            addObject(plugin);
+        }
+        return type.cast(findPlugin(type));
+    }
+
+    public Plugin getPlugin(String id) {
+        Plugin plugin = findPlugin(id);
+        if (plugin == null) {
+            throw new UnknownPluginException("Plugin with id " + id + " has not been used.");
+        }
+        return plugin;
+    }
+
+    public Plugin getAt(String id) throws UnknownPluginException {
+        return getPlugin(id);
+    }
+
+    public <T extends Plugin> T getAt(Class<T> type) throws UnknownPluginException {
+        return getPlugin(type);
+    }
+
+    public <T extends Plugin> T getPlugin(Class<T> type) throws UnknownPluginException {
+        Plugin plugin = findPlugin(type);
+        if (plugin == null) {
+            throw new UnknownPluginException("Plugin with type " + type + " has not been used.");
+        }
+        return type.cast(plugin);
+    }
+
+    protected Class<? extends Plugin> getTypeForId(String id) {
+        return pluginRegistry.getTypeForId(id);
+    }
+
+    private Plugin<Project> providePlugin(Class<? extends Plugin> type) {
+        Plugin<Project> plugin = pluginRegistry.loadPlugin(type);
+        plugin.apply(project);
+        return plugin;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/EmbeddableJavaProject.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/EmbeddableJavaProject.java
new file mode 100644
index 0000000..357fa9b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/EmbeddableJavaProject.java
@@ -0,0 +1,31 @@
+/*
+ * 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.plugins;
+
+import org.gradle.api.file.FileCollection;
+
+import java.util.Collection;
+
+/**
+ * Meta-info about a Java project which can be embedded in the build.
+ */
+public interface EmbeddableJavaProject {
+    Collection<String> getRebuildTasks();
+
+    Collection<String> getBuildTasks();
+
+    FileCollection getRuntimeClasspath();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/PluginRegistry.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/PluginRegistry.java
new file mode 100644
index 0000000..8205d35
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/plugins/PluginRegistry.java
@@ -0,0 +1,31 @@
+/*
+ * 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.plugins;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.plugins.PluginInstantiationException;
+
+/**
+ * @author Hans Dockter
+ */
+public interface PluginRegistry {
+    <T extends Plugin> T loadPlugin(Class<T> pluginClass) throws PluginInstantiationException;
+
+    Class<? extends Plugin> getTypeForId(String pluginId);
+
+    PluginRegistry createChild(ClassLoader childClassPath);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/AbstractProject.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/AbstractProject.java
new file mode 100644
index 0000000..4f5f853
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/AbstractProject.java
@@ -0,0 +1,944 @@
+/*
+ * 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.project;
+
+import groovy.lang.Closure;
+import groovy.lang.MissingPropertyException;
+import groovy.lang.Script;
+import org.gradle.api.*;
+import org.gradle.api.artifacts.ConfigurationContainer;
+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.artifacts.dsl.RepositoryHandlerFactory;
+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.*;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.file.FileOperations;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.initialization.ScriptClassLoaderProvider;
+import org.gradle.api.internal.plugins.DefaultObjectConfigurationAction;
+import org.gradle.api.internal.tasks.TaskContainerInternal;
+import org.gradle.api.logging.*;
+import org.gradle.api.plugins.Convention;
+import org.gradle.api.plugins.PluginContainer;
+import org.gradle.api.tasks.Directory;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.configuration.ProjectEvaluator;
+import org.gradle.configuration.ScriptPlugin;
+import org.gradle.configuration.ScriptPluginFactory;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.listener.ListenerBroadcast;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.logging.StandardOutputCapture;
+import org.gradle.process.ExecResult;
+import org.gradle.util.Configurable;
+import org.gradle.util.ConfigureUtil;
+import org.gradle.util.DeprecationLogger;
+import org.gradle.util.PathHelper;
+
+import java.io.File;
+import java.net.URI;
+import java.util.*;
+
+import static java.util.Collections.*;
+import static org.gradle.util.GUtil.*;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractProject implements ProjectInternal, DynamicObjectAware {
+    private static Logger buildLogger = Logging.getLogger(Project.class);
+    private ServiceRegistryFactory services;
+
+    private final Project rootProject;
+
+    private final GradleInternal gradle;
+
+    private ProjectEvaluator projectEvaluator;
+
+    private ScriptSource buildScriptSource;
+
+    private final File projectDir;
+
+    private final ProjectInternal parent;
+
+    private final String name;
+
+    private Object group;
+
+    private Object version;
+
+    private Object status;
+
+    private final Map<String, Project> childProjects = new HashMap<String, Project>();
+
+    private List<String> defaultTasks = new ArrayList<String>();
+
+    private Set<Project> dependsOnProjects = new HashSet<Project>();
+
+    private ProjectStateInternal state;
+
+    private FileResolver fileResolver;
+    private FileOperations fileOperations;
+
+    private AntBuilderFactory antBuilderFactory;
+
+    private AntBuilder ant;
+
+    private Object buildDir = Project.DEFAULT_BUILD_DIR_NAME;
+
+    private PluginContainer pluginContainer;
+
+    private final String path;
+
+    private final int depth;
+
+    private TaskContainerInternal taskContainer;
+
+    private IProjectRegistry<ProjectInternal> projectRegistry;
+
+    private DependencyHandler dependencyHandler;
+
+    private ConfigurationContainer configurationContainer;
+
+    private ArtifactHandler artifactHandler;
+
+    private RepositoryHandlerFactory repositoryHandlerFactory;
+
+    private RepositoryHandler repositoryHandler;
+
+    private ScriptHandler scriptHandler;
+
+    private ScriptClassLoaderProvider scriptClassLoaderProvider;
+
+    private ListenerBroadcast<ProjectEvaluationListener> evaluationListener = new ListenerBroadcast<ProjectEvaluationListener>(ProjectEvaluationListener.class);
+
+    private LoggingManagerInternal loggingManager;
+
+    private DynamicObjectHelper dynamicObjectHelper;
+
+    public AbstractProject(String name,
+                           ProjectInternal parent,
+                           File projectDir,
+                           ScriptSource buildScriptSource,
+                           GradleInternal gradle,
+                           ServiceRegistryFactory serviceRegistryFactory) {
+        assert name != null;
+        this.rootProject = parent != null ? parent.getRootProject() : this;
+        this.projectDir = projectDir;
+        this.parent = parent;
+        this.name = name;
+        this.state = new ProjectStateInternal();
+        this.buildScriptSource = buildScriptSource;
+        this.gradle = gradle;
+
+        if (parent == null) {
+            path = Project.PATH_SEPARATOR;
+            depth = 0;
+        } else {
+            path = parent.absolutePath(name);
+            depth = parent.getDepth() + 1;
+        }
+
+        services = serviceRegistryFactory.createFor(this);
+        fileResolver = services.get(FileResolver.class);
+        fileOperations = services.get(FileOperations.class);
+        antBuilderFactory = services.get(AntBuilderFactory.class);
+        taskContainer = services.get(TaskContainerInternal.class);
+        repositoryHandlerFactory = services.get(RepositoryHandlerFactory.class);
+        projectEvaluator = services.get(ProjectEvaluator.class);
+        repositoryHandler = services.get(RepositoryHandler.class);
+        configurationContainer = services.get(ConfigurationContainer.class);
+        pluginContainer = services.get(PluginContainer.class);
+        artifactHandler = services.get(ArtifactHandler.class);
+        dependencyHandler = services.get(DependencyHandler.class);
+        scriptHandler = services.get(ScriptHandler.class);
+        scriptClassLoaderProvider = services.get(ScriptClassLoaderProvider.class);
+        projectRegistry = services.get(IProjectRegistry.class);
+        loggingManager = services.get(LoggingManagerInternal.class);
+
+        dynamicObjectHelper = new DynamicObjectHelper(this);
+        dynamicObjectHelper.setConvention(services.get(Convention.class));
+        if (parent != null) {
+            dynamicObjectHelper.setParent(parent.getInheritedScope());
+        }
+        dynamicObjectHelper.addObject(taskContainer.getAsDynamicObject(), DynamicObjectHelper.Location.AfterConvention);
+
+        evaluationListener.add(gradle.getProjectEvaluationBroadcaster());
+    }
+
+    public RepositoryHandler createRepositoryHandler() {
+        RepositoryHandler handler = repositoryHandlerFactory.createRepositoryHandler(getConvention());
+        ((IConventionAware) handler).setConventionMapping(((IConventionAware) repositoryHandler).getConventionMapping());
+        return handler;
+    }
+
+    public Project getRootProject() {
+        return rootProject;
+    }
+
+    public GradleInternal getGradle() {
+        return gradle;
+    }
+
+    public PluginContainer getPlugins() {
+        return pluginContainer;
+    }
+
+    public ProjectEvaluator getProjectEvaluator() {
+        return projectEvaluator;
+    }
+
+    public void setProjectEvaluator(ProjectEvaluator projectEvaluator) {
+        this.projectEvaluator = projectEvaluator;
+    }
+
+    public ScriptHandler getBuildscript() {
+        return scriptHandler;
+    }
+
+    public void beforeCompile(ScriptPlugin configurer) {
+        if (configurer.getSource() != buildScriptSource) {
+            return;
+        }
+        configurer.setScriptBaseClass(ProjectScript.class);
+        configurer.setClassLoaderProvider(scriptClassLoaderProvider);
+    }
+
+    public void afterCompile(ScriptPlugin configurer, org.gradle.groovy.scripts.Script script) {
+        if (configurer.getSource() != buildScriptSource) {
+            return;
+        }
+        setScript(script);
+    }
+
+    public File getBuildFile() {
+        return getBuildscript().getSourceFile();
+    }
+
+    public void setScript(Script buildScript) {
+        dynamicObjectHelper.addObject(new BeanDynamicObject(buildScript).withNoProperties(),
+                DynamicObjectHelper.Location.BeforeConvention);
+    }
+
+    public ScriptSource getBuildScriptSource() {
+        return buildScriptSource;
+    }
+
+    public File getRootDir() {
+        return rootProject.getProjectDir();
+    }
+
+    public ProjectInternal getParent() {
+        return parent;
+    }
+
+    public ProjectIdentifier getParentIdentifier() {
+        return parent;
+    }
+
+    public DynamicObject getAsDynamicObject() {
+        return dynamicObjectHelper;
+    }
+
+    public DynamicObject getInheritedScope() {
+        return dynamicObjectHelper.getInheritable();
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Object getGroup() {
+        if (group != null) {
+            return group;
+        } else if (this == rootProject) {
+            return "";
+        }
+        return rootProject.getName() + ( getParent() == rootProject ? "" : "." + getParent().getPath().substring(1).replace(':', '.'));
+    }
+
+    public void setGroup(Object group) {
+        this.group = group;
+    }
+
+    public Object getVersion() {
+        return version == null ? DEFAULT_VERSION : version;
+    }
+
+    public void setVersion(Object version) {
+        this.version = version;
+    }
+
+    public Object getStatus() {
+        return status == null ? DEFAULT_STATUS : status;
+    }
+
+    public void setStatus(Object status) {
+        this.status = status;
+    }
+
+    public Map<String, Project> getChildProjects() {
+        return childProjects;
+    }
+
+    public List<String> getDefaultTasks() {
+        return defaultTasks;
+    }
+
+    public void setDefaultTasks(List<String> defaultTasks) {
+        this.defaultTasks = defaultTasks;
+    }
+
+    public Set<Project> getDependsOnProjects() {
+        return dependsOnProjects;
+    }
+
+    public Map<String, Object> getAdditionalProperties() {
+        return dynamicObjectHelper.getAdditionalProperties();
+    }
+
+    public ProjectStateInternal getState() {
+        return state;
+    }
+
+    public FileResolver getFileResolver() {
+        return fileResolver;
+    }
+
+    public void setFileResolver(FileResolver fileResolver) {
+        this.fileResolver = fileResolver;
+    }
+
+    public void setAnt(AntBuilder ant) {
+        this.ant = ant;
+    }
+
+    public ArtifactHandler getArtifacts() {
+        return artifactHandler;
+    }
+
+    public void setArtifactHandler(ArtifactHandler artifactHandler) {
+        this.artifactHandler = artifactHandler;
+    }
+
+    public RepositoryHandler getRepositories() {
+        return repositoryHandler;
+    }
+
+    public RepositoryHandlerFactory getRepositoryHandlerFactory() {
+        return repositoryHandlerFactory;
+    }
+
+    public ConfigurationContainer getConfigurations() {
+        return configurationContainer;
+    }
+
+    public void setConfigurationContainer(ConfigurationContainer configurationContainer) {
+        this.configurationContainer = configurationContainer;
+    }
+
+    public String getBuildDirName() {
+        return buildDir.toString();
+    }
+
+    public void setBuildDirName(String buildDirName) {
+        DeprecationLogger.nagUser("Project.setBuildDirName()", "setBuildDir()");
+        this.buildDir = buildDirName;
+    }
+
+    public Convention getConvention() {
+        return dynamicObjectHelper.getConvention();
+    }
+
+    public void setConvention(Convention convention) {
+        dynamicObjectHelper.setConvention(convention);
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    public int getDepth() {
+        return depth;
+    }
+
+    public IProjectRegistry<ProjectInternal> getProjectRegistry() {
+        return projectRegistry;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        AbstractProject that = (AbstractProject) o;
+
+        return path.equals(that.path);
+    }
+
+    @Override
+    public int hashCode() {
+        return path.hashCode();
+    }
+
+    public int depthCompare(Project otherProject) {
+        return new Integer(getDepth()).compareTo(otherProject.getDepth());
+    }
+
+    public int compareTo(Project otherProject) {
+        int depthCompare = depthCompare(otherProject);
+        if (depthCompare == 0) {
+            return path.compareTo(otherProject.getPath());
+        } else {
+            return depthCompare;
+        }
+    }
+
+    public String absolutePath(String path) {
+        if (!isAbsolutePath(path)) {
+            String prefix = this == rootProject ? "" : Project.PATH_SEPARATOR;
+            return this.path + prefix + path;
+        }
+        return path;
+    }
+
+    public static boolean isAbsolutePath(String path) {
+        return PathHelper.isAbsolutePath(path);
+    }
+
+    public Project project(String path) {
+        Project project = findProject(path);
+        if (project == null) {
+            throw new UnknownProjectException(String.format("Project with path '%s' could not be found in %s.", path, this));
+        }
+        return project;
+    }
+
+    public Project findProject(String path) {
+        if (!isTrue(path)) {
+            throw new InvalidUserDataException("A path must be specified!");
+        }
+        return projectRegistry.getProject(isAbsolutePath(path) ? path : absolutePath(path));
+    }
+
+    public Set<Project> getAllprojects() {
+        return new TreeSet<Project>(projectRegistry.getAllProjects(this.path));
+    }
+
+    public Set<Project> getSubprojects() {
+        return new TreeSet<Project>(projectRegistry.getSubProjects(this.path));
+    }
+
+    public void subprojects(Action<? super Project> action) {
+        configure(getSubprojects(), action);
+    }
+
+    public void allprojects(Action<? super Project> action) {
+        configure(getAllprojects(), action);
+    }
+
+    public <T> Iterable<T> configure(Iterable<T> objects, Action<? super T> configureAction) {
+        for (T object : objects) {
+            configureAction.execute(object);
+        }
+        return objects;
+    }
+
+    public AntBuilder getAnt() {
+        if (ant == null) {
+            ant = createAntBuilder();
+        }
+        return ant;
+    }
+
+    public AntBuilder createAntBuilder() {
+        return antBuilderFactory.createAntBuilder();
+    }
+
+    /**
+     * This method is used when scripts access the project via project.x
+     */
+    public Project getProject() {
+        return this;
+    }
+
+    public AbstractProject evaluate() {
+        projectEvaluator.evaluate(this, state);
+        state.rethrowFailure();
+        return this;
+    }
+
+    public Project usePlugin(String pluginId) {
+        warnUsePluginDeprecated();
+        pluginContainer.apply(pluginId);
+        return this;
+    }
+
+    public Project usePlugin(Class<? extends Plugin> pluginClass) {
+        warnUsePluginDeprecated();
+        pluginContainer.apply(pluginClass);
+        return this;
+    }
+
+    public TaskContainerInternal getTasks() {
+        return taskContainer;
+    }
+
+    public void defaultTasks(String... defaultTasks) {
+        if (defaultTasks == null) {
+            throw new InvalidUserDataException("Default tasks must not be null!");
+        }
+        this.defaultTasks = new ArrayList<String>();
+        for (String defaultTask : defaultTasks) {
+            if (defaultTask == null) {
+                throw new InvalidUserDataException("Default tasks must not be null!");
+            }
+            this.defaultTasks.add(defaultTask);
+        }
+    }
+
+    public Task createTask(String name) {
+        return createTask(new HashMap<String, Object>(), name, (Action) null);
+    }
+
+    public Task createTask(Map<String, ?> args, String name) {
+        return createTask(args, name, (Action) null);
+    }
+
+    public Task createTask(String name, Action<? super Task> action) {
+        return createTask(new HashMap<String, Object>(), name, action);
+    }
+
+    public Task createTask(String name, Closure action) {
+        return createTask(new HashMap<String, Object>(), name, action);
+    }
+
+    public Task createTask(Map args, String name, Closure action) {
+        warnCreateTaskDeprecated();
+        Map<String, Object> allArgs = new HashMap<String, Object>(args);
+        allArgs.put(Task.TASK_NAME, name);
+        allArgs.put(Task.TASK_ACTION, action);
+        return taskContainer.add(allArgs);
+    }
+
+    public Task createTask(Map<String, ?> args, String name, Action<? super Task> action) {
+        warnCreateTaskDeprecated();
+        Map<String, Object> allArgs = new HashMap<String, Object>(args);
+        allArgs.put(Task.TASK_NAME, name);
+        if (action != null) {
+            allArgs.put(Task.TASK_ACTION, action);
+        }
+        return taskContainer.add(allArgs);
+    }
+
+    private void warnCreateTaskDeprecated() {
+        DeprecationLogger.nagUser("Project.createTask()", "task()");
+    }
+
+    private void warnUsePluginDeprecated() {
+        DeprecationLogger.nagUser("Project.usePlugin()", "apply()");
+    }
+
+    public void addChildProject(ProjectInternal childProject) {
+        childProjects.put(childProject.getName(), childProject);
+    }
+
+    public File getProjectDir() {
+        return projectDir;
+    }
+
+    public File getBuildDir() {
+        return file(buildDir);
+    }
+
+    public void setBuildDir(Object path) {
+        buildDir = path;
+    }
+
+    public void dependsOn(String path) {
+        dependsOn(path, true);
+    }
+
+    public void dependsOn(String path, boolean evaluateDependsOnProject) {
+        if (!isTrue(path)) {
+            throw new InvalidUserDataException("You must specify a project!");
+        }
+        dependsOnProjects.add(project(path));
+        if (evaluateDependsOnProject) {
+            evaluationDependsOn(path);
+        }
+    }
+
+    public Project evaluationDependsOn(String path) {
+        if (!isTrue(path)) {
+            throw new InvalidUserDataException("You must specify a project!");
+        }
+        DefaultProject projectToEvaluate = (DefaultProject) project(path);
+        if (projectToEvaluate.getState().getExecuting()) {
+            throw new CircularReferenceException(String.format("Circular referencing during evaluation for %s.",
+                    projectToEvaluate));
+        }
+        return projectToEvaluate.evaluate();
+    }
+
+    public Project childrenDependOnMe() {
+        for (Project project : childProjects.values()) {
+            project.dependsOn(this.path, false);
+        }
+        return this;
+    }
+
+    public Project dependsOnChildren() {
+        return dependsOnChildren(false);
+    }
+
+    public Project dependsOnChildren(boolean evaluateDependsOnProject) {
+        for (Project project : childProjects.values()) {
+            dependsOn(project.getPath(), evaluateDependsOnProject);
+        }
+        return this;
+    }
+
+    public String toString() {
+        if (parent != null) {
+            return String.format("project '%s'", path);
+        } else {
+            return String.format("root project '%s'", name);
+        }
+    }
+
+    public Map<Project, Set<Task>> getAllTasks(boolean recursive) {
+        final Map<Project, Set<Task>> foundTargets = new TreeMap<Project, Set<Task>>();
+        Action<Project> action = new Action<Project>() {
+            public void execute(Project project) {
+                foundTargets.put(project, new TreeSet<Task>(project.getTasks().getAll()));
+            }
+        };
+        if (recursive) {
+            allprojects(action);
+        } else {
+            action.execute(this);
+        }
+        return foundTargets;
+    }
+
+    public Set<Task> getTasksByName(final String name, boolean recursive) {
+        if (!isTrue(name)) {
+            throw new InvalidUserDataException("Name is not specified!");
+        }
+        final Set<Task> foundTasks = new HashSet<Task>();
+        Action<Project> action = new Action<Project>() {
+            public void execute(Project project) {
+                Task task = project.getTasks().findByName(name);
+                if (task != null) {
+                    foundTasks.add(task);
+                }
+            }
+        };
+        if (recursive) {
+            allprojects(action);
+        } else {
+            action.execute(this);
+        }
+        return foundTasks;
+    }
+
+    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 closure) {
+        return fileOperations.files(paths, closure);
+    }
+
+    public ConfigurableFileTree fileTree(Object baseDir) {
+        return fileOperations.fileTree(baseDir);
+    }
+
+    public ConfigurableFileTree fileTree(Map<String, ?> args) {
+        return fileOperations.fileTree(args);
+    }
+
+    public ConfigurableFileTree fileTree(Closure closure) {
+        return fileOperations.fileTree(closure);
+    }
+
+    public FileTree zipTree(Object zipPath) {
+        return fileOperations.zipTree(zipPath);
+    }
+
+    public FileTree tarTree(Object tarPath) {
+        return fileOperations.tarTree(tarPath);
+    }
+
+    public String relativePath(Object path) {
+        return fileOperations.relativePath(path);
+    }
+
+    public File mkdir(Object path) {
+        return fileOperations.mkdir(path);
+    }
+
+    public boolean delete(Object... paths) {
+        return fileOperations.delete(paths);
+    }
+
+    public Directory dir(String path) {
+        String[] pathElements = path.split("/");
+        String name = "";
+        Directory dirTask = null;
+        for (String pathElement : pathElements) {
+            name += name.length() != 0 ? "/" + pathElement : pathElement;
+            Task task = taskContainer.findByName(name);
+            if (task instanceof Directory) {
+                dirTask = (Directory) task;
+            } else if (task != null) {
+                throw new InvalidUserDataException(String.format("Cannot add directory task '%s' as a non-directory task with this name already exists.", name));
+            } else {
+                dirTask = taskContainer.add(name, Directory.class);
+            }
+        }
+        return dirTask;
+    }
+
+    public void setTaskContainer(TaskContainerInternal taskContainer) {
+        this.taskContainer = taskContainer;
+    }
+
+    public AntBuilderFactory getAntBuilderFactory() {
+        return antBuilderFactory;
+    }
+
+    public void setAntBuilderFactory(AntBuilderFactory antBuilderFactory) {
+        this.antBuilderFactory = antBuilderFactory;
+    }
+
+    public DependencyHandler getDependencies() {
+        return dependencyHandler;
+    }
+
+    public void setDependencyHandler(DependencyHandler dependencyHandler) {
+        this.dependencyHandler = dependencyHandler;
+    }
+
+    public ProjectEvaluationListener getProjectEvaluationBroadcaster() {
+        return evaluationListener.getSource();
+    }
+
+    public void beforeEvaluate(Action<? super Project> action) {
+        evaluationListener.add("beforeEvaluate", action);
+    }
+
+    public void afterEvaluate(Action<? super Project> action) {
+        evaluationListener.add("afterEvaluate", action);
+    }
+
+    public void beforeEvaluate(Closure closure) {
+        evaluationListener.add("beforeEvaluate", closure);
+    }
+
+    public void afterEvaluate(Closure closure) {
+        evaluationListener.add("afterEvaluate", closure);
+    }
+
+    public Logger getLogger() {
+        return buildLogger;
+    }
+
+    public StandardOutputCapture getStandardOutputCapture() {
+        return loggingManager;
+    }
+
+    @Override
+    public LoggingManager getLogging() {
+        return loggingManager;
+    }
+
+    public void disableStandardOutputCapture() {
+        DeprecationLogger.nagUser("Project.disableStandardOutputCapture()");
+        loggingManager.disableStandardOutputCapture();
+    }
+
+    public void captureStandardOutput(LogLevel level) {
+        DeprecationLogger.nagUser("Project.captureStandardOutput()", "getLogging().captureStandardOutput()");
+        loggingManager.captureStandardOutput(level);
+    }
+
+    public Object property(String propertyName) throws MissingPropertyException {
+        return dynamicObjectHelper.getProperty(propertyName);
+    }
+
+    public void setProperty(String name, Object value) {
+        dynamicObjectHelper.setProperty(name, value);
+    }
+
+    public boolean hasProperty(String propertyName) {
+        return dynamicObjectHelper.hasProperty(propertyName);
+    }
+
+    public Map<String, ?> getProperties() {
+        return dynamicObjectHelper.getProperties();
+    }
+
+    public WorkResult copy(Closure closure) {
+        return fileOperations.copy(closure);
+    }
+
+    public CopySpec copySpec(Closure closure) {
+        return fileOperations.copySpec(closure);
+    }
+
+    public ExecResult javaexec(Closure closure) {
+        return fileOperations.javaexec(closure);
+    }
+
+    public ExecResult exec(Closure closure) {
+        return fileOperations.exec(closure);
+    }
+
+    public ServiceRegistryFactory getServiceRegistryFactory() {
+        return services;
+    }
+
+    public Module getModule() {
+        return getServiceRegistryFactory().get(DependencyMetaDataProvider.class).getModule();
+    }
+
+    public void apply(Closure closure) {
+        DefaultObjectConfigurationAction action = new DefaultObjectConfigurationAction(fileResolver, services.get(
+                ScriptPluginFactory.class), this);
+        configure(action, closure);
+        action.execute();
+    }
+
+    public void apply(Map<String, ?> options) {
+        DefaultObjectConfigurationAction action = new DefaultObjectConfigurationAction(fileResolver, services.get(
+                ScriptPluginFactory.class), this);
+        ConfigureUtil.configureByMap(options, action);
+        action.execute();
+    }
+
+    public AntBuilder ant(Closure configureClosure) {
+        return ConfigureUtil.configure(configureClosure, getAnt());
+    }
+
+    public void subprojects(Closure configureClosure) {
+        configure(getSubprojects(), configureClosure);
+    }
+
+    public void allprojects(Closure configureClosure) {
+        configure(getAllprojects(), configureClosure);
+    }
+
+    public Project project(String path, Closure configureClosure) {
+        return ConfigureUtil.configure(configureClosure, project(path));
+    }
+
+    public Object configure(Object object, Closure configureClosure) {
+        return ConfigureUtil.configure(configureClosure, object);
+    }
+
+    public Iterable<?> configure(Iterable<?> objects, Closure configureClosure) {
+        for (Object object : objects) {
+            configure(object, configureClosure);
+        }
+        return objects;
+    }
+
+    public void configurations(Closure configureClosure) {
+        ((Configurable<?>) getConfigurations()).configure(configureClosure);
+    }
+
+    public void repositories(Closure configureClosure) {
+        ConfigureUtil.configure(configureClosure, getRepositories());
+    }
+
+    public void dependencies(Closure configureClosure) {
+        ConfigureUtil.configure(configureClosure, getDependencies());
+    }
+
+    public void artifacts(Closure configureClosure) {
+        ConfigureUtil.configure(configureClosure, getArtifacts());
+    }
+
+    public void buildscript(Closure configureClosure) {
+        ConfigureUtil.configure(configureClosure, getBuildscript());
+    }
+
+    public Task task(String task) {
+        return taskContainer.add(task);
+    }
+
+    public Task task(Object task) {
+        return taskContainer.add(task.toString());
+    }
+
+    public Task task(String task, Closure configureClosure) {
+        return taskContainer.add(task).configure(configureClosure);
+    }
+
+    public Task task(Object task, Closure configureClosure) {
+        return task(task.toString(), configureClosure);
+    }
+
+    public Task task(Map options, String task) {
+        return taskContainer.add(addMaps(options, singletonMap(Task.TASK_NAME, task)));
+    }
+
+    public Task task(Map options, Object task) {
+        return task(options, task.toString());
+    }
+
+    public Task task(Map options, String task, Closure configureClosure) {
+        return taskContainer.add(addMaps(options, singletonMap(Task.TASK_NAME, task))).configure(configureClosure);
+    }
+
+    public Task task(Map options, Object task, Closure configureClosure) {
+        return task(options, task.toString(), configureClosure);
+    }
+
+    /**
+     * This is called by the task creation DSL. Need to find a cleaner way to do this...
+     */
+    public Object passThrough(Object object) {
+        return object;
+    }
+
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/AntBuilderFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/AntBuilderFactory.java
new file mode 100644
index 0000000..02f3365
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/AntBuilderFactory.java
@@ -0,0 +1,25 @@
+/*
+ * 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.project;
+
+import org.gradle.api.AntBuilder;
+
+/**
+ * @author Hans Dockter
+ */
+public interface AntBuilderFactory {
+    AntBuilder createAntBuilder();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/DefaultAntBuilder.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/DefaultAntBuilder.groovy
new file mode 100644
index 0000000..9cd1aa6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/DefaultAntBuilder.groovy
@@ -0,0 +1,96 @@
+/*
+ * 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.project
+
+import org.apache.tools.ant.MagicNames
+import org.apache.tools.ant.ProjectHelper
+import org.apache.tools.ant.Target
+import org.gradle.api.GradleException
+import org.gradle.api.Project
+import org.gradle.api.internal.project.ant.BasicAntBuilder
+import org.gradle.api.tasks.ant.AntTarget
+import java.beans.PropertyChangeListener
+import java.beans.PropertyChangeEvent
+import org.apache.tools.ant.PropertyHelper
+
+public class DefaultAntBuilder extends BasicAntBuilder {
+    private final Project gradleProject
+
+    def DefaultAntBuilder(Project gradleProject) {
+        this.gradleProject = gradleProject;
+    }
+
+    def Object invokeMethod(String methodName, Object args) {
+        super.invokeMethod(methodName, args)
+    }
+
+    def propertyMissing(String property, Object newValue) {
+        doSetProperty(property, newValue)
+    }
+
+    private def doSetProperty(String property, newValue) {
+        PropertyHelper.getPropertyHelper(project).setUserProperty(null, property, newValue)
+    }
+
+    def propertyMissing(String name) {
+        if (project.properties.containsKey(name)) {
+            return project.properties[name]
+        }
+        throw new MissingPropertyException(name, getClass())
+    }
+
+    public Map getProperties() {
+        ObservableMap map = new ObservableMap(project.properties)
+        map.addPropertyChangeListener({PropertyChangeEvent event -> doSetProperty(event.propertyName, event.newValue) } as PropertyChangeListener)
+        map
+    }
+
+    public Map getReferences() {
+        ObservableMap map = new ObservableMap(project.references)
+        map.addPropertyChangeListener({PropertyChangeEvent event -> project.addReference(event.propertyName, event.newValue) } as PropertyChangeListener)
+        map
+    }
+
+    public void importBuild(Object antBuildFile) {
+        File file = gradleProject.file(antBuildFile)
+        File baseDir = file.parentFile
+
+        Set existingAntTargets = new HashSet(antProject.targets.keySet())
+        File oldBaseDir = antProject.baseDir
+        antProject.baseDir = baseDir
+        try {
+            antProject.setUserProperty(MagicNames.ANT_FILE, file.getAbsolutePath())
+            ProjectHelper.configureProject(antProject, file)
+        } catch(Exception e) {
+            throw new GradleException("Could not import Ant build file '$file'.", e)
+        } finally {
+            antProject.baseDir = oldBaseDir
+        }
+
+        // Chuck away the implicit target. It has already been executed
+        antProject.targets.remove('')
+
+        // Add an adapter for each newly added target
+        Set newAntTargets = new HashSet(antProject.targets.keySet())
+        newAntTargets.removeAll(existingAntTargets)
+        newAntTargets.each {name ->
+            Target target = antProject.targets[name]
+            AntTarget task = gradleProject.tasks.add(target.name, AntTarget)
+            task.target = target
+            task.baseDir = baseDir
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/DefaultAntBuilderFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/DefaultAntBuilderFactory.java
new file mode 100644
index 0000000..cb761c3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/DefaultAntBuilderFactory.java
@@ -0,0 +1,40 @@
+/*
+ * 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.project;
+
+import org.apache.tools.ant.BuildListener;
+import org.gradle.api.Project;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultAntBuilderFactory implements AntBuilderFactory {
+    private final BuildListener buildListener;
+    private final Project project;
+
+    public DefaultAntBuilderFactory(BuildListener buildListener, Project project) {
+        this.buildListener = buildListener;
+        this.project = project;
+    }
+
+    public DefaultAntBuilder createAntBuilder() {
+        DefaultAntBuilder antBuilder = new DefaultAntBuilder(project);
+        antBuilder.getProject().setBaseDir(project.getProjectDir());
+        antBuilder.getProject().removeBuildListener((BuildListener) antBuilder.getProject().getBuildListeners().get(0));
+        antBuilder.getProject().addBuildListener(buildListener);
+        return antBuilder;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilder.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilder.groovy
new file mode 100644
index 0000000..8e2d099
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilder.groovy
@@ -0,0 +1,143 @@
+/*
+ * 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.project
+
+import org.gradle.api.internal.project.ant.BasicAntBuilder
+import org.gradle.api.internal.project.ant.AntLoggingAdapter
+import org.gradle.util.*
+import org.gradle.api.internal.ClassPathRegistry
+
+class DefaultIsolatedAntBuilder implements IsolatedAntBuilder {
+    private final Map<List<File>, Map<String, Object>> classloaders
+    private final ClassPathRegistry classPathRegistry
+    private final Iterable<File> groovyClasspath
+    private final Iterable<File> libClasspath
+
+    def DefaultIsolatedAntBuilder(ClassPathRegistry classPathRegistry) {
+        this.classPathRegistry = classPathRegistry
+        classloaders = [:]
+        groovyClasspath = classPathRegistry.getClassPathFiles("LOCAL_GROOVY")
+        libClasspath = []
+    }
+
+    private DefaultIsolatedAntBuilder(DefaultIsolatedAntBuilder copy, Iterable<File> groovyClasspath, Iterable<File> libClasspath) {
+        this.classPathRegistry = copy.classPathRegistry
+        this.classloaders = copy.classloaders
+        this.groovyClasspath = groovyClasspath
+        this.libClasspath = libClasspath
+    }
+
+    IsolatedAntBuilder withGroovy(Iterable<File> classpath) {
+        return new DefaultIsolatedAntBuilder(this, classpath, libClasspath);
+    }
+
+    IsolatedAntBuilder withClasspath(Iterable<File> classpath) {
+        return new DefaultIsolatedAntBuilder(this, groovyClasspath, classpath);
+    }
+
+    void execute(Closure antClosure) {
+        List<File> normalisedClasspath = []
+        normalisedClasspath.addAll(classPathRegistry.getClassPathFiles("ANT"))
+        normalisedClasspath.addAll(groovyClasspath as List)
+        normalisedClasspath.addAll(libClasspath as List)
+
+        Map<String, Object> classloadersForPath = classloaders[normalisedClasspath]
+        ClassLoader antLoader
+        ClassLoader gradleLoader
+        if (!classloadersForPath) {
+            // Need tools.jar for compile tasks
+            List<File> fullClasspath = normalisedClasspath
+            File toolsJar = Jvm.current().toolsJar
+            if (toolsJar) {
+                fullClasspath += toolsJar
+            }
+
+            Closure converter = {File file -> file.toURI().toURL() }
+            URL[] classpathUrls = fullClasspath.collect(converter)
+            // Need gradle core to pick up ant logging adapter
+            URL[] gradleCoreUrls = classPathRegistry.getClassPathUrls("GRADLE_CORE")
+
+            FilteringClassLoader loggingLoader = new FilteringClassLoader(getClass().classLoader)
+            loggingLoader.allowPackage('org.slf4j')
+
+            antLoader = new URLClassLoader(classpathUrls, ClassLoader.systemClassLoader.parent)
+            gradleLoader = new URLClassLoader(gradleCoreUrls, new MultiParentClassLoader(antLoader, loggingLoader))
+
+            classloaders[normalisedClasspath] = [antLoader: antLoader, gradleLoader: gradleLoader]
+        } else {
+            antLoader = classloadersForPath.antLoader
+            gradleLoader = classloadersForPath.gradleLoader
+        }
+
+        ClassLoader originalLoader = Thread.currentThread().contextClassLoader
+        Thread.currentThread().contextClassLoader = antLoader
+        try {
+            Object antBuilder = gradleLoader.loadClass(BasicAntBuilder.class.name).newInstance()
+
+            Object antLogger = gradleLoader.loadClass(AntLoggingAdapter.class.name).newInstance()
+            antBuilder.project.removeBuildListener(antBuilder.project.getBuildListeners()[0])
+            antBuilder.project.addBuildListener(antLogger)
+
+            // Ideally, we'd delegate directly to the AntBuilder, but it's Closure class is different to our caller's
+            // Closure class, so the AntBuilder's methodMissing() doesn't work. It just converts our Closures to String
+            // because they are not an instanceof it's Closure class
+            Object delegate = new AntBuilderDelegate(antBuilder)
+            ConfigureUtil.configure(antClosure, delegate)
+        } finally {
+            Thread.currentThread().contextClassLoader = originalLoader
+        }
+    }
+}
+
+class AntBuilderDelegate extends BuilderSupport {
+    def Object builder
+
+    def AntBuilderDelegate(builder) {
+        this.builder = builder;
+    }
+
+    def propertyMissing(String name) {
+        builder."$name"
+    }
+
+    protected Object createNode(Object name) {
+        builder.createNode(name)
+    }
+
+    protected Object createNode(Object name, Map attributes) {
+        builder.createNode(name, attributes)
+    }
+
+    protected Object createNode(Object name, Map attributes, Object value) {
+        builder.createNode(name, attributes, value)
+    }
+
+    protected Object createNode(Object name, Object value) {
+        builder.createNode(name, value)
+    }
+
+    protected void setParent(Object parent, Object child) {
+        builder.setParent(parent, child)
+    }
+
+    protected void nodeCompleted(Object parent, Object node) {
+        builder.nodeCompleted(parent, node)
+    }
+
+    protected Object postNodeCompletion(Object parent, Object node) {
+        builder.postNodeCompletion(parent, node)
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/DefaultProject.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/DefaultProject.java
new file mode 100644
index 0000000..9981c87
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/DefaultProject.java
@@ -0,0 +1,31 @@
+/*
+ * 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.project;
+
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.NoConventionMapping;
+import org.gradle.groovy.scripts.ScriptSource;
+
+import java.io.File;
+
+ at NoConventionMapping
+public class DefaultProject extends AbstractProject {
+    public DefaultProject(String name, ProjectInternal parent, File projectDir, ScriptSource buildScriptSource,
+                           GradleInternal gradle, ServiceRegistryFactory serviceRegistryFactory) {
+        super(name, parent, projectDir, buildScriptSource, gradle, serviceRegistryFactory);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/DefaultProjectRegistry.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/DefaultProjectRegistry.java
new file mode 100644
index 0000000..38e6870
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/DefaultProjectRegistry.java
@@ -0,0 +1,103 @@
+/*
+ * 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.project;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.specs.Spec;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.HashSet;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultProjectRegistry<T extends ProjectIdentifier> implements IProjectRegistry<T> {
+    private Map<String, T> projects = new HashMap<String, T>();
+    private Map<String, Set<T>> subProjects = new HashMap<String, Set<T>>();
+
+    public void addProject(T project) {
+        projects.put(project.getPath(), project);
+        subProjects.put(project.getPath(), new HashSet<T>());
+        addProjectToParentSubProjects(project);
+    }
+
+    public T removeProject(String path) {
+        T project = projects.remove(path);
+        assert project != null;
+        subProjects.remove(path);
+        ProjectIdentifier loopProject = project.getParentIdentifier();
+        while (loopProject != null) {
+            subProjects.get(loopProject.getPath()).remove(project);
+            loopProject = loopProject.getParentIdentifier();
+        }
+        return project;
+    }
+    
+    private void addProjectToParentSubProjects(T project) {
+        ProjectIdentifier loopProject = project.getParentIdentifier();
+        while (loopProject != null) {
+            subProjects.get(loopProject.getPath()).add(project);
+            loopProject = loopProject.getParentIdentifier();
+        }
+    }
+
+    public Set<T> getAllProjects() {
+        return new HashSet<T>(projects.values());
+    }
+
+    public T getProject(String path) {
+        return projects.get(path);
+    }
+
+    public T getProject(final File projectDir) {
+        Set<T> projects = findAll(new Spec<T>() {
+            public boolean isSatisfiedBy(T element) {
+                return element.getProjectDir().equals(projectDir);
+            }
+        });
+        if (projects.size() > 1) {
+            throw new InvalidUserDataException(String.format("Found multiple projects with project directory '%s': %s",
+                    projectDir, projects));
+        }
+        return projects.size() == 1 ? projects.iterator().next() : null;
+    }
+
+    public Set<T> getAllProjects(String path) {
+        Set<T> result = new HashSet<T>(getSubProjects(path));
+        if (projects.get(path) != null) {
+            result.add(projects.get(path));
+        }
+        return result;
+    }
+
+    public Set<T> getSubProjects(String path) {
+        return GUtil.elvis(subProjects.get(path), new HashSet<T>());
+    }
+
+    public Set<T> findAll(Spec<? super T> constraint) {
+        Set<T> matches = new HashSet<T>();
+        for (T project : projects.values()) {
+            if (constraint.isSatisfiedBy(project)) {
+                matches.add(project);
+            }
+        }
+        return matches;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/DefaultServiceRegistry.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/DefaultServiceRegistry.java
new file mode 100644
index 0000000..fc7926f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/DefaultServiceRegistry.java
@@ -0,0 +1,244 @@
+/*
+ * 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.project;
+
+import org.gradle.messaging.concurrent.CompositeStoppable;
+import org.gradle.messaging.concurrent.Stoppable;
+import org.gradle.util.UncheckedException;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A hierarchical {@link ServiceRegistry} implementation. Subclasses can register services by:
+ *
+ * <ul> <li>Calling {@link #add(org.gradle.api.internal.project.DefaultServiceRegistry.Service)} to register a factory
+ * for the service.</li>
+ *
+ * <li>Calling {@link #add(Class, Object)} to register a service instance.</li>
+ *
+ * <li>Adding a factory method. A factory method should have a name that starts with 'create', take no parameters, and
+ * have a non-void return type. For example, <code>protected SomeService createSomeService() { .... }</code>.</li>
+ *
+ * <li>Adding a decorator method. A decorator method should have a name that starts with 'decorate', take a single parameter, and a have a non-void return type. 
+ *
+ *  </ul>
+ *
+ * <p>Service instances are created on demand. If a service of a given type cannot be located, the registry uses its
+ * parent registry, if any, to locate the service.</p>
+ */
+public class DefaultServiceRegistry implements ServiceRegistry {
+    private final List<Service> services = new ArrayList<Service>();
+    private final ServiceRegistry parent;
+    private boolean closed;
+
+    public DefaultServiceRegistry() {
+        this(null);
+    }
+
+    public DefaultServiceRegistry(ServiceRegistry parent) {
+        this.parent = parent;
+        for (Class<?> type = getClass(); type != Object.class; type = type.getSuperclass()) {
+            findFactoryMethods(type);
+            findDecoratorMethods(type);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName();
+    }
+
+    private void findFactoryMethods(Class<?> type) {
+        for (Method method : type.getDeclaredMethods()) {
+            if (method.getName().startsWith("create")
+                    && method.getParameterTypes().length == 0
+                    && method.getReturnType() != Void.class) {
+                add(new FactoryMethodService(method));
+            }
+        }
+    }
+
+    private void findDecoratorMethods(Class<?> type) {
+        for (Method method : type.getDeclaredMethods()) {
+            if (method.getName().startsWith("create")
+                    && method.getParameterTypes().length == 1
+                    && method.getReturnType() != Void.class
+                    && method.getParameterTypes()[0].equals(method.getReturnType())) {
+                add(new DecoratorMethodService(method));
+            }
+        }
+    }
+
+    protected void add(Service service) {
+        services.add(0, service);
+    }
+
+    public <T> void add(Class<T> serviceType, final T serviceInstance) {
+        add(new FixedInstanceService<T>(serviceType, serviceInstance));
+    }
+
+    /**
+     * Closes all services for this registry. For each service, if the service has a public void close() method, that
+     * method is called to close the service.
+     */
+    public void close() {
+        try {
+            new CompositeStoppable(services).stop();
+        } finally {
+            closed = true;
+            services.clear();
+        }
+    }
+
+    public <T> T get(Class<T> serviceType) throws IllegalArgumentException {
+        if (closed) {
+            throw new IllegalStateException(String.format("Cannot locate service of type %s, as %s has been closed.",
+                    serviceType.getSimpleName(), this));
+        }
+
+        for (Service service : services) {
+            T t = service.getService(serviceType);
+            if (t != null) {
+                return t;
+            }
+        }
+
+        if (parent != null) {
+            try {
+                return parent.get(serviceType);
+            } catch (UnknownServiceException e) {
+                if (!e.type.equals(serviceType)) {
+                    throw e;
+                }
+                // Ignore
+            }
+        }
+
+        throw new UnknownServiceException(serviceType, String.format("No service of type %s available in %s.",
+                serviceType.getSimpleName(), this));
+    }
+
+    private static Object invoke(Method method, Object target, Object... args) {
+        try {
+            method.setAccessible(true);
+            return method.invoke(target, args);
+        } catch (InvocationTargetException e) {
+            if (e.getCause() instanceof RuntimeException) {
+                throw (RuntimeException) e.getCause();
+            }
+            throw UncheckedException.asUncheckedException(e.getCause());
+        } catch (Exception e) {
+            throw UncheckedException.asUncheckedException(e);
+        }
+    }
+
+    protected static abstract class Service implements Stoppable {
+        final Class<?> serviceType;
+        Object service;
+
+        Service(Class<?> serviceType) {
+            this.serviceType = serviceType;
+        }
+
+        <T> T getService(Class<T> serviceType) {
+            if (!serviceType.isAssignableFrom(this.serviceType)) {
+                return null;
+            }
+            if (service == null) {
+                service = create();
+                assert service != null;
+            }
+            return serviceType.cast(service);
+        }
+
+        protected abstract Object create();
+
+        public void stop() {
+            try {
+                if (service != null) {
+                    try {
+                        invoke(service.getClass().getMethod("stop"), service);
+                    } catch (NoSuchMethodException e) {
+                        // ignore
+                    }
+                    try {
+                        invoke(service.getClass().getMethod("close"), service);
+                    } catch (NoSuchMethodException e) {
+                        // ignore
+                    }
+                }
+            } finally {
+                service = null;
+            }
+        }
+    }
+
+    private class FactoryMethodService extends Service {
+        private final Method method;
+
+        public FactoryMethodService(Method method) {
+            super(method.getReturnType());
+            this.method = method;
+        }
+
+        @Override
+        protected Object create() {
+            return invoke(method, DefaultServiceRegistry.this);
+        }
+    }
+
+    private static class FixedInstanceService<T> extends Service {
+        private final T serviceInstance;
+
+        public FixedInstanceService(Class<T> serviceType, T serviceInstance) {
+            super(serviceType);
+            this.serviceInstance = serviceInstance;
+            getService(serviceType);
+        }
+
+        @Override
+        protected Object create() {
+            return serviceInstance;
+        }
+    }
+
+    private class DecoratorMethodService extends Service {
+        private final Method method;
+
+        public DecoratorMethodService(Method method) {
+            super(method.getReturnType());
+            this.method = method;
+        }
+
+        @Override
+        protected Object create() {
+            return invoke(method, DefaultServiceRegistry.this, parent.get(method.getParameterTypes()[0]));
+        }
+    }
+
+    static class UnknownServiceException extends IllegalArgumentException {
+
+        private final Class<?> type;
+
+        UnknownServiceException(Class<?> type, String message) {
+            super(message);
+            this.type = type;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/GlobalServicesRegistry.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/GlobalServicesRegistry.java
new file mode 100644
index 0000000..4d7b262
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/GlobalServicesRegistry.java
@@ -0,0 +1,76 @@
+/*
+ * 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.project;
+
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.DefaultClassPathProvider;
+import org.gradle.api.internal.DefaultClassPathRegistry;
+import org.gradle.api.internal.GradleDistributionLocator;
+import org.gradle.cache.AutoCloseCacheFactory;
+import org.gradle.cache.CacheFactory;
+import org.gradle.cache.DefaultCacheFactory;
+import org.gradle.initialization.ClassLoaderFactory;
+import org.gradle.initialization.CommandLine2StartParameterConverter;
+import org.gradle.initialization.DefaultClassLoaderFactory;
+import org.gradle.initialization.DefaultCommandLine2StartParameterConverter;
+import org.gradle.listener.DefaultListenerManager;
+import org.gradle.listener.ListenerManager;
+import org.gradle.logging.DefaultProgressLoggerFactory;
+import org.gradle.logging.LoggingServiceRegistry;
+import org.gradle.logging.ProgressLoggerFactory;
+
+/**
+ * Contains the services shared by all builds in a given process.
+ */
+public class GlobalServicesRegistry extends DefaultServiceRegistry {
+    public GlobalServicesRegistry() {
+        super(new LoggingServiceRegistry());
+    }
+
+    protected CommandLine2StartParameterConverter createCommandLine2StartParameterConverter() {
+        return new DefaultCommandLine2StartParameterConverter();
+    }
+
+    protected ClassPathRegistry createClassPathRegistry() {
+        return new DefaultClassPathRegistry();
+    }
+
+    protected CacheFactory createCacheFactory() {
+        return new AutoCloseCacheFactory(new DefaultCacheFactory());
+    }
+
+    protected ClassLoaderFactory createClassLoaderFactory() {
+        return new DefaultClassLoaderFactory(get(ClassPathRegistry.class));
+    }
+
+    protected ListenerManager createListenerManager() {
+        return new DefaultListenerManager();
+    }
+
+    protected ProgressLoggerFactory createProgressLoggerFactory() {
+        return new DefaultProgressLoggerFactory(get(ListenerManager.class));
+    }
+    
+    protected GradleDistributionLocator createGradleDistributionLocator() {
+        return new DefaultClassPathProvider();
+    }
+    
+    protected IsolatedAntBuilder createIsolatedAntBuilder() {
+        return new DefaultIsolatedAntBuilder(get(ClassPathRegistry.class));
+    }
+    
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistry.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistry.java
new file mode 100644
index 0000000..bed26be
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistry.java
@@ -0,0 +1,71 @@
+/*
+ * 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.project;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.repositories.InternalRepository;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.PublishModuleDescriptorConverter;
+import org.gradle.api.internal.artifacts.repositories.DefaultInternalRepository;
+import org.gradle.api.internal.plugins.DefaultPluginRegistry;
+import org.gradle.api.internal.plugins.PluginRegistry;
+import org.gradle.execution.DefaultTaskGraphExecuter;
+import org.gradle.execution.TaskGraphExecuter;
+import org.gradle.listener.ListenerManager;
+
+/**
+ * Contains the services for a given {@link GradleInternal} instance.
+ */
+public class GradleInternalServiceRegistry extends DefaultServiceRegistry implements ServiceRegistryFactory {
+    private final GradleInternal gradle;
+
+    public GradleInternalServiceRegistry(ServiceRegistry parent, final GradleInternal gradle) {
+        super(parent);
+        this.gradle = gradle;
+    }
+
+    protected ProjectFinder createProjectFinder() {
+        return new ProjectFinder() {
+            public Project getProject(String path) {
+                return gradle.getRootProject().project(path);
+            }
+        };
+    }
+
+    protected IProjectRegistry createIProjectRegistry() {
+        return new DefaultProjectRegistry<ProjectInternal>();
+    }
+
+    protected TaskGraphExecuter createTaskGraphExecuter() {
+        return new DefaultTaskGraphExecuter(get(ListenerManager.class));
+    }
+
+    protected PluginRegistry createPluginRegistry() {
+        return new DefaultPluginRegistry(gradle.getScriptClassLoader());
+    }
+
+    protected InternalRepository createInternalRepository() {
+        return new DefaultInternalRepository(gradle, get(PublishModuleDescriptorConverter.class));
+    }
+
+    public ServiceRegistryFactory createFor(Object domainObject) {
+        if (domainObject instanceof ProjectInternal) {
+            return new ProjectInternalServiceRegistry(this, (ProjectInternal) domainObject);
+        }
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/IProjectFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/IProjectFactory.java
new file mode 100644
index 0000000..aaa8a91
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/IProjectFactory.java
@@ -0,0 +1,28 @@
+/*
+ * 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.project;
+
+import org.gradle.api.initialization.ProjectDescriptor;
+import org.gradle.api.internal.GradleInternal;
+
+/**
+ * Creates a {@link ProjectInternal} implementation.
+ *
+ * @author Hans Dockter
+ */
+public interface IProjectFactory {
+    ProjectInternal createProject(ProjectDescriptor projectDescriptor, ProjectInternal parent, GradleInternal gradle);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/IProjectRegistry.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/IProjectRegistry.java
new file mode 100644
index 0000000..7136df8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/IProjectRegistry.java
@@ -0,0 +1,40 @@
+/*
+ * 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.project;
+
+import org.gradle.api.specs.Spec;
+
+import java.util.Set;
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public interface IProjectRegistry<T extends ProjectIdentifier> {
+    void addProject(T project);
+
+    T getProject(String path);
+
+    T getProject(File projectDir);
+
+    Set<T> getAllProjects();
+    
+    Set<T> getAllProjects(String path);
+
+    Set<T> getSubProjects(String path);
+
+    Set<T> findAll(Spec<? super T> constraint);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/IsolatedAntBuilder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/IsolatedAntBuilder.java
new file mode 100644
index 0000000..23213bf
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/IsolatedAntBuilder.java
@@ -0,0 +1,52 @@
+/*
+ * 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.project;
+
+import groovy.lang.Closure;
+
+import java.io.File;
+
+/**
+ * Executes a closure against an isolated {@link org.gradle.api.AntBuilder} instance.
+ */
+public interface IsolatedAntBuilder {
+    /**
+     * Creates a copy of this builder which uses the given version of Groovy. The default is to use the version of
+     * Groovy which Gradle is using.
+     *
+     * @param classpath The Groovy classpath.
+     * @return a copy of this builder
+     */
+    IsolatedAntBuilder withGroovy(Iterable<File> classpath);
+
+    /**
+     * Creates a copy of this builder which uses the given libraries. These classes are visible for use in
+     * taskdef/typedef tasks.
+     *
+     * @param classpath The library classpath
+     * @return a copy of this builder
+     */
+    IsolatedAntBuilder withClasspath(Iterable<File> classpath);
+
+    /**
+     * Executes the given closure against an isolated {@link org.gradle.api.AntBuilder} instance. The builder will
+     * have visible to it an isolated version of Ant, Groovy and the specified libraries (if any). Each call to this
+     * method is given a separate Ant project.
+     *
+     * @param antClosure The closure to execute
+     */
+    void execute(Closure antClosure);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ProjectFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ProjectFactory.java
new file mode 100644
index 0000000..f3e05f6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ProjectFactory.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.api.internal.project;
+
+import org.gradle.api.initialization.ProjectDescriptor;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.groovy.scripts.StringScriptSource;
+import org.gradle.groovy.scripts.UriScriptSource;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class ProjectFactory implements IProjectFactory {
+    private ScriptSource embeddedScript;
+    private final ClassGenerator classGenerator;
+
+    public ProjectFactory(ScriptSource embeddedScript, ClassGenerator classGenerator) {
+        this.embeddedScript = embeddedScript;
+        this.classGenerator = classGenerator;
+    }
+
+    public DefaultProject createProject(ProjectDescriptor projectDescriptor, ProjectInternal parent, GradleInternal gradle) {
+        File buildFile = projectDescriptor.getBuildFile();
+        ScriptSource source;
+        if (embeddedScript != null) {
+            source = embeddedScript;
+        } else if (!buildFile.exists()) {
+            source = new StringScriptSource("empty build file", "");
+        } else {
+            source = new UriScriptSource("build file", buildFile);
+        }
+
+        DefaultProject project = classGenerator.newInstance(DefaultProject.class,
+                projectDescriptor.getName(),
+                parent,
+                projectDescriptor.getProjectDir(),
+                source,
+                gradle,
+                gradle.getServiceRegistryFactory());
+
+        if (parent != null) {
+            parent.addChildProject(project);
+        }
+        gradle.getProjectRegistry().addProject(project);
+
+        return project;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ProjectIdentifier.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ProjectIdentifier.java
new file mode 100644
index 0000000..e5e6f34
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ProjectIdentifier.java
@@ -0,0 +1,31 @@
+/*
+ * 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.project;
+
+import java.io.File;
+
+// TODO need a better name for this
+public interface ProjectIdentifier {
+    String getName();
+
+    String getPath();
+
+    ProjectIdentifier getParentIdentifier();
+
+    File getProjectDir();
+
+    File getBuildFile();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ProjectInternal.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ProjectInternal.java
new file mode 100644
index 0000000..6e00577
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ProjectInternal.java
@@ -0,0 +1,58 @@
+/*
+ * 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.project;
+
+import org.gradle.api.Project;
+import org.gradle.api.ProjectEvaluationListener;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.internal.DomainObjectContext;
+import org.gradle.api.internal.DynamicObject;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.file.FileOperations;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.tasks.TaskContainerInternal;
+import org.gradle.logging.StandardOutputCapture;
+import org.gradle.groovy.scripts.ScriptAware;
+import org.gradle.groovy.scripts.ScriptSource;
+
+public interface ProjectInternal extends Project, ProjectIdentifier, ScriptAware, FileOperations, DomainObjectContext {
+    ProjectInternal getParent();
+
+    Project evaluate();
+
+    TaskContainerInternal getTasks();
+
+    ScriptSource getBuildScriptSource();
+
+    void addChildProject(ProjectInternal childProject);
+
+    IProjectRegistry<ProjectInternal> getProjectRegistry();
+
+    DynamicObject getInheritedScope();
+
+    GradleInternal getGradle();
+
+    ProjectEvaluationListener getProjectEvaluationBroadcaster();
+
+    FileResolver getFileResolver();
+
+    ServiceRegistryFactory getServiceRegistryFactory();
+
+    Module getModule();
+
+    StandardOutputCapture getStandardOutputCapture();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistry.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistry.java
new file mode 100644
index 0000000..5d9c399
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistry.java
@@ -0,0 +1,172 @@
+/*
+ * 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.project;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.ConfigurationContainer;
+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.artifacts.dsl.RepositoryHandlerFactory;
+import org.gradle.api.artifacts.repositories.InternalRepository;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.artifacts.ConfigurationContainerFactory;
+import org.gradle.api.internal.artifacts.DefaultModule;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
+import org.gradle.api.internal.artifacts.dsl.DefaultArtifactHandler;
+import org.gradle.api.internal.artifacts.dsl.PublishArtifactFactory;
+import org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler;
+import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder;
+import org.gradle.api.internal.file.*;
+import org.gradle.api.internal.initialization.DefaultScriptHandlerFactory;
+import org.gradle.api.internal.initialization.ScriptClassLoaderProvider;
+import org.gradle.api.internal.initialization.ScriptHandlerInternal;
+import org.gradle.api.internal.plugins.DefaultConvention;
+import org.gradle.api.internal.plugins.DefaultProjectsPluginContainer;
+import org.gradle.api.internal.plugins.PluginRegistry;
+import org.gradle.api.internal.project.ant.AntLoggingAdapter;
+import org.gradle.api.internal.project.taskfactory.ITaskFactory;
+import org.gradle.api.internal.tasks.DefaultTaskContainer;
+import org.gradle.api.internal.tasks.TaskContainerInternal;
+import org.gradle.api.internal.tasks.TaskResolver;
+import org.gradle.api.plugins.Convention;
+import org.gradle.api.plugins.PluginContainer;
+import org.gradle.logging.LoggingManagerFactory;
+import org.gradle.logging.LoggingManagerInternal;
+
+import java.io.File;
+
+/**
+ * Contains the services for a given project.
+ */
+public class ProjectInternalServiceRegistry extends DefaultServiceRegistry implements ServiceRegistryFactory {
+    private final ProjectInternal project;
+
+    public ProjectInternalServiceRegistry(ServiceRegistry parent, final ProjectInternal project) {
+        super(parent);
+        this.project = project;
+    }
+
+    protected PluginRegistry createPluginRegistry(PluginRegistry parentRegistry) {
+        return parentRegistry.createChild(get(ScriptClassLoaderProvider.class).getClassLoader());
+    }
+
+    protected FileResolver createFileResolver() {
+        return new BaseDirConverter(project.getProjectDir());
+    }
+
+    protected LoggingManagerInternal createLoggingManager() {
+        return get(LoggingManagerFactory.class).create();
+    }
+
+    protected FileOperations createFileOperations() {
+        return new DefaultFileOperations(get(FileResolver.class), get(TaskResolver.class), get(TemporaryFileProvider.class));
+    }
+
+    protected TemporaryFileProvider createTemporaryFileProvider() {
+        return new DefaultTemporaryFileProvider(new FileSource() {
+            public File get() {
+                return new File(project.getBuildDir(), "tmp");
+            }
+        });
+    }
+
+    protected AntBuilderFactory createAntBuilderFactory() {
+        return new DefaultAntBuilderFactory(new AntLoggingAdapter(), project);
+    }
+
+    protected PluginContainer createPluginContainer() {
+        return new DefaultProjectsPluginContainer(get(PluginRegistry.class), project);
+    }
+
+    protected TaskContainerInternal createTaskContainerInternal() {
+        ClassGenerator classGenerator = get(ClassGenerator.class);
+        return classGenerator.newInstance(DefaultTaskContainer.class, project, classGenerator, get(ITaskFactory.class));
+    }
+
+    protected Convention createConvention() {
+        return new DefaultConvention();
+    }
+
+    protected RepositoryHandler createRepositoryHandler() {
+        return get(RepositoryHandlerFactory.class).createRepositoryHandler(get(Convention.class));
+    }
+
+    protected ConfigurationContainer createConfigurationContainer() {
+        return get(ConfigurationContainerFactory.class).createConfigurationContainer(get(ResolverProvider.class),
+                get(DependencyMetaDataProvider.class), project);
+    }
+
+    protected ArtifactHandler createArtifactHandler() {
+        return new DefaultArtifactHandler(get(ConfigurationContainer.class), get(PublishArtifactFactory.class));
+    }
+
+    protected ProjectFinder createProjectFinder() {
+        return new ProjectFinder() {
+            public Project getProject(String path) {
+                return project.project(path);
+            }
+        };
+    }
+
+    protected DependencyHandler createDependencyHandler() {
+        return new DefaultDependencyHandler(get(ConfigurationContainer.class), get(DependencyFactory.class),
+                get(ProjectFinder.class));
+    }
+
+    protected ScriptHandlerInternal createScriptHandler() {
+        DefaultScriptHandlerFactory factory = new DefaultScriptHandlerFactory(
+                get(RepositoryHandlerFactory.class),
+                get(ConfigurationContainerFactory.class),
+                get(DependencyMetaDataProvider.class),
+                get(DependencyFactory.class));
+        ClassLoader parentClassLoader;
+        if (project.getParent() != null) {
+            parentClassLoader = project.getParent().getBuildscript().getClassLoader();
+        } else {
+            parentClassLoader = project.getGradle().getScriptClassLoader();
+        }
+        return factory.create(project.getBuildScriptSource(), parentClassLoader, project);
+    }
+
+    protected DependencyMetaDataProvider createDependencyMetaDataProvider() {
+        return new DependencyMetaDataProvider() {
+            public InternalRepository getInternalRepository() {
+                return get(InternalRepository.class);
+            }
+
+            public File getGradleUserHomeDir() {
+                return project.getGradle().getGradleUserHomeDir();
+            }
+
+            public Module getModule() {
+                return new DefaultModule(project.getGroup().toString(), project.getName(), project.getVersion().toString(), project.getStatus().toString());
+            }
+        };
+    }
+
+    public ServiceRegistryFactory createFor(Object domainObject) {
+        if (domainObject instanceof TaskInternal) {
+            return new TaskInternalServiceRegistry(this, project, (TaskInternal)domainObject);
+        }
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ProjectScript.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ProjectScript.groovy
new file mode 100644
index 0000000..6d21ae5
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ProjectScript.groovy
@@ -0,0 +1,69 @@
+/*
+ * 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.project
+
+import org.gradle.api.initialization.dsl.ScriptHandler
+import org.gradle.api.logging.LogLevel
+import org.gradle.api.logging.Logger
+import org.gradle.groovy.scripts.DefaultScript
+import org.gradle.logging.StandardOutputCapture
+import org.gradle.api.logging.LoggingManager
+
+abstract class ProjectScript extends DefaultScript {
+
+    def void apply(Closure closure) {
+        scriptTarget.apply(closure)
+    }
+
+    def void apply(Map options) {
+        scriptTarget.apply(options)
+    }
+
+    def ScriptHandler getBuildscript() {
+        scriptTarget.buildscript
+    }
+
+    def void buildscript(Closure configureClosure) {
+        scriptTarget.buildscript(configureClosure)
+    }
+
+    def void disableStandardOutputCapture() {
+        scriptTarget.disableStandardOutputCapture()
+    }
+
+    def void captureStandardOutput(LogLevel level) {
+        scriptTarget.captureStandardOutput(level)
+    }
+
+    def StandardOutputCapture getStandardOutputCapture() {
+        scriptTarget.standardOutputCapture
+    }
+
+    def LoggingManager getLogging() {
+        scriptTarget.logging
+    }
+
+    def Logger getLogger() {
+        scriptTarget.logger
+    }
+
+    def String toString() {
+        scriptTarget.toString()
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ProjectStateInternal.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ProjectStateInternal.java
new file mode 100644
index 0000000..e35b207
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ProjectStateInternal.java
@@ -0,0 +1,59 @@
+/*
+ * 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.project;
+
+import org.gradle.api.ProjectState;
+import org.gradle.util.UncheckedException;
+
+public class ProjectStateInternal implements ProjectState {
+    private boolean executing;
+    private boolean executed;
+    private Throwable failure;
+
+    public boolean getExecuted() {
+        return executed;
+    }
+
+    public void executed() {
+        executed = true;
+    }
+
+    public void executed(Throwable failure) {
+        assert this.failure == null;
+        this.failure = failure;
+        executed = true;
+    }
+
+    public boolean getExecuting() {
+        return executing;
+    }
+
+    public void setExecuting(boolean executing) {
+        this.executing = executing;
+    }
+
+    public Throwable getFailure() {
+        return failure;
+    }
+
+    public void rethrowFailure() {
+        if (failure == null) {
+            return;
+        }
+        throw UncheckedException.asUncheckedException(failure);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ServiceRegistry.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ServiceRegistry.java
new file mode 100644
index 0000000..757223d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ServiceRegistry.java
@@ -0,0 +1,21 @@
+/*
+ * 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.project;
+
+public interface ServiceRegistry
+{
+    <T> T get(Class<T> serviceType) throws IllegalArgumentException;
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ServiceRegistryFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ServiceRegistryFactory.java
new file mode 100644
index 0000000..7a3e61b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ServiceRegistryFactory.java
@@ -0,0 +1,29 @@
+/*
+ * 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.project;
+
+/**
+ * A heirarchical service registry.
+ */
+public interface ServiceRegistryFactory extends ServiceRegistry {
+    /**
+     * Creates the services for the given domain object.
+     *
+     * @param domainObject The domain object.
+     * @return The registry containing the services for the domain object.
+     */
+    ServiceRegistryFactory createFor(Object domainObject);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistry.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistry.java
new file mode 100644
index 0000000..be6a4bf
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistry.java
@@ -0,0 +1,55 @@
+/*
+ * 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.project;
+
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.TaskOutputsInternal;
+import org.gradle.api.internal.tasks.DefaultTaskInputs;
+import org.gradle.api.internal.tasks.DefaultTaskOutputs;
+import org.gradle.api.tasks.TaskInputs;
+import org.gradle.logging.LoggingManagerFactory;
+import org.gradle.logging.LoggingManagerInternal;
+
+/**
+ * Contains the services for a given task.
+ */
+public class TaskInternalServiceRegistry extends DefaultServiceRegistry implements ServiceRegistryFactory {
+    private final ProjectInternal project;
+    private final TaskInternal taskInternal;
+
+    public TaskInternalServiceRegistry(ServiceRegistry parent, final ProjectInternal project, TaskInternal taskInternal) {
+        super(parent);
+        this.project = project;
+        this.taskInternal = taskInternal;
+    }
+
+    protected TaskInputs createTaskInputs() {
+        return new DefaultTaskInputs(project.getFileResolver());
+    }
+
+    protected TaskOutputsInternal createTaskOutputs() {
+        return new DefaultTaskOutputs(project.getFileResolver(), taskInternal);
+    }
+
+    protected LoggingManagerInternal createLoggingManager() {
+        return get(LoggingManagerFactory.class).create();
+    }
+
+    public ServiceRegistryFactory createFor(Object domainObject) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistry.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistry.java
new file mode 100644
index 0000000..2228045
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistry.java
@@ -0,0 +1,359 @@
+/*
+ * 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.project;
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.plugins.resolver.ChainResolver;
+import org.gradle.StartParameter;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.artifacts.dsl.RepositoryHandlerFactory;
+import org.gradle.api.artifacts.repositories.InternalRepository;
+import org.gradle.api.execution.TaskActionListener;
+import org.gradle.api.internal.*;
+import org.gradle.api.internal.artifacts.ConfigurationContainerFactory;
+import org.gradle.api.internal.artifacts.DefaultConfigurationContainerFactory;
+import org.gradle.api.internal.artifacts.DefaultModule;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.dsl.DefaultPublishArtifactFactory;
+import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandlerFactory;
+import org.gradle.api.internal.artifacts.dsl.PublishArtifactFactory;
+import org.gradle.api.internal.artifacts.dsl.dependencies.*;
+import org.gradle.api.internal.artifacts.ivyservice.*;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.*;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.*;
+import org.gradle.api.internal.changedetection.*;
+import org.gradle.api.internal.file.IdentityFileResolver;
+import org.gradle.api.internal.initialization.DefaultScriptHandlerFactory;
+import org.gradle.api.internal.initialization.ScriptHandlerFactory;
+import org.gradle.api.internal.project.taskfactory.*;
+import org.gradle.api.internal.tasks.DefaultTaskExecuter;
+import org.gradle.api.internal.tasks.ExecuteAtMostOnceTaskExecuter;
+import org.gradle.api.internal.tasks.SkipTaskExecuter;
+import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.cache.AutoCloseCacheFactory;
+import org.gradle.cache.CacheFactory;
+import org.gradle.cache.CacheRepository;
+import org.gradle.cache.DefaultCacheRepository;
+import org.gradle.configuration.*;
+import org.gradle.groovy.scripts.*;
+import org.gradle.initialization.*;
+import org.gradle.listener.ListenerManager;
+import org.gradle.logging.LoggingManagerFactory;
+import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.messaging.concurrent.DefaultExecutorFactory;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.gradle.messaging.remote.MessagingServer;
+import org.gradle.messaging.remote.internal.TcpMessagingServer;
+import org.gradle.messaging.actor.ActorFactory;
+import org.gradle.messaging.actor.internal.DefaultActorFactory;
+import org.gradle.process.internal.DefaultWorkerProcessFactory;
+import org.gradle.process.internal.WorkerProcessFactory;
+import org.gradle.process.internal.child.WorkerProcessClassPathProvider;
+import org.gradle.util.*;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Contains the singleton services which are shared by all builds executed by a single {@link org.gradle.GradleLauncher}
+ * invocation.
+ */
+public class TopLevelBuildServiceRegistry extends DefaultServiceRegistry implements ServiceRegistryFactory {
+    private final StartParameter startParameter;
+    private final Map<String, ModuleDescriptor> clientModuleRegistry = new HashMap<String, ModuleDescriptor>();
+
+    public TopLevelBuildServiceRegistry(final ServiceRegistry parent, final StartParameter startParameter) {
+        super(parent);
+        this.startParameter = startParameter;
+    }
+
+    protected PublishArtifactFactory createPublishArtifactFactory() {
+        return new DefaultPublishArtifactFactory();
+    }
+
+    protected ImportsReader createImportsReader() {
+        return new ImportsReader();
+    }
+    protected ClassGenerator createClassGenerator() {
+        return new AsmBackedClassGenerator();
+    }
+
+    protected TimeProvider createTimeProvider() {
+        return new TrueTimeProvider();
+    }
+    
+    protected ExecutorFactory createExecutorFactory() {
+        return new DefaultExecutorFactory();
+    }
+
+    protected IProjectFactory createProjectFactory() {
+        return new ProjectFactory(
+                startParameter.getBuildScriptSource(),
+                get(ClassGenerator.class));
+    }
+
+    protected ListenerManager createListenerManager(ListenerManager listenerManager) {
+        return listenerManager.createChild();
+    }
+
+    protected CacheFactory createCacheFactory(CacheFactory parentFactory) {
+        return new AutoCloseCacheFactory(parentFactory);
+    }
+
+    protected ClassPathRegistry createClassPathRegistry() {
+        return new DefaultClassPathRegistry(new WorkerProcessClassPathProvider(get(CacheRepository.class)));
+    }
+    
+    protected ActorFactory createActorFactory() {
+        return new DefaultActorFactory(get(ExecutorFactory.class));
+    }
+
+    protected TaskExecuter createTaskExecuter() {
+        return new ExecuteAtMostOnceTaskExecuter(
+                new SkipTaskExecuter(
+                        new ExecutionShortCircuitTaskExecuter(
+                                new PostExecutionAnalysisTaskExecuter(
+                                        new DefaultTaskExecuter(
+                                                get(ListenerManager.class).getBroadcaster(TaskActionListener.class))),
+                                        get(TaskArtifactStateRepository.class))));
+    }
+
+    protected RepositoryHandlerFactory createRepositoryHandlerFactory() {
+        return new DefaultRepositoryHandlerFactory(
+                new DefaultResolverFactory(
+                        get(LoggingManagerFactory.class)),
+                get(ClassGenerator.class));
+    }
+
+    protected CacheRepository createCacheRepository() {
+        return new DefaultCacheRepository(startParameter.getGradleUserHomeDir(),
+                startParameter.getCacheUsage(), get(CacheFactory.class));
+    }
+
+    protected ModuleDescriptorFactory createModuleDescriptorFactory() {
+        return new DefaultModuleDescriptorFactory();
+    }
+
+    protected ExcludeRuleConverter createExcludeRuleConverter() {
+        return new DefaultExcludeRuleConverter();
+    }
+
+    protected ExternalModuleDependencyDescriptorFactory createExternalModuleDependencyDescriptorFactory() {
+        return new ExternalModuleDependencyDescriptorFactory(get(ExcludeRuleConverter.class));
+    }
+
+    protected ConfigurationsToModuleDescriptorConverter createConfigurationsToModuleDescriptorConverter() {
+        return new DefaultConfigurationsToModuleDescriptorConverter();
+    }
+    
+    protected ResolveModuleDescriptorConverter createResolveModuleDescriptorConverter() {
+        return createResolveModuleDescriptorConverter(ProjectDependencyDescriptorFactory.RESOLVE_DESCRIPTOR_STRATEGY);
+    }
+
+    protected ResolveModuleDescriptorConverter createResolveModuleDescriptorConverter(ProjectDependencyDescriptorStrategy projectDependencyStrategy) {
+        DefaultModuleDescriptorFactoryForClientModule clientModuleDescriptorFactory = new DefaultModuleDescriptorFactoryForClientModule();
+        DependencyDescriptorFactoryDelegate dependencyDescriptorFactoryDelegate = new DependencyDescriptorFactoryDelegate(
+                new ClientModuleDependencyDescriptorFactory(
+                        get(ExcludeRuleConverter.class), clientModuleDescriptorFactory, clientModuleRegistry),
+                new ProjectDependencyDescriptorFactory(
+                        get(ExcludeRuleConverter.class),
+                        projectDependencyStrategy),
+                get(ExternalModuleDependencyDescriptorFactory.class));
+        clientModuleDescriptorFactory.setDependencyDescriptorFactory(dependencyDescriptorFactoryDelegate);
+        return new ResolveModuleDescriptorConverter(
+                get(ModuleDescriptorFactory.class),
+                get(ConfigurationsToModuleDescriptorConverter.class),
+                new DefaultDependenciesToModuleDescriptorConverter(
+                        dependencyDescriptorFactoryDelegate,
+                        get(ExcludeRuleConverter.class)));
+    }
+
+    protected PublishModuleDescriptorConverter createPublishModuleDescriptorConverter() {
+        return new PublishModuleDescriptorConverter(
+                createResolveModuleDescriptorConverter(ProjectDependencyDescriptorFactory.RESOLVE_DESCRIPTOR_STRATEGY),
+                new DefaultArtifactsToModuleDescriptorConverter(DefaultArtifactsToModuleDescriptorConverter.RESOLVE_STRATEGY));
+    }
+
+    protected ConfigurationContainerFactory createConfigurationContainerFactory() {
+        // todo this creation is duplicate. When we improve our service registry to allow multiple instances for same type
+        // we should consolidate.
+        DefaultModuleDescriptorFactoryForClientModule clientModuleDescriptorFactory = new DefaultModuleDescriptorFactoryForClientModule();
+        DependencyDescriptorFactoryDelegate dependencyDescriptorFactoryDelegate = new DependencyDescriptorFactoryDelegate(
+                new ClientModuleDependencyDescriptorFactory(
+                        get(ExcludeRuleConverter.class), clientModuleDescriptorFactory, clientModuleRegistry),
+                new ProjectDependencyDescriptorFactory(
+                        get(ExcludeRuleConverter.class),
+                        ProjectDependencyDescriptorFactory.RESOLVE_DESCRIPTOR_STRATEGY),
+                get(ExternalModuleDependencyDescriptorFactory.class));
+        clientModuleDescriptorFactory.setDependencyDescriptorFactory(dependencyDescriptorFactoryDelegate);
+        PublishModuleDescriptorConverter fileModuleDescriptorConverter = new PublishModuleDescriptorConverter(
+                createResolveModuleDescriptorConverter(ProjectDependencyDescriptorFactory.IVY_FILE_DESCRIPTOR_STRATEGY),
+                new DefaultArtifactsToModuleDescriptorConverter(DefaultArtifactsToModuleDescriptorConverter.IVY_FILE_STRATEGY));
+
+        return new DefaultConfigurationContainerFactory(clientModuleRegistry,
+                new DefaultSettingsConverter(
+                        get(ProgressLoggerFactory.class)
+                ),
+                get(ResolveModuleDescriptorConverter.class),
+                get(PublishModuleDescriptorConverter.class),
+                fileModuleDescriptorConverter,
+                new DefaultIvyFactory(),
+                new SelfResolvingDependencyResolver(
+                        new DefaultIvyDependencyResolver(
+                                new DefaultIvyReportConverter(dependencyDescriptorFactoryDelegate))),
+                new DefaultIvyDependencyPublisher(new DefaultPublishOptionsFactory()),
+                get(ClassGenerator.class));
+    }
+
+    protected DependencyFactory createDependencyFactory() {
+        ClassGenerator classGenerator = get(ClassGenerator.class);
+        DefaultProjectDependencyFactory projectDependencyFactory = new DefaultProjectDependencyFactory(
+                startParameter.getProjectDependenciesBuildInstruction(),
+                classGenerator);
+        return new DefaultDependencyFactory(
+                WrapUtil.<IDependencyImplementationFactory>toSet(
+                        new ModuleDependencyFactory(
+                                classGenerator),
+                        new SelfResolvingDependencyFactory(
+                                classGenerator),
+                        new ClassPathDependencyFactory(
+                                classGenerator,
+                                get(ClassPathRegistry.class),
+                                new IdentityFileResolver()),
+                        projectDependencyFactory),
+                new DefaultClientModuleFactory(
+                        classGenerator),
+                projectDependencyFactory);
+    }
+
+    protected ProjectEvaluator createProjectEvaluator() {
+        return new DefaultProjectEvaluator(
+                new BuildScriptProcessor(
+                        get(ScriptPluginFactory.class)));
+    }
+
+    protected ITaskFactory createITaskFactory() {
+        return new DependencyAutoWireTaskFactory(
+                new AnnotationProcessingTaskFactory(
+                        new TaskFactory(
+                                get(ClassGenerator.class))));
+    }
+
+    protected TaskArtifactStateRepository createTaskArtifactStateRepository() {
+        CacheRepository cacheRepository = get(CacheRepository.class);
+        FileSnapshotter fileSnapshotter = new DefaultFileSnapshotter(
+                new CachingHasher(
+                        new DefaultHasher(),
+                        cacheRepository));
+
+        FileSnapshotter outputFilesSnapshotter = new OutputFilesSnapshotter(fileSnapshotter, new RandomLongIdGenerator(), cacheRepository);
+        return new ShortCircuitTaskArtifactStateRepository(
+                startParameter,
+                new DefaultTaskArtifactStateRepository(cacheRepository,
+                        fileSnapshotter,
+                        outputFilesSnapshotter));
+    }
+
+    protected ScriptCompilerFactory createScriptCompileFactory() {
+        ScriptExecutionListener scriptExecutionListener = get(ListenerManager.class).getBroadcaster(ScriptExecutionListener.class);
+        return new DefaultScriptCompilerFactory(
+                new CachingScriptCompilationHandler(
+                        new DefaultScriptCompilationHandler()),
+                new DefaultScriptRunnerFactory(
+                        scriptExecutionListener),
+                get(CacheRepository.class));
+    }
+
+    protected ScriptPluginFactory createScriptObjectConfigurerFactory() {
+        return new DefaultScriptPluginFactory(
+                get(ScriptCompilerFactory.class),
+                get(ImportsReader.class),
+                get(ScriptHandlerFactory.class),
+                get(ClassLoader.class),
+                get(LoggingManagerFactory.class));
+    }
+
+    protected MultiParentClassLoader createRootClassLoader() {
+        return get(ClassLoaderFactory.class).createScriptClassLoader();
+    }
+    
+    protected InitScriptHandler createInitScriptHandler() {
+        return new InitScriptHandler(
+                new UserHomeInitScriptFinder(
+                        new DefaultInitScriptFinder()),
+                new DefaultInitScriptProcessor(
+                        get(ScriptPluginFactory.class)));
+
+    }
+
+    protected SettingsProcessor createSettingsProcessor() {
+        return new PropertiesLoadingSettingsProcessor(new
+                ScriptEvaluatingSettingsProcessor(
+                    get(ScriptPluginFactory.class),
+                    new SettingsFactory(
+                        new DefaultProjectDescriptorRegistry())));
+    }
+
+    protected ExceptionAnalyser createExceptionAnalyser() {
+        return new DefaultExceptionAnalyser(get(ListenerManager.class));
+    }
+
+    protected ScriptHandlerFactory createScriptHandlerFactory() {
+        return new DefaultScriptHandlerFactory(
+                get(RepositoryHandlerFactory.class),
+                get(ConfigurationContainerFactory.class),
+                new DependencyMetaDataProviderImpl(), 
+                get(DependencyFactory.class));
+    }
+
+    protected WorkerProcessFactory createWorkerProcessFactory() {
+        ClassPathRegistry classPathRegistry = get(ClassPathRegistry.class);
+        return new DefaultWorkerProcessFactory(startParameter.getLogLevel(), get(MessagingServer.class), classPathRegistry,
+                new IdentityFileResolver(), new LongIdGenerator());
+    }
+    
+    protected MessagingServer createMessagingServer() {
+        return new TcpMessagingServer(get(ClassLoaderFactory.class).getRootClassLoader());
+    }
+    
+    public ServiceRegistryFactory createFor(Object domainObject) {
+        if (domainObject instanceof GradleInternal) {
+            return new GradleInternalServiceRegistry(this, (GradleInternal) domainObject);
+        }
+        throw new IllegalArgumentException(String.format("Cannot create services for unknown domain object of type %s.",
+                domainObject.getClass().getSimpleName()));
+    }
+
+    private class DependencyMetaDataProviderImpl implements DependencyMetaDataProvider {
+        public InternalRepository getInternalRepository() {
+            return new EmptyInternalRepository();
+        }
+
+        public File getGradleUserHomeDir() {
+            return startParameter.getGradleUserHomeDir();
+        }
+
+        public Module getModule() {
+            return new DefaultModule("unspecified", "unspecified", Project.DEFAULT_VERSION, Project.DEFAULT_STATUS);
+        }
+    }
+
+    private static class EmptyInternalRepository extends ChainResolver implements InternalRepository {
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ant/AntLoggingAdapter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ant/AntLoggingAdapter.java
new file mode 100644
index 0000000..ada6e6d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ant/AntLoggingAdapter.java
@@ -0,0 +1,89 @@
+/*
+ * 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.project.ant;
+
+import org.apache.tools.ant.BuildEvent;
+import org.apache.tools.ant.BuildLogger;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+import java.io.PrintStream;
+
+/**
+ * @author Hans Dockter
+ */
+public class AntLoggingAdapter implements BuildLogger {
+    private final Logger logger = Logging.getLogger(AntLoggingAdapter.class);
+
+    public void setMessageOutputLevel(int level) {
+        // ignore
+    }
+
+    public void setOutputPrintStream(PrintStream output) {
+        // ignore
+    }
+
+    public void setEmacsMode(boolean emacsMode) {
+        // ignore
+    }
+
+    public void setErrorPrintStream(PrintStream err) {
+        // ignore
+    }
+
+    public void buildStarted(BuildEvent event) {
+        // ignore
+    }
+
+    public void buildFinished(BuildEvent event) {
+        // ignore
+    }
+
+    public void targetStarted(BuildEvent event) {
+        // ignore
+    }
+
+    public void targetFinished(BuildEvent event) {
+        // ignore
+    }
+
+    public void taskStarted(BuildEvent event) {
+        // ignore
+    }
+
+    public void taskFinished(BuildEvent event) {
+        // ignore
+    }
+
+    public void messageLogged(BuildEvent event) {
+        final StringBuffer message = new StringBuffer();
+        if (event.getTask() != null) {
+            String taskName = event.getTask().getTaskName();
+            message.append("[ant:").append(taskName).append("] ");
+        }
+        final String messageText = event.getMessage();
+        message.append(messageText);
+
+        LogLevel level = Logging.ANT_IVY_2_SLF4J_LEVEL_MAPPER.get(event.getPriority());
+
+        if (event.getException() != null) {
+            logger.log(level, message.toString(), event.getException());
+        } else {
+            logger.log(level, message.toString());
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ant/BasicAntBuilder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ant/BasicAntBuilder.java
new file mode 100644
index 0000000..7e0d061
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/ant/BasicAntBuilder.java
@@ -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.api.internal.project.ant;
+
+import groovy.util.AntBuilder;
+import org.apache.tools.ant.Target;
+import org.gradle.api.internal.file.ant.AntFileResource;
+import org.gradle.api.internal.file.ant.BaseDirSelector;
+
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Map;
+
+public class BasicAntBuilder extends org.gradle.api.AntBuilder {
+    private final Field nodeField;
+    private final List children;
+
+    public BasicAntBuilder() {
+        // These are used to discard references to tasks so they can be garbage collected
+        Field collectorField;
+        try {
+            nodeField = AntBuilder.class.getDeclaredField("lastCompletedNode");
+            nodeField.setAccessible(true);
+            collectorField = AntBuilder.class.getDeclaredField("collectorTarget");
+            collectorField.setAccessible(true);
+            Target target = (Target) collectorField.get(this);
+            Field childrenField = Target.class.getDeclaredField("children");
+            childrenField.setAccessible(true);
+            children = (List) childrenField.get(target);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+        getAntProject().addDataTypeDefinition("gradleFileResource", AntFileResource.class);
+        getAntProject().addDataTypeDefinition("gradleBaseDirSelector", BaseDirSelector.class);
+    }
+
+    @Override
+    public Map<String, Object> getProperties() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Map<String, Object> getReferences() {
+        throw new UnsupportedOperationException();
+    }
+
+    public void importBuild(Object antBuildFile) {
+        throw new UnsupportedOperationException();
+    }
+
+    protected Object postNodeCompletion(Object parent, Object node) {
+        try {
+            return nodeField.get(this);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected Object doInvokeMethod(String methodName, Object name, Object args) {
+        Object value = super.doInvokeMethod(methodName, name, args);
+        // Discard the node so it can be garbage collected. Some Ant tasks cache a potentially large amount of state
+        // in fields.
+        try {
+            nodeField.set(this, null);
+            children.clear();
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException(e);
+        }
+        return value;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactory.java
new file mode 100644
index 0000000..b8a94e8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactory.java
@@ -0,0 +1,362 @@
+/*
+ * 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.project.taskfactory;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.Action;
+import org.gradle.api.GradleException;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Task;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.util.ReflectionUtil;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.*;
+import java.util.concurrent.Callable;
+
+/**
+ * A {@link ITaskFactory} which determines task actions, inputs and outputs based on annotation attached to the task
+ * properties. Also provides some validation based on these annotations.
+ */
+public class AnnotationProcessingTaskFactory implements ITaskFactory {
+    private final ITaskFactory taskFactory;
+    private final Map<Class, List<Action<Task>>> actionsForType = new HashMap<Class, List<Action<Task>>>();
+    private final List<? extends PropertyAnnotationHandler> handlers = Arrays.asList(
+            new InputFilePropertyAnnotationHandler(),
+            new InputDirectoryPropertyAnnotationHandler(),
+            new InputFilesPropertyAnnotationHandler(),
+            new OutputFilePropertyAnnotationHandler(),
+            new OutputDirectoryPropertyAnnotationHandler(),
+            new InputPropertyAnnotationHandler(),
+            new NestedBeanPropertyAnnotationHandler());
+    private final ValidationAction notNullValidator = new ValidationAction() {
+        public void validate(String propertyName, Object value) throws InvalidUserDataException {
+            if (value == null) {
+                throw new InvalidUserDataException(String.format("No value has been specified for property '%s'.",
+                        propertyName));
+            }
+        }
+    };
+
+    public AnnotationProcessingTaskFactory(ITaskFactory taskFactory) {
+        this.taskFactory = taskFactory;
+    }
+
+    public TaskInternal createTask(ProjectInternal project, Map<String, ?> args) {
+        TaskInternal task = taskFactory.createTask(project, args);
+
+        Class<? extends Task> type = task.getClass();
+        List<Action<Task>> actions = actionsForType.get(type);
+        if (actions == null) {
+            actions = createActionsForType(type);
+            actionsForType.put(type, actions);
+        }
+
+        for (Action<Task> action : actions) {
+            task.doFirst(action);
+            if (action instanceof Validator) {
+                Validator validator = (Validator) action;
+                validator.addInputsAndOutputs(task);
+            }
+        }
+
+        return task;
+    }
+
+    private List<Action<Task>> createActionsForType(Class<? extends Task> type) {
+        List<Action<Task>> actions = new ArrayList<Action<Task>>();
+        findTaskActions(type, actions);
+        findProperties(type, actions);
+        return actions;
+    }
+
+    private void findProperties(Class<? extends Task> type, List<Action<Task>> actions) {
+        Validator validator = new Validator();
+
+        validator.attachActions(null, type);
+
+        if (!validator.properties.isEmpty()) {
+            actions.add(validator);
+        }
+    }
+
+    private void findTaskActions(Class<? extends Task> type, List<Action<Task>> actions) {
+        Set<String> methods = new HashSet<String>();
+        for (Class current = type; current != null; current = current.getSuperclass()) {
+            for (Method method : current.getDeclaredMethods()) {
+                attachTaskAction(method, actions, methods);
+            }
+        }
+    }
+
+    private void attachTaskAction(final Method method, Collection<Action<Task>> actions, Collection<String> methods) {
+        if (method.getAnnotation(TaskAction.class) == null) {
+            return;
+        }
+        if (Modifier.isStatic(method.getModifiers())) {
+            throw new GradleException(String.format("Cannot use @TaskAction annotation on static method %s.%s().",
+                    method.getDeclaringClass().getSimpleName(), method.getName()));
+        }
+        if (method.getParameterTypes().length > 0) {
+            throw new GradleException(String.format(
+                    "Cannot use @TaskAction annotation on method %s.%s() as this method takes parameters.",
+                    method.getDeclaringClass().getSimpleName(), method.getName()));
+        }
+        if (methods.contains(method.getName())) {
+            return;
+        }
+        methods.add(method.getName());
+        actions.add(new Action<Task>() {
+            public void execute(Task task) {
+                ReflectionUtil.invoke(task, method.getName(), new Object[0]);
+            }
+        });
+    }
+
+    private static boolean isGetter(Method method) {
+        return method.getName().startsWith("get") && method.getReturnType() != Void.TYPE
+                && method.getParameterTypes().length == 0 && !Modifier.isStatic(method.getModifiers());
+    }
+
+    private class Validator implements Action<Task> {
+        private Set<PropertyInfo> properties = new LinkedHashSet<PropertyInfo>();
+
+        public void addInputsAndOutputs(final Task task) {
+            for (final PropertyInfo property : properties) {
+                Callable<Object> futureValue = new Callable<Object>() {
+                    public Object call() throws Exception {
+                        return property.getValue(task).getValue();
+                    }
+                };
+
+                property.configureAction.update(task, futureValue);
+            }
+        }
+
+        public void execute(Task task) {
+            try {
+                List<PropertyValue> propertyValues = new ArrayList<PropertyValue>();
+                for (PropertyInfo property : properties) {
+                    propertyValues.add(property.getValue(task));
+                }
+                for (PropertyValue propertyValue : propertyValues) {
+                    propertyValue.checkNotNull();
+                }
+                for (PropertyValue propertyValue : propertyValues) {
+                    propertyValue.checkSkip();
+                }
+                for (PropertyValue propertyValue : propertyValues) {
+                    propertyValue.checkValid();
+                }
+            } catch (InvalidUserDataException e) {
+                throw new InvalidUserDataException(String.format("Error validating %s: %s", task, e.getMessage()), e);
+            }
+        }
+
+        public void attachActions(PropertyInfo parent, Class<?> type) {
+            if (type.getSuperclass() != null) {
+                attachActions(parent, type.getSuperclass());
+            }
+            for (Method method : type.getDeclaredMethods()) {
+                if (!isGetter(method)) {
+                    continue;
+                }
+
+                String fieldName = StringUtils.uncapitalize(method.getName().substring(3));
+                String propertyName = fieldName;
+                if (parent != null) {
+                    propertyName = parent.getName() + '.' + propertyName;
+                }
+                PropertyInfo propertyInfo = new PropertyInfo(this, parent, propertyName, method);
+
+                attachValidationActions(propertyInfo, fieldName);
+
+                if (propertyInfo.required) {
+                    properties.add(propertyInfo);
+                }
+            }
+        }
+
+        private void attachValidationActions(PropertyInfo propertyInfo, String fieldName) {
+            for (PropertyAnnotationHandler handler : handlers) {
+                attachValidationAction(handler, propertyInfo, fieldName);
+            }
+        }
+
+        private void attachValidationAction(PropertyAnnotationHandler handler, PropertyInfo propertyInfo, String fieldName) {
+            final Method method = propertyInfo.method;
+            Class<? extends Annotation> annotationType = handler.getAnnotationType();
+
+            AnnotatedElement annotationTarget = null;
+            if (method.getAnnotation(annotationType) != null) {
+                annotationTarget = method;
+            } else {
+                try {
+                    Field field = method.getDeclaringClass().getDeclaredField(fieldName);
+                    if (field.getAnnotation(annotationType) != null) {
+                        annotationTarget = field;
+                    }
+                } catch (NoSuchFieldException e) {
+                    // ok - ignore
+                }
+            }
+            if (annotationTarget == null) {
+                return;
+            }
+
+            Annotation optional = annotationTarget.getAnnotation(Optional.class);
+            if (optional == null) {
+                propertyInfo.setNotNullValidator(notNullValidator);
+            }
+
+            propertyInfo.attachActions(handler);
+        }
+    }
+
+    private interface PropertyValue {
+        Object getValue();
+
+        void checkNotNull();
+
+        void checkSkip();
+
+        void checkValid();
+    }
+
+    private static class PropertyInfo implements PropertyActionContext {
+        private static final ValidationAction NO_OP_VALIDATION_ACTION = new ValidationAction() {
+            public void validate(String propertyName, Object value) throws InvalidUserDataException {
+            }
+        };
+        private static final PropertyValue NO_OP_VALUE = new PropertyValue() {
+            public Object getValue() {
+                return null;
+            }
+
+            public void checkNotNull() {
+            }
+
+            public void checkSkip() {
+            }
+
+            public void checkValid() {
+            }
+        };
+        private static final UpdateAction NO_OP_CONFIGURATION_ACTION = new UpdateAction() {
+            public void update(Task task, Callable<Object> futureValue) {
+            }
+        };
+
+        private final Validator validator;
+        private final PropertyInfo parent;
+        private final String propertyName;
+        private final Method method;
+        private ValidationAction skipAction = NO_OP_VALIDATION_ACTION;
+        private ValidationAction validationAction = NO_OP_VALIDATION_ACTION;
+        private ValidationAction notNullValidator = NO_OP_VALIDATION_ACTION;
+        private UpdateAction configureAction = NO_OP_CONFIGURATION_ACTION;
+        public boolean required;
+
+        private PropertyInfo(Validator validator, PropertyInfo parent, String propertyName, Method method) {
+            this.validator = validator;
+            this.parent = parent;
+            this.propertyName = propertyName;
+            this.method = method;
+        }
+
+        @Override
+        public String toString() {
+            return propertyName;
+        }
+
+        public String getName() {
+            return propertyName;
+        }
+
+        public Class<?> getType() {
+            return method.getReturnType();
+        }
+
+        public AnnotatedElement getTarget() {
+            return method;
+        }
+
+        public void setSkipAction(ValidationAction action) {
+            skipAction = action;
+        }
+
+        public void setValidationAction(ValidationAction action) {
+            validationAction = action;
+        }
+
+        public void setConfigureAction(UpdateAction action) {
+            configureAction = action;
+        }
+
+        public void setNotNullValidator(ValidationAction notNullValidator) {
+            this.notNullValidator = notNullValidator;
+        }
+
+        public void attachActions(Class<?> type) {
+            validator.attachActions(this, type);
+        }
+
+        public PropertyValue getValue(Object rootObject) {
+            Object bean = rootObject;
+            if (parent != null) {
+                PropertyValue parentValue = parent.getValue(rootObject);
+                if (parentValue.getValue() == null) {
+                    return NO_OP_VALUE;
+                }
+                bean = parentValue.getValue();
+            }
+
+            final Object value = ReflectionUtil.invoke(bean, method.getName(), new Object[0]);
+
+            return new PropertyValue() {
+                public Object getValue() {
+                    return value;
+                }
+
+                public void checkNotNull() {
+                    notNullValidator.validate(propertyName, value);
+                }
+
+                public void checkSkip() {
+                    skipAction.validate(propertyName, value);
+                }
+
+                public void checkValid() {
+                    if (value != null) {
+                        validationAction.validate(propertyName, value);
+                    }
+                }
+            };
+        }
+
+        public void attachActions(PropertyAnnotationHandler handler) {
+            handler.attachActions(this);
+            required = true;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/DependencyAutoWireTaskFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/DependencyAutoWireTaskFactory.java
new file mode 100644
index 0000000..9a8c17a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/DependencyAutoWireTaskFactory.java
@@ -0,0 +1,50 @@
+/*
+ * 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.project.taskfactory;
+
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.project.ProjectInternal;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A {@link ITaskFactory} which wires the dependencies of a task based on its input files.
+ */
+public class DependencyAutoWireTaskFactory implements ITaskFactory {
+    public static final String DEPENDENCY_AUTO_WIRE = "dependencyAutoWire";
+    private final ITaskFactory taskFactory;
+
+    public DependencyAutoWireTaskFactory(ITaskFactory taskFactory) {
+        this.taskFactory = taskFactory;
+    }
+
+    public TaskInternal createTask(ProjectInternal project, Map<String, ?> args) {
+        Map<String, Object> actualArgs = new HashMap<String, Object>(args);
+        boolean autoWire = remove(actualArgs, DEPENDENCY_AUTO_WIRE);
+
+        TaskInternal task = taskFactory.createTask(project, actualArgs);
+        if (autoWire) {
+            task.dependsOn(task.getInputs().getFiles());
+        }
+        return task;
+    }
+
+    private boolean remove(Map<String, ?> args, String key) {
+        Object value = args.remove(key);
+        return value == null ? true : Boolean.valueOf(value.toString());
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/ExecutionShortCircuitTaskExecuter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/ExecutionShortCircuitTaskExecuter.java
new file mode 100644
index 0000000..660b7be
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/ExecutionShortCircuitTaskExecuter.java
@@ -0,0 +1,58 @@
+/*
+ * 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.project.taskfactory;
+
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.changedetection.TaskArtifactState;
+import org.gradle.api.internal.changedetection.TaskArtifactStateRepository;
+import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.api.internal.tasks.TaskStateInternal;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ExecutionShortCircuitTaskExecuter implements TaskExecuter {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ExecutionShortCircuitTaskExecuter.class);
+    private final TaskExecuter executer;
+    private final TaskArtifactStateRepository repository;
+
+    public ExecutionShortCircuitTaskExecuter(TaskExecuter executer, TaskArtifactStateRepository repository) {
+        this.executer = executer;
+        this.repository = repository;
+    }
+
+    public void execute(TaskInternal task, TaskStateInternal state) {
+        LOGGER.debug("Determining if {} is up-to-date", task);
+        TaskArtifactState taskArtifactState = repository.getStateFor(task);
+        if (taskArtifactState.isUpToDate()) {
+            LOGGER.debug("{} is up-to-date", task);
+            state.upToDate();
+            return;
+
+        }
+        LOGGER.debug("{} is not up-to-date", task);
+
+        task.getOutputs().setHistory(taskArtifactState);
+        try {
+            executer.execute(task, state);
+            if (state.getFailure() == null) {
+                taskArtifactState.update();
+            }
+        } finally {
+            task.getOutputs().setHistory(null);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/ITaskFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/ITaskFactory.java
new file mode 100644
index 0000000..f292891
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/ITaskFactory.java
@@ -0,0 +1,28 @@
+/*
+ * 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.project.taskfactory;
+
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.project.ProjectInternal;
+
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ITaskFactory {
+    public TaskInternal createTask(ProjectInternal project, Map<String, ?> args);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/InputDirectoryPropertyAnnotationHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/InputDirectoryPropertyAnnotationHandler.java
new file mode 100644
index 0000000..049552e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/InputDirectoryPropertyAnnotationHandler.java
@@ -0,0 +1,67 @@
+/*
+ * 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.project.taskfactory;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Task;
+import org.gradle.api.file.ConfigurableFileTree;
+import org.gradle.api.tasks.InputDirectory;
+import org.gradle.api.tasks.SkipWhenEmpty;
+import org.gradle.api.tasks.StopExecutionException;
+
+import java.io.File;
+import java.lang.annotation.Annotation;
+import java.util.concurrent.Callable;
+
+public class InputDirectoryPropertyAnnotationHandler implements PropertyAnnotationHandler {
+    private final ValidationAction inputDirValidation = new ValidationAction() {
+        public void validate(String propertyName, Object value) throws InvalidUserDataException {
+            File fileValue = (value instanceof ConfigurableFileTree) ? ((ConfigurableFileTree) value).getDir() : (File) value;
+            if (!fileValue.exists()) {
+                throw new InvalidUserDataException(String.format(
+                        "Directory '%s' specified for property '%s' does not exist.", fileValue, propertyName));
+            }
+            if (!fileValue.isDirectory()) {
+                throw new InvalidUserDataException(String.format(
+                        "Directory '%s' specified for property '%s' is not a directory.", fileValue, propertyName));
+            }
+        }
+    };
+    private final ValidationAction skipEmptyDirectoryAction = new ValidationAction() {
+        public void validate(String propertyName, Object value) throws InvalidUserDataException {
+            File fileValue = (File) value;
+            if (!fileValue.exists() || fileValue.isDirectory() && fileValue.list().length == 0) {
+                throw new StopExecutionException(String.format("Directory %s is empty or does not exist.", fileValue));
+            }
+        }
+    };
+
+    public Class<? extends Annotation> getAnnotationType() {
+        return InputDirectory.class;
+    }
+
+    public void attachActions(PropertyActionContext context) {
+        context.setValidationAction(inputDirValidation);
+        if (context.getTarget().getAnnotation(SkipWhenEmpty.class) != null) {
+            context.setSkipAction(skipEmptyDirectoryAction);
+        }
+        context.setConfigureAction(new UpdateAction() {
+            public void update(Task task, Callable<Object> futureValue) {
+                task.getInputs().dir(futureValue);
+            }
+        });
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/InputFilePropertyAnnotationHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/InputFilePropertyAnnotationHandler.java
new file mode 100644
index 0000000..ea430c1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/InputFilePropertyAnnotationHandler.java
@@ -0,0 +1,53 @@
+/*
+ * 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.project.taskfactory;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.InputFile;
+
+import java.io.File;
+import java.lang.annotation.Annotation;
+import java.util.concurrent.Callable;
+
+public class InputFilePropertyAnnotationHandler implements PropertyAnnotationHandler {
+    private final ValidationAction inputFileValidation = new ValidationAction() {
+        public void validate(String propertyName, Object value) throws InvalidUserDataException {
+            File fileValue = (File) value;
+            if (!fileValue.exists()) {
+                throw new InvalidUserDataException(String.format(
+                        "File '%s' specified for property '%s' does not exist.", fileValue, propertyName));
+            }
+            if (!fileValue.isFile()) {
+                throw new InvalidUserDataException(String.format("File '%s' specified for property '%s' is not a file.",
+                        fileValue, propertyName));
+            }
+        }
+    };
+
+    public Class<? extends Annotation> getAnnotationType() {
+        return InputFile.class;
+    }
+
+    public void attachActions(PropertyActionContext context) {
+        context.setValidationAction(inputFileValidation);
+        context.setConfigureAction(new UpdateAction() {
+            public void update(Task task, Callable<Object> futureValue) {
+                task.getInputs().files(futureValue);
+            }
+        });
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/InputFilesPropertyAnnotationHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/InputFilesPropertyAnnotationHandler.java
new file mode 100644
index 0000000..14bc0f7
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/InputFilesPropertyAnnotationHandler.java
@@ -0,0 +1,50 @@
+/*
+ * 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.project.taskfactory;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Task;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.SkipWhenEmpty;
+
+import java.lang.annotation.Annotation;
+import java.util.concurrent.Callable;
+
+public class InputFilesPropertyAnnotationHandler implements PropertyAnnotationHandler {
+    private final ValidationAction skipEmptyFileCollection = new ValidationAction() {
+        public void validate(String propertyName, Object value) throws InvalidUserDataException {
+            if (value instanceof FileCollection) {
+                ((FileCollection) value).stopExecutionIfEmpty();
+            }
+        }
+    };
+
+    public Class<? extends Annotation> getAnnotationType() {
+        return InputFiles.class;
+    }
+
+    public void attachActions(PropertyActionContext context) {
+        if (context.getTarget().getAnnotation(SkipWhenEmpty.class) != null) {
+            context.setSkipAction(skipEmptyFileCollection);
+        }
+        context.setConfigureAction(new UpdateAction() {
+            public void update(Task task, Callable<Object> futureValue) {
+                task.getInputs().files(futureValue);
+            }
+        });
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/InputPropertyAnnotationHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/InputPropertyAnnotationHandler.java
new file mode 100644
index 0000000..933f3a7
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/InputPropertyAnnotationHandler.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.api.internal.project.taskfactory;
+
+import org.gradle.api.Task;
+import org.gradle.api.tasks.Input;
+
+import java.lang.annotation.Annotation;
+import java.util.concurrent.Callable;
+
+public class InputPropertyAnnotationHandler implements PropertyAnnotationHandler {
+    public Class<? extends Annotation> getAnnotationType() {
+        return Input.class;
+    }
+
+    public void attachActions(final PropertyActionContext context) {
+        context.setConfigureAction(new UpdateAction() {
+            public void update(Task task, Callable<Object> futureValue) {
+                task.getInputs().property(context.getName(), futureValue);
+            }
+        });
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/NestedBeanPropertyAnnotationHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/NestedBeanPropertyAnnotationHandler.java
new file mode 100644
index 0000000..c1e0bf0
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/NestedBeanPropertyAnnotationHandler.java
@@ -0,0 +1,44 @@
+/*
+ * 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.project.taskfactory;
+
+import org.gradle.api.Task;
+import org.gradle.api.tasks.Nested;
+
+import java.lang.annotation.Annotation;
+import java.util.concurrent.Callable;
+
+public class NestedBeanPropertyAnnotationHandler implements PropertyAnnotationHandler {
+    public Class<? extends Annotation> getAnnotationType() {
+        return Nested.class;
+    }
+
+    public void attachActions(final PropertyActionContext context) {
+        context.attachActions(context.getType());
+        context.setConfigureAction(new UpdateAction() {
+            public void update(Task task, final Callable<Object> futureValue) {
+                task.getInputs().property(context.getName() + ".class", new Callable<Object>() {
+                    @Override
+                    public Object call() throws Exception {
+                        Object bean = futureValue.call();
+                        return bean == null ? null : bean.getClass().getName();
+                    }
+                });
+            }
+        });
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputDirectoryPropertyAnnotationHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputDirectoryPropertyAnnotationHandler.java
new file mode 100644
index 0000000..6d6341f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputDirectoryPropertyAnnotationHandler.java
@@ -0,0 +1,49 @@
+/*
+ * 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.project.taskfactory;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.OutputDirectory;
+
+import java.io.File;
+import java.lang.annotation.Annotation;
+import java.util.concurrent.Callable;
+
+public class OutputDirectoryPropertyAnnotationHandler implements PropertyAnnotationHandler {
+    private final ValidationAction outputDirValidation = new ValidationAction() {
+        public void validate(String propertyName, Object value) throws InvalidUserDataException {
+            File fileValue = (File) value;
+            if (!fileValue.isDirectory() && !fileValue.mkdirs()) {
+                throw new InvalidUserDataException(String.format(
+                        "Cannot create directory '%s' specified for property '%s'.", fileValue, propertyName));
+            }
+        }
+    };
+
+    public Class<? extends Annotation> getAnnotationType() {
+        return OutputDirectory.class;
+    }
+
+    public void attachActions(PropertyActionContext context) {
+        context.setValidationAction(outputDirValidation);
+        context.setConfigureAction(new UpdateAction() {
+            public void update(Task task, Callable<Object> futureValue) {
+                task.getOutputs().files(futureValue);
+            }
+        });
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputFilePropertyAnnotationHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputFilePropertyAnnotationHandler.java
new file mode 100644
index 0000000..29a3f82
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/OutputFilePropertyAnnotationHandler.java
@@ -0,0 +1,56 @@
+/*
+ * 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.project.taskfactory;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.lang.annotation.Annotation;
+import java.util.concurrent.Callable;
+
+public class OutputFilePropertyAnnotationHandler implements PropertyAnnotationHandler {
+    private final ValidationAction ouputFileValidation = new ValidationAction() {
+        public void validate(String propertyName, Object value) throws InvalidUserDataException {
+            File fileValue = GFileUtils.canonicalise((File) value);
+            if (fileValue.exists() && !fileValue.isFile()) {
+                throw new InvalidUserDataException(String.format(
+                        "Cannot write to file '%s' specified for property '%s' as it is a directory.", fileValue,
+                        propertyName));
+            }
+            if (!fileValue.getParentFile().isDirectory() && !fileValue.getParentFile().mkdirs()) {
+                throw new InvalidUserDataException(String.format(
+                        "Cannot create parent directory '%s' of file specified for property '%s'.",
+                        fileValue.getParentFile(), propertyName));
+            }
+        }
+    };
+
+    public Class<? extends Annotation> getAnnotationType() {
+        return OutputFile.class;
+    }
+
+    public void attachActions(PropertyActionContext context) {
+        context.setValidationAction(ouputFileValidation);
+        context.setConfigureAction(new UpdateAction() {
+            public void update(Task task, Callable<Object> futureValue) {
+                task.getOutputs().files(futureValue);
+            }
+        });
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/PostExecutionAnalysisTaskExecuter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/PostExecutionAnalysisTaskExecuter.java
new file mode 100644
index 0000000..4472e89
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/PostExecutionAnalysisTaskExecuter.java
@@ -0,0 +1,49 @@
+/*
+ * 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.project.taskfactory;
+
+import org.gradle.api.Task;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.api.internal.tasks.TaskStateInternal;
+
+public class PostExecutionAnalysisTaskExecuter implements TaskExecuter {
+    private final TaskExecuter executer;
+
+    public PostExecutionAnalysisTaskExecuter(TaskExecuter executer) {
+        this.executer = executer;
+    }
+
+    public void execute(TaskInternal task, TaskStateInternal state) {
+        executer.execute(task, state);
+        if (task.getActions().isEmpty()) {
+            boolean upToDate = true;
+            for (Task dependency : task.getTaskDependencies().getDependencies(task)) {
+                if (!dependency.getState().getSkipped()) {
+                    upToDate = false;
+                    break;
+                }
+            }
+            if (upToDate) {
+                state.upToDate();
+            }
+        }
+        else if (!state.getDidWork()) {
+            state.upToDate();
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/PropertyActionContext.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/PropertyActionContext.java
new file mode 100644
index 0000000..f912dfb
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/PropertyActionContext.java
@@ -0,0 +1,55 @@
+/*
+ * 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.project.taskfactory;
+
+import java.lang.reflect.AnnotatedElement;
+
+public interface PropertyActionContext {
+    /**
+     * Returns the name of this property.
+     */
+    String getName();
+
+    /**
+     * Returns the declared type of this property.
+     */
+    Class<?> getType();
+
+    /**
+     * Returns the target for this property.
+     */
+    AnnotatedElement getTarget();
+
+    /**
+     * Specifies the action used to validate the value of this property. This action is only executed when the property
+     * value is not null.
+     */
+    void setValidationAction(ValidationAction action);
+
+    /**
+     * Specifies the action used to skip the task based on the value of this property. Note that this action is called
+     * before the validation action.
+     */
+    void setSkipAction(ValidationAction action);
+
+    /**
+     * Specified the action used to configure the task based on the value of this property. Note that this action is
+     * called before the skip and validation actions.
+     */
+    void setConfigureAction(UpdateAction action);
+
+    void attachActions(Class<?> type);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/PropertyAnnotationHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/PropertyAnnotationHandler.java
new file mode 100644
index 0000000..a621cab
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/PropertyAnnotationHandler.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.api.internal.project.taskfactory;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * Handles validation, dependency handling, and skipping for a property marked with a given annotation.
+ */
+public interface PropertyAnnotationHandler {
+    /**
+     * The annotation type which this handler is responsible for.
+     *
+     * @return The annotation.
+     */
+    Class<? extends Annotation> getAnnotationType();
+
+    /**
+     * Attaches the actions for the given property.
+     */
+    void attachActions(PropertyActionContext context);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/TaskFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/TaskFactory.java
new file mode 100644
index 0000000..957afc5
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/TaskFactory.java
@@ -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.api.internal.project.taskfactory;
+
+import groovy.lang.Closure;
+import org.gradle.api.*;
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.tasks.TaskInstantiationException;
+import org.gradle.util.GUtil;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+/**
+ * @author Hans Dockter
+ */
+public class TaskFactory implements ITaskFactory {
+    public static final String GENERATE_SUBCLASS = "generateSubclass";
+    private final ClassGenerator generator;
+
+    public TaskFactory(ClassGenerator generator) {
+        this.generator = generator;
+    }
+
+    public TaskInternal createTask(ProjectInternal project, Map<String, ?> args) {
+        Map<String, Object> actualArgs = new HashMap<String, Object>(args);
+        checkTaskArgsAndCreateDefaultValues(actualArgs);
+
+        String name = actualArgs.get(Task.TASK_NAME).toString();
+        if (!GUtil.isTrue(name)) {
+            throw new InvalidUserDataException("The task name must be provided.");
+        }
+
+        Class<? extends TaskInternal> type = (Class) actualArgs.get(Task.TASK_TYPE);
+        Boolean generateSubclass = Boolean.valueOf(actualArgs.get(GENERATE_SUBCLASS).toString());
+        TaskInternal task = createTaskObject(project, type, name, generateSubclass);
+
+        Object dependsOnTasks = actualArgs.get(Task.TASK_DEPENDS_ON);
+        if (dependsOnTasks != null) {
+            task.dependsOn(dependsOnTasks);
+        }
+        Object description = actualArgs.get(Task.TASK_DESCRIPTION);
+        if (description != null) {
+            task.setDescription(description.toString());
+        }
+        Object action = actualArgs.get(Task.TASK_ACTION);
+        if (action instanceof Action) {
+            Action<? super Task> taskAction = (Action<? super Task>) action;
+            task.doFirst(taskAction);
+        } else if (action != null) {
+            Closure closure = (Closure) action;
+            task.doFirst(closure);
+        }
+
+        return task;
+    }
+
+    private TaskInternal createTaskObject(ProjectInternal project, final Class<? extends TaskInternal> type, String name, boolean generateGetters) {
+        if (!Task.class.isAssignableFrom(type)) {
+            throw new InvalidUserDataException(String.format(
+                    "Cannot create task of type '%s' as it does not implement the Task interface.",
+                    type.getSimpleName()));
+        }
+
+        Class<? extends TaskInternal> generatedType;
+        if (generateGetters) {
+            generatedType = generator.generate(type);
+        } else {
+            generatedType = type;
+        }
+
+        final Constructor<? extends TaskInternal> constructor;
+        final Object[] params;
+        try {
+            constructor = generatedType.getDeclaredConstructor();
+            params = new Object[0];
+        } catch (NoSuchMethodException e) {
+            // Ignore
+            throw new InvalidUserDataException(String.format(
+                    "Cannot create task of type '%s' as it does not have a public no-args constructor.",
+                    type.getSimpleName()));
+        }
+
+        return AbstractTask.injectIntoNewInstance(project, name, new Callable<TaskInternal>() {
+            public TaskInternal call() throws Exception {
+                try {
+                    return constructor.newInstance(params);
+                } catch (InvocationTargetException e) {
+                    throw new TaskInstantiationException(String.format("Could not create task of type '%s'.", type.getSimpleName()),
+                            e.getCause());
+                } catch (Exception e) {
+                    throw new TaskInstantiationException(String.format("Could not create task of type '%s'.", type.getSimpleName()), e);
+                }
+            }
+        });
+    }
+
+    private void checkTaskArgsAndCreateDefaultValues(Map<String, Object> args) {
+        setIfNull(args, Task.TASK_NAME, "");
+        setIfNull(args, Task.TASK_TYPE, DefaultTask.class);
+        if (((Class) args.get(Task.TASK_TYPE)).isAssignableFrom(DefaultTask.class)) {
+            args.put(Task.TASK_TYPE, DefaultTask.class);
+        }
+        setIfNull(args, GENERATE_SUBCLASS, "true");
+    }
+
+    private void setIfNull(Map<String, Object> map, String key, Object defaultValue) {
+        if (map.get(key) == null) {
+            map.put(key, defaultValue);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/UpdateAction.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/UpdateAction.java
new file mode 100644
index 0000000..2d5f918
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/UpdateAction.java
@@ -0,0 +1,24 @@
+/*
+ * 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.project.taskfactory;
+
+import org.gradle.api.Task;
+
+import java.util.concurrent.Callable;
+
+public interface UpdateAction {
+    void update(Task task, Callable<Object> futureValue);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/ValidationAction.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/ValidationAction.java
new file mode 100644
index 0000000..b7cd4f1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/project/taskfactory/ValidationAction.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.api.internal.project.taskfactory;
+
+import org.gradle.api.InvalidUserDataException;
+
+interface ValidationAction {
+    void validate(String propertyName, Object value) throws InvalidUserDataException;
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/CachingResource.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/CachingResource.java
new file mode 100644
index 0000000..6098db0
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/CachingResource.java
@@ -0,0 +1,45 @@
+/*
+ * 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.resource;
+
+public class CachingResource extends DelegatingResource {
+    private String content;
+    private boolean fetched;
+
+    public CachingResource(Resource resource) {
+        super(resource);
+    }
+
+    @Override
+    public boolean getExists() {
+        maybeFetch();
+        return content != null;
+    }
+
+    @Override
+    public String getText() {
+        maybeFetch();
+        return content;
+    }
+
+    private void maybeFetch() {
+        if (!fetched) {
+            content = getResource().getText();
+            fetched = true;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/DelegatingResource.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/DelegatingResource.java
new file mode 100644
index 0000000..8a2be29
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/DelegatingResource.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.api.internal.resource;
+
+import java.io.File;
+import java.net.URI;
+
+public class DelegatingResource implements Resource {
+    private final Resource resource;
+
+    public DelegatingResource(Resource resource) {
+        this.resource = resource;
+    }
+
+    public Resource getResource() {
+        return resource;
+    }
+
+    public String getDisplayName() {
+        return resource.getDisplayName();
+    }
+
+    public File getFile() {
+        return resource.getFile();
+    }
+
+    public URI getURI() {
+        return resource.getURI();
+    }
+
+    public boolean getExists() {
+        return resource.getExists();
+    }
+
+    public String getText() {
+        return resource.getText();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/Resource.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/Resource.java
new file mode 100644
index 0000000..bb6f60a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/Resource.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.api.internal.resource;
+
+import java.io.File;
+import java.net.URI;
+
+/**
+ * A {@code Resource} represents some binary artifact.
+ *
+ * <p>Implementations are not required to be thread-safe.</p>
+ */
+public interface Resource {
+    /**
+     * Returns a display name for this resource. This can be used in log and error messages.
+     *
+     * @return the display name
+     */
+    String getDisplayName();
+
+    /**
+     * Returns a file representing this resource. Not all resources are available as a file.
+     *
+     * @return A file representing this resource. Returns null if this resource is not available as a file.
+     */
+    File getFile();
+
+    /**
+     * Returns the URI for this resource. Not all resources have a URI.
+     *
+     * @return The URI for this resource. Returns null if this resource does not have a URI.
+     */
+    URI getURI();
+
+    /**
+     * Returns true if this resource exists, false if it does not exist.
+     *
+     * @return true if this resource exists.
+     */
+    boolean getExists();
+
+    /**
+     * Returns the content of this resource, as a String.
+     *
+     * @return the content. Never returns null.
+     * @throws ResourceNotFoundException When this resource does not exist.
+     */
+    String getText() throws ResourceNotFoundException;
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/ResourceException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/ResourceException.java
new file mode 100644
index 0000000..1ab913b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/ResourceException.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.api.internal.resource;
+
+import org.gradle.api.GradleException;
+
+public class ResourceException extends GradleException {
+    public ResourceException(String message) {
+        super(message);
+    }
+
+    public ResourceException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/ResourceNotFoundException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/ResourceNotFoundException.java
new file mode 100644
index 0000000..15e94ac
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/ResourceNotFoundException.java
@@ -0,0 +1,26 @@
+/*
+ * 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.resource;
+
+/**
+ * An exception thrown when attempting to access the content of a {@link Resource} which does not exist.
+ */
+public class ResourceNotFoundException extends ResourceException {
+    public ResourceNotFoundException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/StringResource.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/StringResource.java
new file mode 100644
index 0000000..af98ef3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/StringResource.java
@@ -0,0 +1,50 @@
+/*
+ * 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.resource;
+
+import java.io.File;
+import java.net.URI;
+
+public class StringResource implements Resource {
+    private final String displayName;
+    private final CharSequence contents;
+
+    public StringResource(String displayName, CharSequence contents) {
+        this.displayName = displayName;
+        this.contents = contents;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public String getText() {
+        return contents.toString();
+    }
+
+    public File getFile() {
+        return null;
+    }
+
+    public URI getURI() {
+        return null;
+    }
+
+    public boolean getExists() {
+        return true;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/UriResource.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/UriResource.java
new file mode 100644
index 0000000..a0a7215
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/resource/UriResource.java
@@ -0,0 +1,90 @@
+/*
+ * 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.resource;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.net.URI;
+
+import static org.gradle.util.GFileUtils.*;
+
+public class UriResource implements Resource {
+    private final File sourceFile;
+    private final URI sourceUri;
+    private final String description;
+
+    public UriResource(String description, File sourceFile) {
+        this.description = description;
+        this.sourceFile = canonicalise(sourceFile);
+        this.sourceUri = sourceFile.toURI();
+    }
+
+    public UriResource(String description, URI sourceUri) {
+        this.description = description;
+        this.sourceFile = sourceUri.getScheme().equals("file") ? canonicalise(new File(sourceUri.getPath())) : null;
+        this.sourceUri = sourceUri;
+    }
+
+    public String getDisplayName() {
+        return String.format("%s '%s'", description, sourceFile != null ? sourceFile.getAbsolutePath() : sourceUri);
+    }
+
+    public String getText() {
+        if (sourceFile != null && sourceFile.isDirectory()) {
+            throw new ResourceException(String.format("Could not read %s as it is a directory.", getDisplayName()));
+        }
+        try {
+            InputStream inputStream = sourceUri.toURL().openStream();
+            try {
+                return IOUtils.toString(inputStream);
+            } finally {
+                inputStream.close();
+            }
+        } catch (FileNotFoundException e) {
+            throw new ResourceNotFoundException(String.format(String.format("Could not read %s as it does not exist.",
+                    getDisplayName())));
+        } catch (Exception e) {
+            throw new ResourceException(String.format("Could not read %s.", getDisplayName()), e);
+        }
+    }
+
+    public boolean getExists() {
+        try {
+            InputStream inputStream = sourceUri.toURL().openStream();
+            try {
+                return true;
+            } finally {
+                inputStream.close();
+            }
+        } catch (FileNotFoundException e ) {
+            return false;
+        } catch (Exception e) {
+            throw new ResourceException(String.format("Could not determine if %s exists.", getDisplayName()), e);
+        }
+    }
+
+    public File getFile() {
+        return sourceFile;
+    }
+
+    public URI getURI() {
+        return sourceUri;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/AbstractTaskDependency.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/AbstractTaskDependency.java
new file mode 100644
index 0000000..3f62243
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/AbstractTaskDependency.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.api.internal.tasks;
+
+import org.gradle.api.Task;
+
+import java.util.Set;
+
+public abstract class AbstractTaskDependency implements TaskDependencyInternal {
+    public Set<Task> getDependencies(Task task) {
+        CachingTaskDependencyResolveContext context = new CachingTaskDependencyResolveContext();
+        context.add(this);
+        return context.resolve(task);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/CachingTaskDependencyResolveContext.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/CachingTaskDependencyResolveContext.java
new file mode 100644
index 0000000..e235a3a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/CachingTaskDependencyResolveContext.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+import org.gradle.api.Buildable;
+import org.gradle.api.GradleException;
+import org.gradle.api.Task;
+import org.gradle.api.internal.CachingDirectedGraphWalker;
+import org.gradle.api.internal.DirectedGraph;
+import org.gradle.api.tasks.TaskDependency;
+
+import java.util.*;
+
+/**
+ * <p>A {@link TaskDependencyResolveContext} which caches the dependencies for each {@link
+ * org.gradle.api.tasks.TaskDependency} and {@link org.gradle.api.Buildable} instance during traversal of task
+ * dependencies.</p>
+ *
+ * <p>Supported types:</p> <ul>
+ *
+ * <li>{@link org.gradle.api.Task}</li>
+ *
+ * <li>{@link org.gradle.api.tasks.TaskDependency}</li>
+ *
+ * <li>{@link org.gradle.api.internal.tasks.TaskDependencyInternal}</li>
+ *
+ * <li>{@link org.gradle.api.Buildable}</li>
+ *
+ * </ul>
+ */
+public class CachingTaskDependencyResolveContext implements TaskDependencyResolveContext, TaskDependency {
+    private final LinkedList<Object> queue = new LinkedList<Object>();
+    private final CachingDirectedGraphWalker<Object, Task> walker = new CachingDirectedGraphWalker<Object, Task>(
+            new TaskGraphImpl());
+    private Task task;
+
+    public Set<? extends Task> getDependencies(Task task) {
+        add(task.getTaskDependencies());
+        return resolve(task);
+    }
+
+    public Task getTask() {
+        return task;
+    }
+
+    public Set<Task> resolve(Task task) {
+        this.task = task;
+        try {
+            return doResolve();
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not determine the dependencies of %s.", task), e);
+        } finally {
+            queue.clear();
+            this.task = null;
+        }
+    }
+
+    private Set<Task> doResolve() {
+        walker.add(queue);
+        return walker.findValues();
+    }
+
+    public void add(Object dependency) {
+        queue.add(dependency);
+    }
+
+    private class TaskGraphImpl implements DirectedGraph<Object, Task> {
+        public void getNodeValues(Object node, Collection<Task> values, Collection<Object> connectedNodes) {
+            if (node instanceof TaskDependencyInternal) {
+                TaskDependencyInternal taskDependency = (TaskDependencyInternal) node;
+                queue.clear();
+                taskDependency.resolve(CachingTaskDependencyResolveContext.this);
+                connectedNodes.addAll(queue);
+            } else if (node instanceof Buildable) {
+                Buildable buildable = (Buildable) node;
+                connectedNodes.add(buildable.getBuildDependencies());
+            } else if (node instanceof TaskDependency) {
+                TaskDependency dependency = (TaskDependency) node;
+                values.addAll(dependency.getDependencies(task));
+            } else if (node instanceof Task) {
+                values.add((Task) node);
+            } else {
+                throw new IllegalArgumentException(String.format("Cannot resolve object of unknown type %s to a Task.",
+                        node.getClass().getSimpleName()));
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskCollection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskCollection.java
new file mode 100644
index 0000000..1718370
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskCollection.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.api.internal.tasks;
+
+import groovy.lang.Closure;
+import org.gradle.api.*;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.DefaultNamedDomainObjectContainer;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.api.tasks.TaskCollection;
+
+public class DefaultTaskCollection<T extends Task> extends DefaultNamedDomainObjectContainer<T> implements TaskCollection<T> {
+    protected final ProjectInternal project;
+
+    public DefaultTaskCollection(Class<T> type, ClassGenerator classGenerator, ProjectInternal project) {
+        super(type, classGenerator);
+        this.project = project;
+    }
+
+    public DefaultTaskCollection(Class<T> type, ClassGenerator classGenerator, ProjectInternal project, NamedObjectStore<T> store) {
+        super(type, classGenerator, store);
+        this.project = project;
+    }
+
+    @Override
+    public TaskCollection<T> matching(Spec<? super T> spec) {
+        return getClassGenerator().newInstance(DefaultTaskCollection.class, getType(), getClassGenerator(), project, storeWithSpec(spec));
+    }
+
+    @Override
+    public TaskCollection<T> matching(Closure spec) {
+        return matching(Specs.convertClosureToSpec(spec));
+    }
+
+    @Override
+    public <S extends T> TaskCollection<S> withType(Class<S> type) {
+        return getClassGenerator().newInstance(DefaultTaskCollection.class, type, getClassGenerator(), project, storeWithType(type));
+    }
+
+    public Action<? super T> whenTaskAdded(Action<? super T> action) {
+        return whenObjectAdded(action);
+    }
+
+    public void whenTaskAdded(Closure closure) {
+        whenObjectAdded(closure);
+    }
+
+    public void allTasks(Action<? super T> action) {
+        allObjects(action);
+    }
+
+    public void allTasks(Closure action) {
+        allObjects(action);
+    }
+
+    @Override
+    public String getTypeDisplayName() {
+        return "task";
+    }
+
+    @Override
+    protected UnknownDomainObjectException createNotFoundException(String name) {
+        return new UnknownTaskException(String.format("Task with name '%s' not found in %s.", name, project));
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainer.java
new file mode 100644
index 0000000..12be9cd
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskContainer.java
@@ -0,0 +1,113 @@
+/*
+ * 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;
+
+import groovy.lang.Closure;
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.UnknownTaskException;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.project.taskfactory.ITaskFactory;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.util.GUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultTaskContainer extends DefaultTaskCollection<Task> implements TaskContainerInternal {
+    private final ITaskFactory taskFactory;
+
+    public DefaultTaskContainer(ProjectInternal project, ClassGenerator classGenerator, ITaskFactory taskFactory) {
+        super(Task.class, classGenerator, project);
+        this.taskFactory = taskFactory;
+    }
+
+    public Task add(Map<String, ?> options) {
+        Map<String, Object> mutableOptions = new HashMap<String, Object>(options);
+
+        Object replaceStr = mutableOptions.remove(Task.TASK_OVERWRITE);
+        boolean replace = replaceStr != null && "true".equals(replaceStr.toString());
+
+        Task task = taskFactory.createTask(project, mutableOptions);
+        String name = task.getName();
+
+        if (!replace && findByName(name) != null) {
+            throw new InvalidUserDataException(String.format(
+                    "Cannot add %s as a task with that name already exists.", task));
+        }
+
+        addObject(name, task);
+
+        return task;
+    }
+
+    public Task add(Map<String, ?> options, Closure configureClosure) throws InvalidUserDataException {
+        return add(options).configure(configureClosure);
+    }
+
+    public <T extends Task> T add(String name, Class<T> type) {
+        return type.cast(add(GUtil.map(Task.TASK_NAME, name, Task.TASK_TYPE, type)));
+    }
+
+    public Task add(String name) {
+        return add(GUtil.map(Task.TASK_NAME, name));
+    }
+
+    public Task replace(String name) {
+        return add(GUtil.map(Task.TASK_NAME, name, Task.TASK_OVERWRITE, true));
+    }
+
+    public Task add(String name, Closure configureClosure) {
+        return add(GUtil.map(Task.TASK_NAME, name)).configure(configureClosure);
+    }
+
+    public <T extends Task> T replace(String name, Class<T> type) {
+        return type.cast(add(GUtil.map(Task.TASK_NAME, name, Task.TASK_TYPE, type, Task.TASK_OVERWRITE, true)));
+    }
+
+    public Task findByPath(String path) {
+        if (!GUtil.isTrue(path)) {
+            throw new InvalidUserDataException("A path must be specified!");
+        }
+        if (!path.contains(Project.PATH_SEPARATOR)) {
+            return findByName(path);
+        }
+
+        String projectPath = StringUtils.substringBeforeLast(path, Project.PATH_SEPARATOR);
+        Project project = this.project.findProject(!GUtil.isTrue(projectPath) ? Project.PATH_SEPARATOR : projectPath);
+        if (project == null) {
+            return null;
+        }
+        return project.getTasks().findByName(StringUtils.substringAfterLast(path, Project.PATH_SEPARATOR));
+    }
+
+    public Task resolveTask(Object path) {
+        if (!GUtil.isTrue(path)) {
+            throw new InvalidUserDataException("A path must be specified!");
+        }
+        return getByPath(path.toString());
+    }
+
+    public Task getByPath(String path) throws UnknownTaskException {
+        Task task = findByPath(path);
+        if (task == null) {
+            throw new UnknownTaskException(String.format("Task with path '%s' not found in %s.", path, project));
+        }
+        return task;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskDependency.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskDependency.java
new file mode 100644
index 0000000..7298fa8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskDependency.java
@@ -0,0 +1,113 @@
+/*
+ * 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;
+
+import groovy.lang.Closure;
+import org.gradle.api.Buildable;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.util.GUtil;
+import org.gradle.util.UncheckedException;
+
+import java.util.*;
+import java.util.concurrent.Callable;
+
+public class DefaultTaskDependency extends AbstractTaskDependency {
+    private static final TaskResolver FAILING_RESOLVER = new TaskResolver() {
+        public Task resolveTask(Object path) {
+            throw new IllegalArgumentException(String.format("Cannot convert %s to a task.", path));
+        }
+    };
+    private final Set<Object> values = new HashSet<Object>();
+    private final TaskResolver resolver;
+
+    public DefaultTaskDependency() {
+        this(null);
+    }
+
+    public DefaultTaskDependency(TaskResolver resolver) {
+        this.resolver = resolver == null ? FAILING_RESOLVER : resolver;
+    }
+
+    public void resolve(TaskDependencyResolveContext context) {
+        LinkedList<Object> queue = new LinkedList<Object>(values);
+        while (!queue.isEmpty()) {
+            Object dependency = queue.removeFirst();
+            if (dependency instanceof Buildable) {
+                context.add(dependency);
+            } else if (dependency instanceof Task) {
+                context.add(dependency);
+            } else if (dependency instanceof TaskDependency) {
+                context.add(dependency);
+            } else if (dependency instanceof Closure) {
+                Closure closure = (Closure) dependency;
+                Object closureResult = closure.call(context.getTask());
+                if (closureResult != null) {
+                    queue.add(0, closureResult);
+                }
+            } else if (dependency instanceof Iterable) {
+                Iterable<?> iterable = (Iterable) dependency;
+                queue.addAll(0, GUtil.addToCollection(new ArrayList<Object>(), iterable));
+            } else if (dependency instanceof Map) {
+                Map<?, ?> map = (Map) dependency;
+                queue.addAll(0, map.values());
+            } else if (dependency instanceof Object[]) {
+                Object[] array = (Object[]) dependency;
+                queue.addAll(0, Arrays.asList(array));
+            } else if (dependency instanceof Callable) {
+                Callable callable = (Callable) dependency;
+                Object callableResult;
+                try {
+                    callableResult = callable.call();
+                } catch (Exception e) {
+                    throw UncheckedException.asUncheckedException(e);
+                }
+                if (callableResult != null) {
+                    queue.add(0, callableResult);
+                }
+            } else {
+                context.add(resolver.resolveTask(dependency));
+            }
+        }
+    }
+
+    public Set<Object> getValues() {
+        return values;
+    }
+
+    public void setValues(Iterable<?> values) {
+        this.values.clear();
+        for (Object value : values) {
+            addValue(value);
+        }
+    }
+
+    public DefaultTaskDependency add(Object... values) {
+        for (Object value : values) {
+            addValue(value);
+        }
+        return this;
+    }
+
+    private void addValue(Object dependency) {
+        if (dependency == null) {
+            throw new InvalidUserDataException("A dependency must not be empty");
+        }
+        this.values.add(dependency);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskExecuter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskExecuter.java
new file mode 100644
index 0000000..41a7e21
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskExecuter.java
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+import org.gradle.api.Action;
+import org.gradle.api.GradleException;
+import org.gradle.api.Task;
+import org.gradle.api.execution.TaskActionListener;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.tasks.StopActionException;
+import org.gradle.api.tasks.StopExecutionException;
+import org.gradle.api.tasks.TaskExecutionException;
+
+public class DefaultTaskExecuter implements TaskExecuter {
+    private static Logger logger = Logging.getLogger(DefaultTaskExecuter.class);
+    private final TaskActionListener listener;
+
+    public DefaultTaskExecuter(TaskActionListener listener) {
+        this.listener = listener;
+    }
+
+    public void execute(TaskInternal task, TaskStateInternal state) {
+        listener.beforeActions(task);
+        state.setExecuting(true);
+        try {
+            GradleException failure = executeActions(task, state);
+            state.executed(failure);
+        } finally {
+            state.setExecuting(false);
+            listener.afterActions(task);
+        }
+    }
+
+    private GradleException executeActions(TaskInternal task, TaskStateInternal state) {
+        logger.debug("Executing actions for {}.", task);
+        for (Action<? super Task> action : task.getActions()) {
+            state.setDidWork(true);
+            task.getStandardOutputCapture().start();
+            try {
+                action.execute(task);
+            } catch (StopActionException e) {
+                // Ignore
+                logger.debug("Action stopped by some action with message: {}", e.getMessage());
+            } catch (StopExecutionException e) {
+                logger.info("Execution stopped by some action with message: {}", e.getMessage());
+                break;
+            } catch (Throwable t) {
+                return new TaskExecutionException(task, t);
+            } finally {
+                task.getStandardOutputCapture().stop();
+            }
+        }
+        return null;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskInputs.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskInputs.java
new file mode 100644
index 0000000..110bfce
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskInputs.java
@@ -0,0 +1,96 @@
+/*
+ * 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;
+
+import groovy.lang.Closure;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.PathResolvingFileCollection;
+import org.gradle.api.tasks.TaskInputs;
+import org.gradle.util.UncheckedException;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+public class DefaultTaskInputs implements TaskInputs {
+    private final PathResolvingFileCollection inputFiles;
+    private final FileResolver resolver;
+    private final Map<String, Object> properties = new HashMap<String, Object>();
+
+    public DefaultTaskInputs(FileResolver resolver) {
+        this.resolver = resolver;
+        inputFiles = new PathResolvingFileCollection("task input files", resolver, null);
+    }
+
+    public boolean getHasInputs() {
+        return !inputFiles.getSources().isEmpty() || !properties.isEmpty();
+    }
+
+    public FileCollection getFiles() {
+        return inputFiles;
+    }
+
+    public TaskInputs files(Object... paths) {
+        inputFiles.from(paths);
+        return this;
+    }
+
+    public TaskInputs dir(Object dirPath) {
+        inputFiles.from(resolver.resolveFilesAsTree(dirPath));
+        return this;
+    }
+
+    public Map<String, Object> getProperties() {
+        Map<String, Object> actualProperties = new HashMap<String, Object>();
+        for (Map.Entry<String, Object> entry : properties.entrySet()) {
+            Object value = unwrap(entry.getValue());
+            actualProperties.put(entry.getKey(), value);
+        }
+        return actualProperties;
+    }
+
+    private Object unwrap(Object value) {
+        while (true) {
+            if (value instanceof Callable) {
+                Callable callable = (Callable) value;
+                try {
+                    value = callable.call();
+                } catch (Exception e) {
+                    throw UncheckedException.asUncheckedException(e);
+                }
+            } else if (value instanceof Closure) {
+                Closure closure = (Closure) value;
+                value = closure.call();
+            } else if (value instanceof FileCollection) {
+                FileCollection fileCollection = (FileCollection) value;
+                return fileCollection.getFiles();
+            } else {
+                return value;
+            }
+        }
+    }
+
+    public TaskInputs property(String name, Object value) {
+        properties.put(name, value);
+        return this;
+    }
+
+    public TaskInputs properties(Map<String, ?> properties) {
+        this.properties.putAll(properties);
+        return this;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskOutputs.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskOutputs.java
new file mode 100644
index 0000000..6f7b4db
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/DefaultTaskOutputs.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.api.internal.tasks;
+
+import groovy.lang.Closure;
+import org.gradle.api.Task;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.TaskExecutionHistory;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.TaskOutputsInternal;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.PathResolvingFileCollection;
+import org.gradle.api.specs.AndSpec;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.TaskOutputs;
+
+public class DefaultTaskOutputs implements TaskOutputsInternal {
+    private final PathResolvingFileCollection outputFiles;
+    private AndSpec<TaskInternal> upToDateSpec = new AndSpec<TaskInternal>();
+    private TaskExecutionHistory history;
+
+    public DefaultTaskOutputs(FileResolver resolver, TaskInternal task) {
+        outputFiles = new PathResolvingFileCollection("task output files", resolver, null);
+        outputFiles.builtBy(task);
+    }
+
+    public Spec<? super TaskInternal> getUpToDateSpec() {
+        return upToDateSpec;
+    }
+
+    public void upToDateWhen(Closure upToDateClosure) {
+        upToDateSpec = upToDateSpec.and(upToDateClosure);
+    }
+
+    public void upToDateWhen(Spec<? super Task> upToDateSpec) {
+        this.upToDateSpec = this.upToDateSpec.and(upToDateSpec);
+    }
+
+    public boolean getHasOutput() {
+        return !outputFiles.getSources().isEmpty() || !upToDateSpec.getSpecs().isEmpty();
+    }
+
+    public FileCollection getFiles() {
+        return outputFiles;
+    }
+
+    public TaskOutputs files(Object... paths) {
+        outputFiles.from(paths);
+        return this;
+    }
+
+    public TaskOutputs dir(Object path) {
+        files(path);
+        return this;
+    }
+
+    public FileCollection getPreviousFiles() {
+        if (history == null) {
+            throw new IllegalStateException("Task history is currently not available for this task.");
+        }
+        return history.getOutputFiles();
+    }
+
+    public void setHistory(TaskExecutionHistory history) {
+        this.history = history;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/ExecuteAtMostOnceTaskExecuter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/ExecuteAtMostOnceTaskExecuter.java
new file mode 100644
index 0000000..512b8ff
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/ExecuteAtMostOnceTaskExecuter.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+import org.gradle.api.internal.TaskInternal;
+
+public class ExecuteAtMostOnceTaskExecuter implements TaskExecuter {
+    private final TaskExecuter executer;
+
+    public ExecuteAtMostOnceTaskExecuter(TaskExecuter executer) {
+        this.executer = executer;
+    }
+
+    public void execute(TaskInternal task, TaskStateInternal state) {
+        if (state.getExecuted()) {
+            return;
+        }
+        executer.execute(task, state);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/SkipTaskExecuter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/SkipTaskExecuter.java
new file mode 100644
index 0000000..9b7a512
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/SkipTaskExecuter.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+public class SkipTaskExecuter implements TaskExecuter {
+    private static Logger logger = Logging.getLogger(SkipTaskExecuter.class);
+    private final TaskExecuter executer;
+
+    public SkipTaskExecuter(TaskExecuter executer) {
+        this.executer = executer;
+    }
+
+    public void execute(TaskInternal task, TaskStateInternal state) {
+        logger.debug("Starting to execute {}", task);
+        try {
+            doExecute(task, state);
+        } finally {
+            state.executed();
+            logger.debug("Finished executing {}", task);
+        }
+    }
+
+    private void doExecute(TaskInternal task, TaskStateInternal state) {
+        boolean skip;
+        try {
+            skip = !task.getOnlyIf().isSatisfiedBy(task);
+        } catch (Throwable t) {
+            state.executed(new GradleException(String.format("Could not evaluate onlyIf predicate for %s.", task), t));
+            return;
+        }
+
+        if (skip) {
+            logger.info("Skipping execution as task onlyIf is false.");
+            state.skipped("SKIPPED");
+            return;
+        }
+
+        executer.execute(task, state);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/TaskContainerInternal.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/TaskContainerInternal.java
new file mode 100644
index 0000000..db445f0
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/TaskContainerInternal.java
@@ -0,0 +1,23 @@
+/*
+ * 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.tasks;
+
+import org.gradle.api.tasks.TaskContainer;
+import org.gradle.api.internal.DynamicObject;
+
+public interface TaskContainerInternal extends TaskContainer, TaskResolver {
+    DynamicObject getAsDynamicObject();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/TaskDependencyInternal.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/TaskDependencyInternal.java
new file mode 100644
index 0000000..cea0d8e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/TaskDependencyInternal.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+import org.gradle.api.tasks.TaskDependency;
+
+public interface TaskDependencyInternal extends TaskDependency {
+    void resolve(TaskDependencyResolveContext context);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/TaskDependencyResolveContext.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/TaskDependencyResolveContext.java
new file mode 100644
index 0000000..d0aa231
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/TaskDependencyResolveContext.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+import org.gradle.api.Task;
+
+public interface TaskDependencyResolveContext {
+    /**
+     * Adds a dependency to the result.
+     */
+    void add(Object dependency);
+
+    /**
+     * Returns the task whose dependencies are being resolved.
+     */
+    Task getTask();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/TaskExecuter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/TaskExecuter.java
new file mode 100644
index 0000000..08aa32a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/TaskExecuter.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+import org.gradle.api.internal.TaskInternal;
+
+public interface TaskExecuter {
+    /**
+     * Executes the given task. If the task fails with an exception, the exception is packaged in the provided task
+     * state.
+     */
+    void execute(TaskInternal task, TaskStateInternal state);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/TaskResolver.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/TaskResolver.java
new file mode 100644
index 0000000..45fe037
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/TaskResolver.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.api.internal.tasks;
+
+import org.gradle.api.Task;
+
+public interface TaskResolver {
+    Task resolveTask(Object path);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/TaskStateInternal.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/TaskStateInternal.java
new file mode 100644
index 0000000..01b7470
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/internal/tasks/TaskStateInternal.java
@@ -0,0 +1,112 @@
+/*
+ * 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;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.GradleException;
+import org.gradle.api.tasks.TaskState;
+
+public class TaskStateInternal implements TaskState {
+    private boolean executing;
+    private boolean executed;
+    private boolean didWork;
+    private Throwable failure;
+    private String description;
+    private String skippedMessage;
+    private boolean skipped;
+
+    public TaskStateInternal(String description) {
+        this.description = description;
+    }
+
+    public boolean getDidWork() {
+        return didWork;
+    }
+
+    public void setDidWork(boolean didWork) {
+        this.didWork = didWork;
+    }
+
+    public boolean getExecuted() {
+        return executed;
+    }
+
+    /**
+     * Marks this task as executed.
+     */
+    public void executed() {
+        this.executed = true;
+    }
+
+    /**
+     * Marks this task as executed with the given failure.
+     */
+    public void executed(Throwable failure) {
+        assert this.failure == null;
+        this.executed = true;
+        this.failure = failure;
+    }
+
+    /**
+     * Marks this task as skipped.
+     */
+    public void skipped(String skipMessage) {
+        this.executed = true;
+        skipped = true;
+        this.skippedMessage = skipMessage;
+    }
+
+    /**
+     * Marks this task as up-to-date.
+     */
+    public void upToDate() {
+        skipped("UP-TO-DATE");
+    }
+    
+    public boolean getExecuting() {
+        return executing;
+    }
+
+    public void setExecuting(boolean executing) {
+        this.executing = executing;
+    }
+
+    public Throwable getFailure() {
+        return failure;
+    }
+
+    public void rethrowFailure() {
+        if (failure == null) {
+            return;
+        }
+        if (failure instanceof RuntimeException) {
+            throw (RuntimeException) failure;
+        }
+        if (failure instanceof Error) {
+            throw (Error) failure;
+        }
+        throw new GradleException(String.format("%s failed with an exception.", StringUtils.capitalize(description)), failure);
+    }
+
+    public boolean getSkipped() {
+        return skipped;
+    }
+
+    public String getSkipMessage() {
+        return skippedMessage;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/invocation/Gradle.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/invocation/Gradle.java
new file mode 100644
index 0000000..e3991d3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/invocation/Gradle.java
@@ -0,0 +1,178 @@
+/*
+ * 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.invocation;
+
+import groovy.lang.Closure;
+import org.gradle.BuildListener;
+import org.gradle.StartParameter;
+import org.gradle.api.Project;
+import org.gradle.api.ProjectEvaluationListener;
+import org.gradle.api.execution.TaskExecutionGraph;
+
+import java.io.File;
+
+/**
+ * <p>A {@code Gradle} represents an invocation of Gradle.</p>
+ *
+ * <p>You can obtain a {@code Gradle} instance by calling {@link Project#getGradle()}. In your build file you can use
+ * {@code gradle} to access it.</p>
+ */
+public interface Gradle {
+    /**
+     * <p>Returns the current Gradle version.</p>
+     *
+     * @return The Gradle version. Never returns null.
+     */
+    String getGradleVersion();
+
+    /**
+     * <p>Returns the Gradle user home directory. This directory is used to cache downloaded resources.</p>
+     *
+     * @return The user home directory. Never returns null.
+     */
+    File getGradleUserHomeDir();
+
+    /**
+     * <p>Returns the Gradle home directory, if any. This directory is the directory containing the Gradle distribution
+     * executing this build.</p>
+     *
+     * @return The home directory. May return null.
+     * @deprecated No replacement
+     */
+    @Deprecated
+    File getGradleHomeDir();
+
+    /**
+     * <p>Returns the parent build of this build, if any.</p>
+     *
+     * @return The parent build. May return null.
+     */
+    Gradle getParent();
+
+    /**
+     * <p>Returns the root project of this build.</p>
+     *
+     * @return The root project. Never returns null.
+     */
+    Project getRootProject();
+
+    /**
+     * <p>Returns the {@link TaskExecutionGraph} for this build.</p>
+     *
+     * @return The task graph. Never returns null.
+     */
+    TaskExecutionGraph getTaskGraph();
+
+    /**
+     * Returns the {@link StartParameter} used to start this build.
+     *
+     * @return The start parameter. Never returns null.
+     */
+    StartParameter getStartParameter();
+
+    /**
+     * Adds a listener to this build, to receive notifications as projects are evaluated.
+     *
+     * @param listener The listener to add. Does nothing if this listener has already been added.
+     * @return The added listener.
+     */
+    ProjectEvaluationListener addProjectEvaluationListener(ProjectEvaluationListener listener);
+
+    /**
+     * Removes the given listener from this build.
+     *
+     * @param listener The listener to remove. Does nothing if this listener has not been added.
+     */
+    void removeProjectEvaluationListener(ProjectEvaluationListener listener);
+
+    /**
+     * Adds a closure to be called immediately before a project is evaluated. The project is passed to the closure as a
+     * parameter.
+     *
+     * @param closure The closure to execute.
+     */
+    void beforeProject(Closure closure);
+
+    /**
+     * Adds a closure to be called immediately after a project is evaluated. The project is passed to the closure as the
+     * first parameter. The project evaluation failure, if any, is passed as the second parameter. Both parameters are
+     * options.
+     *
+     * @param closure The closure to execute.
+     */
+    void afterProject(Closure closure);
+
+    /**
+     * <p>Adds a {@link BuildListener} to this Build instance. The listener is notified of events which occur during the
+     * execution of the build.</p>
+     *
+     * @param buildListener The listener to add.
+     */
+    void addBuildListener(BuildListener buildListener);
+
+    /**
+     * Adds the given listener to this build. The listener may implement any of the given listener interfaces:
+     *
+     * <ul>
+     *
+     * <li>{@link org.gradle.BuildListener}
+     *
+     * <li>{@link org.gradle.api.execution.TaskExecutionGraphListener}
+     *
+     * <li>{@link org.gradle.api.ProjectEvaluationListener}
+     *
+     * <li>{@link org.gradle.api.execution.TaskExecutionListener}
+     *
+     * <li>{@link org.gradle.api.execution.TaskActionListener}
+     *
+     * <li>{@link org.gradle.api.logging.StandardOutputListener}
+     *
+     * <li>org.gradle.api.tasks.testing.TestListener
+     *
+     * </ul>
+     *
+     * @param listener The listener to add. Does nothing if this listener has already been added.
+     */
+    public void addListener(Object listener);
+
+    /**
+     * Removes the given listener from this build.
+     *
+     * @param listener The listener to remove. Does nothing if this listener has not been added.
+     */
+    public void removeListener(Object listener);
+
+    /**
+     * Uses the given object as a logger. The logger object may implement any of the listener interfaces supported by
+     * {@link #addListener(Object)}. Each listener interface has exactly one associated logger. When you call this
+     * method with a logger of a given listener type, the new logger will replace whichever logger is currently
+     * associated with the listener type. This allows you to selectively replace the standard logging which Gradle
+     * provides with your own implementation, for certain types of events.
+     *
+     * @param logger The logger to use.
+     */
+    public void useLogger(Object logger);
+
+    /**
+     * Returns this {@code Gradle} instance. This method is useful in init scripts to explicitly access Gradle
+     * properties and methods. For example, using <code>gradle.parent</code> can express your intent better than using
+     * <code>parent</code>. This property also allows you to access Gradle properties from a scope where the property
+     * may be hidden, such as, for example, from a method or closure.
+     *
+     * @return this. Never returns null.
+     */
+    Gradle getGradle();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/invocation/package-info.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/invocation/package-info.java
new file mode 100644
index 0000000..4b4e5f8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/invocation/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes for invoking and monitoring gradle builds.
+ */
+package org.gradle.api.invocation;
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/LogLevel.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/LogLevel.java
new file mode 100644
index 0000000..b0a1b6e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/LogLevel.java
@@ -0,0 +1,108 @@
+/*
+ * 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.logging;
+
+/**
+ * @author Hans Dockter
+ */
+public enum LogLevel {
+    DEBUG {
+        boolean isEnabled(Logger logger) {
+            return logger.isDebugEnabled();
+        }
+        void log(Logger logger, String message) {
+            logger.debug(message);
+        }
+        void log(Logger logger, String message, Object... objects) {
+            logger.debug(message, objects);
+        }
+        void log(Logger logger, String message, Throwable throwable) {
+            logger.debug(message, throwable);
+        }},
+    INFO {
+        boolean isEnabled(Logger logger) {
+            return logger.isInfoEnabled();
+        }
+        void log(Logger logger, String message) {
+            logger.info(message);
+        }
+        void log(Logger logger, String message, Object... objects) {
+            logger.info(message, objects);
+        }
+        void log(Logger logger, String message, Throwable throwable) {
+            logger.info(message, throwable);
+        }},
+    LIFECYCLE {
+        boolean isEnabled(Logger logger) {
+            return logger.isLifecycleEnabled();
+        }
+        void log(Logger logger, String message) {
+            logger.lifecycle(message);
+        }
+        void log(Logger logger, String message, Object... objects) {
+            logger.lifecycle(message, objects);
+        }
+        void log(Logger logger, String message, Throwable throwable) {
+            logger.lifecycle(message, throwable);
+        }},
+    WARN {
+        boolean isEnabled(Logger logger) {
+            return logger.isWarnEnabled();
+        }
+        void log(Logger logger, String message) {
+            logger.warn(message);
+        }
+        void log(Logger logger, String message, Object... objects) {
+            logger.warn(message, objects);
+        }
+        void log(Logger logger, String message, Throwable throwable) {
+            logger.warn(message, throwable);
+        }},
+    QUIET {
+        boolean isEnabled(Logger logger) {
+            return logger.isQuietEnabled();
+        }
+        void log(Logger logger, String message) {
+            logger.quiet(message);
+        }
+        void log(Logger logger, String message, Object... objects) {
+            logger.quiet(message, objects);
+        }
+        void log(Logger logger, String message, Throwable throwable) {
+            logger.quiet(message, throwable);
+        }},
+    ERROR {
+        boolean isEnabled(Logger logger) {
+            return logger.isErrorEnabled();
+        }
+        void log(Logger logger, String message) {
+            logger.error(message);
+        }
+        void log(Logger logger, String message, Object... objects) {
+            logger.error(message, objects);
+        }
+        void log(Logger logger, String message, Throwable throwable) {
+            logger.error(message, throwable);
+        }};
+
+    abstract boolean isEnabled(Logger logger);
+
+    abstract void log(Logger logger, String message);
+
+    abstract void log(Logger logger, String message, Object... objects);
+
+    abstract void log(Logger logger, String message, Throwable throwable);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/Logger.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/Logger.java
new file mode 100644
index 0000000..2a2d70d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/Logger.java
@@ -0,0 +1,115 @@
+/*
+ * 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.logging;
+
+/**
+ * <p>An extension to the SLF4J {@code Logger} interface, which adds the {@code quiet} and {@code lifecycle} log
+ * levels.</p>
+ *
+ * <p>You can obtain a {@code Logger} instance using {@link Logging#getLogger(Class)} or {@link
+ * Logging#getLogger(String)}. A {@code Logger} instance is also available through {@link
+ * org.gradle.api.Project#getLogger()}, {@link org.gradle.api.Task#getLogger()} and {@link
+ * org.gradle.api.Script#getLogger()}.</p>
+ */
+public interface Logger extends org.slf4j.Logger {
+    /**
+     * Returns true if lifecycle log level is enabled for this logger.
+     */
+    boolean isLifecycleEnabled();
+
+    /**
+     * Logs the given message at lifecycle log level.
+     *
+     * @param message the log message.
+     */
+    void lifecycle(String message);
+
+    /**
+     * Logs the given message at lifecycle log level.
+     *
+     * @param message the log message.
+     * @param objects the log message parameters.
+     */
+    void lifecycle(String message, Object... objects);
+
+    /**
+     * Logs the given message at lifecycle log level.
+     *
+     * @param message the log message.
+     * @param throwable the exception to log.
+     */
+    void lifecycle(String message, Throwable throwable);
+
+    /**
+     * Returns true if quiet log level is enabled for this logger.
+     */
+    boolean isQuietEnabled();
+
+    /**
+     * Logs the given message at quiet log level.
+     *
+     * @param message the log message.
+     */
+    void quiet(String message);
+
+    /**
+     * Logs the given message at quiet log level.
+     *
+     * @param message the log message.
+     * @param objects the log message parameters.
+     */
+    void quiet(String message, Object... objects);
+
+    /**
+     * Logs the given message at quiet log level.
+     *
+     * @param message the log message.
+     * @param throwable the exception to log.
+     */
+    void quiet(String message, Throwable throwable);
+
+    /**
+     * Returns true if the given log level is enabled for this logger.
+     */
+    boolean isEnabled(LogLevel level);
+
+    /**
+     * Logs the given message at the given log level.
+     *
+     * @param level the log level.
+     * @param message the log message.
+     */
+    void log(LogLevel level, String message);
+
+    /**
+     * Logs the given message at the given log level.
+     *
+     * @param level the log level.
+     * @param message the log message.
+     * @param objects the log message parameters.
+     */
+    void log(LogLevel level, String message, Object... objects);
+
+    /**
+     * Logs the given message at the given log level.
+     *
+     * @param level the log level.
+     * @param message the log message.
+     * @param throwable the exception to log.
+     */
+    void log(LogLevel level, String message, Throwable throwable);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/Logging.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/Logging.java
new file mode 100644
index 0000000..9e551f8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/Logging.java
@@ -0,0 +1,373 @@
+/*
+ * 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.logging;
+
+import org.apache.ivy.util.Message;
+import org.slf4j.MarkerFactory;
+import org.slf4j.Marker;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * <p>The main entry point for Gradle's logging system. Gradle routes all logging via SLF4J. You can use either an SLF4J
+ * {@link org.slf4j.Logger} or a Gradle {@link Logger} to perform logging.</p>
+ *
+ * @author Hans Dockter
+ */
+public class Logging {
+    public static final Marker LIFECYCLE = MarkerFactory.getDetachedMarker("LIFECYCLE");
+    public static final Marker PROGRESS = MarkerFactory.getDetachedMarker("PROGRESS");
+    public static final Marker PROGRESS_STARTED = MarkerFactory.getDetachedMarker("PROGRESS_START");
+    public static final Marker PROGRESS_COMPLETE = MarkerFactory.getDetachedMarker("PROGRESS_COMPLETE");
+    public static final Marker QUIET = MarkerFactory.getDetachedMarker("QUIET");
+
+    static {
+        PROGRESS_STARTED.add(PROGRESS);
+        PROGRESS_COMPLETE.add(PROGRESS);
+    }
+    
+    /**
+     * Returns the logger for the given class.
+     *
+     * @param c the class.
+     * @return the logger. Never returns null.
+     */
+    public static Logger getLogger(Class c) {
+        return new LoggerImpl(LoggerFactory.getLogger(c));
+    }
+
+    /**
+     * Returns the logger with the given name.
+     *
+     * @param name the logger name.
+     * @return the logger. Never returns null.
+     */
+    public static Logger getLogger(String name) {
+        return new LoggerImpl(LoggerFactory.getLogger(name));
+    }
+
+    public static final Map<Integer, LogLevel> ANT_IVY_2_SLF4J_LEVEL_MAPPER = new HashMap<Integer, LogLevel>() {
+        {
+            put(Message.MSG_ERR, LogLevel.ERROR);
+            put(Message.MSG_WARN, LogLevel.WARN);
+            put(Message.MSG_INFO, LogLevel.INFO);
+            put(Message.MSG_DEBUG, LogLevel.DEBUG);
+            put(Message.MSG_VERBOSE, LogLevel.DEBUG);
+        }};
+
+    private static class LoggerImpl implements Logger {
+        private final org.slf4j.Logger logger;
+
+        public LoggerImpl(org.slf4j.Logger logger) {
+            this.logger = logger;
+        }
+
+        public boolean isEnabled(LogLevel level) {
+            return level.isEnabled(this);
+        }
+
+        public void log(LogLevel level, String message) {
+            level.log(this, message);
+        }
+
+        public void log(LogLevel level, String message, Object... objects) {
+            level.log(this, message, objects);
+        }
+
+        public void log(LogLevel level, String message, Throwable throwable) {
+            level.log(this, message, throwable);
+        }
+
+        public boolean isLifecycleEnabled() {
+            return logger.isInfoEnabled(LIFECYCLE);
+        }
+
+        public void lifecycle(String message) {
+            logger.info(LIFECYCLE, message);
+        }
+
+        public void lifecycle(String message, Object... objects) {
+            logger.info(LIFECYCLE, message, objects);
+        }
+
+        public void lifecycle(String message, Throwable throwable) {
+            logger.info(LIFECYCLE, message, throwable);
+        }
+
+        public boolean isQuietEnabled() {
+            return logger.isInfoEnabled(QUIET);
+        }
+
+        public void quiet(String message) {
+            logger.info(QUIET, message);
+        }
+
+        public void quiet(String message, Object... objects) {
+            logger.info(QUIET, message, objects);
+        }
+
+        public void quiet(String message, Throwable throwable) {
+            logger.info(QUIET, message, throwable);
+        }
+
+        public void debug(Marker marker, String s) {
+            logger.debug(marker, s);
+        }
+
+        public void debug(Marker marker, String s, Object o) {
+            logger.debug(marker, s, o);
+        }
+
+        public void debug(Marker marker, String s, Object o, Object o1) {
+            logger.debug(marker, s, o, o1);
+        }
+
+        public void debug(Marker marker, String s, Object[] objects) {
+            logger.debug(marker, s, objects);
+        }
+
+        public void debug(Marker marker, String s, Throwable throwable) {
+            logger.debug(marker, s, throwable);
+        }
+
+        public void debug(String s) {
+            logger.debug(s);
+        }
+
+        public void debug(String s, Object o) {
+            logger.debug(s, o);
+        }
+
+        public void debug(String s, Object o, Object o1) {
+            logger.debug(s, o, o1);
+        }
+
+        public void debug(String s, Object[] objects) {
+            logger.debug(s, objects);
+        }
+
+        public void debug(String s, Throwable throwable) {
+            logger.debug(s, throwable);
+        }
+
+        public void error(Marker marker, String s) {
+            logger.error(marker, s);
+        }
+
+        public void error(Marker marker, String s, Object o) {
+            logger.error(marker, s, o);
+        }
+
+        public void error(Marker marker, String s, Object o, Object o1) {
+            logger.error(marker, s, o, o1);
+        }
+
+        public void error(Marker marker, String s, Object[] objects) {
+            logger.error(marker, s, objects);
+        }
+
+        public void error(Marker marker, String s, Throwable throwable) {
+            logger.error(marker, s, throwable);
+        }
+
+        public void error(String s) {
+            logger.error(s);
+        }
+
+        public void error(String s, Object o) {
+            logger.error(s, o);
+        }
+
+        public void error(String s, Object o, Object o1) {
+            logger.error(s, o, o1);
+        }
+
+        public void error(String s, Object[] objects) {
+            logger.error(s, objects);
+        }
+
+        public void error(String s, Throwable throwable) {
+            logger.error(s, throwable);
+        }
+
+        public String getName() {
+            return logger.getName();
+        }
+
+        public void info(Marker marker, String s) {
+            logger.info(marker, s);
+        }
+
+        public void info(Marker marker, String s, Object o) {
+            logger.info(marker, s, o);
+        }
+
+        public void info(Marker marker, String s, Object o, Object o1) {
+            logger.info(marker, s, o, o1);
+        }
+
+        public void info(Marker marker, String s, Object[] objects) {
+            logger.info(marker, s, objects);
+        }
+
+        public void info(Marker marker, String s, Throwable throwable) {
+            logger.info(marker, s, throwable);
+        }
+
+        public void info(String s) {
+            logger.info(s);
+        }
+
+        public void info(String s, Object o) {
+            logger.info(s, o);
+        }
+
+        public void info(String s, Object o, Object o1) {
+            logger.info(s, o, o1);
+        }
+
+        public void info(String s, Object[] objects) {
+            logger.info(s, objects);
+        }
+
+        public void info(String s, Throwable throwable) {
+            logger.info(s, throwable);
+        }
+
+        public boolean isDebugEnabled() {
+            return logger.isDebugEnabled();
+        }
+
+        public boolean isDebugEnabled(Marker marker) {
+            return logger.isDebugEnabled(marker);
+        }
+
+        public boolean isErrorEnabled() {
+            return logger.isErrorEnabled();
+        }
+
+        public boolean isErrorEnabled(Marker marker) {
+            return logger.isErrorEnabled(marker);
+        }
+
+        public boolean isInfoEnabled() {
+            return logger.isInfoEnabled();
+        }
+
+        public boolean isInfoEnabled(Marker marker) {
+            return logger.isInfoEnabled(marker);
+        }
+
+        public boolean isTraceEnabled() {
+            return logger.isTraceEnabled();
+        }
+
+        public boolean isTraceEnabled(Marker marker) {
+            return logger.isTraceEnabled(marker);
+        }
+
+        public boolean isWarnEnabled() {
+            return logger.isWarnEnabled();
+        }
+
+        public boolean isWarnEnabled(Marker marker) {
+            return logger.isWarnEnabled(marker);
+        }
+
+        public void trace(Marker marker, String s) {
+            logger.trace(marker, s);
+        }
+
+        public void trace(Marker marker, String s, Object o) {
+            logger.trace(marker, s, o);
+        }
+
+        public void trace(Marker marker, String s, Object o, Object o1) {
+            logger.trace(marker, s, o, o1);
+        }
+
+        public void trace(Marker marker, String s, Object[] objects) {
+            logger.trace(marker, s, objects);
+        }
+
+        public void trace(Marker marker, String s, Throwable throwable) {
+            logger.trace(marker, s, throwable);
+        }
+
+        public void trace(String s) {
+            logger.trace(s);
+        }
+
+        public void trace(String s, Object o) {
+            logger.trace(s, o);
+        }
+
+        public void trace(String s, Object o, Object o1) {
+            logger.trace(s, o, o1);
+        }
+
+        public void trace(String s, Object[] objects) {
+            logger.trace(s, objects);
+        }
+
+        public void trace(String s, Throwable throwable) {
+            logger.trace(s, throwable);
+        }
+
+        public void warn(Marker marker, String s) {
+            logger.warn(marker, s);
+        }
+
+        public void warn(Marker marker, String s, Object o) {
+            logger.warn(marker, s, o);
+        }
+
+        public void warn(Marker marker, String s, Object o, Object o1) {
+            logger.warn(marker, s, o, o1);
+        }
+
+        public void warn(Marker marker, String s, Object[] objects) {
+            logger.warn(marker, s, objects);
+        }
+
+        public void warn(Marker marker, String s, Throwable throwable) {
+            logger.warn(marker, s, throwable);
+        }
+
+        public void warn(String s) {
+            logger.warn(s);
+        }
+
+        public void warn(String s, Object o) {
+            logger.warn(s, o);
+        }
+
+        public void warn(String s, Object o, Object o1) {
+            logger.warn(s, o, o1);
+        }
+
+        public void warn(String s, Object[] objects) {
+            logger.warn(s, objects);
+        }
+
+        public void warn(String s, Throwable throwable) {
+            logger.warn(s, throwable);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/LoggingManager.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/LoggingManager.java
new file mode 100644
index 0000000..24f6266
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/LoggingManager.java
@@ -0,0 +1,79 @@
+/*
+ * 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.logging;
+
+/**
+ * <p>A {@code LoggingManager} provides access to and control over the Gradle logging system. Using this interface, you
+ * can control the current logging level and standard output and error capture.</p>
+ */
+public interface LoggingManager extends LoggingOutput {
+    /**
+     * Requests that output written to System.out be routed to Gradle's logging system. The default is that System.out
+     * is routed to {@link LogLevel#QUIET}
+     *
+     * @param level The log level to route System.out to.
+     * @return this
+     */
+    LoggingManager captureStandardOutput(LogLevel level);
+
+    /**
+     * Requests that output written to System.err be routed to Gradle's logging system. The default is that System.err
+     * is routed to {@link LogLevel#ERROR}.
+     *
+     * @param level The log level to route System.err to.
+     * @return this
+     */
+    LoggingManager captureStandardError(LogLevel level);
+
+    /**
+     * Disables routing System.out and System.err to Gradle's logging system.
+     *
+     * @return this
+     */
+    @Deprecated
+    LoggingManager disableStandardOutputCapture();
+
+    /**
+     * Returns true when standard output capture is enabled.
+     *
+     * @return true when standard output capture is enabled.
+     */
+    @Deprecated
+    boolean isStandardOutputCaptureEnabled();
+
+    /**
+     * Returns the log level that output written to System.out will be mapped to.
+     *
+     * @return The log level. Returns null when standard output capture is disabled.
+     */
+    LogLevel getStandardOutputCaptureLevel();
+
+    /**
+     * Returns the log level that output written to System.err will be mapped to.
+     *
+     * @return The log level. Returns null when standard error capture is disabled.
+     */
+    LogLevel getStandardErrorCaptureLevel();
+
+    /**
+     * Sets the minimum logging level. All messages at a lower level are discarded.
+     *
+     * @param logLevel The minimum logging level.
+     * @return this
+     */
+    LoggingManager setLevel(LogLevel logLevel);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/LoggingOutput.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/LoggingOutput.java
new file mode 100644
index 0000000..6ea5845
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/LoggingOutput.java
@@ -0,0 +1,50 @@
+/*
+ * 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.logging;
+
+/**
+ * Provides access to the output of the Gradle logging system.
+ */
+public interface LoggingOutput {
+    /**
+     * Adds a listener which receives output written to standard output by the Gradle logging system.
+     *
+     * @param listener The listener to add.
+     */
+    void addStandardOutputListener(StandardOutputListener listener);
+
+    /**
+     * Removes a listener on standard output
+     *
+     * @param listener The listener to remove.
+     */
+    void removeStandardOutputListener(StandardOutputListener listener);
+
+    /**
+     * Adds a listener which receives output written to standard error by the Gradle logging system.
+     *
+     * @param listener The listener to add.
+     */
+    void addStandardErrorListener(StandardOutputListener listener);
+
+    /**
+     * Removes a listener on standard error
+     *
+     * @param listener The listener to remove.
+     */
+    void removeStandardErrorListener(StandardOutputListener listener);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/StandardOutputListener.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/StandardOutputListener.java
new file mode 100644
index 0000000..d9f199a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/StandardOutputListener.java
@@ -0,0 +1,29 @@
+/*
+ * 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.logging;
+
+/**
+ * <p>A {@code StandardOutputListener} receives text written by Gradle's logging system to standard output or
+ * error.</p>
+ */
+public interface StandardOutputListener {
+    /**
+     * Called when some output is written by the logging system.
+     *
+     * @param output The text.
+     */
+    void onOutput(CharSequence output);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/package-info.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/package-info.java
new file mode 100644
index 0000000..801f849
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/logging/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 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.
+ */
+
+/**
+ * Classes for managing logging in Gradle.
+ */
+package org.gradle.api.logging;
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/package-info.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/package-info.java
new file mode 100644
index 0000000..5b78f5e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/package-info.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 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.
+ */
+
+/**
+ * <p><b>Start Here:</b> Gradle's {@link org.gradle.api.Project} API, which is available from your build files. The
+ * API used from your build files is made up of 2 main interfaces:</p>
+ *
+ * <ul>
+ * <li>{@link org.gradle.api.Project}</li>
+ * <li>{@link org.gradle.api.Task}</li>
+ * </ul>
+ */
+package org.gradle.api;
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/Convention.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/Convention.java
new file mode 100644
index 0000000..047df3b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/Convention.java
@@ -0,0 +1,57 @@
+/*
+ * 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.plugins;
+
+import org.gradle.api.internal.DynamicObject;
+
+import java.util.Map;
+
+/**
+ * <p>A {@code Convention} manages a set of <i>convention objects</i>. When you add a convention object to a {@code
+ * Convention}, and the properties and methods of the convention object become available as properties and methods of
+ * the object which the convention is associated to. A convention object is simply a POJO or POGO. Usually, a {@code
+ * Convention} is used by plugins to extend a {@link org.gradle.api.Project} or a {@link org.gradle.api.Task}.</p>
+ *
+ * @author Hans Dockter
+ */
+public interface Convention extends DynamicObject {
+
+    /**
+     * Returns the plugin convention objects contained in this convention.
+     *
+     * @return The plugins. Returns an empty map when this convention does not contain any convention objects.
+     */
+    Map<String, Object> getPlugins();
+
+    /**
+     * Locates the plugin convention object with the given type.
+     *
+     * @param type The convention object type.
+     * @return The object. Never returns null.
+     * @throws IllegalStateException When there is no such object contained in this convention, or when there are
+     * multiple such objects.
+     */
+    <T> T getPlugin(Class<T> type) throws IllegalStateException;
+
+    /**
+     * Locates the plugin convention object with the given type.
+     *
+     * @param type The convention object type.
+     * @return The object. Returns null if there is no such object.
+     * @throws IllegalStateException When there there are multiple matching objects.
+     */
+    <T> T findPlugin(Class<T> type) throws IllegalStateException;
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/ObjectConfigurationAction.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/ObjectConfigurationAction.java
new file mode 100644
index 0000000..f4399c3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/ObjectConfigurationAction.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.api.plugins;
+
+import org.gradle.api.Plugin;
+
+/**
+ * <p>An {@code ObjectConfigurationAction} allows you to apply {@link org.gradle.api.Plugin}s and scripts to an object
+ * or objects.</p>
+ */
+public interface ObjectConfigurationAction {
+    /**
+     * <p>Specifies some target objects to be configured. Any collections or arrays in the given parameters will be
+     * flattened, and the script applied to each object in the result, in the order given. Each call to this method adds
+     * some additional target objects.</p>
+     *
+     * @param targets The target objects.
+     * @return this
+     */
+    ObjectConfigurationAction to(Object... targets);
+
+    /**
+     * Adds a script to use to configure the target objects. You can call this method multiple times, to use multiple
+     * scripts. Scripts and plugins are applied in the order that they are added.
+     *
+     * @param script The script. Evaluated as for {@link org.gradle.api.Project#file(Object)}. However, note that
+     * a URL can also be used, allowing the script to be fetched using HTTP, for example.
+     * @return this
+     */
+    ObjectConfigurationAction from(Object script);
+
+    /**
+     * Adds a {@link org.gradle.api.Plugin} to use to configure the target objects. You can call this method multiple
+     * times, to use multiple plugins. Scripts and plugins are applied in the order that they are added.
+     *
+     * @param pluginClass The plugin to apply.
+     * @return this
+     */
+    ObjectConfigurationAction plugin(Class<? extends Plugin> pluginClass);
+
+    /**
+     * Adds a {@link org.gradle.api.Plugin} to use to configure the target objects. You can call this method multiple
+     * times, to use multiple plugins. Scripts and plugins are applied in the order that they are added.
+     *
+     * @param pluginId The id of the plugin to apply.
+     * @return this
+     */
+    ObjectConfigurationAction plugin(String pluginId);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/PluginCollection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/PluginCollection.java
new file mode 100644
index 0000000..0a1ca3d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/PluginCollection.java
@@ -0,0 +1,77 @@
+/*
+ * 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.DomainObjectCollection;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.Action;
+import org.gradle.api.Plugin;
+
+import groovy.lang.Closure;
+
+/**
+ * <p>A {@code PluginCollection} represents a collection of {@link org.gradle.api.Plugin} instances.</p>
+ * 
+ * @author Hans Dockter
+ */
+public interface PluginCollection<T extends Plugin> extends DomainObjectCollection<T> {
+    /**
+     * {@inheritDoc}
+     */
+    PluginCollection<T> matching(Spec<? super T> spec);
+
+    /**
+     * {@inheritDoc}
+     */
+    PluginCollection<T> matching(Closure closure);
+
+    /**
+     * {@inheritDoc}
+     */
+    <S extends T> PluginCollection<S> withType(Class<S> type);
+
+    /**
+     * Adds an {@code Action} to be executed when a plugin is added to this collection.
+     *
+     * @param action The action to be executed
+     * @return the supplied action
+     */
+    Action<? super T> whenPluginAdded(Action<? super T> action);
+
+    /**
+     * Adds a closure to be called when a plugin is added to this collection. The plugin is passed to the closure as the
+     * parameter.
+     *
+     * @param closure The closure to be called
+     */
+    void whenPluginAdded(Closure closure);
+
+    /**
+     * Executes the given action against all plugins in this collection, and any plugins subsequently added to this
+     * collection.
+     *
+     * @param action The action to be executed
+     */
+    void allPlugins(Action<? super T> action);
+
+    /**
+     * Executes the given closure against all plugins in this collection, and any plugins subsequently added to this
+     * collection.
+     *
+     * @param closure The closure to be called
+     */
+    void allPlugins(Closure closure);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/PluginContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/PluginContainer.java
new file mode 100644
index 0000000..c90d288
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/PluginContainer.java
@@ -0,0 +1,113 @@
+/*
+ * 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.Plugin;
+
+/**
+ * <p>A {@code PluginContainer} is used to manage a set of {@link org.gradle.api.Plugin} instances applied to a
+ * particular project.</p>
+ *
+ * <p>Plugins can be specified using either an id or type. The id of a plugin is specified using a
+ * META-INF/gradle-plugins/${id}.properties classpath resource.</p>
+ *
+ * @author Hans Dockter
+ */
+public interface PluginContainer extends PluginCollection<Plugin> {
+    /**
+     * Has the same behavior as {@link #apply(Class)} except that the the plugin is specified via its id. Not all
+     * plugins have an id.
+     *
+     * @param id The id of the plugin to be applied.
+     * @return The plugin which has been used against the project.
+     */
+    Plugin apply(String id);
+
+    /**
+     * Applies a plugin to the project. This usually means that the plugin uses the project API to add and modify the
+     * state of the project. This method can be called an arbitrary number of times for a particular plugin type. The
+     * plugin will be actually used only the first time this method is called.
+     *
+     * @param type The type of the plugin to be used
+     * @return The plugin which has been used against the project.
+     */
+    <T extends Plugin> T apply(Class<T> type);
+
+    /**
+     * Returns true if the container has a plugin with the given id, false otherwise.
+     *
+     * @param id The id of the plugin
+     */
+    boolean hasPlugin(String id);
+
+    /**
+     * Returns true if the container has a plugin with the given type, false otherwise.
+     *
+     * @param type The type of the plugin
+     */
+    boolean hasPlugin(Class<? extends Plugin> type);
+
+    /**
+     * Returns the plugin for the given id.
+     *
+     * @param id The id of the plugin
+     * @return the plugin or null if no plugin for the given id exists.
+     */
+    Plugin findPlugin(String id);
+
+    /**
+     * Returns the plugin for the given type.
+     *
+     * @param type The type of the plugin
+     * @return the plugin or null if no plugin for the given type exists.
+     */
+    <T extends Plugin> T findPlugin(Class<T> type);
+
+    /**
+     * Returns a plugin with the specified id if this plugin has been used in the project.
+     *
+     * @param id The id of the plugin
+     * @throws UnknownPluginException When there is no plugin with the given id.
+     */
+    Plugin getPlugin(String id) throws UnknownPluginException;
+
+    /**
+     * Returns a plugin with the specified type if this plugin has been used in the project.
+     *
+     * @param type The type of the plugin
+     * @throws UnknownPluginException When there is no plugin with the given type.
+     */
+    <T extends Plugin> T getPlugin(Class<T> type) throws UnknownPluginException;
+
+    /**
+     * Returns a plugin with the specified id if this plugin has been used in the project. You can use the Groovy
+     * {@code []} operator to call this method from a build script.
+     *
+     * @param id The id of the plugin
+     * @throws UnknownPluginException When there is no plugin with the given id.
+     */
+    Plugin getAt(String id) throws UnknownPluginException;
+
+    /**
+     * Returns a plugin with the specified type if this plugin has been used in the project. You can use the Groovy
+     * {@code []} operator to call this method from a build script.
+     *
+     * @param type The type of the plugin
+     * @throws UnknownPluginException When there is no plugin with the given type.
+     */
+    <T extends Plugin> T getAt(Class<T> type) throws UnknownPluginException;
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/PluginInstantiationException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/PluginInstantiationException.java
new file mode 100644
index 0000000..e93ed3e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/PluginInstantiationException.java
@@ -0,0 +1,31 @@
+/*
+ * 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.GradleException;
+
+/**
+ * @author Hans Dockter
+ */
+public class PluginInstantiationException extends GradleException {
+    public PluginInstantiationException(String message) {
+        super(message);
+    }
+
+    public PluginInstantiationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/UnknownPluginException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/UnknownPluginException.java
new file mode 100644
index 0000000..0af007b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/UnknownPluginException.java
@@ -0,0 +1,27 @@
+/*
+ * 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.plugins;
+
+import org.gradle.api.InvalidUserDataException;
+
+/**
+ * @author Hans Dockter
+ */
+public class UnknownPluginException extends InvalidUserDataException {
+    public UnknownPluginException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/package-info.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/package-info.java
new file mode 100644
index 0000000..fc0fc66
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/plugins/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 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.
+ */
+
+/**
+ * The standard {@link org.gradle.api.Plugin} implementations.
+ */
+package org.gradle.api.plugins;
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/AndSpec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/AndSpec.java
new file mode 100644
index 0000000..d21da61
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/AndSpec.java
@@ -0,0 +1,51 @@
+/*
+ * 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.specs;
+
+import groovy.lang.Closure;
+import org.gradle.util.GUtil;
+
+import java.util.Arrays;
+
+/**
+ * @author Hans Dockter
+ */
+public class AndSpec<T> extends CompositeSpec<T> {
+    public AndSpec(Spec<? super T>... specs) {
+        super(specs);
+    }
+
+    public AndSpec(Iterable<? extends Spec<? super T>> specs) {
+        super(specs);
+    }
+
+    public boolean isSatisfiedBy(T object) {
+        for (Spec<? super T> spec : getSpecs()) {
+            if (!spec.isSatisfiedBy(object)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public AndSpec<T> and(Spec<? super T>... specs) {
+        return new AndSpec<T>(GUtil.addLists(getSpecs(), Arrays.asList(specs)));
+    }
+
+    public AndSpec<T> and(Closure spec) {
+        return and(Specs.<T>convertClosureToSpec(spec));
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/CompositeSpec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/CompositeSpec.java
new file mode 100644
index 0000000..ae0b61b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/CompositeSpec.java
@@ -0,0 +1,64 @@
+/*
+ * 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.specs;
+
+import org.gradle.util.GUtil;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+abstract public class CompositeSpec<T> implements Spec<T> {
+    private List<Spec<? super T>> specs;
+
+    protected CompositeSpec(Spec<? super T>... specs) {
+        this.specs = Arrays.asList(specs);
+    }
+
+    protected CompositeSpec(Iterable<? extends Spec<? super T>> specs) {
+        this.specs = GUtil.addLists(specs);
+    }
+
+    public List<Spec<? super T>> getSpecs() {
+        return Collections.unmodifiableList(specs);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof CompositeSpec)) {
+            return false;
+        }
+
+        CompositeSpec that = (CompositeSpec) o;
+
+        if (specs != null ? !specs.equals(that.specs) : that.specs != null) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return specs != null ? specs.hashCode() : 0;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/NotSpec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/NotSpec.java
new file mode 100644
index 0000000..ce41c55
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/NotSpec.java
@@ -0,0 +1,31 @@
+/*
+ * 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.specs;
+
+/**
+ * @author Hans Dockter
+ */
+public class NotSpec<T> implements Spec<T> {
+    private Spec<? super T> sourceSpec;
+
+    public NotSpec(Spec<? super T> sourceSpec) {
+        this.sourceSpec = sourceSpec;
+    }
+
+    public boolean isSatisfiedBy(T element) {
+        return !sourceSpec.isSatisfiedBy(element);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/OrSpec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/OrSpec.java
new file mode 100644
index 0000000..43b4cac
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/OrSpec.java
@@ -0,0 +1,43 @@
+/*
+ * 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.specs;
+
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public class OrSpec<T> extends CompositeSpec<T> {
+    public OrSpec(Spec<? super T>... specs) {
+        super(specs);
+    }
+
+    public boolean isSatisfiedBy(T object) {
+        List<Spec<? super T>> specs = getSpecs();
+        if (specs.isEmpty()) {
+            return true;
+        }
+
+        for (Spec<? super T> spec : specs) {
+            if (spec.isSatisfiedBy(object)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/Spec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/Spec.java
new file mode 100644
index 0000000..e4d5155
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/Spec.java
@@ -0,0 +1,23 @@
+/*
+ * 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.specs;
+
+/**
+ * @author Hans Dockter
+ */
+public interface Spec<T> {
+    boolean isSatisfiedBy(T element);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/Specs.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/Specs.java
new file mode 100644
index 0000000..8cff137
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/Specs.java
@@ -0,0 +1,91 @@
+/*
+ * 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.specs;
+
+import groovy.lang.Closure;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class Specs {
+    public static final Spec SATISFIES_ALL = new Spec() {
+        public boolean isSatisfiedBy(Object dependency) {
+            return true;
+        }
+    };
+
+    public static <T> Spec<T> satisfyAll() {
+        return new Spec<T>() {
+            public boolean isSatisfiedBy(T element) {
+                return true;
+            }
+        };
+    }
+
+    public static <T> Spec<T> satisfyNone() {
+        return new Spec<T>() {
+            public boolean isSatisfiedBy(T element) {
+                return false;
+            }
+        };
+    }
+
+    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> Set<T> filterIterable(Iterable<? extends T> iterable, Spec<? super T> spec) {
+        Set<T> result = new LinkedHashSet<T>();
+        for (T t : iterable) {
+            if (spec.isSatisfiedBy(t)) {
+                result.add(t);
+            }
+        }
+        return result;
+    }
+
+    public static <T> AndSpec<T> and(Spec<? super T>... specs) {
+        return new AndSpec<T>(specs);  
+    }
+
+    public static <T> OrSpec<T> or(Spec<? super T>... specs) {
+        return new OrSpec<T>(specs);
+    }
+
+    public static <T> NotSpec<T> not(Spec<? super T> spec) {
+        return new NotSpec<T>(spec);  
+    }
+
+    public static <T> Spec<T> or(final boolean defaultWhenNoSpecs, List<? extends Spec<? super T>> specs) {
+        if (specs.isEmpty()) {
+            return new Spec<T>() {
+                public boolean isSatisfiedBy(T element) {
+                    return defaultWhenNoSpecs;
+                }
+            };
+        }
+        return new OrSpec<T>(specs.toArray(new Spec[specs.size()]));
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/package-info.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/package-info.java
new file mode 100644
index 0000000..ffe2a9f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/specs/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes for defining general purpose criteria.
+ */
+package org.gradle.api.specs;
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/AbstractCopyTask.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/AbstractCopyTask.java
new file mode 100644
index 0000000..1c61f01
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/AbstractCopyTask.java
@@ -0,0 +1,325 @@
+/*
+ * 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 groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.file.*;
+import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.internal.file.copy.CopyActionImpl;
+import org.gradle.api.internal.file.copy.CopySpecSource;
+import org.gradle.api.internal.file.copy.ReadableCopySpec;
+import org.gradle.api.specs.Spec;
+
+import java.io.FilterReader;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * {@code AbstractCopyTask} is the base class for all copy tasks.
+ */
+public abstract class AbstractCopyTask extends ConventionTask implements CopyAction, CopySpecSource {
+
+    @TaskAction
+    protected void copy() {
+        configureRootSpec();
+        getCopyAction().execute();
+        setDidWork(getCopyAction().getDidWork());
+    }
+
+    protected void configureRootSpec() {
+        if (!getCopyAction().hasSource()) {
+            Object srcDirs = getDefaultSource();
+            if (srcDirs != null) {
+                from(srcDirs);
+            }
+        }
+    }
+    
+    public FileCollection getDefaultSource() {
+        return null;
+    }
+
+    @InputFiles @SkipWhenEmpty @Optional
+    public FileCollection getSource() {
+        return getCopyAction().hasSource() ? getCopyAction().getAllSource() : getDefaultSource();
+    }
+    
+    protected abstract CopyActionImpl getCopyAction();
+
+    public ReadableCopySpec getRootSpec() {
+        return getCopyAction().getRootSpec();
+    }
+
+    // -----------------------------------------------
+    // ---- Delegate CopySpec methods to rootSpec ----
+    // -----------------------------------------------
+
+    protected CopySpec getMainSpec() {
+        return getCopyAction();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isCaseSensitive() {
+        return getMainSpec().isCaseSensitive();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setCaseSensitive(boolean caseSensitive) {
+        getMainSpec().setCaseSensitive(caseSensitive);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask from(Object... sourcePaths) {
+        getMainSpec().from(sourcePaths);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask from(Object sourcePath, Closure c) {
+        getMainSpec().from(sourcePath, c);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public CopySpec with(CopySpec... sourceSpecs) {
+        getMainSpec().with(sourceSpecs);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask into(Object destDir) {
+        getMainSpec().into(destDir);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask into(Object destPath, Closure configureClosure) {
+        getMainSpec().into(destPath, configureClosure);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask include(String... includes) {
+        getMainSpec().include(includes);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask include(Iterable<String> includes) {
+        getMainSpec().include(includes);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask include(Spec<FileTreeElement> includeSpec) {
+        getMainSpec().include(includeSpec);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask include(Closure includeSpec) {
+        getMainSpec().include(includeSpec);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask exclude(String... excludes) {
+        getMainSpec().exclude(excludes);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask exclude(Iterable<String> excludes) {
+        getMainSpec().exclude(excludes);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask exclude(Spec<FileTreeElement> excludeSpec) {
+        getMainSpec().exclude(excludeSpec);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask exclude(Closure excludeSpec) {
+        getMainSpec().exclude(excludeSpec);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask setIncludes(Iterable<String> includes) {
+        getMainSpec().setIncludes(includes);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Set<String> getIncludes() {
+        return getMainSpec().getIncludes();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask setExcludes(Iterable<String> excludes) {
+        getMainSpec().setExcludes(excludes);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Set<String> getExcludes() {
+        return getMainSpec().getExcludes();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask rename(Closure closure) {
+        getMainSpec().rename(closure);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask rename(String sourceRegEx, String replaceWith) {
+        getMainSpec().rename(sourceRegEx, replaceWith);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask rename(Pattern sourceRegEx, String replaceWith) {
+        getMainSpec().rename(sourceRegEx, replaceWith);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask filter(Map<String, ?> properties, Class<? extends FilterReader> filterType) {
+        getMainSpec().filter(properties, filterType);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask filter(Class<? extends FilterReader> filterType) {
+        getMainSpec().filter(filterType);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask filter(Closure closure) {
+        getMainSpec().filter(closure);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask expand(Map<String, ?> properties) {
+        getMainSpec().expand(properties);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getDirMode() {
+        return getMainSpec().getDirMode();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getFileMode() {
+        return getMainSpec().getFileMode();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask setDirMode(int mode) {
+        getMainSpec().setDirMode(mode);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask setFileMode(int mode) {
+        getMainSpec().setFileMode(mode);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask eachFile(Action<? super FileCopyDetails> action) {
+        getMainSpec().eachFile(action);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public AbstractCopyTask eachFile(Closure closure) {
+        getMainSpec().eachFile(closure);
+        return this;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/AntBuilderAware.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/AntBuilderAware.groovy
new file mode 100644
index 0000000..d1a9b5b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/AntBuilderAware.groovy
@@ -0,0 +1,27 @@
+/*
+ * 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.api.tasks
+
+/**
+ * An {@code AntBuilderAware} represents an object which can add itself to Ant tasks, using an
+ * {@link org.gradle.api.AntBuilder}.
+ *
+ * @author Hans Dockter
+ */
+interface AntBuilderAware {
+    def addToAntBuilder(node, String childNodeName)
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/ConventionValue.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/ConventionValue.java
new file mode 100644
index 0000000..c66efba
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/ConventionValue.java
@@ -0,0 +1,35 @@
+/*
+ * 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.tasks;
+
+import org.gradle.api.internal.IConventionAware;
+import org.gradle.api.plugins.Convention;
+
+/**
+ * A ConventionValue can be assigned to a {@link org.gradle.api.internal.IConventionAware} task. If a property
+ * of such an object is not set internally, a ConventionValue is used to calculate the value for the property.
+ *
+ * @author Hans Dockter
+ */
+public interface ConventionValue {
+    /**
+     * Returns some object.
+     * 
+     * @param convention The convention object belonging to the task's project
+     * @param conventionAwareObject The convention aware object  
+     */
+    Object getValue(Convention convention, IConventionAware conventionAwareObject);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Copy.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Copy.java
new file mode 100644
index 0000000..5ae9ad6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Copy.java
@@ -0,0 +1,89 @@
+/*
+ * 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 org.gradle.api.internal.file.*;
+import org.gradle.api.internal.file.copy.FileCopyActionImpl;
+import org.gradle.api.internal.file.copy.FileCopySpecVisitor;
+
+import java.io.File;
+
+/**
+ * Task for copying files.  This task can also rename and filter files as it copies.
+ * The task implements {@link org.gradle.api.file.CopySpec CopySpec} for specifying
+ * what to copy.
+ * <p>
+ * Examples:
+ * <pre>
+ * task(mydoc, type:Copy) {
+ *    from 'src/main/doc'
+ *    into 'build/target/doc'
+ * }
+ *
+ * task(initconfig, type:Copy) {
+ *    from('src/main/config') {
+ *       include '**/*.properties'
+ *       include '**/*.xml'
+ *       filter(ReplaceTokens, tokens:[version:'2.3.1'])
+ *    }
+ *    from('src/main/config') {
+ *       exclude '**/*.properties', '**/*.xml'  
+ *    }
+ *    from('src/main/languages') {
+ *       rename 'EN_US_(*.)', '$1'
+ *    }
+ *    into 'build/target/config'
+ *    exclude '**/*.bak'
+ * }
+ * </pre>
+ * @author Steve Appling
+ */
+public class Copy extends AbstractCopyTask {
+    private FileCopyActionImpl copyAction;
+
+    public Copy() {
+        FileResolver fileResolver = getServices().get(FileResolver.class);
+        copyAction = new FileCopyActionImpl(fileResolver, new FileCopySpecVisitor());
+    }
+
+    protected void configureRootSpec() {
+        super.configureRootSpec();
+        if (getCopyAction().getDestinationDir() == null) {
+            File destDir = getDestinationDir();
+            if (destDir != null) {
+                into(destDir);
+            }
+        }
+    }
+
+    public FileCopyActionImpl getCopyAction() {
+        return copyAction;
+    }
+
+    public void setCopyAction(FileCopyActionImpl copyAction) {
+        this.copyAction = copyAction;
+    }
+
+    @OutputDirectory
+    public File getDestinationDir() {
+        return getCopyAction().getDestinationDir();
+    }
+
+    public void setDestinationDir(File destinationDir) {
+        into(destinationDir);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Delete.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Delete.java
new file mode 100644
index 0000000..44832f8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Delete.java
@@ -0,0 +1,77 @@
+/*
+ * 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 org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.ConventionTask;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * <p>Deletes the specified target files or directories.</p>
+ *
+ * @author Hans Dockter
+ */
+public class Delete extends ConventionTask {
+    private Set<Object> delete = new LinkedHashSet<Object>();
+
+    @TaskAction
+    protected void clean() {
+        setDidWork(getProject().delete(delete));
+    }
+
+    /**
+     * Returns the resolved set of files which will be deleted by this task.
+     *
+     * @return The files. Never returns null.
+     */
+    public FileCollection getTargetFiles() {
+        return getProject().files(delete);
+    }
+
+    /**
+     * Returns the set of files which will be deleted by this task.
+     *
+     * @return The files. Never returns null.
+     */
+    public Set<Object> getDelete() {
+        return delete;
+    }
+
+    /**
+     * Sets the files to be deleted by this task.
+     *
+     * @param target Any type of object accepted by {@link org.gradle.api.Project#files(Object...)}
+     */
+    public void setDelete(Object target) {
+        delete.clear();
+        this.delete.add(target);
+    }
+
+    /**
+     * Adds some files to be deleted by this task.
+     *
+     * @param targets Any type of object accepted by {@link org.gradle.api.Project#files(Object...)}
+     */
+    public Delete delete(Object... targets) {
+        for (Object target : targets) {
+            this.delete.add(target);
+        }
+        return this;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Directory.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Directory.groovy
new file mode 100644
index 0000000..176aa16
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Directory.groovy
@@ -0,0 +1,43 @@
+/*
+ * 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.api.tasks
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.InvalidUserDataException
+
+/**
+ * @author Hans Dockter
+ */
+public class Directory extends DefaultTask {
+    File dir
+    
+    Directory() {
+        if (new File(name).isAbsolute()) { throw new InvalidUserDataException('Path must not be absolute.')}
+        dir = project.file(name)
+    }
+
+    @TaskAction
+    protected void mkdir() {
+        if (dir.exists()) {
+            if (dir.isFile()) {
+                throw new InvalidUserDataException("The directory $name can't be created. There exists a file already with this path.")
+            }
+        } else {
+            dir.mkdirs()
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Exec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Exec.java
new file mode 100644
index 0000000..831b29c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Exec.java
@@ -0,0 +1,242 @@
+/*
+ * 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 org.gradle.api.internal.ConventionTask;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.process.ExecResult;
+import org.gradle.process.ExecSpec;
+import org.gradle.process.ProcessForkOptions;
+import org.gradle.process.internal.DefaultExecAction;
+import org.gradle.process.internal.ExecAction;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A task for executing a command line process.
+ * 
+ * @author Hans Dockter
+ */
+public class Exec extends ConventionTask implements ExecSpec {
+    private ExecAction execAction;
+    private ExecResult execResult;
+
+    public Exec() {
+        execAction = new DefaultExecAction(getServices().get(FileResolver.class));
+    }
+
+    @TaskAction
+    void exec() {
+        execResult = execAction.execute();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Exec commandLine(Object... arguments) {
+        execAction.commandLine(arguments);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ExecSpec commandLine(Iterable<?> args) {
+        execAction.commandLine(args);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Exec args(Object... args) {
+        execAction.args(args);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ExecSpec args(Iterable<?> args) {
+        execAction.args(args);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Exec setArgs(Iterable<?> arguments) {
+        execAction.setArgs(arguments);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List<String> getArgs() {
+        return execAction.getArgs();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List<String> getCommandLine() {
+        return execAction.getCommandLine();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getExecutable() {
+        return execAction.getExecutable();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setExecutable(Object executable) {
+        execAction.setExecutable(executable);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Exec executable(Object executable) {
+        execAction.executable(executable);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public File getWorkingDir() {
+        return execAction.getWorkingDir();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setWorkingDir(Object dir) {
+        execAction.setWorkingDir(dir);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Exec workingDir(Object dir) {
+        execAction.workingDir(dir);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Map<String, Object> getEnvironment() {
+        return execAction.getEnvironment();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setEnvironment(Map<String, ?> environmentVariables) {
+        execAction.setEnvironment(environmentVariables);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Exec environment(String name, Object value) {
+        execAction.environment(name, value);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Exec environment(Map<String, ?> environmentVariables) {
+        execAction.environment(environmentVariables);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Exec copyTo(ProcessForkOptions target) {
+        execAction.copyTo(target);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Exec setStandardInput(InputStream inputStream) {
+        execAction.setStandardInput(inputStream);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public InputStream getStandardInput() {
+        return execAction.getStandardInput();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Exec setStandardOutput(OutputStream outputStream) {
+        execAction.setStandardOutput(outputStream);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Exec setErrorOutput(OutputStream outputStream) {
+        execAction.setErrorOutput(outputStream);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ExecSpec setIgnoreExitValue(boolean ignoreExitValue) {
+        execAction.setIgnoreExitValue(ignoreExitValue);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isIgnoreExitValue() {
+        return execAction.isIgnoreExitValue();
+    }
+
+    void setExecAction(ExecAction execAction) {
+        this.execAction = execAction;
+    }
+
+    /**
+     * Returns the ExecResult object for the command run by this task. Returns null if the task has not been executed yet.
+     */
+    public ExecResult getExecResult() {
+        return execResult;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/GradleBuild.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/GradleBuild.java
new file mode 100644
index 0000000..729f695
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/GradleBuild.java
@@ -0,0 +1,112 @@
+/*
+ * 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.tasks;
+
+import org.gradle.GradleLauncher;
+import org.gradle.StartParameter;
+import org.gradle.api.internal.ConventionTask;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * A {@code GradleBuild} task executes another Gradle build.
+ */
+public class GradleBuild extends ConventionTask {
+    private StartParameter startParameter;
+
+    public GradleBuild() {
+        this.startParameter = getProject().getGradle().getStartParameter().newBuild();
+    }
+
+    /**
+     * Returns the full set of parameters that will be used to execute the build.
+     *
+     * @return the parameters. Never returns null.
+     */
+    public StartParameter getStartParameter() {
+        return startParameter;
+    }
+
+    /**
+     * Sets the full set of parameters that will be used to execute the build.
+     *
+     * @param startParameter the parameters. Should not be null.
+     */
+    public void setStartParameter(StartParameter startParameter) {
+        this.startParameter = startParameter;
+    }
+
+    /**
+     * Returns the project directory for the build.
+     *
+     * @return The project directory. Never returns null.
+     */
+    public File getDir() {
+        return getStartParameter().getCurrentDir();
+    }
+
+    /**
+     * Sets the project directory for the build.
+     *
+     * @param dir The project directory. Should not be null.
+     */
+    public void setDir(Object dir) {
+        getStartParameter().setCurrentDir(getProject().file(dir));
+    }
+
+    /**
+     * Returns the build file that should be used for this build.
+     *
+     * @return The build file. May be null.
+     */
+    public File getBuildFile() {
+        return getStartParameter().getBuildFile();
+    }
+
+    /**
+     * Sets the build file that should be used for this build
+     *
+     * @param file The build file. May be null to use the default build file for the build.
+     */
+    public void setBuildFile(Object file) {
+        getStartParameter().setBuildFile(getProject().file(file));
+    }
+
+    /**
+     * Returns the sequence of tasks that should be executed for this build.
+     *
+     * @return The sequence. May be empty. Never returns null.
+     */
+    public List<String> getTasks() {
+        return getStartParameter().getTaskNames();
+    }
+
+    /**
+     * Sets the tasks that should be executed for this build.
+     *
+     * @param tasks The task names. May be empty or null to use the default tasks for the build.
+     */
+    public void setTasks(Collection<String> tasks) {
+        getStartParameter().setTaskNames(tasks);
+    }
+
+    @TaskAction
+    void build() {
+        GradleLauncher.newInstance(getStartParameter()).run().rethrowFailure();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Input.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Input.java
new file mode 100644
index 0000000..bedccef
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Input.java
@@ -0,0 +1,31 @@
+/*
+ * 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 {
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/InputDirectory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/InputDirectory.java
new file mode 100644
index 0000000..8ba1941
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/InputDirectory.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.api.tasks;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>Marks a property as specifying an input directory for a task.</p> <p>This annotation should be attached to the
+ * getter method or the field for the property.</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 InputDirectory {
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/InputFile.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/InputFile.java
new file mode 100644
index 0000000..9ba2862
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/InputFile.java
@@ -0,0 +1,31 @@
+/*
+ * 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>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 Retention(RetentionPolicy.RUNTIME)
+ at Target({ElementType.METHOD, ElementType.FIELD})
+public @interface InputFile {
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/InputFiles.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/InputFiles.java
new file mode 100644
index 0000000..301cd7a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/InputFiles.java
@@ -0,0 +1,31 @@
+/*
+ * 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>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 Retention(RetentionPolicy.RUNTIME)
+ at Target({ElementType.METHOD, ElementType.FIELD})
+public @interface InputFiles {
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/JavaExec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/JavaExec.java
new file mode 100644
index 0000000..06f3683
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/JavaExec.java
@@ -0,0 +1,401 @@
+/*
+ * 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 org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.process.JavaExecSpec;
+import org.gradle.process.JavaForkOptions;
+import org.gradle.process.ProcessForkOptions;
+import org.gradle.process.internal.DefaultJavaExecAction;
+import org.gradle.process.internal.JavaExecAction;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A task for executing a Java main class.
+ *
+ * @author Hans Dockter
+ */
+public class JavaExec extends ConventionTask implements JavaExecSpec {
+    private JavaExecAction javaExecHandleBuilder;
+
+    public JavaExec() {
+        FileResolver fileResolver = getServices().get(FileResolver.class);
+        javaExecHandleBuilder = new DefaultJavaExecAction(fileResolver);
+    }
+
+    @TaskAction
+    void exec() {
+        javaExecHandleBuilder.execute();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List<String> getAllJvmArgs() {
+        return javaExecHandleBuilder.getAllJvmArgs();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setAllJvmArgs(Iterable<?> arguments) {
+        javaExecHandleBuilder.setAllJvmArgs(arguments);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List<String> getJvmArgs() {
+        return javaExecHandleBuilder.getJvmArgs();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setJvmArgs(Iterable<?> arguments) {
+        javaExecHandleBuilder.setJvmArgs(arguments);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExec jvmArgs(Iterable<?> arguments) {
+        javaExecHandleBuilder.jvmArgs(arguments);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExec jvmArgs(Object... arguments) {
+        javaExecHandleBuilder.jvmArgs(arguments);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Map<String, Object> getSystemProperties() {
+        return javaExecHandleBuilder.getSystemProperties();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setSystemProperties(Map<String, ?> properties) {
+        javaExecHandleBuilder.setSystemProperties(properties);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExec systemProperties(Map<String, ?> properties) {
+        javaExecHandleBuilder.systemProperties(properties);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExec systemProperty(String name, Object value) {
+        javaExecHandleBuilder.systemProperty(name, value);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public FileCollection getBootstrapClasspath() {
+        return javaExecHandleBuilder.getBootstrapClasspath();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setBootstrapClasspath(FileCollection classpath) {
+        javaExecHandleBuilder.setBootstrapClasspath(classpath);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExec bootstrapClasspath(Object... classpath) {
+        javaExecHandleBuilder.bootstrapClasspath(classpath);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getMaxHeapSize() {
+        return javaExecHandleBuilder.getMaxHeapSize();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setMaxHeapSize(String heapSize) {
+        javaExecHandleBuilder.setMaxHeapSize(heapSize);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean getEnableAssertions() {
+        return javaExecHandleBuilder.getEnableAssertions();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setEnableAssertions(boolean enabled) {
+        javaExecHandleBuilder.setEnableAssertions(enabled);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean getDebug() {
+        return javaExecHandleBuilder.getDebug();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setDebug(boolean enabled) {
+        javaExecHandleBuilder.setDebug(enabled);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getMain() {
+        return javaExecHandleBuilder.getMain();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExec setMain(String mainClassName) {
+        javaExecHandleBuilder.setMain(mainClassName);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List<String> getArgs() {
+        return javaExecHandleBuilder.getArgs();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExec setArgs(Iterable<?> applicationArgs) {
+        javaExecHandleBuilder.setArgs(applicationArgs);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExec args(Object... args) {
+        javaExecHandleBuilder.args(args);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExecSpec args(Iterable<?> args) {
+        javaExecHandleBuilder.args(args);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExec setClasspath(FileCollection classpath) {
+        javaExecHandleBuilder.setClasspath(classpath);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExec classpath(Object... paths) {
+        javaExecHandleBuilder.classpath(paths);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public FileCollection getClasspath() {
+        return javaExecHandleBuilder.getClasspath();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExec copyTo(JavaForkOptions options) {
+        javaExecHandleBuilder.copyTo(options);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getExecutable() {
+        return javaExecHandleBuilder.getExecutable();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setExecutable(Object executable) {
+        javaExecHandleBuilder.setExecutable(executable);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExec executable(Object executable) {
+        javaExecHandleBuilder.executable(executable);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public File getWorkingDir() {
+        return javaExecHandleBuilder.getWorkingDir();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setWorkingDir(Object dir) {
+        javaExecHandleBuilder.setWorkingDir(dir);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExec workingDir(Object dir) {
+        javaExecHandleBuilder.workingDir(dir);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Map<String, Object> getEnvironment() {
+        return javaExecHandleBuilder.getEnvironment();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setEnvironment(Map<String, ?> environmentVariables) {
+        javaExecHandleBuilder.setEnvironment(environmentVariables);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExec environment(String name, Object value) {
+        javaExecHandleBuilder.environment(name, value);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExec environment(Map<String, ?> environmentVariables) {
+        javaExecHandleBuilder.environment(environmentVariables);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExec copyTo(ProcessForkOptions target) {
+        javaExecHandleBuilder.copyTo(target);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExec setStandardInput(InputStream inputStream) {
+        javaExecHandleBuilder.setStandardInput(inputStream);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public InputStream getStandardInput() {
+        return javaExecHandleBuilder.getStandardInput();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExec setStandardOutput(OutputStream outputStream) {
+        javaExecHandleBuilder.setStandardOutput(outputStream);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExec setErrorOutput(OutputStream outputStream) {
+        javaExecHandleBuilder.setErrorOutput(outputStream);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public JavaExecSpec setIgnoreExitValue(boolean ignoreExitValue) {
+        javaExecHandleBuilder.setIgnoreExitValue(ignoreExitValue);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isIgnoreExitValue() {
+        return javaExecHandleBuilder.isIgnoreExitValue();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List<String> getCommandLine() {
+        return javaExecHandleBuilder.getCommandLine();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Nested.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Nested.java
new file mode 100644
index 0000000..84ec093
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Nested.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.api.tasks;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <p>Marks a property as specifying a nested bean, whose properties should be checked for annotations.</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 Nested {
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Optional.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Optional.java
new file mode 100644
index 0000000..d2fbe7c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Optional.java
@@ -0,0 +1,44 @@
+/*
+ * 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>Marks a task property as optional. This means that a value does not have to be specified for the property, but any
+ * value specified must meet the validation constraints for the property.</p>
+ *
+ * <p>This annotation can be used with any of the following property annotations:</p>
+ *
+ * <ul> <li>{@link org.gradle.api.tasks.Input}</li>
+ *
+ * <li>{@link org.gradle.api.tasks.InputFile}</li>
+ *
+ * <li>{@link org.gradle.api.tasks.InputDirectory}</li>
+ *
+ * <li>{@link org.gradle.api.tasks.InputFiles}</li>
+ *
+ * <li>{@link org.gradle.api.tasks.OutputFile}</li>
+ *
+ * <li>{@link org.gradle.api.tasks.OutputDirectory}</li> </ul>
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target({ElementType.METHOD, ElementType.FIELD})
+public @interface Optional {
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/OutputDirectory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/OutputDirectory.java
new file mode 100644
index 0000000..102b9cb
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/OutputDirectory.java
@@ -0,0 +1,31 @@
+/*
+ * 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.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+
+/**
+ * <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 Retention(RetentionPolicy.RUNTIME)
+ at Target({ElementType.METHOD, ElementType.FIELD})
+public @interface OutputDirectory {
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/OutputFile.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/OutputFile.java
new file mode 100644
index 0000000..3d4c621
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/OutputFile.java
@@ -0,0 +1,31 @@
+/*
+ * 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.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+
+/**
+ * <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 Retention(RetentionPolicy.RUNTIME)
+ at Target({ElementType.METHOD, ElementType.FIELD})
+public @interface OutputFile {
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/SkipWhenEmpty.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/SkipWhenEmpty.java
new file mode 100644
index 0000000..ebd21fd
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/SkipWhenEmpty.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.api.tasks;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.annotation.ElementType;
+
+/**
+ * <p>Attached to a task property to indicate that the task should be skipped when the value of the property is an empty
+ * {@link org.gradle.api.file.FileCollection} or directory.</p>
+ *
+ * <p>This annotation can be used with the following annotations:</p>
+ *
+ * <ul><li>{@link org.gradle.api.tasks.InputFiles}</li>
+ *
+ * <li>{@link org.gradle.api.tasks.InputDirectory}</li> </ul>
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target({ElementType.METHOD, ElementType.FIELD})
+public @interface SkipWhenEmpty {
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/SourceTask.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/SourceTask.java
new file mode 100644
index 0000000..024aa0f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/SourceTask.java
@@ -0,0 +1,179 @@
+/*
+ * 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 org.gradle.api.file.FileTree;
+import org.gradle.api.file.FileTreeElement;
+import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.tasks.util.PatternFilterable;
+import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.api.specs.Spec;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import groovy.lang.Closure;
+
+/**
+ * A {@code SourceTask} performs some operation on source files.
+ */
+public class SourceTask extends ConventionTask implements PatternFilterable {
+    private final List<Object> source = new ArrayList<Object>();
+    private final PatternFilterable patternSet = new PatternSet();
+
+    /**
+     * Returns the source for this task, after the include and exclude patterns have been applied. Ignores source files
+     * which do not exist.
+     *
+     * @return The source.
+     */
+    @InputFiles
+    @SkipWhenEmpty
+    public FileTree getSource() {
+        FileTree src = this.source.isEmpty() ? getDefaultSource() : getProject().files(new ArrayList<Object>(this.source)).getAsFileTree();
+        return src == null ? getProject().files().getAsFileTree() : src.matching(patternSet);
+    }
+
+    /**
+     * Returns the default source for this task, if any.
+     *
+     * @return The source. May return null.
+     */
+    protected FileTree getDefaultSource() {
+        return null;
+    }
+
+    /**
+     * Sets the source for this task. The given source object is evaluated as for {@link
+     * org.gradle.api.Project#files(Object...)}.
+     *
+     * @param source The source.
+     */
+    public void setSource(Object source) {
+        this.source.clear();
+        this.source.add(source);
+    }
+
+    /**
+     * Adds some source to this task. The given source objects will be evaluated as for {@link
+     * org.gradle.api.Project#files(Object...)}.
+     *
+     * @param sources The source to add
+     * @return this
+     */
+    public SourceTask source(Object... sources) {
+        for (Object source : sources) {
+            this.source.add(source);
+        }
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public SourceTask include(String... includes) {
+        patternSet.include(includes);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public SourceTask include(Iterable<String> includes) {
+        patternSet.include(includes);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public SourceTask include(Spec<FileTreeElement> includeSpec) {
+        patternSet.include(includeSpec);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public SourceTask include(Closure includeSpec) {
+        patternSet.include(includeSpec);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public SourceTask exclude(String... excludes) {
+        patternSet.exclude(excludes);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public SourceTask exclude(Iterable<String> excludes) {
+        patternSet.exclude(excludes);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public SourceTask exclude(Spec<FileTreeElement> excludeSpec) {
+        patternSet.exclude(excludeSpec);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public SourceTask exclude(Closure excludeSpec) {
+        patternSet.exclude(excludeSpec);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Set<String> getIncludes() {
+        return patternSet.getIncludes();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public SourceTask setIncludes(Iterable<String> includes) {
+        patternSet.setIncludes(includes);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Set<String> getExcludes() {
+        return patternSet.getExcludes();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public SourceTask setExcludes(Iterable<String> excludes) {
+        patternSet.setExcludes(excludes);
+        return this;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/StopActionException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/StopActionException.java
new file mode 100644
index 0000000..3def548
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/StopActionException.java
@@ -0,0 +1,46 @@
+/*
+ * 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.tasks;
+
+import org.gradle.api.GradleException;
+
+/**
+ * <p>A <code>StopActionException</code> is be thrown by a task {@link org.gradle.api.Action} or task action closure to
+ * stop its own execution and to start execution of the task's next action. An action can usually be stopped by just
+ * calling return inside the action closure. But if the action works with helper methods that can lead to redundant
+ * code. For example:</p>
+ *
+ * <pre>
+ *     List existentSourceDirs = HelperUtil.getExistentSourceDirs()
+ *     if (!existentSourceDirs) {return}
+ * </pre>
+ * <p>If the <code>getExistentSourceDirs()</code> throws a <code>StopActionException</code> instead, the tasks does not
+ * need the if statement.</p>
+ *
+ * <p>Note that throwing this exception does not fail the execution of the task or the build.</p>
+ *
+ * @author Hans Dockter
+ */
+public class StopActionException extends GradleException {
+    public StopActionException() {
+        super();
+    }
+
+    public StopActionException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/StopExecutionException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/StopExecutionException.java
new file mode 100644
index 0000000..233e3f1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/StopExecutionException.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.api.tasks;
+
+/**
+ * <p>A <code>StopExecutionException</code> is thrown by a {@link org.gradle.api.Action} or task action closure to
+ * stop execution of the current task and start execution of the next task. This allows, for example, precondition
+ * actions to be added to a task which abort execution of the task if the preconditions are not met.</p>
+ *
+ * <p>Note that throwing this exception does not fail the execution of the task or the build.</p>
+ *
+ * @author Hans Dockter
+ */
+public class StopExecutionException extends RuntimeException {
+
+    public StopExecutionException() {
+        super();
+    }
+
+    public StopExecutionException(String message) {
+        super(message);
+    }
+
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Sync.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Sync.java
new file mode 100644
index 0000000..0139249
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Sync.java
@@ -0,0 +1,47 @@
+/*
+ * 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 org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.copy.FileCopyActionImpl;
+import org.gradle.api.internal.file.copy.FileCopySpecVisitor;
+import org.gradle.api.internal.file.copy.SyncCopySpecVisitor;
+
+import java.io.File;
+
+/**
+ * Task for synchronizing the contents of a directory.
+ *
+ */
+public class Sync extends AbstractCopyTask {
+    private FileCopyActionImpl action;
+
+    public Sync() {
+        FileResolver fileResolver = getServices().get(FileResolver.class);
+        action = new FileCopyActionImpl(fileResolver, new SyncCopySpecVisitor(new FileCopySpecVisitor()));
+    }
+
+    @Override
+    protected FileCopyActionImpl getCopyAction() {
+        return action;
+    }
+
+    @OutputDirectory
+    public File getDestinationDir() {
+        return getCopyAction().getDestinationDir();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskAction.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskAction.java
new file mode 100644
index 0000000..566f937
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskAction.java
@@ -0,0 +1,27 @@
+/*
+ * 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.tasks;
+
+import java.lang.annotation.*;
+
+/**
+ * Marks a method as the action to run when the task is executed.
+ */
+ at Retention(RetentionPolicy.RUNTIME)
+ at Target(ElementType.METHOD)
+ at Inherited
+public @interface TaskAction {
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskCollection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskCollection.java
new file mode 100644
index 0000000..5cc0428
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskCollection.java
@@ -0,0 +1,88 @@
+/*
+ * 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.tasks;
+
+import groovy.lang.Closure;
+import org.gradle.api.*;
+import org.gradle.api.specs.Spec;
+
+/**
+ * A {@code TaskCollection} contains a set of {@link Task} instances, and provides a number of query methods.
+ */
+public interface TaskCollection<T extends Task> extends NamedDomainObjectCollection<T> {
+
+    /**
+     * {@inheritDoc}
+     */
+    TaskCollection<T> matching(Spec<? super T> spec);
+
+    /**
+     * {@inheritDoc}
+     */
+    TaskCollection<T> matching(Closure closure);
+
+    /**
+     * {@inheritDoc}
+     */
+    T getByName(String name, Closure configureClosure) throws UnknownTaskException;
+
+    /**
+     * {@inheritDoc}
+     */
+    T getByName(String name) throws UnknownTaskException;
+
+    /**
+     * {@inheritDoc}
+     */
+    <S extends T> TaskCollection<S> withType(Class<S> type);
+
+    /**
+     * Adds an {@code Action} to be executed when a task is added to this collection.
+     *
+     * @param action The action to be executed
+     * @return the supplied action
+     */
+    Action<? super T> whenTaskAdded(Action<? super T> action);
+
+    /**
+     * Adds a closure to be called when a task is added to this collection. The task is passed to the closure as the
+     * parameter.
+     *
+     * @param closure The closure to be called
+     */
+    void whenTaskAdded(Closure closure);
+
+    /**
+     * Executes the given action against all tasks in this collection, and any tasks subsequently added to this
+     * collection.
+     *
+     * @param action The action to be executed
+     */
+    void allTasks(Action<? super T> action);
+
+    /**
+     * Executes the given closure against all tasks in this collection, and any tasks subsequently added to this
+     * collection.
+     *
+     * @param closure The closure to be called
+     */
+    void allTasks(Closure closure);
+
+    /**
+     * {@inheritDoc}
+     */
+    T getAt(String name) throws UnknownTaskException;
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskContainer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskContainer.java
new file mode 100644
index 0000000..e90959e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskContainer.java
@@ -0,0 +1,168 @@
+/*
+ * 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.tasks;
+
+import groovy.lang.Closure;
+import org.gradle.api.*;
+
+import java.util.Map;
+
+/**
+ * <p>A {@code TaskContainer} is responsible for managing a set of {@link Task} instances.</p>
+ *
+ * <p>You can obtain a {@code TaskContainer} instance by calling {@link org.gradle.api.Project#getTasks()}, or using the
+ * {@code tasks} property in your build script.</p>
+ */
+public interface TaskContainer extends TaskCollection<Task>, NamedDomainObjectContainer<Task> {
+    /**
+     * <p>Locates a task by path. You can supply a task name, a relative path, or an absolute path. Relative paths are
+     * interpreted relative to the project for this container. This method returns null if no task with the given path
+     * exists.</p>
+     *
+     * @param path the path of the task to be returned
+     * @return The task. Returns null if so such task exists.
+     */
+    Task findByPath(String path);
+
+    /**
+     * <p>Locates a task by path. You can supply a task name, a relative path, or an absolute path. Relative paths are
+     * interpreted relative to the project for this container. This method throws an exception if no task with the given
+     * path exists.</p>
+     *
+     * @param path the path of the task to be returned
+     * @return The task. Never returns null
+     * @throws UnknownTaskException If no task with the given path exists.
+     */
+    Task getByPath(String path) throws UnknownTaskException;
+
+    /**
+     * <p>Creates a {@link Task} and adds it to this container. A map of creation options can be passed to this method
+     * to control how the task is created. The following options are available:</p>
+     *
+     * <table>
+     *
+     * <tr><th>Option</th><th>Description</th><th>Default Value</th></tr>
+     *
+     * <tr><td><code>{@value org.gradle.api.Task#TASK_NAME}</code></td><td>The name of the task to create.</td><td>None.
+     * Must be specified.</td></tr>
+     *
+     * <tr><td><code>{@value org.gradle.api.Task#TASK_TYPE}</code></td><td>The class of the task to
+     * create.</td><td>{@link org.gradle.api.DefaultTask}</td></tr>
+     *
+     * <tr><td><code>{@value org.gradle.api.Task#TASK_ACTION}</code></td><td>The closure or {@link TaskAction} to
+     * execute when the task executes. See {@link Task#doFirst(Action)}.</td><td><code>null</code></td></tr>
+     *
+     * <tr><td><code>{@value org.gradle.api.Task#TASK_OVERWRITE}</code></td><td>Replace an existing
+     * task?</td><td><code>false</code></td></tr>
+     *
+     * <tr><td><code>{@value org.gradle.api.Task#TASK_DEPENDS_ON}</code></td><td>The dependencies of the task. See <a
+     * href="../Task.html#dependencies">here</a> for more details.</td><td><code>[]</code></td></tr>
+     *
+     * </table>
+     *
+     * <p>After the task is added, it is made available as a property of the project, so that you can reference the task
+     * by name in your build file.  See <a href="../Project.html#properties">here</a> for more details.</p>
+     *
+     * <p>If a task with the given name already exists in this container and the <code>override</code> option is not set
+     * to true, an exception is thrown.</p>
+     *
+     * @param options The task creation options.
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exsists in this project.
+     */
+    Task add(Map<String, ?> options) throws InvalidUserDataException;
+
+    /**
+     * <p>Creates a {@link Task} adds it to this container. A map of creation options can be passed to this method to
+     * control how the task is created. See {@link #add(java.util.Map)} for the list of options available. The given
+     * closure is used to configure the task before it is returned by this method.</p>
+     *
+     * <p>After the task is added, it is made available as a property of the project, so that you can reference the task
+     * by name in your build file. See <a href="../Project.html#properties">here</a> for more details.</p>
+     *
+     * @param options The task creation options.
+     * @param configureClosure The closure to use to configure the task.
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exsists in this project.
+     */
+    Task add(Map<String, ?> options, Closure configureClosure) throws InvalidUserDataException;
+
+    /**
+     * <p>Creates a {@link Task} with the given name adds it to this container. The given closure is used to configure
+     * the task before it is returned by this method.</p>
+     *
+     * <p>After the task is added, it is made available as a property of the project, so that you can reference the task
+     * by name in your build file. See <a href="../Project.html#properties">here</a> for more details.</p>
+     *
+     * @param name The name of the task to be created
+     * @param configureClosure The closure to use to configure the task.
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exsists in this project.
+     */
+    Task add(String name, Closure configureClosure) throws InvalidUserDataException;
+
+    /**
+     * <p>Creates a {@link Task} with the given name and adds it to this container.</p>
+     *
+     * <p>After the task is added, it is made available as a property of the project, so that you can reference the task
+     * by name in your build file. See <a href="../Project.html#properties">here</a> for more details.</p>
+     *
+     * @param name The name of the task to be created
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exsists in this project.
+     */
+    Task add(String name) throws InvalidUserDataException;
+
+    /**
+     * <p>Creates a {@link Task} with the given name and type, and adds it to this container.</p>
+     *
+     * <p>After the task is added, it is made available as a property of the project, so that you can reference the task
+     * by name in your build file. See <a href="../Project.html#properties">here</a> for more details.</p>
+     *
+     * @param name The name of the task to be created.
+     * @param type The type of task to create.
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exsists in this project.
+     */
+    <T extends Task> T add(String name, Class<T> type) throws InvalidUserDataException;
+
+    /**
+     * <p>Creates a {@link Task} with the given name and adds it to this container, replacing any existing task with the
+     * same name.</p>
+     *
+     * <p>After the task is added, it is made available as a property of the project, so that you can reference the task
+     * by name in your build file. See <a href="../Project.html#properties">here</a> for more details.</p>
+     *
+     * @param name The name of the task to be created
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exsists in this project.
+     */
+    Task replace(String name);
+
+    /**
+     * <p>Creates a {@link Task} with the given name and type, and adds it to this container, replacing any existing
+     * task of the same name.</p>
+     *
+     * <p>After the task is added, it is made available as a property of the project, so that you can reference the task
+     * by name in your build file. See <a href="../Project.html#properties">here</a> for more details.</p>
+     *
+     * @param name The name of the task to be created.
+     * @param type The type of task to create.
+     * @return The newly created task object
+     * @throws InvalidUserDataException If a task with the given name already exsists in this project.
+     */
+    <T extends Task> T replace(String name, Class<T> type);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskDependency.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskDependency.java
new file mode 100644
index 0000000..64b9c9b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskDependency.java
@@ -0,0 +1,39 @@
+/*
+ * 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.tasks;
+
+import org.gradle.api.Task;
+
+import java.util.Set;
+
+/**
+ * <p>A <code>TaskDependency</code> represents an <em>unordered</em> set of tasks which a {@link Task} depends on.
+ * Gradle ensures that all the dependencies of a task are executed before the task itself is executed.</p>
+ *
+ * <p>You can add a <code>TaskDependency</code> to a task by calling the task's {@link Task#dependsOn(Object...)}
+ * method.</p>
+ */
+public interface TaskDependency {
+    /**
+     * <p>Determines the dependencies for the given {@link Task}. This method is called when Gradle assembles the task
+     * execution graph for a build. This occurs after all the projects have been evaluated, and before any task
+     * execution begins.</p>
+     *
+     * @param task The task to determine the dependencies for.
+     * @return The tasks which the given task depends on. Returns an empty set if the task has no dependencies.
+     */
+    Set<? extends Task> getDependencies(Task task);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskExecutionException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskExecutionException.java
new file mode 100644
index 0000000..9dfc083
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskExecutionException.java
@@ -0,0 +1,42 @@
+/*
+ * 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.tasks;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.Task;
+import org.gradle.api.internal.Contextual;
+
+/**
+ * <p>A {@code TaskExecutionException} is thrown when a task fails to execute successfully.</p>
+ */
+ at Contextual
+public class TaskExecutionException extends GradleException {
+    private final Task task;
+
+    // Required for @Contextual
+    public TaskExecutionException(TaskExecutionException source) {
+        this(source.task, source.getCause());
+    }
+    
+    public TaskExecutionException(Task task, Throwable cause) {
+        super(String.format("Execution failed for %s.", task), cause);
+        this.task = task;
+    }
+
+    public Task getTask() {
+        return task;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskInputs.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskInputs.java
new file mode 100644
index 0000000..96a4832
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskInputs.java
@@ -0,0 +1,88 @@
+/*
+ * 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 org.gradle.api.file.FileCollection;
+
+import java.util.Map;
+
+/**
+ * <p>A {@code TaskInputs} represents the inputs for a task.</p>
+ *
+ * <p>You can obtain a {@code TaskInputs} instance using {@link org.gradle.api.Task#getInputs()}.</p>
+ */
+public interface TaskInputs {
+    /**
+     * Returns true if this task has declared the inputs that it consumes.
+     *
+     * @return true if this task has declared any inputs.
+     */
+    boolean getHasInputs();
+
+    /**
+     * Returns the input files of this task.
+     *
+     * @return The input files. Returns an empty collection if this task has no input files.
+     */
+    FileCollection getFiles();
+
+    /**
+     * Registers some input files for this task.
+     *
+     * @param paths The input files. The given paths are evaluated as for {@link org.gradle.api.Project#files(Object...)}.
+     * @return this
+     */
+    TaskInputs files(Object... paths);
+
+    /**
+     * Registers an input directory hierarchy. All files found under the given directory are treated as input files for
+     * this task.
+     *
+     * @param dirPath The directory. The path is evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     * @return this
+     */
+    TaskInputs dir(Object dirPath);
+
+    /**
+     * Returns the set of input properties for this task.
+     *
+     * @return The properties.
+     */
+    Map<String, Object> getProperties();
+
+    /**
+     * <p>Registers an input property for this task. This value is persisted when the task executes, and is compared
+     * against the property value for later invocations of the task, to determine if the task is up-to-date.</p>
+     *
+     * <p>The given value for the property must be Serializable, so that it can be persisted. It should also provide a
+     * useful {@code equals()} method.</p>
+     *
+     * <p>You can specify a closure or {@code Callable} as the value of the property. In which case, the closure or
+     * {@code Callable} is executed to determine the actual property value.</p>
+     *
+     * @param name The name of the property. Must not be null.
+     * @param value The value for the property. Can be null.
+     */
+    TaskInputs property(String name, Object value);
+
+    /**
+     * Registers a set of input properties for this task. See {@link #property(String, Object)} for details.
+     *
+     * @param properties The properties.
+     */
+    TaskInputs properties(Map<String, ?> properties);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskInstantiationException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskInstantiationException.java
new file mode 100644
index 0000000..f4a86d2
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskInstantiationException.java
@@ -0,0 +1,27 @@
+/*
+ * 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.tasks;
+
+import org.gradle.api.GradleException;
+
+/**
+ * A {@code TaskInstantiationException} is thrown when a task cannot be instantiated for some reason.
+ */
+public class TaskInstantiationException extends GradleException {
+    public TaskInstantiationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskOutputs.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskOutputs.java
new file mode 100644
index 0000000..9a91f7b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskOutputs.java
@@ -0,0 +1,84 @@
+/*
+ * 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 groovy.lang.Closure;
+import org.gradle.api.Task;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.specs.Spec;
+
+/**
+ * <p>A {@code TaskOutputs} represents the outputs of a task.</p>
+ *
+ * <p>You can obtain a {@code TaskOutputs} instance using {@link org.gradle.api.Task#getOutputs()}.</p>
+ */
+public interface TaskOutputs {
+    /**
+     * <p>Adds a predicate to determine whether the outputs of this task are up-to-date. The given closure is executed
+     * at task execution time. The closure is passed the task as a parameter. If the closure returns false, the task
+     * outputs are considered out-of-date and the task will be executed executed.</p>
+     *
+     * <p>You can add multiple such predicates. The task outputs are considered out-of-date when any predicate returns
+     * false.<p>
+     *
+     * @param upToDateClosure The closure to use to determine whether the task outputs are up-to-date.
+     */
+    void upToDateWhen(Closure upToDateClosure);
+
+    /**
+     * <p>Adds a predicate to determine whether the outputs of this task are up-to-date. The given spec is evaluated at
+     * task execution time. If the spec returns false, the task outputs are considered out-of-date and the task will be
+     * executed.</p>
+     *
+     * <p>You can add multiple such predicates. The task outputs are considered out-of-date when any predicate returns
+     * false.<p>
+     *
+     * @param upToDateSpec The spec to use to determine whether the task outputs are up-to-date.
+     */
+    void upToDateWhen(Spec<? super Task> upToDateSpec);
+
+    /**
+     * Returns true if this task has declared any outputs. Note that a task may be able to produce output files and
+     * still have an empty set of output files.
+     *
+     * @return true if this task has declared any outputs, otherwise false.
+     */
+    boolean getHasOutput();
+
+    /**
+     * Returns the output files of this task.
+     *
+     * @return The output files. Returns an empty collection if this task has no output files.
+     */
+    FileCollection getFiles();
+
+    /**
+     * Registers some output files/directories for this task.
+     *
+     * @param paths The output files. The given paths are evaluated as for {@link org.gradle.api.Project#files(Object...)}.
+     * @return this
+     */
+    TaskOutputs files(Object... paths);
+
+    /**
+     * Registers an output directory for this task.
+     *
+     * @param path The output directory. The given path is evaluated as for {@link org.gradle.api.Project#file(Object)}.
+     * @return this
+     */
+    TaskOutputs dir(Object path);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskState.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskState.java
new file mode 100644
index 0000000..773a775
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/TaskState.java
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+/**
+ * {@code TaskState} provides information about the execution state of a {@link org.gradle.api.Task}. You can obtain a
+ * {@code TaskState} instance by calling {@link org.gradle.api.Task#getState()}.
+ */
+public interface TaskState {
+    /**
+     * <p>Returns true if this task has been executed.</p>
+     *
+     * @return true if this task has been executed.
+     */
+    boolean getExecuted();
+
+    /**
+     * Returns the exception describing the task failure, if any.
+     *
+     * @return The exception, or null if the task did not fail.
+     */
+    Throwable getFailure();
+
+    /**
+     * Throws the task failure, if any. Does nothing if the task did not fail.
+     */
+    void rethrowFailure();
+
+    /**
+     * <p>Checks if the task actually did any work.  Even if a task executes, it may determine that it has nothing to
+     * do.  For example, the Compile task may determine that source files have not changed since the last time a the
+     * task was run.</p>
+     *
+     * @return true if this task has been executed and did any work.
+     */
+    boolean getDidWork();
+
+    /**
+     * Returns true if the execution of this task was skipped for some reason.
+     *
+     * @return true if this task has been executed and skipped.
+     */
+    boolean getSkipped();
+
+    /**
+     * Returns a message describing why the task was skipped.
+     *
+     * @return the message. returns null if the task was not skipped.
+     */
+    String getSkipMessage();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Upload.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Upload.java
new file mode 100644
index 0000000..aee34a8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/Upload.java
@@ -0,0 +1,106 @@
+/*
+ * 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.api.tasks;
+
+import groovy.lang.Closure;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.ConventionTask;
+import org.gradle.util.ConfigureUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+/**
+ * An upload task uploads files to the repositories assigned to it.  The files that get uploaded are the artifacts
+ * of your project, if they belong to the configuration associated with the upload task.
+ * 
+ * @author Hans Dockter
+ */
+public class Upload extends ConventionTask {
+    private static Logger logger = LoggerFactory.getLogger(Upload.class);
+
+    private Configuration configuration;
+
+    private boolean uploadDescriptor;
+
+    private File descriptorDestination;
+
+    /**
+     * The resolvers to delegate the uploads to. Usually a resolver corresponds to a repository.
+     */
+    private RepositoryHandler repositories;
+
+    public Upload() {
+        repositories = getProject().createRepositoryHandler();
+    }
+
+    @TaskAction
+    protected void upload() {
+        logger.info("Publishing configurations: " + configuration);
+        configuration.publish(repositories.getResolvers(), isUploadDescriptor() ? getDescriptorDestination() : null);
+    }
+
+    public boolean isUploadDescriptor() {
+        return uploadDescriptor;
+    }
+
+    public void setUploadDescriptor(boolean uploadDescriptor) {
+        this.uploadDescriptor = uploadDescriptor;
+    }
+
+    public File getDescriptorDestination() {
+        return descriptorDestination;
+    }
+
+    public void setDescriptorDestination(File descriptorDestination) {
+        this.descriptorDestination = descriptorDestination;
+    }
+
+    public RepositoryHandler getRepositories() {
+        return repositories;
+    }
+
+    public void setRepositories(RepositoryHandler repositories) {
+        this.repositories = repositories;
+    }
+
+    public Configuration getConfiguration() {
+        return configuration;
+    }
+
+    public void setConfiguration(Configuration configuration) {
+        this.configuration = configuration;
+    }
+
+    public RepositoryHandler repositories(Closure configureClosure) {
+        return ConfigureUtil.configure(configureClosure, repositories);
+    }
+
+    /**
+     * Returns the artifacts which will be uploaded.
+     * 
+     * @return the artifacts.
+     */
+    @InputFiles
+    public FileCollection getArtifacts() {
+        Configuration configuration = getConfiguration();
+        return configuration == null ? null : configuration.getAllArtifactFiles();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/VerificationTask.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/VerificationTask.java
new file mode 100644
index 0000000..a545f3b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/VerificationTask.java
@@ -0,0 +1,36 @@
+/*
+ * 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.tasks;
+
+/**
+ * A {@code VerificationTask} is a task which performs some verification of the artifacts produced by a build.
+ */
+public interface VerificationTask {
+    /**
+     * Specify whether the build should break when the verifications performed by this task fail.
+     *
+     * @param ignoreFailures false to break the build on failure, true to ignore the failures. The default is false.
+     * @return this
+     */
+    VerificationTask setIgnoreFailures(boolean ignoreFailures);
+
+    /**
+     * Returns whether the build should break when the verifications performed by this task fail.
+     *
+     * @return false, when the build should break on failure, true when the failures should be ignored.
+     */
+    boolean isIgnoreFailures();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/WorkResult.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/WorkResult.java
new file mode 100644
index 0000000..e44eef4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/WorkResult.java
@@ -0,0 +1,20 @@
+/*
+ * 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.tasks;
+
+public interface WorkResult {
+    public boolean getDidWork();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/ant/AntTarget.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/ant/AntTarget.java
new file mode 100644
index 0000000..af4665e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/ant/AntTarget.java
@@ -0,0 +1,110 @@
+/*
+ * 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.tasks.ant;
+
+import org.apache.tools.ant.Target;
+import org.gradle.api.Task;
+import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.TaskDependency;
+
+import java.io.File;
+import java.util.Enumeration;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * A task which executes an Ant target.
+ */
+public class AntTarget extends ConventionTask {
+    private Target target;
+    private File baseDir;
+
+    public AntTarget() {
+        dependsOn(new TaskDependency() {
+            public Set<? extends Task> getDependencies(Task task) {
+                return getAntTargetDependencies();
+            }
+        });
+    }
+
+    private Set<Task> getAntTargetDependencies() {
+        Set<Task> tasks = new LinkedHashSet<Task>();
+        Enumeration dependencies = target.getDependencies();
+        while (dependencies.hasMoreElements()) {
+            String name = (String) dependencies.nextElement();
+            tasks.add(getProject().getTasks().getByName(name));
+        }
+        return tasks;
+    }
+
+    @TaskAction
+    protected void executeAntTarget() {
+        File oldBaseDir = target.getProject().getBaseDir();
+        target.getProject().setBaseDir(baseDir);
+        try {
+            target.performTasks();
+        } finally {
+            target.getProject().setBaseDir(oldBaseDir);
+        }
+    }
+
+    /**
+     * Returns the Ant target to execute.
+     */
+    public Target getTarget() {
+        return target;
+    }
+
+    /**
+     * Sets the Ant target to execute.
+     */
+    public void setTarget(Target target) {
+        this.target = target;
+    }
+
+    /**
+     * Returns the Ant project base directory to use when executing the target.
+     */
+    public File getBaseDir() {
+        return baseDir;
+    }
+
+    /**
+     * Sets the Ant project base directory to use when executing the target.
+     */
+    public void setBaseDir(File baseDir) {
+        this.baseDir = baseDir;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getDescription() {
+        return target == null ? null : target.getDescription();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setDescription(String description) {
+        if (target != null) {
+            target.setDescription(description);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/ant/package-info.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/ant/package-info.java
new file mode 100644
index 0000000..1f5c94d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/ant/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * The Ant integration {@link org.gradle.api.Task} implementations.
+ */
+package org.gradle.api.tasks.ant;
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTask.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTask.java
new file mode 100644
index 0000000..eacf432
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTask.java
@@ -0,0 +1,160 @@
+/*
+ * 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.tasks.bundling;
+
+import org.gradle.api.tasks.AbstractCopyTask;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+
+/**
+ * {@code AbstractArchiveTask} is the base class for all archive tasks.
+ *
+ * @author Hans Dockter
+ */
+public abstract class AbstractArchiveTask extends AbstractCopyTask {
+    private File destinationDir;
+    private String customName;
+    private String baseName;
+    private String appendix;
+    private String version;
+    private String extension;
+    private String classifier = "";
+
+    /**
+     * Returns the archive name. If the name has not been explicitly set, the pattern for the name is:
+     * [baseName]-[appendix]-[version]-[classifier].[extension]
+     *
+     * @return the archive name.
+     */
+    public String getArchiveName() {
+        if (customName != null) {
+            return customName;
+        }
+        String name = GUtil.elvis(getBaseName(), "") + maybe(getBaseName(), getAppendix());
+        name += maybe(name, getVersion());
+        name += maybe(name, getClassifier());
+        name += GUtil.isTrue(getExtension()) ? "." + getExtension() : "";
+        return name;
+    }
+
+    /**
+     * Sets the archive name.
+     *
+     * @param name the archive name.
+     */
+    public void setArchiveName(String name) {
+        customName = name;
+    }
+
+    private String maybe(String prefix, String value) {
+        if (GUtil.isTrue(value)) {
+            if (GUtil.isTrue(prefix)) {
+                return String.format("-%s", value);
+            } else {
+                return value;
+            }
+        }
+        return "";
+    }
+
+    /**
+     * The path where the archive is constructed. The path is simply the destinationDir plus the archiveName.
+     *
+     * @return a File object with the path to the archive
+     */
+    @OutputFile
+    public File getArchivePath() {
+        return new File(getDestinationDir(), getArchiveName());
+    }
+
+    /**
+     * Returns the directory where the archive is generated into.
+     *
+     * @return the directory
+     */
+    public File getDestinationDir() {
+        return destinationDir;
+    }
+
+    public void setDestinationDir(File destinationDir) {
+        this.destinationDir = destinationDir;
+    }
+
+    /**
+     * Returns the base name of the archive.
+     *
+     * @return the base name.
+     */
+    public String getBaseName() {
+        return baseName;
+    }
+
+    public void setBaseName(String baseName) {
+        this.baseName = baseName;
+    }
+
+    /**
+     * Returns the appendix part of the archive name, if any.
+     *
+     * @return the appendix. May be null
+     */
+    public String getAppendix() {
+        return appendix;
+    }
+
+    public void setAppendix(String appendix) {
+        this.appendix = appendix;
+    }
+
+    /**
+     * Returns the version part of the archive name, if any.
+     *
+     * @return the version. May be null.
+     */
+    public String getVersion() {
+        return version;
+    }
+
+    public void setVersion(String version) {
+        this.version = version;
+    }
+
+    /**
+     * Returns the extension part of the archive name
+     */
+    public String getExtension() {
+        return extension;
+    }
+
+    public void setExtension(String extension) {
+        this.extension = extension;
+    }
+
+    /**
+     * Returns the classifier part of the archive name, if any.
+     *
+     * @return The classifier. May be null.
+     */
+    public String getClassifier() {
+        return classifier;
+    }
+
+    public void setClassifier(String classifier) {
+        this.classifier = classifier;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/bundling/Compression.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/bundling/Compression.java
new file mode 100644
index 0000000..b5acb29
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/bundling/Compression.java
@@ -0,0 +1,35 @@
+/*
+ * 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.tasks.bundling;
+
+/**
+ * @author Hans Dockter
+ */
+public enum Compression {
+    NONE("tar"),
+    GZIP("tgz"),
+    BZIP2("tbz2");
+
+    private final String extension;
+
+    private Compression(String extension) {
+        this.extension = extension;
+    }
+
+    public String getExtension() {
+        return extension;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/bundling/LongFile.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/bundling/LongFile.java
new file mode 100644
index 0000000..c3db065
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/bundling/LongFile.java
@@ -0,0 +1,37 @@
+/*
+ * 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.tasks.bundling;
+
+/**
+ * @author Hans Dockter
+ */
+public class LongFile {
+    public static final LongFile TRUNCATE = new LongFile("truncate");
+    public static final LongFile WARN = new LongFile("warn");
+    public static final LongFile GNU = new LongFile("gnu");
+    public static final LongFile OMIT = new LongFile("omit");
+    public static final LongFile FAIL = new LongFile("fail");
+
+    private final String antValue; 
+
+    private LongFile(String name) {
+        antValue = name;
+    }
+
+    public String getAntValue() {
+        return antValue;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/bundling/Tar.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/bundling/Tar.java
new file mode 100644
index 0000000..9778b38
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/bundling/Tar.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.api.tasks.bundling;
+
+import org.gradle.api.internal.file.*;
+import org.gradle.api.internal.file.archive.TarCopyAction;
+import org.gradle.api.internal.file.archive.TarCopySpecVisitor;
+import org.gradle.api.internal.file.copy.CopyActionImpl;
+
+import java.io.File;
+import java.util.concurrent.Callable;
+
+/**
+ * @author Hans Dockter
+ */
+public class Tar extends AbstractArchiveTask {
+    private final CopyActionImpl action;
+
+    private Compression compression;
+
+    private LongFile longFile;
+
+    public Tar() {
+        compression = Compression.NONE;
+        longFile = LongFile.WARN;
+        action = new TarCopyActionImpl(getServices().get(FileResolver.class));
+        getConventionMapping().map("extension", new Callable<Object>(){
+            public Object call() throws Exception {
+                return getCompression().getExtension();
+            }
+        });
+    }
+
+    protected CopyActionImpl getCopyAction() {
+        return action;
+    }
+
+    public Compression getCompression() {
+        return compression;
+    }
+
+    public void setCompression(Compression compression) {
+        this.compression = compression;
+    }
+
+    public LongFile getLongFile() {
+        return longFile;
+    }
+
+    public void setLongFile(LongFile longFile) {
+        this.longFile = longFile;
+    }
+
+    private class TarCopyActionImpl extends CopyActionImpl implements TarCopyAction {
+        public TarCopyActionImpl(FileResolver fileResolver) {
+            super(fileResolver, new TarCopySpecVisitor());
+        }
+
+        public File getArchivePath() {
+            return Tar.this.getArchivePath();
+        }
+
+        public Compression getCompression() {
+            return Tar.this.getCompression();
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/bundling/Zip.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/bundling/Zip.java
new file mode 100644
index 0000000..ad4ed8e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/bundling/Zip.java
@@ -0,0 +1,50 @@
+/*
+ * 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.bundling;
+
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.archive.ZipCopySpecVisitor;
+import org.gradle.api.internal.file.copy.ArchiveCopyAction;
+import org.gradle.api.internal.file.copy.CopyActionImpl;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class Zip extends AbstractArchiveTask {
+    public static final String ZIP_EXTENSION = "zip";
+    private final CopyActionImpl action;
+
+    public Zip() {
+        setExtension(ZIP_EXTENSION);
+        action = new ZipCopyAction(getServices().get(FileResolver.class));
+    }
+
+    protected CopyActionImpl getCopyAction() {
+        return action;
+    }
+
+    private class ZipCopyAction extends CopyActionImpl implements ArchiveCopyAction {
+        public ZipCopyAction(FileResolver fileResolver) {
+            super(fileResolver, new ZipCopySpecVisitor());
+        }
+
+        public File getArchivePath() {
+            return Zip.this.getArchivePath();
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/bundling/package-info.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/bundling/package-info.java
new file mode 100644
index 0000000..b97c8b6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/bundling/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 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.
+ */
+
+/**
+ * The archive bundling {@link org.gradle.api.Task} implementations.
+ */
+package org.gradle.api.tasks.bundling;
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTask.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTask.java
new file mode 100644
index 0000000..54f66c8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTask.java
@@ -0,0 +1,99 @@
+/*
+ * 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.diagnostics;
+
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.Optional;
+import org.gradle.api.tasks.OutputFile;
+import org.gradle.api.tasks.TaskAction;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * The base class for all project report tasks.
+ */
+public abstract class AbstractReportTask extends ConventionTask {
+    private File outputFile;
+
+    // todo annotate as required 
+    private Set<Project> projects;
+
+    protected AbstractReportTask() {
+        getOutputs().upToDateWhen(new Spec<Task>() {
+            public boolean isSatisfiedBy(Task element) {
+                return getOutputFile() != null;
+            }
+        });
+    }
+
+    @TaskAction
+    public void generate() {
+        try {
+            ProjectReportRenderer renderer = getRenderer();
+            File outputFile = getOutputFile();
+            if (outputFile != null) {
+                renderer.setOutputFile(outputFile);
+            }
+            Set<Project> projects = new TreeSet<Project>(getProjects());
+            for (Project project : projects) {
+                renderer.startProject(project);
+                generate(project);
+                renderer.completeProject(project);
+            }
+            renderer.complete();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    protected abstract ProjectReportRenderer getRenderer();
+
+    protected abstract void generate(Project project) throws IOException;
+
+    /**
+     * Returns the file which the report will be written to. When set to null, the report is written to stdout.
+     *
+     * @return The output file. May be null.
+     */
+    @OutputFile @Optional
+    public File getOutputFile() {
+        return outputFile;
+    }
+
+    /**
+     * Sets the file which the report will be written to. Set this to null to write the report to stdout.
+     *
+     * @param outputFile The output file. May be null.
+     */
+    public void setOutputFile(File outputFile) {
+        this.outputFile = outputFile;
+    }
+
+    public Set<Project> getProjects() {
+        return projects;
+    }
+
+    public void setProjects(Set<Project> projects) {
+        this.projects = projects;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/AsciiReportRenderer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/AsciiReportRenderer.java
new file mode 100644
index 0000000..a5070c8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/AsciiReportRenderer.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright 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.tasks.diagnostics;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.artifacts.ResolvedConfiguration;
+import org.gradle.api.artifacts.ResolvedDependency;
+import org.gradle.util.GUtil;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * Simple dependency graph renderer that emits an ASCII tree.
+ *
+ * @author Phil Messenger
+ */
+public class AsciiReportRenderer extends TextProjectReportRenderer implements DependencyReportRenderer {
+    private boolean hasConfigs;
+
+    public AsciiReportRenderer() {
+    }
+
+    public AsciiReportRenderer(Appendable writer) {
+        super(writer);
+    }
+
+    @Override
+    public void startProject(Project project) {
+        super.startProject(project);
+        hasConfigs = false;
+    }
+
+    @Override
+    public void completeProject(Project project) {
+        if (!hasConfigs) {
+            getFormatter().format("No configurations%n");
+        }
+        super.completeProject(project);
+    }
+
+    public void startConfiguration(Configuration configuration) {
+        hasConfigs = true;
+        getFormatter().format("%s%s%n", configuration.getName(), getDescription(configuration));
+    }
+
+    private String getDescription(Configuration configuration) {
+        return GUtil.isTrue(configuration.getDescription()) ? " - " + configuration.getDescription() : "";
+    }
+
+    public void completeConfiguration(Configuration configuration) {
+    }
+
+    public void render(ResolvedConfiguration resolvedConfiguration) throws IOException
+    {
+        Set<ResolvedDependency> mergedRoots = mergeChildren(resolvedConfiguration.getFirstLevelModuleDependencies());
+        for (ResolvedDependency root : mergedRoots) {
+            render(root, 1);
+        }
+    }
+
+    private void render(ResolvedDependency resolvedDependency, int depth) throws IOException
+    {
+        getFormatter().format(getIndent(depth));
+		getFormatter().format("%s:%s%n", resolvedDependency.getName(),
+                resolvedDependency.getConfiguration());
+
+        Collection<ResolvedDependency> mergedChildren = mergeChildren(resolvedDependency.getChildren());
+
+		for(ResolvedDependency childResolvedDependency : mergedChildren)
+		{
+			render(childResolvedDependency, depth + 1);
+		}
+    }
+
+    private Set<ResolvedDependency> mergeChildren(Set<ResolvedDependency> children) {
+        Map<String, Set<ResolvedDependency>> mergedGroups = new HashMap<String, Set<ResolvedDependency>>();
+        for (ResolvedDependency child : children) {
+            Set<ResolvedDependency> mergeGroup = mergedGroups.get(child.getName());
+            if (mergeGroup == null) {
+                mergedGroups.put(child.getName(), mergeGroup = new HashSet<ResolvedDependency>());
+            }
+            mergeGroup.add(child);
+        }
+        Set<ResolvedDependency> mergedChildren = new HashSet<ResolvedDependency>();
+        for (Set<ResolvedDependency> mergedGroup : mergedGroups.values()) {
+            mergedChildren.add(new MergedResolvedDependency(mergedGroup));
+        }
+        return mergedChildren;
+    }
+
+    private String getIndent(int depth)
+	{
+		StringBuilder buffer = new StringBuilder();
+
+		for(int x = 0; x < depth - 1; x++)
+		{
+            if(x > 0)
+            {
+                buffer.append("|");
+            }
+
+			buffer.append("      ");
+		}
+
+		buffer.append("|-----");
+
+		return buffer.toString();
+	}
+
+    private static class MergedResolvedDependency implements ResolvedDependency {
+        private Set<ResolvedDependency> mergedResolvedDependencies = new LinkedHashSet<ResolvedDependency>();
+
+        public MergedResolvedDependency(Set<ResolvedDependency> mergedResolvedDependencies) {
+            assert !mergedResolvedDependencies.isEmpty();
+            this.mergedResolvedDependencies = mergedResolvedDependencies;
+        }
+
+        public String getName() {
+            return mergedResolvedDependencies.iterator().next().getName();
+        }
+
+        public String getModuleName() {
+            return mergedResolvedDependencies.iterator().next().getModuleName();
+        }
+
+        public String getModuleGroup() {
+            return mergedResolvedDependencies.iterator().next().getModuleGroup();
+        }
+
+        public String getModuleVersion() {
+            return mergedResolvedDependencies.iterator().next().getModuleVersion();
+        }
+
+        public String getConfiguration() {
+            String mergedConfiguration = "";
+            for (ResolvedDependency mergedResolvedDependency : mergedResolvedDependencies) {
+                mergedConfiguration += mergedResolvedDependency.getConfiguration() + ",";
+            }
+            return mergedConfiguration.substring(0, mergedConfiguration.length() - 1);
+        }
+
+        public Set<ResolvedDependency> getChildren() {
+            Set<ResolvedDependency> mergedChildren = new LinkedHashSet<ResolvedDependency>();
+            for (ResolvedDependency mergedResolvedDependency : mergedResolvedDependencies) {
+                mergedChildren.addAll(mergedResolvedDependency.getChildren());
+            }
+            return mergedChildren;
+        }
+
+        public Set<ResolvedDependency> getParents() {
+            throw new UnsupportedOperationException();
+        }
+
+        public Set<ResolvedArtifact> getModuleArtifacts() {
+            Set<ResolvedArtifact> mergedModuleArtifacts = new LinkedHashSet<ResolvedArtifact>();
+            for (ResolvedDependency mergedResolvedDependency : mergedResolvedDependencies) {
+                mergedModuleArtifacts.addAll(mergedResolvedDependency.getModuleArtifacts());
+            }
+            return mergedModuleArtifacts;
+        }
+
+        public Set<ResolvedArtifact> getAllModuleArtifacts() {
+            throw new UnsupportedOperationException();
+        }
+
+        public Set<ResolvedArtifact> getParentArtifacts(ResolvedDependency parent) {
+            throw new UnsupportedOperationException();
+        }
+
+        public Set<ResolvedArtifact> getArtifacts(ResolvedDependency parent) {
+            throw new UnsupportedOperationException();
+        }
+
+        public Set<ResolvedArtifact> getAllArtifacts(ResolvedDependency parent) {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyReportRenderer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyReportRenderer.java
new file mode 100644
index 0000000..89c9b3b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyReportRenderer.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 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.tasks.diagnostics;
+
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ResolvedConfiguration;
+
+import java.io.IOException;
+
+/**
+ * A {@code DependencyReportRenderer} is responsible for rendering the model of a project dependency report.
+ *
+ * @author Phil Messenger
+ */
+public interface DependencyReportRenderer extends ProjectReportRenderer {
+    /**
+     * Starts rendering the given configuration.
+     * @param configuration The configuration.
+     */
+    void startConfiguration(Configuration configuration);
+
+    /**
+     * Writes the given dependency graph for the current configuration.
+     *
+     * @param resolvedConfiguration The resolved configuration.
+     */
+    void render(ResolvedConfiguration resolvedConfiguration) throws IOException;
+
+    /**
+     * Completes the rendering of the given configuration.
+     * @param configuration The configuration
+     */
+    void completeConfiguration(Configuration configuration);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTask.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTask.java
new file mode 100644
index 0000000..0e16f1a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTask.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 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.tasks.diagnostics;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * The {@code DependencyReportTask} displays the dependency tree for a project. Can be configured to output to a file,
+ * and to optionally output a graphviz compatible "dot" graph. This task is used when you execute the dependency list
+ * command-line option.
+ *
+ * @author Phil Messenger
+ */
+public class DependencyReportTask extends AbstractReportTask {
+
+    private DependencyReportRenderer renderer = new AsciiReportRenderer();
+
+    private Set<Configuration> configurations;
+
+    public ProjectReportRenderer getRenderer() {
+        return renderer;
+    }
+
+    /**
+     * Set the renderer to use to build a report. If unset, AsciiGraphRenderer will be used.
+     */
+    public void setRenderer(DependencyReportRenderer renderer) {
+        this.renderer = renderer;
+    }
+
+    public void generate(Project project) throws IOException {
+        SortedSet<Configuration> sortedConfigurations = new TreeSet<Configuration>(
+                new Comparator<Configuration>() {
+                    public int compare(Configuration conf1, Configuration conf2) {
+                        return conf1.getName().compareTo(conf2.getName());
+                    }
+                });
+        sortedConfigurations.addAll(getConfigurations(project));
+        for (Configuration configuration : sortedConfigurations) {
+            renderer.startConfiguration(configuration);
+            renderer.render(configuration.getResolvedConfiguration());
+            renderer.completeConfiguration(configuration);
+        }
+    }
+
+    private Set<Configuration> getConfigurations(Project project) {
+        return configurations != null ? configurations : project.getConfigurations().getAll();
+    }
+
+    /**
+     * Returns the configurations to use to build a report. If unset, all project configurations will be used.
+     */
+    public Set<Configuration> getConfigurations() {
+        return configurations;
+    }
+
+    /**
+     * Set the configurations to use to build a report. If unset, all project configurations will be used.
+     */
+    public void setConfigurations(Set<Configuration> configurations) {
+        this.configurations = configurations;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/GraphvizReportRenderer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/GraphvizReportRenderer.java
new file mode 100644
index 0000000..905d556
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/GraphvizReportRenderer.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 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.tasks.diagnostics;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ResolvedConfiguration;
+import org.gradle.api.artifacts.ResolvedDependency;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * DependencyGrAaphRenderer that emits simple graphviz dot notation for a dependency tree.
+ *
+ * @author Phil Messenger
+ */
+public class GraphvizReportRenderer extends TextProjectReportRenderer implements DependencyReportRenderer {
+    @Override
+    public void startProject(Project project) {
+        // Do nothing
+    }
+
+    public void startConfiguration(Configuration configuration) {
+        // Do nothing
+    }
+
+    public void completeConfiguration(Configuration configuration) {
+        // Do nothing
+    }
+
+    public void render(ResolvedConfiguration resolvedConfiguration) throws IOException {
+        getFormatter().format("digraph %s{%n", "SomeConf");
+
+        Set<String> edges = new HashSet<String>();
+
+        for (ResolvedDependency resolvedDependency : resolvedConfiguration.getFirstLevelModuleDependencies()) {
+            buildDotDependencyTree(resolvedDependency, edges);
+        }
+
+        for (String edge : edges) {
+            getFormatter().format("%s%n", edge);
+        }
+
+        getFormatter().format("}%n");
+    }
+
+    private void buildDotDependencyTree(ResolvedDependency root, Set<String> edges) {
+        if (root.getAllModuleArtifacts().isEmpty()) {
+            return;
+        }
+        for (ResolvedDependency dep : root.getChildren()) {
+            String edge = "\"" + root.toString() + "\" -> \"" + dep.toString().replace('-', '_') + "\";";
+            edges.add(edge);
+        }
+
+        for (ResolvedDependency dep : root.getChildren()) {
+            buildDotDependencyTree(dep, edges);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/ProjectReportRenderer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/ProjectReportRenderer.java
new file mode 100644
index 0000000..1b92eb7
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/ProjectReportRenderer.java
@@ -0,0 +1,52 @@
+/*
+ * 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.tasks.diagnostics;
+
+import org.gradle.api.Project;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * <p>A {@code ProjectReportRenderer} is responsible for rendering the model of a project report.</p>
+ */
+public interface ProjectReportRenderer {
+    /**
+     * Sets the output file for the report. This method must be called before any other methods on this renderer.
+     *
+     * @param file The output file, never null.
+     */
+    void setOutputFile(File file) throws IOException;
+
+    /**
+     * Starts visiting a project.
+     *
+     * @param project The project, never null.
+     */
+    void startProject(Project project);
+
+    /**
+     * Completes visiting a project.
+     *
+     * @param project The project, never null.
+     */
+    void completeProject(Project project);
+
+    /**
+     * Completes this report. This method must be called last on this renderer.
+     */
+    void complete() throws IOException;
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/PropertyReportRenderer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/PropertyReportRenderer.java
new file mode 100644
index 0000000..e6c93f4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/PropertyReportRenderer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 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.tasks.diagnostics;
+
+/**
+ * <p>A {@code PropertyReportRenderer} is responsible for rendering the model of a property report.</p>
+ */
+public class PropertyReportRenderer extends TextProjectReportRenderer {
+
+    public PropertyReportRenderer() {
+    }
+
+    public PropertyReportRenderer(Appendable out) {
+        super(out);
+    }
+
+    /**
+     * Writes a property for the current project.
+     *
+     * @param name The name of the property
+     * @param value The value of the property
+     */
+    public void addProperty(String name, Object value) {
+        getFormatter().format("%s: %s%n", name, value);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/PropertyReportTask.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/PropertyReportTask.java
new file mode 100644
index 0000000..44372df
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/PropertyReportTask.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 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.tasks.diagnostics;
+
+import org.gradle.api.Project;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * The {@code PropertyListTask} prints out the properties of a project, and its sub-projects and tasks. This task is
+ * used when you execute the property list command-line option.
+ */
+public class PropertyReportTask extends AbstractReportTask {
+    private PropertyReportRenderer renderer = new PropertyReportRenderer();
+
+    public ProjectReportRenderer getRenderer() {
+        return renderer;
+    }
+
+    public void setRenderer(PropertyReportRenderer renderer) {
+        this.renderer = renderer;
+    }
+
+    public void generate(Project project) throws IOException {
+        for (Map.Entry<String, ?> entry : new TreeMap<String, Object>(project.getProperties()).entrySet()) {
+            if (entry.getKey().equals("properties")) {
+                renderer.addProperty(entry.getKey(), "{...}");
+            } else {
+                renderer.addProperty(entry.getKey(), entry.getValue());
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskDetails.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskDetails.java
new file mode 100644
index 0000000..78ffeba
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskDetails.java
@@ -0,0 +1,28 @@
+/*
+ * 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.diagnostics;
+
+import java.util.Set;
+
+public interface TaskDetails extends Comparable<TaskDetails> {
+    String getPath();
+
+    String getDescription();
+
+    Set<String> getDependencies();
+
+    Set<TaskDetails> getChildren();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportModel.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportModel.java
new file mode 100644
index 0000000..fb5b3cd
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportModel.java
@@ -0,0 +1,121 @@
+/*
+ * 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.diagnostics;
+
+import com.google.common.collect.Ordering;
+import com.google.common.collect.SetMultimap;
+import com.google.common.collect.TreeMultimap;
+import org.gradle.api.Task;
+import org.gradle.api.internal.DirectedGraph;
+import org.gradle.api.internal.GraphAggregator;
+import org.gradle.util.GUtil;
+
+import java.util.*;
+
+public class TaskReportModel {
+    private final SetMultimap<String, TaskDetails> groups = TreeMultimap.create(GUtil.emptyLast(GUtil.caseInsensitive()), Ordering.natural());
+
+    public void calculate(final Collection<? extends Task> tasks) {
+        Set<Task> topLevelTasks = new LinkedHashSet<Task>();
+        for (final Task task : tasks) {
+            if (GUtil.isTrue(task.getGroup())) {
+                topLevelTasks.add(task);
+            }
+        }
+        GraphAggregator<Task> aggregator = new GraphAggregator<Task>(new DirectedGraph<Task, Object>() {
+            public void getNodeValues(Task node, Collection<Object> values, Collection<Task> connectedNodes) {
+                for (Task dep : node.getTaskDependencies().getDependencies(node)) {
+                    if (tasks.contains(dep)) {
+                        connectedNodes.add(dep);
+                    }
+                }
+            }
+        });
+
+        GraphAggregator.Result<Task> result = aggregator.group(topLevelTasks, tasks);
+        for (Task task : result.getTopLevelNodes()) {
+            Set<Task> nodesForThisTask = new TreeSet<Task>(result.getNodes(task));
+            Set<TaskDetails> children = new LinkedHashSet<TaskDetails>();
+            Set<String> dependencies = new TreeSet<String>();
+            for (Task node : nodesForThisTask) {
+                if (node != task) {
+                    children.add(new TaskDetailsImpl(node, Collections.<TaskDetails>emptySet(),
+                            Collections.<String>emptySet()));
+                }
+                for (Task dep : node.getTaskDependencies().getDependencies(node)) {
+                    if (topLevelTasks.contains(dep) || !tasks.contains(dep)) {
+                        dependencies.add(dep.getPath());
+                    }
+                }
+            }
+
+            String group = topLevelTasks.contains(task) ? task.getGroup() : "";
+            groups.put(group, new TaskDetailsImpl(task, children, dependencies));
+        }
+    }
+
+    public Set<String> getGroups() {
+        return groups.keySet();
+    }
+
+    public Set<TaskDetails> getTasksForGroup(String group) {
+        if (!groups.containsKey(group)) {
+            throw new IllegalArgumentException(String.format("Unknown group '%s'", group));
+        }
+        return groups.get(group);
+    }
+
+    private static class TaskDetailsImpl implements TaskDetails {
+        private final Task task;
+        private final Set<TaskDetails> children;
+        private final Set<String> dependencies;
+
+        public TaskDetailsImpl(Task task, Set<TaskDetails> children, Set<String> dependencies) {
+            this.task = task;
+            this.children = children;
+            this.dependencies = dependencies;
+        }
+
+        public String getDescription() {
+            return task.getDescription();
+        }
+
+        public String getPath() {
+            return task.getPath();
+        }
+
+        @Override
+        public String toString() {
+            return task.toString();
+        }
+
+        public Task getTask() {
+            return task;
+        }
+
+        public Set<String> getDependencies() {
+            return dependencies;
+        }
+
+        public Set<TaskDetails> getChildren() {
+            return children;
+        }
+
+        public int compareTo(TaskDetails o) {
+            return getPath().compareTo(o.getPath());
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportRenderer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportRenderer.java
new file mode 100644
index 0000000..73f2d99
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportRenderer.java
@@ -0,0 +1,147 @@
+/*
+ * 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.api.tasks.diagnostics;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.Project;
+import org.gradle.api.Rule;
+import org.gradle.util.GUtil;
+
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * <p>A {@code TaskReportRenderer} is responsible for rendering the model of a project task report.</p>
+ *
+ * @author Hans Dockter
+ */
+public class TaskReportRenderer extends TextProjectReportRenderer {
+    private boolean currentProjectHasTasks;
+    private boolean currentProjectHasRules;
+    private boolean hasContent;
+    private boolean detail;
+
+    public TaskReportRenderer() {
+    }
+
+    public TaskReportRenderer(Appendable writer) {
+        super(writer);
+    }
+
+    @Override
+    public void startProject(Project project) {
+        currentProjectHasTasks = false;
+        currentProjectHasRules = false;
+        hasContent = true;
+        detail = false;
+        super.startProject(project);
+    }
+
+    public void showDetail(boolean detail) {
+        this.detail = detail;
+    }
+    
+    /**
+     * Writes the default task names for the current project.
+     *
+     * @param defaultTaskNames The default task names (must not be null)
+     */
+    public void addDefaultTasks(List<String> defaultTaskNames) {
+        if (defaultTaskNames.size() > 0) {
+            getFormatter().format("Default tasks: %s%n", GUtil.join(defaultTaskNames, ", "));
+            hasContent = true;
+        }
+    }
+
+    public void startTaskGroup(String taskGroup) {
+        if (!GUtil.isTrue(taskGroup)) {
+            addHeader(currentProjectHasTasks ? "Other tasks" : "Tasks");
+        } else {
+            addHeader(StringUtils.capitalize(taskGroup) + " tasks");
+        }
+        currentProjectHasTasks = true;
+    }
+
+    /**
+     * Writes a task for the current project.
+     *
+     * @param task The task
+     */
+    public void addTask(TaskDetails task) {
+        writeTask(task, "");
+    }
+
+    public void addChildTask(TaskDetails task) {
+        if (detail) {
+            writeTask(task, "    ");
+        }
+    }
+
+    private void writeTask(TaskDetails task, String prefix) {
+        getFormatter().format("%s%s%s", prefix, task.getPath(), getDescription(task));
+        if (detail) {
+            SortedSet<String> sortedDependencies = new TreeSet<String>();
+            for (String dependency : task.getDependencies()) {
+                sortedDependencies.add(dependency);
+            }
+            if (sortedDependencies.size() > 0) {
+                getFormatter().format(" [%s]", GUtil.join(sortedDependencies, ", "));
+            }
+        }
+        getFormatter().format("%n");
+    }
+
+    private void addHeader(String header) {
+        if (hasContent) {
+            getFormatter().format("%n");
+        }
+        hasContent = true;
+        getFormatter().format("%s%n", header);
+        for (int i = 0; i < header.length(); i++) {
+            getFormatter().format("-");
+        }
+        getFormatter().format("%n");
+    }
+
+    private String getDescription(TaskDetails task) {
+        return GUtil.isTrue(task.getDescription()) ? " - " + task.getDescription() : "";
+    }
+
+    /**
+     * Marks the end of the tasks for the current project.
+     */
+    public void completeTasks() {
+        if (!currentProjectHasTasks) {
+            getFormatter().format("No tasks%n");
+            hasContent = true;
+        }
+    }
+
+    /**
+     * Writes a rule for the current project.
+     *
+     * @param rule The rule
+     */
+    public void addRule(Rule rule) {
+        if (!currentProjectHasRules) {
+            addHeader("Rules");
+        }
+        getFormatter().format("%s%n", GUtil.elvis(rule.getDescription(), ""));
+        currentProjectHasRules = true;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportTask.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportTask.java
new file mode 100644
index 0000000..3633ae7
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/TaskReportTask.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 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.tasks.diagnostics;
+
+import org.gradle.api.Project;
+import org.gradle.api.Rule;
+
+import java.io.IOException;
+
+/**
+ * <p>The {@code TaskReportTask} prints out the list of tasks in the project, and its subprojects. It is used when you
+ *  use the task list command-line option.</p>
+ */
+public class TaskReportTask extends AbstractReportTask {
+    private TaskReportRenderer renderer = new TaskReportRenderer();
+    private boolean detail;
+
+    public ProjectReportRenderer getRenderer() {
+        return renderer;
+    }
+
+    public void setRenderer(TaskReportRenderer renderer) {
+        this.renderer = renderer;
+    }
+
+    public void setShowDetail(boolean detail) {
+        this.detail = detail;
+    }
+
+    public boolean isDetail() {
+        return detail;
+    }
+
+    public void generate(Project project) throws IOException {
+        renderer.showDetail(isDetail());
+        renderer.addDefaultTasks(project.getDefaultTasks());
+
+        TaskReportModel model = new TaskReportModel();
+        model.calculate(project.getTasks().getAll());
+
+        for (String group : model.getGroups()) {
+            renderer.startTaskGroup(group);
+            for (TaskDetails task : model.getTasksForGroup(group)) {
+                renderer.addTask(task);
+                for (TaskDetails child : task.getChildren()) {
+                    renderer.addChildTask(child);
+                }
+            }
+        }
+
+        renderer.completeTasks();
+        for (Rule rule : project.getTasks().getRules()) {
+            renderer.addRule(rule);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/TextProjectReportRenderer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/TextProjectReportRenderer.java
new file mode 100644
index 0000000..2bad8bb
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/TextProjectReportRenderer.java
@@ -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.api.tasks.diagnostics;
+
+import org.gradle.api.Project;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Formatter;
+
+/**
+ * <p>A basic {@link ProjectReportRenderer} which writes out a text report.
+ */
+public class TextProjectReportRenderer implements ProjectReportRenderer {
+    public static final String SEPARATOR = "------------------------------------------------------------";
+    private Appendable writer;
+    private boolean close;
+    private Formatter formatter;
+
+    public TextProjectReportRenderer() {
+        this(System.out);
+    }
+
+    public TextProjectReportRenderer(Appendable writer) {
+        setWriter(writer, false);
+    }
+
+    public void setOutputFile(File file) throws IOException {
+        cleanupWriter();
+        setWriter(new FileWriter(file), true);
+    }
+
+    public void startProject(Project project) {
+        formatter.format("%n%s%n", SEPARATOR);
+        if (project.getRootProject() == project) {
+            formatter.format("Root Project%n");
+        } else {
+            formatter.format("Project %s%n", project.getPath());
+        }
+        formatter.format("%s%n", SEPARATOR);
+    }
+
+    public void completeProject(Project project) {
+    }
+
+    public void complete() throws IOException {
+        cleanupWriter();
+        setWriter(System.out, false);
+    }
+
+    private void setWriter(Appendable writer, boolean close) {
+        this.writer = writer;
+        this.close = close;
+        formatter = new Formatter(writer);
+    }
+
+    private void cleanupWriter() throws IOException {
+        formatter.flush();
+        if (close) {
+            ((Closeable) writer).close();
+        }
+    }
+
+    protected Appendable getWriter() {
+        return writer;
+    }
+
+    protected Formatter getFormatter() {
+        return formatter;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/package-info.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/package-info.java
new file mode 100644
index 0000000..3df8c89
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/diagnostics/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * The built-in diagnostic {@link org.gradle.api.Task} implementations.
+ */
+package org.gradle.api.tasks.diagnostics;
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/package-info.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/package-info.java
new file mode 100644
index 0000000..c5f2a74
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 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.
+ */
+
+/**
+ * The standard {@link org.gradle.api.Task} implementations.
+ */
+package org.gradle.api.tasks;
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/util/PatternFilterable.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/util/PatternFilterable.java
new file mode 100644
index 0000000..82366f4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/util/PatternFilterable.java
@@ -0,0 +1,201 @@
+/*
+ * 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.tasks.util;
+
+import org.gradle.api.specs.Spec;
+import org.gradle.api.file.FileTreeElement;
+
+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>
+ *
+ * <p>Patterns may include:</p>
+ *
+ * <ul>
+ *
+ * <li>'*' to match any number of characters
+ *
+ * <li>'?' to match any single character
+ *
+ * <li>'**' to match any number of directories or files
+ *
+ * </ul>
+ *
+ * <p>Either '/' or '\' may be used in a pattern to separate directories. Patterns ending with '/' or '\' will have '**'
+ * automatically appended.</p>
+ *
+ * <p>Examples:</p>
+ * <pre>
+ * all files ending with 'jsp' (including subdirectories)
+ *    **/*.jsp
+ *
+ * all files beginning with 'template_' in the level1/level2 directory
+ *    level1/level2/template_*
+ *
+ * all files (including subdirectories) beneath src/main/webapp
+ *   src/main/webapp/
+ *
+ * all files beneath any .svn directory (including subdirectories) under src/main/java
+ *   src/main/java/**/.svn/**
+ * </pre>
+ *
+ * <p>You may also use a closure or {@link Spec} to specify which files to include or exclude. The closure or {@link Spec}
+ * is passed a {@link org.gradle.api.file.FileTreeElement}, and must return a boolean value.</p>
+ *
+ * <p>If no include patterns or specs are specified, then all files in this container will be included. If any include
+ * patterns or specs are specified, then a file is included if it matches any of the patterns or specs.</p>
+ *
+ * <p>If no exclude patterns or spec are specified, then no files will be excluded. If any exclude patterns or specs are
+ * specified, then a file is include only if it matches none of the patterns or specs.</p>
+ */
+public interface PatternFilterable {
+
+    /**
+     * Get the set of include patterns.
+     */
+    Set<String> getIncludes();
+
+    /**
+     * Get the set of exclude patterns.
+     */
+    Set<String> getExcludes();
+
+    /**
+     * Set the allowable include patterns.  Note that unlike {@link #include(Iterable)} this replaces any previously
+     * defined includes.
+     *
+     * @param includes an Iterable providing new include patterns
+     * @return this
+     * @see PatternFilterable Pattern Format
+     */
+    PatternFilterable setIncludes(Iterable<String> includes);
+
+    /**
+     * Set the allowable exclude patterns.  Note that unlike {@link #exclude(Iterable)} this replaces any previously
+     * defined excludes.
+     *
+     * @param excludes an Iterable providing new exclude patterns
+     * @return this
+     * @see PatternFilterable Pattern Format
+     */
+    PatternFilterable setExcludes(Iterable<String> excludes);
+
+    /**
+     * Adds an ANT style include pattern. This method may be called multiple times to append new patterns and multiple
+     * patterns may be specified in a single call.
+     *
+     * If includes are not provided, then all files in this container will be included. If includes are provided, then a
+     * file must match at least one of the include patterns to be processed.
+     *
+     * @param includes a vararg list of include patterns
+     * @return this
+     * @see PatternFilterable Pattern Format
+     */
+    PatternFilterable include(String... includes);
+
+    /**
+     * Adds an ANT style include pattern. This method may be called multiple times to append new patterns and multiple
+     * patterns may be specified in a single call.
+     *
+     * If includes are not provided, then all files in this container will be included. If includes are provided, then a
+     * file must match at least one of the include patterns to be processed.
+     *
+     * @param includes a Iterable providing more include patterns
+     * @return this
+     * @see PatternFilterable Pattern Format
+     */
+    PatternFilterable include(Iterable<String> includes);
+
+    /**
+     * Adds an include spec. This method may be called multiple times to append new specs.
+     *
+     * If includes are not provided, then all files in this container will be included. If includes are provided, then a
+     * file must match at least one of the include patterns or specs to be included.
+     *
+     * @param includeSpec the spec to add
+     * @return this
+     * @see PatternFilterable Pattern Format
+     */
+    PatternFilterable include(Spec<FileTreeElement> includeSpec);
+
+    /**
+     * Adds an include 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.
+     *
+     * If includes are not provided, then all files in this container will be included. If includes are provided, then a
+     * file must match at least one of the include patterns or specs to be included.
+     *
+     * @param includeSpec the spec to add
+     * @return this
+     * @see PatternFilterable Pattern Format
+     */
+    PatternFilterable include(Closure includeSpec);
+
+    /**
+     * Adds an ANT style exclude pattern. This method may be called multiple times to append new patterns and multiple
+     * patterns may be specified in a single call.
+     *
+     * 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 excludes a vararg list of exclude patterns
+     * @return this
+     * @see PatternFilterable Pattern Format
+     */
+    PatternFilterable exclude(String... excludes);
+
+    /**
+     * Adds an ANT style exclude pattern. This method may be called multiple times to append new patterns and multiple
+     * patterns may be specified in a single call.
+     *
+     * 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 excludes a Iterable providing new exclude patterns
+     * @return this
+     * @see PatternFilterable Pattern Format
+     */
+    PatternFilterable exclude(Iterable<String> excludes);
+
+    /**
+     * Adds an exclude spec. This method may be called multiple times to append new specs.
+     *
+     * 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
+     */
+    PatternFilterable exclude(Spec<FileTreeElement> excludeSpec);
+
+    /**
+     * 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.
+     *
+     * 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
+     */
+    PatternFilterable exclude(Closure excludeSpec);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/util/PatternSet.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/util/PatternSet.groovy
new file mode 100644
index 0000000..b259099
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/util/PatternSet.groovy
@@ -0,0 +1,242 @@
+/*
+ * 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.api.tasks.util
+
+import org.gradle.api.tasks.AntBuilderAware
+import org.gradle.util.GUtil
+
+import org.gradle.api.specs.Spec
+import org.gradle.api.specs.Specs
+import org.gradle.api.internal.file.pattern.PatternMatcherFactory
+import org.gradle.api.specs.AndSpec
+import org.gradle.api.specs.NotSpec
+import org.gradle.api.specs.OrSpec
+import org.apache.tools.ant.DirectoryScanner
+import org.gradle.api.file.FileTreeElement
+
+/**
+ * @author Hans Dockter
+ */
+class PatternSet implements AntBuilderAware, PatternFilterable {
+    PatternSet() {
+    }
+
+    PatternSet(Map args) {
+        args.each {String key, value ->
+            this."$key" = value
+        }
+    }
+
+    private static final Set<String> GLOBAL_EXCLUDES = new HashSet<String>()
+
+    private Set includes = [] as LinkedHashSet
+    private Set excludes = [] as LinkedHashSet
+    private Set includeSpecs = [] as LinkedHashSet
+    private Set excludeSpecs = [] as LinkedHashSet
+    def boolean caseSensitive = true
+
+    static {
+        GLOBAL_EXCLUDES.addAll(DirectoryScanner.DEFAULTEXCLUDES as Collection)
+    }
+
+    static def setGlobalExcludes(Collection<String> excludes) {
+        GLOBAL_EXCLUDES.clear()
+        GLOBAL_EXCLUDES.addAll(excludes)
+    }
+    
+    def boolean equals(Object o) {
+        if (o.is(this)) {
+            return true
+        }
+        if (o == null || o.getClass() != getClass()) {
+            return false
+        }
+        return o.includes.equals(includes) && o.excludes.equals(excludes) && o.caseSensitive == caseSensitive
+    }
+
+    def int hashCode() {
+        return includes.hashCode() ^ excludes.hashCode()
+    }
+
+    public PatternSet copyFrom(PatternFilterable sourcePattern) {
+        setIncludes(sourcePattern.includes)
+        setExcludes(sourcePattern.excludes)
+        PatternSet other = sourcePattern
+        includeSpecs.clear()
+        includeSpecs.addAll(other.includeSpecs)
+        excludeSpecs.clear()
+        excludeSpecs.addAll(other.excludeSpecs)
+        this
+    }
+
+    public PatternSet intersect() {
+        return new IntersectionPatternSet(this)
+    }
+    
+    public Spec<FileTreeElement> getAsSpec() {
+        Spec<FileTreeElement> includeSpec = Specs.satisfyAll()
+
+        boolean hasIncludes = includes || includeSpecs
+        if (hasIncludes) {
+            List<Spec<FileTreeElement>> matchers = new ArrayList<Spec<FileTreeElement>>()
+            for (String include: includes) {
+                matchers.add(new RelativePathSpec(PatternMatcherFactory.getPatternMatcher(true, caseSensitive, include)))
+            }
+            matchers.addAll(includeSpecs)
+            includeSpec = new OrSpec<FileTreeElement>(matchers as Spec[])
+        }
+
+        Collection<String> allExcludes = excludes + GLOBAL_EXCLUDES
+        boolean hasExcludes = allExcludes || excludeSpecs
+        if (!hasExcludes) {
+            return includeSpec
+        }
+
+        List<Spec<FileTreeElement>> matchers = new ArrayList<Spec<FileTreeElement>>()
+        for (String exclude: allExcludes) {
+            matchers.add(new RelativePathSpec(PatternMatcherFactory.getPatternMatcher(false, caseSensitive, exclude)))
+        }
+        matchers.addAll(excludeSpecs)
+        Spec<FileTreeElement> excludeSpec = new NotSpec<FileTreeElement>(new OrSpec<FileTreeElement>(matchers as Spec[]))
+
+        if (!hasIncludes) {
+            return excludeSpec
+        }
+
+        return new AndSpec<FileTreeElement>([includeSpec, excludeSpec] as Spec[])
+    }
+
+    public Set<String> getIncludes() {
+        includes
+    }
+
+    public Set<Spec<FileTreeElement>> getIncludeSpecs() {
+        return includeSpecs
+    }
+
+    public PatternSet setIncludes(Iterable<String> includes) {
+        this.includes.clear()
+        include(includes)
+    }
+
+    public Set<String> getExcludes() {
+        excludes
+    }
+
+    public Set<Spec<FileTreeElement>> getExcludeSpecs() {
+       return excludeSpecs
+    }
+
+    public PatternSet setExcludes(Iterable<String> excludes) {
+        this.excludes.clear()
+        exclude(excludes)
+    }
+
+    public PatternFilterable include(String... includes) {
+        include(includes as List)
+    }
+
+    public PatternFilterable include(Iterable<String> includes) {
+        GUtil.addToCollection(this.includes, includes)
+        this
+    }
+
+    public PatternFilterable include(Spec<FileTreeElement> spec) {
+        includeSpecs << spec
+        this
+    }
+
+    /*
+    This can't be called just include, because it has the same erasure as include(Iterable<String>)
+     */
+    public PatternFilterable includeSpecs(Iterable<Spec<FileTreeElement>> includes) {
+        GUtil.addToCollection(this.includeSpecs, includes)
+        this
+    }
+
+    public PatternFilterable include(Closure closure) {
+        include(closure as Spec)
+        this
+    }
+
+    public PatternFilterable exclude(String... excludes) {
+        exclude(excludes as List)
+    }
+
+    public PatternFilterable exclude(Iterable<String> excludes) {
+        GUtil.addToCollection(this.excludes, excludes)
+        this
+    }
+
+    public PatternFilterable exclude(Spec<FileTreeElement> spec) {
+        excludeSpecs << spec
+        this
+    }
+
+    public PatternFilterable excludeSpecs(Iterable<Spec<FileTreeElement>> excludes) {
+        GUtil.addToCollection(this.excludeSpecs, excludes)
+        this
+    }
+
+    public PatternFilterable exclude(Closure closure) {
+        exclude(closure as Spec)
+        this
+    }
+
+    def addToAntBuilder(node, String childNodeName = null) {
+        if (!includeSpecs.empty || !excludeSpecs.empty) {
+            throw new UnsupportedOperationException('Cannot add include/exclude specs to Ant node. Only include/exclude patterns are currently supported.')
+        }
+        node.and {
+            if (includes) {
+                or {
+                    includes.each {
+                        filename(name: it, casesensitive: this.caseSensitive)
+                    }
+                }
+            }
+            if (excludes) {
+                not {
+                    or {
+                        excludes.each {
+                            filename(name: it, casesensitive: this.caseSensitive)
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+class IntersectionPatternSet extends PatternSet {
+    private final PatternSet other
+
+    def IntersectionPatternSet(PatternSet other) {
+        this.other = other
+    }
+
+    def Spec<FileTreeElement> getAsSpec() {
+        return new AndSpec<FileTreeElement>([super.getAsSpec(), other.getAsSpec()] as Spec[])
+    }
+
+    def addToAntBuilder(Object node, String childNodeName) {
+        node.and {
+            super.addToAntBuilder(node, null)
+            other.addToAntBuilder(node, null)
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/util/RelativePathSpec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/util/RelativePathSpec.java
new file mode 100644
index 0000000..bd7990a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/util/RelativePathSpec.java
@@ -0,0 +1,32 @@
+/*
+ * 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.tasks.util;
+
+import org.gradle.api.file.FileTreeElement;
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.specs.Spec;
+
+class RelativePathSpec implements Spec<FileTreeElement> {
+    private final Spec<? super RelativePath> pathSpec;
+
+    public RelativePathSpec(Spec<? super RelativePath> pathSpec) {
+        this.pathSpec = pathSpec;
+    }
+
+    public boolean isSatisfiedBy(FileTreeElement element) {
+        return pathSpec.isSatisfiedBy(element.getRelativePath());
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/util/package-info.java b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/util/package-info.java
new file mode 100644
index 0000000..549bbda
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/api/tasks/util/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 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.
+ */
+
+/**
+ * Utility classes used by the standard task implementations.
+ */
+package org.gradle.api.tasks.util;
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/AutoCloseCacheFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/AutoCloseCacheFactory.java
new file mode 100644
index 0000000..b362f6f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/AutoCloseCacheFactory.java
@@ -0,0 +1,93 @@
+/*
+ * 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.cache;
+
+import org.gradle.CacheUsage;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+public class AutoCloseCacheFactory implements CacheFactory {
+    private final CacheFactory cacheFactory;
+    private final Map<File, CacheInfo> openCaches = new HashMap<File, CacheInfo>();
+
+    public AutoCloseCacheFactory(CacheFactory cacheFactory) {
+        this.cacheFactory = cacheFactory;
+    }
+
+    public PersistentCache open(File cacheDir, CacheUsage usage, Map<String, ?> properties) {
+        File canonicalDir = GFileUtils.canonicalise(cacheDir);
+        CacheInfo cacheInfo = openCaches.get(canonicalDir);
+        if (cacheInfo == null) {
+            PersistentCache cache = cacheFactory.open(cacheDir, usage, properties);
+            cacheInfo = new CacheInfo(cache, properties);
+            openCaches.put(canonicalDir, cacheInfo);
+        }
+        else {
+            if (!properties.equals(cacheInfo.properties)) {
+                throw new UnsupportedOperationException(String.format(
+                        "Cache '%s' is already open with different state.", cacheDir));
+            }
+        }
+        cacheInfo.addReference();
+        return cacheInfo.cache;
+    }
+
+    public void close(PersistentCache cache) {
+        for (CacheInfo cacheInfo : openCaches.values()) {
+            if (cacheInfo.cache == cache) {
+                if (cacheInfo.removeReference()) {
+                    openCaches.values().remove(cacheInfo);
+                    cacheFactory.close(cacheInfo.cache);
+                }
+                return;
+            }
+        }
+        throw new IllegalArgumentException("Attempting to close unknown cache " + cache);
+    }
+
+    public void close() {
+        try {
+            for (CacheInfo cacheInfo : openCaches.values()) {
+                cacheFactory.close(cacheInfo.cache);
+            }
+        } finally {
+            openCaches.clear();
+        }
+    }
+
+    private static class CacheInfo {
+        int count;
+        final Map<String, ?> properties;
+        final PersistentCache cache;
+
+        private CacheInfo(PersistentCache cache, Map<String, ?> properties) {
+            this.cache = cache;
+            this.properties = new HashMap<String, Object>(properties);
+        }
+
+        public void addReference() {
+            count++;
+        }
+
+        public boolean removeReference() {
+            count--;
+            return count == 0;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/CacheBuilder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/CacheBuilder.java
new file mode 100644
index 0000000..55e1478
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/CacheBuilder.java
@@ -0,0 +1,51 @@
+/*
+ * 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.cache;
+
+import java.util.Map;
+
+public interface CacheBuilder {
+    /**
+     * Specifies the additional key properties for the cache. The cache is treated as invalid if any of the properties
+     * do not match the properties used to create the cache. The default for this is an empty map.
+     *
+     * @param properties additional properties for the cache.
+     * @return this
+     */
+    CacheBuilder withProperties(Map<String, ?> properties);
+
+    /**
+     * Specifies the target domain object.  This might be a task, project, or similar. The cache is scoped for the given
+     * target object. The default is to use a globally-scoped cache.
+     *
+     * @param target The target domain object which the cache is for.
+     * @return this
+     */
+    CacheBuilder forObject(Object target);
+
+    /**
+     * Invalidates this cache on Gradle version change. The default is to maintain a separate cache for each version.
+     *
+     * @return this
+     */
+    CacheBuilder invalidateOnVersionChange();
+
+    /**
+     * Creates the cache.
+     */
+    PersistentCache open();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/CacheFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/CacheFactory.java
new file mode 100644
index 0000000..4ab018d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/CacheFactory.java
@@ -0,0 +1,31 @@
+/*
+ * 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 org.gradle.CacheUsage;
+
+import java.io.File;
+import java.util.Map;
+
+public interface CacheFactory {
+    PersistentCache open(File cacheDir, CacheUsage usage, Map<String, ?> properties);
+
+    void close(PersistentCache cache);
+}
+
+
+
+
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/CacheRepository.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/CacheRepository.java
new file mode 100644
index 0000000..d3f1463
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/CacheRepository.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+public interface CacheRepository {
+    /**
+     * Returns a builder for the cache with the given key. Default is a Gradle version-specific cache shared by all
+     * builds, though this can be changed using the given builder.
+     *
+     * @param key The cache key.
+     * @return The builder.
+     */
+    CacheBuilder cache(String key);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/DefaultCacheFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/DefaultCacheFactory.java
new file mode 100644
index 0000000..b944f39
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/DefaultCacheFactory.java
@@ -0,0 +1,34 @@
+/*
+ * 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.cache;
+
+import org.gradle.CacheUsage;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.util.Map;
+
+public class DefaultCacheFactory implements CacheFactory {
+
+    public PersistentCache open(File cacheDir, CacheUsage usage, Map<String, ?> properties) {
+        File canonicalDir = GFileUtils.canonicalise(cacheDir);
+        return new DefaultPersistentDirectoryCache(canonicalDir, usage, properties);
+    }
+
+    public void close(PersistentCache cache) {
+        ((DefaultPersistentDirectoryCache) cache).close();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/DefaultCacheRepository.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/DefaultCacheRepository.java
new file mode 100644
index 0000000..95f4966
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/DefaultCacheRepository.java
@@ -0,0 +1,92 @@
+/*
+ * 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 org.gradle.CacheUsage;
+import org.gradle.api.Project;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.util.GradleVersion;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultCacheRepository implements CacheRepository {
+    private final GradleVersion version = new GradleVersion();
+    private final File globalCacheDir;
+    private final CacheUsage cacheUsage;
+    private final CacheFactory factory;
+
+    public DefaultCacheRepository(File userHomeDir, CacheUsage cacheUsage, CacheFactory factory) {
+        this.factory = factory;
+        this.globalCacheDir = new File(userHomeDir, "caches");
+        this.cacheUsage = cacheUsage;
+    }
+
+    public CacheBuilder cache(String key) {
+        return new PersistentCacheBuilder(key);
+    }
+
+    private class PersistentCacheBuilder implements CacheBuilder {
+        private final String key;
+        private Map<String, ?> properties = Collections.emptyMap();
+        private Object target;
+        private boolean invalidateOnVersionChange;
+
+        private PersistentCacheBuilder(String key) {
+            this.key = key;
+        }
+
+        public CacheBuilder withProperties(Map<String, ?> properties) {
+            this.properties = properties;
+            return this;
+        }
+
+        public CacheBuilder forObject(Object target) {
+            this.target = target;
+            return this;
+        }
+
+        public CacheBuilder invalidateOnVersionChange() {
+            invalidateOnVersionChange = true;
+            return this;
+        }
+
+        public PersistentCache open() {
+            File cacheBaseDir;
+            Map<String, Object> properties = new HashMap<String, Object>(this.properties);
+            if (target == null) {
+                cacheBaseDir = globalCacheDir;
+            } else if (target instanceof Gradle) {
+                Gradle gradle = (Gradle) target;
+                cacheBaseDir = new File(gradle.getRootProject().getProjectDir(), Project.TMP_DIR_NAME);
+            } else if (target instanceof File) {
+                File dir = (File) target;
+                cacheBaseDir = new File(dir, Project.TMP_DIR_NAME);
+            } else {
+                throw new IllegalArgumentException(String.format("Cannot create cache for unrecognised domain object %s.", target));
+            }
+            if (invalidateOnVersionChange) {
+                properties.put("gradle.version", version.getVersion());
+                cacheBaseDir = new File(cacheBaseDir, "noVersion");
+            } else {
+                cacheBaseDir = new File(cacheBaseDir, version.getVersion());
+            }
+            return factory.open(new File(cacheBaseDir, key), cacheUsage, properties);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/DefaultPersistentDirectoryCache.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/DefaultPersistentDirectoryCache.java
new file mode 100644
index 0000000..ac3ccc7
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/DefaultPersistentDirectoryCache.java
@@ -0,0 +1,117 @@
+/*
+ * 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 org.gradle.CacheUsage;
+import org.gradle.cache.btree.BTreePersistentIndexedCache;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Properties;
+
+public class DefaultPersistentDirectoryCache implements PersistentCache {
+    private final File dir;
+    private final File propertiesFile;
+    private final Properties properties = new Properties();
+    private boolean valid;
+    private BTreePersistentIndexedCache indexedCache;
+    private SimpleStateCache stateCache;
+
+    public DefaultPersistentDirectoryCache(File dir, CacheUsage cacheUsage, Map<String, ?> properties) {
+        this.dir = dir;
+        propertiesFile = new File(dir, "cache.properties");
+        this.properties.putAll(properties);
+        determineIfCacheIsValid(cacheUsage, properties);
+        buildCacheDir();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Cache %s", dir);
+    }
+
+    private void buildCacheDir() {
+        if (!valid) {
+            GFileUtils.deleteDirectory(dir);
+        }
+        if (!dir.isDirectory()) {
+            dir.mkdirs();
+        }
+    }
+
+    private void determineIfCacheIsValid(CacheUsage cacheUsage, Map<String, ?> properties) {
+        valid = false;
+
+        if (cacheUsage != CacheUsage.ON) {
+            return;
+        }
+
+        if (!propertiesFile.isFile()) {
+            return;
+        }
+
+        Properties currentProperties = GUtil.loadProperties(propertiesFile);
+        for (Map.Entry<String, ?> entry : properties.entrySet()) {
+            if (!entry.getValue().toString().equals(currentProperties.getProperty(entry.getKey()))) {
+                return;
+            }
+        }
+        valid = true;
+    }
+
+    public <K, V> BTreePersistentIndexedCache<K, V> openIndexedCache(Serializer<V> serializer) {
+        if (indexedCache == null) {
+            indexedCache = new BTreePersistentIndexedCache<K,V>(this, serializer);
+        }
+        return indexedCache;
+    }
+
+    public <K, V> BTreePersistentIndexedCache<K, V> openIndexedCache() {
+        return openIndexedCache(new DefaultSerializer<V>());
+    }
+
+    public <T> SimpleStateCache<T> openStateCache() {
+        if (stateCache == null) {
+            stateCache = new SimpleStateCache<T>(this, new DefaultSerializer<T>());
+        }
+        return stateCache;
+    }
+
+    public Properties getProperties() {
+        return properties;
+    }
+
+    public File getBaseDir() {
+        return dir;
+    }
+
+    public boolean isValid() {
+        return valid;
+    }
+
+    public void markValid() {
+        GUtil.saveProperties(properties, propertiesFile);
+        valid = true;
+    }
+
+    public void close() {
+        if (indexedCache != null) {
+            indexedCache.close();
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/DefaultSerializer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/DefaultSerializer.java
new file mode 100644
index 0000000..c866d37
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/DefaultSerializer.java
@@ -0,0 +1,34 @@
+/*
+ * 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.*;
+
+public class DefaultSerializer<T> implements Serializer<T> {
+    public T read(InputStream instr) throws Exception {
+        try {
+            return (T) new ObjectInputStream(instr).readObject();
+        } catch (StreamCorruptedException e) {
+            return null;
+        }
+    }
+
+    public void write(OutputStream outstr, T value) throws Exception {
+        ObjectOutputStream objectStr = new ObjectOutputStream(outstr);
+        objectStr.writeObject(value);
+        objectStr.flush();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/PersistentCache.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/PersistentCache.java
new file mode 100644
index 0000000..23378d4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/PersistentCache.java
@@ -0,0 +1,62 @@
+/*
+ * 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.File;
+
+/**
+ * Represents a directory which can be used for caching.
+ */
+public interface PersistentCache {
+    /**
+     * Returns the base directory for this cache.
+     */
+    File getBaseDir();
+
+    /**
+     * Returns true if this cache is valid. If the cache is valid, its contents can be used. If not, the base directory
+     * will be empty, and the cache contents must be rebuilt. You must call {@link #markValid} to indicate that the
+     * contents have been rebuilt.
+     */
+    boolean isValid();
+
+    /**
+     * Marks the contents of the cache as valid.
+     */
+    void markValid();
+
+    /**
+     * Opens an indexed cache backed by this cache.
+     *
+     * @param serializer The serializer to use to serialise the cache entries.
+     * @return The cache.
+     */
+    <K, V> PersistentIndexedCache<K, V> openIndexedCache(Serializer<V> serializer);
+
+    /**
+     * Opens an indexed cache backed by this cache.
+     *
+     * @return The cache.
+     */
+    <K, V> PersistentIndexedCache<K, V> openIndexedCache();
+
+    /**
+     * Opens a state cache backed by this cache.
+     *
+     * @return The cache.
+     */
+    <T> PersistentStateCache<T> openStateCache();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/PersistentIndexedCache.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/PersistentIndexedCache.java
new file mode 100644
index 0000000..9afa879
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/PersistentIndexedCache.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+/**
+ * A persistent store of objects of type V indexed by a key of type K.
+ */
+public interface PersistentIndexedCache<K, V> {
+    V get(K key);
+
+    void put(K key, V value);
+
+    void remove(K key);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/PersistentStateCache.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/PersistentStateCache.java
new file mode 100644
index 0000000..163122c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/PersistentStateCache.java
@@ -0,0 +1,26 @@
+/*
+ * 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.cache;
+
+/**
+ * A persistent store containing an object of type T.
+ */
+public interface PersistentStateCache<T> {
+    T get();
+
+    void set(T newValue);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/Serializer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/Serializer.java
new file mode 100644
index 0000000..22ffd83
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/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.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/gradle-core/src/main/groovy/org/gradle/cache/SimpleStateCache.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/SimpleStateCache.java
new file mode 100644
index 0000000..c1e7311
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/SimpleStateCache.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.cache;
+
+import org.gradle.api.GradleException;
+
+import java.io.*;
+
+public class SimpleStateCache<T> implements PersistentStateCache<T> {
+    private final Serializer<T> serializer;
+    private final File cacheFile;
+    private PersistentCache cache;
+
+    public SimpleStateCache(PersistentCache cache, Serializer<T> serializer) {
+        this.cache = cache;
+        this.serializer = serializer;
+        cacheFile = new File(cache.getBaseDir(), "state.bin");
+    }
+
+    public T get() {
+        if (!cacheFile.isFile()) {
+            return null;
+        }
+        try {
+            InputStream inStr = new BufferedInputStream(new FileInputStream(cacheFile));
+            try {
+                return serializer.read(inStr);
+            } finally {
+                inStr.close();
+            }
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not read cache value from '%s'.", cacheFile), e);
+        }
+    }
+
+    public void set(T newValue) {
+        try {
+            OutputStream outStr = new BufferedOutputStream(new FileOutputStream(cacheFile));
+            try {
+                serializer.write(outStr, newValue);
+            } finally {
+                outStr.close();
+            }
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not write cache value to '%s'.", cacheFile), e);
+        }
+        cache.markValid();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/BTreePersistentIndexedCache.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/BTreePersistentIndexedCache.java
new file mode 100644
index 0000000..67acb43
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/BTreePersistentIndexedCache.java
@@ -0,0 +1,684 @@
+/*
+ * 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.cache.btree;
+
+import org.gradle.api.UncheckedIOException;
+import org.gradle.cache.PersistentCache;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.cache.Serializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.util.*;
+
+// todo - stream serialised value to file
+// todo - handle hash collisions
+// todo - don't store null links to child blocks in leaf index blocks
+// todo - align block boundaries
+// todo - concurrency control
+// todo - remove the check-sum from each block
+// todo - merge small values into a single data block
+// todo - discard when file corrupt
+// todo - include data directly in index entry when serializer can guarantee small fixed sized data
+// todo - free list leaks disk space
+// todo - merge adjacent free blocks
+// todo - use more efficient lookup for free block with nearest size
+public class BTreePersistentIndexedCache<K, V> implements PersistentIndexedCache<K, V> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(BTreePersistentIndexedCache.class);
+    private final File cacheFile;
+    private final PersistentCache backingCache;
+    private final Serializer<V> serializer;
+    private final short maxChildIndexEntries;
+    private final int minIndexChildNodes;
+    private final StateCheckBlockStore store;
+    private HeaderBlock header;
+
+    public BTreePersistentIndexedCache(PersistentCache backingCache, Serializer<V> serializer) {
+        this(backingCache, serializer, (short) 512, 512);
+    }
+
+    public BTreePersistentIndexedCache(PersistentCache backingCache, Serializer<V> serializer,
+                                       short maxChildIndexEntries, int maxFreeListEntries) {
+        this.backingCache = backingCache;
+        this.serializer = serializer;
+        this.maxChildIndexEntries = maxChildIndexEntries;
+        this.minIndexChildNodes = maxChildIndexEntries / 2;
+        cacheFile = new File(backingCache.getBaseDir(), "cache.bin");
+        BlockStore cachingStore = new CachingBlockStore(new FileBackedBlockStore(cacheFile), IndexBlock.class, FreeListBlockStore.FreeListBlock.class);
+        store = new StateCheckBlockStore(new FreeListBlockStore(cachingStore, maxFreeListEntries));
+        try {
+            open();
+        } catch (Exception e) {
+            throw new UncheckedIOException(String.format("Could not open %s.", this), e);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return String.format("cache '%s'", cacheFile);
+    }
+
+    private void open() throws Exception {
+        try {
+            doOpen();
+        } catch (CorruptedCacheException e) {
+            rebuild();
+        }
+    }
+
+    private void doOpen() throws Exception {
+        BlockStore.Factory factory = new BlockStore.Factory() {
+            public Object create(Class<? extends BlockPayload> type) {
+                if (type == HeaderBlock.class) {
+                    return new HeaderBlock();
+                }
+                if (type == IndexBlock.class) {
+                    return new IndexBlock();
+                }
+                if (type == DataBlock.class) {
+                    return new DataBlock();
+                }
+                throw new UnsupportedOperationException();
+            }
+        };
+        Runnable initAction = new Runnable() {
+            public void run() {
+                header = new HeaderBlock();
+                store.write(header);
+                header.index.newRoot();
+                store.flush();
+                backingCache.markValid();
+            }
+        };
+
+        store.open(initAction, factory);
+        header = store.readFirst(HeaderBlock.class);
+    }
+
+    public V get(K key) {
+        try {
+            try {
+                DataBlock block = header.getRoot().get(key);
+                if (block != null) {
+                    return block.getValue();
+                }
+                return null;
+            } catch (CorruptedCacheException e) {
+                rebuild();
+                return null;
+            }
+        } catch (Exception e) {
+            throw new UncheckedIOException(String.format("Could not read entry '%s' from %s.", key, this), e);
+        }
+    }
+
+    public void put(K key, V value) {
+        try {
+            String keyString = key.toString();
+            long hashCode = keyString.hashCode();
+            Lookup lookup = header.getRoot().find(hashCode);
+            boolean needNewBlock = true;
+            if (lookup.entry != null) {
+                DataBlock block = store.read(lookup.entry.dataBlock, DataBlock.class);
+                needNewBlock = !block.useNewValue(value);
+                if (needNewBlock) {
+                    store.remove(block);
+                }
+            }
+            if (needNewBlock) {
+                DataBlock block = new DataBlock(keyString, value);
+                store.write(block);
+                lookup.indexBlock.put(hashCode, block.getPos());
+            }
+            store.flush();
+        } catch (Exception e) {
+            throw new UncheckedIOException(String.format("Could not add entry '%s' to %s.", key, this), e);
+        }
+    }
+
+    public void remove(K key) {
+        try {
+            Lookup lookup = header.getRoot().find(key.toString());
+            if (lookup.entry == null) {
+                return;
+            }
+            lookup.indexBlock.remove(lookup.entry);
+            DataBlock block = store.read(lookup.entry.dataBlock, DataBlock.class);
+            store.remove(block);
+            store.flush();
+        } catch (Exception e) {
+            throw new UncheckedIOException(String.format("Could not remove entry '%s' from %s.", key, this), e);
+        }
+    }
+
+    private IndexBlock load(BlockPointer pos, IndexRoot root, IndexBlock parent, int index) {
+        IndexBlock block = store.read(pos, IndexBlock.class);
+        block.root = root;
+        block.parent = parent;
+        block.parentEntryIndex = index;
+        return block;
+    }
+
+    public void reset() {
+        close();
+        try {
+            open();
+        } catch (Exception e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void close() {
+        try {
+            store.close();
+        } catch (Exception e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public boolean isOpen() {
+        return store.isOpen();
+    }
+
+    private void rebuild() throws Exception {
+        LOGGER.warn(String.format("%s is corrupt. Discarding.", this));
+        store.clear();
+        close();
+        doOpen();
+    }
+
+    public void verify() {
+        try {
+            doVerify();
+        } catch (Exception e) {
+            throw new UncheckedIOException(String.format("Some problems were found when checking the integrity of %s.",
+                    this), e);
+        }
+    }
+
+    private void doVerify() throws Exception {
+        List<BlockPayload> blocks = new ArrayList<BlockPayload>();
+
+        HeaderBlock header = store.readFirst(HeaderBlock.class);
+        blocks.add(header);
+        verifyTree(header.getRoot(), "", blocks, Long.MAX_VALUE, true);
+
+        Collections.sort(blocks, new Comparator<BlockPayload>() {
+            public int compare(BlockPayload block, BlockPayload block1) {
+                return block.getPos().compareTo(block1.getPos());
+            }
+        });
+
+        for (int i = 0; i < blocks.size() - 1; i++) {
+            Block b1 = blocks.get(i).getBlock();
+            Block b2 = blocks.get(i + 1).getBlock();
+            if (b1.getPos().getPos() + b1.getSize() > b2.getPos().getPos()) {
+                throw new IOException(String.format("%s overlaps with %s", b1, b2));
+            }
+        }
+    }
+
+    private void verifyTree(IndexBlock current, String prefix, Collection<BlockPayload> blocks, long maxValue,
+                            boolean loadData) throws Exception {
+        blocks.add(current);
+
+        if (!prefix.equals("") && current.entries.size() < maxChildIndexEntries / 2) {
+            throw new IOException(String.format("Too few entries found in %s", current));
+        }
+        if (current.entries.size() > maxChildIndexEntries) {
+            throw new IOException(String.format("Too many entries found in %s", current));
+        }
+
+        boolean isLeaf = current.entries.size() == 0 || current.entries.get(0).childIndexBlock.isNull();
+        if (isLeaf ^ current.tailPos.isNull()) {
+            throw new IOException(String.format("Mismatched leaf/tail-node in %s", current));
+        }
+
+        long min = Long.MIN_VALUE;
+        for (IndexEntry entry : current.entries) {
+            if (isLeaf ^ entry.childIndexBlock.isNull()) {
+                throw new IOException(String.format("Mismatched leaf/non-leaf entry in %s", current));
+            }
+            if (entry.hashCode >= maxValue || entry.hashCode <= min) {
+                throw new IOException(String.format("Out-of-order key in %s", current));
+            }
+            min = entry.hashCode;
+            if (!entry.childIndexBlock.isNull()) {
+                IndexBlock child = store.read(entry.childIndexBlock, IndexBlock.class);
+                verifyTree(child, "   " + prefix, blocks, entry.hashCode, loadData);
+            }
+            if (loadData) {
+                DataBlock block = store.read(entry.dataBlock, DataBlock.class);
+                blocks.add(block);
+            }
+        }
+        if (!current.tailPos.isNull()) {
+            IndexBlock tail = store.read(current.tailPos, IndexBlock.class);
+            verifyTree(tail, "   " + prefix, blocks, maxValue, loadData);
+        }
+    }
+
+    private class IndexRoot {
+        private BlockPointer rootPos = new BlockPointer();
+        private HeaderBlock owner;
+
+        private IndexRoot(HeaderBlock owner) {
+            this.owner = owner;
+        }
+
+        public void setRootPos(BlockPointer rootPos) {
+            this.rootPos = rootPos;
+            store.write(owner);
+        }
+
+        public IndexBlock getRoot() {
+            return load(rootPos, this, null, 0);
+        }
+
+        public IndexBlock newRoot() {
+            IndexBlock block = new IndexBlock();
+            store.write(block);
+            setRootPos(block.getPos());
+            return block;
+        }
+    }
+
+    private class HeaderBlock extends BlockPayload {
+        private IndexRoot index;
+
+        private HeaderBlock() {
+            index = new IndexRoot(this);
+        }
+
+        @Override
+        protected int getType() {
+            return 0x55;
+        }
+
+        @Override
+        protected int getSize() {
+            return Block.LONG_SIZE + Block.SHORT_SIZE;
+        }
+
+        @Override
+        protected void read(DataInputStream instr) throws Exception {
+            index.rootPos = new BlockPointer(instr.readLong());
+
+            short actualChildIndexEntries = instr.readShort();
+            if (actualChildIndexEntries != maxChildIndexEntries) {
+                throw blockCorruptedException();
+            }
+        }
+
+        @Override
+        protected void write(DataOutputStream outstr) throws Exception {
+            outstr.writeLong(index.rootPos.getPos());
+            outstr.writeShort(maxChildIndexEntries);
+        }
+
+        public IndexBlock getRoot() throws Exception {
+            return index.getRoot();
+        }
+    }
+
+    private class IndexBlock extends BlockPayload {
+        private final List<IndexEntry> entries = new ArrayList<IndexEntry>();
+        private BlockPointer tailPos = new BlockPointer();
+        // Transient fields
+        private IndexBlock parent;
+        private int parentEntryIndex;
+        private IndexRoot root;
+
+        @Override
+        protected int getType() {
+            return 0x77;
+        }
+
+        @Override
+        protected int getSize() {
+            return Block.INT_SIZE + Block.LONG_SIZE + (3 * Block.LONG_SIZE) * maxChildIndexEntries;
+        }
+
+        public void read(DataInputStream instr) throws IOException {
+            int count = instr.readInt();
+            entries.clear();
+            for (int i = 0; i < count; i++) {
+                IndexEntry entry = new IndexEntry();
+                entry.hashCode = instr.readLong();
+                entry.dataBlock = new BlockPointer(instr.readLong());
+                entry.childIndexBlock = new BlockPointer(instr.readLong());
+                entries.add(entry);
+            }
+            tailPos = new BlockPointer(instr.readLong());
+        }
+
+        public void write(DataOutputStream outstr) throws IOException {
+            outstr.writeInt(entries.size());
+            for (IndexEntry entry : entries) {
+                outstr.writeLong(entry.hashCode);
+                outstr.writeLong(entry.dataBlock.getPos());
+                outstr.writeLong(entry.childIndexBlock.getPos());
+            }
+            outstr.writeLong(tailPos.getPos());
+        }
+
+        public void put(long hashCode, BlockPointer pos) throws Exception {
+            int index = Collections.binarySearch(entries, new IndexEntry(hashCode));
+            IndexEntry entry;
+            if (index >= 0) {
+                entry = entries.get(index);
+            } else {
+                assert tailPos.isNull();
+                entry = new IndexEntry();
+                entry.hashCode = hashCode;
+                entry.childIndexBlock = new BlockPointer();
+                index = -index - 1;
+                entries.add(index, entry);
+            }
+
+            entry.dataBlock = pos;
+            store.write(this);
+
+            maybeSplit();
+        }
+
+        private void maybeSplit() throws Exception {
+            if (entries.size() > maxChildIndexEntries) {
+                int splitPos = entries.size() / 2;
+                IndexEntry splitEntry = entries.remove(splitPos);
+                if (parent == null) {
+                    parent = root.newRoot();
+                }
+                IndexBlock sibling = new IndexBlock();
+                store.write(sibling);
+                List<IndexEntry> siblingEntries = entries.subList(splitPos, entries.size());
+                sibling.entries.addAll(siblingEntries);
+                siblingEntries.clear();
+                sibling.tailPos = tailPos;
+                tailPos = splitEntry.childIndexBlock;
+                splitEntry.childIndexBlock = new BlockPointer();
+                parent.add(this, splitEntry, sibling);
+            }
+        }
+
+        private void add(IndexBlock left, IndexEntry entry, IndexBlock right) throws Exception {
+            int index = left.parentEntryIndex;
+            if (index < entries.size()) {
+                IndexEntry parentEntry = entries.get(index);
+                assert parentEntry.childIndexBlock.equals(left.getPos());
+                parentEntry.childIndexBlock = right.getPos();
+            } else {
+                assert index == entries.size() && (tailPos.isNull() || tailPos.equals(left.getPos()));
+                tailPos = right.getPos();
+            }
+            entries.add(index, entry);
+            entry.childIndexBlock = left.getPos();
+            store.write(this);
+
+            maybeSplit();
+        }
+
+        public DataBlock get(K key) throws Exception {
+            Lookup lookup = find(key.toString());
+            if (lookup.entry == null) {
+                return null;
+            }
+
+            return store.read(lookup.entry.dataBlock, DataBlock.class);
+        }
+
+        public Lookup find(String keyString) throws Exception {
+            return find((long) keyString.hashCode());
+        }
+
+        private Lookup find(long hashCode) throws Exception {
+            int index = Collections.binarySearch(entries, new IndexEntry(hashCode));
+            if (index >= 0) {
+                return new Lookup(this, entries.get(index));
+            }
+
+            index = -index - 1;
+            BlockPointer childBlockPos;
+            if (index == entries.size()) {
+                childBlockPos = tailPos;
+            } else {
+                childBlockPos = entries.get(index).childIndexBlock;
+            }
+            if (childBlockPos.isNull()) {
+                return new Lookup(this, null);
+            }
+
+            IndexBlock childBlock = load(childBlockPos, root, this, index);
+            return childBlock.find(hashCode);
+        }
+
+        public void remove(IndexEntry entry) throws Exception {
+            int index = entries.indexOf(entry);
+            assert index >= 0;
+            entries.remove(index);
+            store.write(this);
+
+            if (entry.childIndexBlock.isNull()) {
+                maybeMerge();
+            } else {
+                // Not a leaf node. Move up an entry from a leaf node, then possibly merge the leaf node
+                IndexBlock leafBlock = load(entry.childIndexBlock, root, this, index);
+                leafBlock = leafBlock.findHighestLeaf();
+                IndexEntry highestEntry = leafBlock.entries.remove(leafBlock.entries.size() - 1);
+                highestEntry.childIndexBlock = entry.childIndexBlock;
+                entries.add(index, highestEntry);
+                store.write(leafBlock);
+                leafBlock.maybeMerge();
+            }
+        }
+
+        private void maybeMerge() throws Exception {
+            if (parent == null) {
+                // This is the root block. Can have any number of children <= maxChildIndexEntries
+                if (entries.size() == 0 && !tailPos.isNull()) {
+                    // This is an empty root block, discard it
+                    header.index.setRootPos(tailPos);
+                    store.remove(this);
+                }
+                return;
+            }
+
+            // This is not the root block. Must have children >= minIndexChildNodes
+            if (entries.size() >= minIndexChildNodes) {
+                return;
+            }
+
+            // Attempt to merge with the left sibling
+            IndexBlock left = parent.getPrevious(this);
+            if (left != null) {
+                assert entries.size() + left.entries.size() <= maxChildIndexEntries * 2;
+                if (left.entries.size() > minIndexChildNodes) {
+                    // There are enough entries in this block and the left sibling to make up 2 blocks, so redistribute
+                    // the entries evenly between them
+                    left.mergeFrom(this);
+                    left.maybeSplit();
+                    return;
+                } else {
+                    // There are only enough entries to make up 1 block, so move the entries of the left sibling into
+                    // this block and discard the left sibling. Might also need to merge the parent
+                    left.mergeFrom(this);
+                    parent.maybeMerge();
+                    return;
+                }
+            }
+
+            // Attempt to merge with the right sibling
+            IndexBlock right = parent.getNext(this);
+            if (right != null) {
+                assert entries.size() + right.entries.size() <= maxChildIndexEntries * 2;
+                if (right.entries.size() > minIndexChildNodes) {
+                    // There are enough entries in this block and the right sibling to make up 2 blocks, so redistribute
+                    // the entries evenly between them
+                    mergeFrom(right);
+                    maybeSplit();
+                    return;
+                } else {
+                    // There are only enough entries to make up 1 block, so move the entries of the right sibling into
+                    // this block and discard this block. Might also need to merge the parent
+                    mergeFrom(right);
+                    parent.maybeMerge();
+                    return;
+                }
+            }
+
+            // Should not happen
+            throw new IllegalStateException(String.format("%s does not have any siblings.", getBlock()));
+        }
+
+        private void mergeFrom(IndexBlock right) throws Exception {
+            IndexEntry newChildEntry = parent.entries.remove(parentEntryIndex);
+            if (right.getPos().equals(parent.tailPos)) {
+                parent.tailPos = getPos();
+            } else {
+                IndexEntry newParentEntry = parent.entries.get(parentEntryIndex);
+                assert newParentEntry.childIndexBlock.equals(right.getPos());
+                newParentEntry.childIndexBlock = getPos();
+            }
+            entries.add(newChildEntry);
+            entries.addAll(right.entries);
+            newChildEntry.childIndexBlock = tailPos;
+            tailPos = right.tailPos;
+            store.write(parent);
+            store.write(this);
+            store.remove(right);
+        }
+
+        private IndexBlock getNext(IndexBlock indexBlock) throws Exception {
+            int index = indexBlock.parentEntryIndex + 1;
+            if (index > entries.size()) {
+                return null;
+            }
+            if (index == entries.size()) {
+                return load(tailPos, root, this, index);
+            }
+            return load(entries.get(index).childIndexBlock, root, this, index);
+        }
+
+        private IndexBlock getPrevious(IndexBlock indexBlock) throws Exception {
+            int index = indexBlock.parentEntryIndex - 1;
+            if (index < 0) {
+                return null;
+            }
+            return load(entries.get(index).childIndexBlock, root, this, index);
+        }
+
+        private IndexBlock findHighestLeaf() throws Exception {
+            if (tailPos.isNull()) {
+                return this;
+            }
+            return load(tailPos, root, this, entries.size()).findHighestLeaf();
+        }
+    }
+
+    private static class IndexEntry implements Comparable<IndexEntry> {
+        long hashCode;
+        BlockPointer dataBlock;
+        BlockPointer childIndexBlock;
+
+        private IndexEntry() {
+        }
+
+        private IndexEntry(long hashCode) {
+            this.hashCode = hashCode;
+        }
+
+        public int compareTo(IndexEntry indexEntry) {
+            if (hashCode > indexEntry.hashCode) {
+                return 1;
+            }
+            if (hashCode < indexEntry.hashCode) {
+                return -1;
+            }
+            return 0;
+        }
+    }
+
+    private class Lookup {
+        final IndexBlock indexBlock;
+        final IndexEntry entry;
+
+        private Lookup(IndexBlock indexBlock, IndexEntry entry) {
+            this.indexBlock = indexBlock;
+            this.entry = entry;
+        }
+    }
+
+    private class DataBlock extends BlockPayload {
+        private int size;
+        private byte[] serialisedValue;
+        private V value;
+
+        private DataBlock() {
+        }
+
+        public DataBlock(String key, V value) throws Exception {
+            this.value = value;
+            setValue(value);
+            size = serialisedValue.length;
+        }
+
+        public void setValue(V value) throws Exception {
+            ByteArrayOutputStream outStr = new ByteArrayOutputStream();
+            serializer.write(outStr, value);
+            this.serialisedValue = outStr.toByteArray();
+        }
+
+        public V getValue() throws Exception {
+            if (value == null) {
+                value = serializer.read(new ByteArrayInputStream(serialisedValue));
+            }
+            return value;
+        }
+
+        @Override
+        protected int getType() {
+            return 0x33;
+        }
+
+        @Override
+        protected int getSize() {
+            return 2 * Block.INT_SIZE + size;
+        }
+
+        public void read(DataInputStream instr) throws Exception {
+            size = instr.readInt();
+            int bytes = instr.readInt();
+            serialisedValue = new byte[bytes];
+            instr.readFully(serialisedValue);
+        }
+
+        public void write(DataOutputStream outstr) throws Exception {
+            outstr.writeInt(size);
+            outstr.writeInt(serialisedValue.length);
+            outstr.write(serialisedValue);
+        }
+
+        public boolean useNewValue(V value) throws Exception {
+            setValue(value);
+            boolean ok = serialisedValue.length <= size;
+            if (ok) {
+                store.write(this);
+            }
+            return ok;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/Block.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/Block.java
new file mode 100644
index 0000000..230318d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/Block.java
@@ -0,0 +1,59 @@
+/*
+ * 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.btree;
+
+public abstract class Block {
+    static final int LONG_SIZE = 8;
+    static final int INT_SIZE = 4;
+    static final int SHORT_SIZE = 2;
+
+    private BlockPayload payload;
+
+    protected Block(BlockPayload payload) {
+        this.payload = payload;
+        payload.setBlock(this);
+    }
+
+    public BlockPayload getPayload() {
+        return payload;
+    }
+
+    protected void detach() {
+        payload.setBlock(null);
+        payload = null;
+    }
+    
+    public abstract BlockPointer getPos();
+
+    public abstract int getSize();
+
+    public abstract RuntimeException blockCorruptedException();
+
+    @Override
+    public String toString() {
+        return String.format("%s %s", payload.getClass().getSimpleName(), getPos());
+    }
+
+    public BlockPointer getNextPos() {
+        return new BlockPointer(getPos().getPos() + getSize());
+    }
+
+    public abstract boolean hasPos();
+
+    public abstract void setPos(BlockPointer pos);
+
+    public abstract void setSize(int size);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/BlockPayload.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/BlockPayload.java
new file mode 100644
index 0000000..e07bc30
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/BlockPayload.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.cache.btree;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+
+public abstract class BlockPayload {
+    private Block block;
+
+    public Block getBlock() {
+        return block;
+    }
+
+    public void setBlock(Block block) {
+        this.block = block;
+    }
+
+    public BlockPointer getPos() {
+        return getBlock().getPos();
+    }
+
+    public BlockPointer getNextPos() {
+        return getBlock().getNextPos();
+    }
+
+    protected abstract int getSize();
+
+    protected abstract int getType();
+
+    protected abstract void read(DataInputStream inputStream) throws Exception;
+
+    protected abstract void write(DataOutputStream outputStream) throws Exception;
+
+    protected RuntimeException blockCorruptedException() {
+        return getBlock().blockCorruptedException();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/BlockPointer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/BlockPointer.java
new file mode 100644
index 0000000..62ef4e2
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/BlockPointer.java
@@ -0,0 +1,68 @@
+/*
+ * 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.btree;
+
+public class BlockPointer implements Comparable<BlockPointer> {
+    private final long pos;
+
+    public BlockPointer() {
+        pos = -1;
+    }
+
+    public BlockPointer(long pos) {
+        this.pos = pos;
+    }
+
+    public boolean isNull() {
+        return pos < 0;
+    }
+
+    public long getPos() {
+        return pos;
+    }
+
+    @Override
+    public String toString() {
+        return String.valueOf(pos);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+        BlockPointer other = (BlockPointer) obj;
+        return pos == other.pos;
+    }
+
+    @Override
+    public int hashCode() {
+        return (int) pos;
+    }
+
+    public int compareTo(BlockPointer o) {
+        if (pos > o.pos) {
+            return 1;
+        }
+        if (pos < o.pos) {
+            return -1;
+        }
+        return 0;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/BlockStore.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/BlockStore.java
new file mode 100644
index 0000000..71db2a8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/BlockStore.java
@@ -0,0 +1,68 @@
+/*
+ * 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.btree;
+
+public interface BlockStore {
+    /**
+     * Opens this store, calling the given action if the store is empty.
+     */
+    void open(Runnable initAction, Factory factory);
+
+    /**
+     * Closes this store.
+     */
+    void close();
+
+    /**
+     * Discards all blocks from this store.
+     */
+    void clear();
+
+    /**
+     * Removes the given block from this store.
+     */
+    void remove(BlockPayload block);
+
+    /**
+     * Reads the first block from this store.
+     */
+    <T extends BlockPayload> T readFirst(Class<T> payloadType);
+    
+    /**
+     * Reads a block from this store.
+     */
+    <T extends BlockPayload> T read(BlockPointer pos, Class<T> payloadType);
+
+    /**
+     * Writes a block to this store, adding the block if required.
+     */
+    void write(BlockPayload block);
+
+    /**
+     * Adds a new block to this store. Allocates space for the block, but does not write the contents of the block
+     * until {@link #write(BlockPayload)} is called.
+     */
+    void attach(BlockPayload block);
+
+    /**
+     * Flushes any pending updates for this store.
+     */
+    void flush();
+
+    interface Factory {
+        Object create(Class<? extends BlockPayload> type);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/CachingBlockStore.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/CachingBlockStore.java
new file mode 100644
index 0000000..77fb95e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/CachingBlockStore.java
@@ -0,0 +1,100 @@
+/*
+ * 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.btree;
+
+import org.apache.commons.collections.map.LRUMap;
+
+import java.util.*;
+
+public class CachingBlockStore implements BlockStore {
+    private final BlockStore store;
+    private final Map<BlockPointer, BlockPayload> dirty = new LinkedHashMap<BlockPointer, BlockPayload>();
+    private final Map<BlockPointer, BlockPayload> indexBlockCache = new LRUMap(100);
+    private final Set<Class<?>> cachableTypes = new HashSet<Class<?>>();
+
+    public CachingBlockStore(BlockStore store, Class<? extends BlockPayload>... cacheableBlockTypes) {
+        this.store = store;
+        cachableTypes.addAll(Arrays.asList(cacheableBlockTypes));
+    }
+
+    public void open(Runnable initAction, Factory factory) {
+        store.open(initAction, factory);
+    }
+
+    public void close() {
+        flush();
+        indexBlockCache.clear();
+        store.close();
+    }
+
+    public void clear() {
+        dirty.clear();
+        indexBlockCache.clear();
+        store.clear();
+    }
+
+    public void flush() {
+        Iterator<BlockPayload> iterator = dirty.values().iterator();
+        while (iterator.hasNext()) {
+            BlockPayload block = iterator.next();
+            iterator.remove();
+            store.write(block);
+        }
+        store.flush();
+    }
+
+    public void attach(BlockPayload block) {
+        store.attach(block);
+    }
+
+    public void remove(BlockPayload block) {
+        dirty.remove(block.getPos());
+        indexBlockCache.remove(block.getPos());
+        store.remove(block);
+    }
+
+    public <T extends BlockPayload> T readFirst(Class<T> payloadType) {
+        T block = store.readFirst(payloadType);
+        maybeCache(block);
+        return block;
+    }
+
+    public <T extends BlockPayload> T read(BlockPointer pos, Class<T> payloadType) {
+        T block = payloadType.cast(dirty.get(pos));
+        if (block != null) {
+            return block;
+        }
+        block = payloadType.cast(indexBlockCache.get(pos));
+        if (block != null) {
+            return block;
+        }
+        block = store.read(pos, payloadType);
+        maybeCache(block);
+        return block;
+    }
+
+    public void write(BlockPayload block) {
+        store.attach(block);
+        maybeCache(block);
+        dirty.put(block.getPos(), block);
+    }
+
+    private <T extends BlockPayload> void maybeCache(T block) {
+        if (cachableTypes.contains(block.getClass())) {
+            indexBlockCache.put(block.getPos(), block);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/CorruptedCacheException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/CorruptedCacheException.java
new file mode 100644
index 0000000..c9af8ba
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/CorruptedCacheException.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.cache.btree;
+
+class CorruptedCacheException extends RuntimeException {
+    CorruptedCacheException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/FileBackedBlockStore.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/FileBackedBlockStore.java
new file mode 100644
index 0000000..540c8d1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/FileBackedBlockStore.java
@@ -0,0 +1,352 @@
+/*
+ * 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.btree;
+
+import org.gradle.api.UncheckedIOException;
+
+import java.io.*;
+import java.util.zip.CRC32;
+
+public class FileBackedBlockStore implements BlockStore {
+    private RandomAccessFile file;
+    private final File cacheFile;
+    private long nextBlock;
+    private Factory factory;
+
+    public FileBackedBlockStore(File cacheFile) {
+        this.cacheFile = cacheFile;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("cache '%s'", cacheFile);
+    }
+
+    public void open(Runnable runnable, Factory factory) {
+        this.factory = factory;
+        try {
+            file = new RandomAccessFile(cacheFile, "rw");
+            nextBlock = file.length();
+            if (file.length() == 0) {
+                runnable.run();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void close() {
+        try {
+            file.close();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void clear() {
+        try {
+            file.setLength(0);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        nextBlock = 0;
+    }
+
+    public void attach(BlockPayload block) {
+        if (block.getBlock() == null) {
+            block.setBlock(new BlockImpl(block));
+        }
+    }
+
+    public void remove(BlockPayload block) {
+        BlockImpl blockImpl = (BlockImpl) block.getBlock();
+        blockImpl.detach();
+    }
+
+    public void flush() {
+    }
+
+    public <T extends BlockPayload> T readFirst(Class<T> payloadType) {
+        return read(new BlockPointer(0), payloadType);
+    }
+
+    public <T extends BlockPayload> T read(BlockPointer pos, Class<T> payloadType) {
+        assert !pos.isNull();
+        try {
+            T payload = payloadType.cast(factory.create(payloadType));
+            BlockImpl block = new BlockImpl(payload, pos);
+            block.read();
+            return payload;
+        } catch (CorruptedCacheException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void write(BlockPayload block) {
+        BlockImpl blockImpl = (BlockImpl) block.getBlock();
+        try {
+            blockImpl.write();
+        } catch (CorruptedCacheException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    private long alloc(long length) {
+        long pos = nextBlock;
+        nextBlock += length;
+        return pos;
+    }
+
+    private final class BlockImpl extends Block {
+        private static final int HEADER_SIZE = 2 + INT_SIZE;
+        private static final int TAIL_SIZE = LONG_SIZE;
+        static final int BLOCK_MARKER = 0xCC;
+
+        private BlockPointer pos;
+        private int payloadSize;
+
+        private BlockImpl(BlockPayload payload, BlockPointer pos) {
+            this(payload);
+            setPos(pos);
+        }
+
+        public BlockImpl(BlockPayload payload) {
+            super(payload);
+            pos = null;
+            payloadSize = -1;
+        }
+
+        @Override
+        public boolean hasPos() {
+            return pos != null;
+        }
+
+        @Override
+        public BlockPointer getPos() {
+            if (pos == null) {
+                pos = new BlockPointer(alloc(getSize()));
+            }
+            return pos;
+        }
+
+        @Override
+        public void setPos(BlockPointer pos) {
+            assert this.pos == null && !pos.isNull();
+            this.pos = pos;
+        }
+
+        public int getSize() {
+            if (payloadSize < 0) {
+                payloadSize = getPayload().getSize();
+            }
+            return payloadSize + HEADER_SIZE + TAIL_SIZE;
+        }
+
+        @Override
+        public void setSize(int size) {
+            int newPayloadSize = size - HEADER_SIZE - TAIL_SIZE;
+            assert newPayloadSize >= payloadSize;
+            payloadSize = newPayloadSize;
+        }
+
+        public void write() throws Exception {
+            long pos = getPos().getPos();
+            file.seek(pos);
+
+            Crc32OutputStream checkSumOutputStream = new Crc32OutputStream(new BufferedOutputStream(
+                    new RandomAccessFileOutputStream(file)));
+            DataOutputStream outputStream = new DataOutputStream(checkSumOutputStream);
+
+            BlockPayload payload = getPayload();
+
+            // Write header
+            outputStream.writeByte(BLOCK_MARKER);
+            outputStream.writeByte(payload.getType());
+            outputStream.writeInt(payloadSize);
+            long finalSize = pos + HEADER_SIZE + TAIL_SIZE + payloadSize;
+
+            // Write body
+            payload.write(outputStream);
+
+            // Write checksum
+            outputStream.writeLong(checkSumOutputStream.checksum.getValue());
+            outputStream.close();
+
+            // Pad
+            if (file.length() < finalSize) {
+                file.setLength(finalSize);
+            }
+        }
+
+        public void read() throws Exception {
+            long pos = getPos().getPos();
+            assert pos >= 0;
+            if (pos + HEADER_SIZE >= file.length()) {
+                throw blockCorruptedException();
+            }
+            file.seek(pos);
+
+            Crc32InputStream checkSumInputStream = new Crc32InputStream(new BufferedInputStream(
+                    new RandomAccessFileInputStream(file)));
+            DataInputStream inputStream = new DataInputStream(checkSumInputStream);
+
+            BlockPayload payload = getPayload();
+
+            // Read header
+            byte type = inputStream.readByte();
+            if (type != (byte) BLOCK_MARKER) {
+                throw blockCorruptedException();
+            }
+            type = inputStream.readByte();
+            if (type != (byte) payload.getType()) {
+                throw blockCorruptedException();
+            }
+
+            // Read body
+            payloadSize = inputStream.readInt();
+            if (pos + HEADER_SIZE + TAIL_SIZE + payloadSize > file.length()) {
+                throw blockCorruptedException();
+            }
+            payload.read(inputStream);
+
+            // Read and verify checksum
+            long actualChecksum = checkSumInputStream.checksum.getValue();
+            long checksum = inputStream.readLong();
+            if (actualChecksum != checksum) {
+                throw blockCorruptedException();
+            }
+            inputStream.close();
+        }
+
+        public RuntimeException blockCorruptedException() {
+            return new CorruptedCacheException(String.format("Corrupted %s found in %s.", this,
+                    FileBackedBlockStore.this));
+        }
+    }
+
+    private static class RandomAccessFileInputStream extends InputStream {
+        private final RandomAccessFile file;
+
+        private RandomAccessFileInputStream(RandomAccessFile file) {
+            this.file = file;
+        }
+
+        @Override
+        public int read(byte[] bytes) throws IOException {
+            return file.read(bytes);
+        }
+
+        @Override
+        public int read() throws IOException {
+            return file.read();
+        }
+
+        @Override
+        public int read(byte[] bytes, int offset, int length) throws IOException {
+            return file.read(bytes, offset, length);
+        }
+    }
+
+    private static class RandomAccessFileOutputStream extends OutputStream {
+        private final RandomAccessFile file;
+
+        private RandomAccessFileOutputStream(RandomAccessFile file) {
+            this.file = file;
+        }
+
+        @Override
+        public void write(int i) throws IOException {
+            file.write(i);
+        }
+
+        @Override
+        public void write(byte[] bytes) throws IOException {
+            file.write(bytes);
+        }
+
+        @Override
+        public void write(byte[] bytes, int offset, int length) throws IOException {
+            file.write(bytes, offset, length);
+        }
+    }
+
+    private static class Crc32InputStream extends FilterInputStream {
+        private final CRC32 checksum;
+
+        private Crc32InputStream(InputStream inputStream) {
+            super(inputStream);
+            checksum = new CRC32();
+        }
+
+        @Override
+        public int read() throws IOException {
+            int b = in.read();
+            if (b >= 0) {
+                checksum.update(b);
+            }
+            return b;
+        }
+
+        @Override
+        public int read(byte[] bytes) throws IOException {
+            int count = in.read(bytes);
+            if (count > 0) {
+                checksum.update(bytes, 0, count);
+            }
+            return count;
+        }
+
+        @Override
+        public int read(byte[] bytes, int offset, int max) throws IOException {
+            int count = in.read(bytes, offset, max);
+            if (count > 0) {
+                checksum.update(bytes, offset, count);
+            }
+            return count;
+        }
+    }
+
+    private static class Crc32OutputStream extends FilterOutputStream {
+        private final CRC32 checksum;
+
+        private Crc32OutputStream(OutputStream outputStream) {
+            super(outputStream);
+            this.checksum = new CRC32();
+        }
+
+        @Override
+        public void write(int b) throws IOException {
+            checksum.update(b);
+            out.write(b);
+        }
+
+        @Override
+        public void write(byte[] bytes) throws IOException {
+            checksum.update(bytes);
+            out.write(bytes);
+        }
+
+        @Override
+        public void write(byte[] bytes, int offset, int count) throws IOException {
+            checksum.update(bytes, offset, count);
+            out.write(bytes, offset, count);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/FreeListBlockStore.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/FreeListBlockStore.java
new file mode 100644
index 0000000..7f4c4e0
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/FreeListBlockStore.java
@@ -0,0 +1,271 @@
+/*
+ * 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.btree;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class FreeListBlockStore implements BlockStore {
+    private final BlockStore store;
+    private final BlockStore freeListStore;
+    private final int maxBlockEntries;
+    private FreeListBlock freeListBlock;
+
+    public FreeListBlockStore(BlockStore store, int maxBlockEntries) {
+        this.store = store;
+        freeListStore = this;
+        this.maxBlockEntries = maxBlockEntries;
+    }
+
+    public void open(final Runnable initAction, final Factory factory) {
+        Runnable freeListInitAction = new Runnable() {
+            public void run() {
+                freeListBlock = new FreeListBlock();
+                store.write(freeListBlock);
+                store.flush();
+                initAction.run();
+            }
+        };
+        Factory freeListFactory = new Factory() {
+            public Object create(Class<? extends BlockPayload> type) {
+                if (type == FreeListBlock.class) {
+                    return new FreeListBlock();
+                }
+                return factory.create(type);
+            }
+        };
+
+        store.open(freeListInitAction, freeListFactory);
+        freeListBlock = store.readFirst(FreeListBlock.class);
+    }
+
+    public void close() {
+        freeListBlock = null;
+        store.close();
+    }
+
+    public void clear() {
+        store.clear();
+    }
+
+    public void remove(BlockPayload block) {
+        Block container = block.getBlock();
+        store.remove(block);
+        freeListBlock.add(container.getPos(), container.getSize());
+    }
+
+    public <T extends BlockPayload> T readFirst(Class<T> payloadType) {
+        return store.read(freeListBlock.getNextPos(), payloadType);
+    }
+
+    public <T extends BlockPayload> T read(BlockPointer pos, Class<T> payloadType) {
+        return store.read(pos, payloadType);
+    }
+
+    public void write(BlockPayload block) {
+        attach(block);
+        store.write(block);
+    }
+
+    public void attach(BlockPayload block) {
+        store.attach(block);
+        freeListBlock.alloc(block.getBlock());
+    }
+
+    public void flush() {
+        store.flush();
+    }
+
+    private void verify() {
+        FreeListBlock block = store.readFirst(FreeListBlock.class);
+        verify(block, Integer.MAX_VALUE);
+    }
+
+    private void verify(FreeListBlock block, int maxValue) {
+        if (block.largestInNextBlock > maxValue) {
+            throw new RuntimeException("corrupt free list");
+        }
+        int current = 0;
+        for (FreeListEntry entry : block.entries) {
+            if (entry.size > maxValue) {
+                throw new RuntimeException("corrupt free list");
+            }
+            if (entry.size < block.largestInNextBlock) {
+                throw new RuntimeException("corrupt free list");
+            }
+            if (entry.size < current) {
+                throw new RuntimeException("corrupt free list");
+            }
+            current = entry.size;
+        }
+        if (!block.nextBlock.isNull()) {
+            verify(store.read(block.nextBlock, FreeListBlock.class), block.largestInNextBlock);
+        }
+    }
+
+    public class FreeListBlock extends BlockPayload {
+        private List<FreeListEntry> entries = new ArrayList<FreeListEntry>();
+        private int largestInNextBlock;
+        private BlockPointer nextBlock = new BlockPointer();
+        // Transient fields
+        private FreeListBlock prev;
+        private FreeListBlock next;
+
+        @Override
+        protected int getSize() {
+            return Block.LONG_SIZE + Block.INT_SIZE + Block.INT_SIZE + maxBlockEntries * (Block.LONG_SIZE
+                    + Block.INT_SIZE);
+        }
+
+        @Override
+        protected int getType() {
+            return 0x44;
+        }
+
+        @Override
+        protected void read(DataInputStream inputStream) throws Exception {
+            nextBlock = new BlockPointer(inputStream.readLong());
+            largestInNextBlock = inputStream.readInt();
+            int count = inputStream.readInt();
+            for (int i = 0; i < count; i++) {
+                BlockPointer pos = new BlockPointer(inputStream.readLong());
+                int size = inputStream.readInt();
+                entries.add(new FreeListEntry(pos, size));
+            }
+        }
+
+        @Override
+        protected void write(DataOutputStream outputStream) throws Exception {
+            outputStream.writeLong(nextBlock.getPos());
+            outputStream.writeInt(largestInNextBlock);
+            outputStream.writeInt(entries.size());
+            for (FreeListEntry entry : entries) {
+                outputStream.writeLong(entry.pos.getPos());
+                outputStream.writeInt(entry.size);
+            }
+        }
+
+        public void add(BlockPointer pos, int size) {
+            assert !pos.isNull() && size >= 0;
+            if (size == 0) {
+                return;
+            }
+
+            if (size < largestInNextBlock) {
+                FreeListBlock next = getNextBlock();
+                next.add(pos, size);
+                return;
+            }
+
+            FreeListEntry entry = new FreeListEntry(pos, size);
+            int index = Collections.binarySearch(entries, entry);
+            if (index < 0) {
+                index = -index - 1;
+            }
+            entries.add(index, entry);
+
+            if (entries.size() > maxBlockEntries) {
+                FreeListBlock newBlock = new FreeListBlock();
+                newBlock.largestInNextBlock = largestInNextBlock;
+                newBlock.nextBlock = nextBlock;
+                newBlock.prev = this;
+                newBlock.next = next;
+                next = newBlock;
+
+                List<FreeListEntry> newBlockEntries = entries.subList(0, entries.size() / 2);
+                newBlock.entries.addAll(newBlockEntries);
+                newBlockEntries.clear();
+                largestInNextBlock = newBlock.entries.get(newBlock.entries.size() - 1).size;
+                freeListStore.write(newBlock);
+                nextBlock = newBlock.getPos();
+            }
+
+            freeListStore.write(this);
+        }
+
+        private FreeListBlock getNextBlock() {
+            if (next == null) {
+                next = freeListStore.read(nextBlock, FreeListBlock.class);
+                next.prev = this;
+            }
+            return next;
+        }
+
+        public void alloc(Block block) {
+            if (block.hasPos()) {
+                return;
+            }
+
+            int requiredSize = block.getSize();
+
+            if (entries.isEmpty() || requiredSize <= largestInNextBlock) {
+                if (nextBlock.isNull()) {
+                    return;
+                }
+                getNextBlock().alloc(block);
+                return;
+            }
+
+            int index = Collections.binarySearch(entries, new FreeListEntry(null, requiredSize));
+            if (index < 0) {
+                index = -index - 1;
+            }
+            if (index == entries.size()) {
+                // Largest free block is too small
+                return;
+            }
+
+            FreeListEntry entry = entries.remove(index);
+            block.setPos(entry.pos);
+            block.setSize(entry.size);
+            freeListStore.write(this);
+
+            if (entries.size() == 0 && prev != null) {
+                prev.nextBlock = nextBlock;
+                prev.largestInNextBlock = largestInNextBlock;
+                prev.next = next;
+                if (next != null) {
+                    next.prev = prev;
+                }
+                freeListStore.write(prev);
+                freeListStore.remove(this);
+            }
+        }
+    }
+
+    private static class FreeListEntry implements Comparable<FreeListEntry> {
+        final BlockPointer pos;
+        final int size;
+
+        private FreeListEntry(BlockPointer pos, int size) {
+            this.pos = pos;
+            this.size = size;
+        }
+
+        public int compareTo(FreeListEntry o) {
+            if (size > o.size) {
+                return 1;
+            }
+            if (size < o.size) {
+                return -1;
+            }
+            return 0;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/StateCheckBlockStore.java b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/StateCheckBlockStore.java
new file mode 100644
index 0000000..5ad762c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/cache/btree/StateCheckBlockStore.java
@@ -0,0 +1,78 @@
+/*
+ * 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.btree;
+
+public class StateCheckBlockStore implements BlockStore {
+    private final BlockStore blockStore;
+    private boolean open;
+
+    public StateCheckBlockStore(BlockStore blockStore) {
+        this.blockStore = blockStore;
+    }
+
+    public void open(Runnable initAction, Factory factory) {
+        assert !open;
+        open = true;
+        blockStore.open(initAction, factory);
+    }
+
+    public boolean isOpen() {
+        return open;
+    }
+
+    public void close() {
+        if (!open) {
+            return;
+        }
+        open = false;
+        blockStore.close();
+    }
+
+    public void clear() {
+        assert open;
+        blockStore.clear();
+    }
+
+    public void remove(BlockPayload block) {
+        assert open;
+        blockStore.remove(block);
+    }
+
+    public <T extends BlockPayload> T readFirst(Class<T> payloadType) {
+        assert open;
+        return blockStore.readFirst(payloadType);
+    }
+
+    public <T extends BlockPayload> T read(BlockPointer pos, Class<T> payloadType) {
+        assert open;
+        return blockStore.read(pos, payloadType);
+    }
+
+    public void write(BlockPayload block) {
+        assert open;
+        blockStore.write(block);
+    }
+
+    public void attach(BlockPayload block) {
+        assert open;
+        blockStore.attach(block);
+    }
+
+    public void flush() {
+        assert open;
+        blockStore.flush();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/BuildConfigurer.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/BuildConfigurer.groovy
new file mode 100644
index 0000000..4d88adc
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/BuildConfigurer.groovy
@@ -0,0 +1,52 @@
+/*
+ * 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.configuration
+
+import org.gradle.api.Project
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.util.Clock
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.gradle.api.Action
+
+/**
+ * @author Hans Dockter
+ */
+class BuildConfigurer {
+    private static Logger logger = LoggerFactory.getLogger(BuildConfigurer)
+
+    ProjectDependencies2TaskResolver projectDependencies2TasksResolver
+
+    Action<Project> projectEvaluateAction
+
+    BuildConfigurer() {}
+
+    BuildConfigurer(ProjectDependencies2TaskResolver projectDependencies2TasksResolver) {
+        this.projectDependencies2TasksResolver = projectDependencies2TasksResolver
+        projectEvaluateAction = {ProjectInternal project ->
+            project.evaluate()
+        } as Action
+    }
+
+    void process(Project rootProject) {
+        logger.debug('Configuring Project objects')
+        Clock clock = new Clock()
+        rootProject.allprojects(projectEvaluateAction)
+        projectDependencies2TasksResolver.resolve(rootProject)
+        logger.debug("Timing: Configuring projects took " + clock.time)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/BuildScriptProcessor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/BuildScriptProcessor.java
new file mode 100644
index 0000000..9d7ffcc
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/BuildScriptProcessor.java
@@ -0,0 +1,45 @@
+/*
+ * 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.configuration;
+
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ProjectStateInternal;
+import org.gradle.util.Clock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class BuildScriptProcessor implements ProjectEvaluator {
+    private static final Logger LOGGER = LoggerFactory.getLogger(BuildScriptProcessor.class);
+    private final ScriptPluginFactory configurerFactory;
+
+    public BuildScriptProcessor(ScriptPluginFactory configurerFactory) {
+        this.configurerFactory = configurerFactory;
+    }
+
+    public void evaluate(ProjectInternal project, ProjectStateInternal state) {
+        LOGGER.info(String.format("Evaluating %s using %s.", project, project.getBuildScriptSource().getDisplayName()));
+        Clock clock = new Clock();
+
+        try {
+            ScriptPlugin configurer = configurerFactory.create(project.getBuildScriptSource());
+            configurer.apply(project);
+        } catch (Exception e) {
+            state.executed(e);
+        }
+
+        LOGGER.debug("Timing: Running the build script took " + clock.getTime());
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/DefaultInitScriptProcessor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/DefaultInitScriptProcessor.java
new file mode 100644
index 0000000..5fa1fce
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/DefaultInitScriptProcessor.java
@@ -0,0 +1,39 @@
+/*
+ * 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.configuration;
+
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.initialization.InitScript;
+
+/**
+ * Processes (and runs) an init script for a specified build.  Handles defining
+ * the classpath based on the initscript {} configuration closure.
+ */
+public class DefaultInitScriptProcessor implements InitScriptProcessor {
+    private final ScriptPluginFactory configurerFactory;
+
+    public DefaultInitScriptProcessor(ScriptPluginFactory configurerFactory) {
+        this.configurerFactory = configurerFactory;
+    }
+
+    public void process(ScriptSource initScript, GradleInternal gradle) {
+        ScriptPlugin configurer = configurerFactory.create(initScript);
+        configurer.setClasspathClosureName("initscript");
+        configurer.setScriptBaseClass(InitScript.class);
+        configurer.apply(gradle);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/DefaultProjectEvaluator.java b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/DefaultProjectEvaluator.java
new file mode 100644
index 0000000..87ba542
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/DefaultProjectEvaluator.java
@@ -0,0 +1,45 @@
+/*
+ * 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.configuration;
+
+import org.gradle.api.ProjectEvaluationListener;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ProjectStateInternal;
+
+public class DefaultProjectEvaluator implements ProjectEvaluator {
+    private final ProjectEvaluator evaluator;
+
+    public DefaultProjectEvaluator(ProjectEvaluator evaluator) {
+        this.evaluator = evaluator;
+    }
+
+    public void evaluate(ProjectInternal project, ProjectStateInternal state) {
+        if (state.getExecuted()) {
+            return;
+        }
+
+        ProjectEvaluationListener listener = project.getProjectEvaluationBroadcaster();
+        listener.beforeEvaluate(project);
+        state.setExecuting(true);
+        try {
+            evaluator.evaluate(project, state);
+        } finally {
+            state.setExecuting(false);
+            state.executed();
+            listener.afterEvaluate(project, state);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/DefaultScriptPluginFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/DefaultScriptPluginFactory.java
new file mode 100644
index 0000000..022c381
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/DefaultScriptPluginFactory.java
@@ -0,0 +1,130 @@
+/*
+ * 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.configuration;
+
+import org.gradle.api.internal.artifacts.dsl.BuildScriptClasspathScriptTransformer;
+import org.gradle.api.internal.artifacts.dsl.BuildScriptTransformer;
+import org.gradle.api.internal.initialization.ScriptClassLoaderProvider;
+import org.gradle.api.internal.initialization.ScriptHandlerFactory;
+import org.gradle.api.internal.initialization.ScriptHandlerInternal;
+import org.gradle.api.internal.project.DefaultServiceRegistry;
+import org.gradle.groovy.scripts.*;
+import org.gradle.logging.LoggingManagerFactory;
+import org.gradle.logging.LoggingManagerInternal;
+
+public class DefaultScriptPluginFactory implements ScriptPluginFactory {
+    private final ScriptCompilerFactory scriptCompilerFactory;
+    private final ImportsReader importsReader;
+    private final ScriptHandlerFactory scriptHandlerFactory;
+    private final ClassLoader defaultClassLoader;
+    private final LoggingManagerFactory loggingManagerFactory;
+
+    public DefaultScriptPluginFactory(ScriptCompilerFactory scriptCompilerFactory,
+                                                ImportsReader importsReader,
+                                                ScriptHandlerFactory scriptHandlerFactory,
+                                                ClassLoader defaultClassLoader,
+                                                LoggingManagerFactory loggingManagerFactory) {
+        this.scriptCompilerFactory = scriptCompilerFactory;
+        this.importsReader = importsReader;
+        this.scriptHandlerFactory = scriptHandlerFactory;
+        this.defaultClassLoader = defaultClassLoader;
+        this.loggingManagerFactory = loggingManagerFactory;
+    }
+
+    public ScriptPlugin create(ScriptSource scriptSource) {
+        return new ScriptPluginImpl(scriptSource);
+    }
+
+    private class ScriptPluginImpl implements ScriptPlugin {
+        private final ScriptSource scriptSource;
+        private String classpathClosureName = "buildscript";
+        private Class<? extends BasicScript> scriptType = DefaultScript.class;
+        private ScriptClassLoaderProvider classLoaderProvider;
+        private ClassLoader classLoader = defaultClassLoader;
+
+        public ScriptPluginImpl(ScriptSource scriptSource) {
+            this.scriptSource = scriptSource;
+        }
+
+        public ScriptSource getSource() {
+            return scriptSource;
+        }
+
+        public ScriptPlugin setClasspathClosureName(String name) {
+            this.classpathClosureName = name;
+            return this;
+        }
+
+        public ScriptPlugin setClassLoader(ClassLoader classLoader) {
+            this.classLoader = classLoader;
+            return this;
+        }
+
+        public ScriptPlugin setClassLoaderProvider(ScriptClassLoaderProvider classLoaderProvider) {
+            this.classLoaderProvider = classLoaderProvider;
+            return this;
+        }
+
+        public ScriptPlugin setScriptBaseClass(Class<? extends BasicScript> type) {
+            scriptType = type;
+            return this;
+        }
+
+        public void apply(Object target) {
+            DefaultServiceRegistry services = new DefaultServiceRegistry();
+            services.add(ScriptPluginFactory.class, DefaultScriptPluginFactory.this);
+            services.add(LoggingManagerInternal.class, loggingManagerFactory.create());
+
+            ScriptAware scriptAware = null;
+            if (target instanceof ScriptAware) {
+                scriptAware = (ScriptAware) target;
+                scriptAware.beforeCompile(this);
+            }
+            ScriptClassLoaderProvider classLoaderProvider = this.classLoaderProvider;
+            ScriptSource withImports = importsReader.withImports(scriptSource);
+
+            if (classLoaderProvider == null) {
+                ScriptHandlerInternal defaultScriptHandler = scriptHandlerFactory.create(withImports, classLoader);
+                services.add(ScriptHandlerInternal.class, defaultScriptHandler);
+                classLoaderProvider = defaultScriptHandler;
+            }
+            
+            ScriptCompiler compiler = scriptCompilerFactory.createCompiler(withImports);
+
+            compiler.setClassloader(classLoaderProvider.getClassLoader());
+
+            BuildScriptClasspathScriptTransformer classpathScriptTransformer
+                    = new BuildScriptClasspathScriptTransformer(classpathClosureName);
+            compiler.setTransformer(classpathScriptTransformer);
+
+            ScriptRunner<? extends BasicScript> classPathScriptRunner = compiler.compile(scriptType);
+            classPathScriptRunner.getScript().init(target, services);
+            classPathScriptRunner.run();
+
+            classLoaderProvider.updateClassPath();
+
+            compiler.setTransformer(new BuildScriptTransformer(classpathScriptTransformer));
+            ScriptRunner<? extends BasicScript> runner = compiler.compile(scriptType);
+
+            runner.getScript().init(target, services);
+            if (scriptAware != null) {
+                scriptAware.afterCompile(this, runner.getScript());
+            }
+            runner.run();
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/ImportsReader.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/ImportsReader.groovy
new file mode 100644
index 0000000..1f762b1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/ImportsReader.groovy
@@ -0,0 +1,32 @@
+/*
+ * 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.configuration
+
+import org.gradle.groovy.scripts.ScriptSource
+
+/**
+ * @author Hans Dockter
+ */
+class ImportsReader {
+    String getImports() {
+        getClass().getResource('default-imports.txt').text
+    }
+
+    ScriptSource withImports(ScriptSource source) {
+        new ImportsScriptSource(source, this)
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/ImportsScriptSource.java b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/ImportsScriptSource.java
new file mode 100644
index 0000000..dd1d108
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/ImportsScriptSource.java
@@ -0,0 +1,56 @@
+/*
+ * 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.configuration;
+
+import org.gradle.api.internal.resource.DelegatingResource;
+import org.gradle.api.internal.resource.Resource;
+import org.gradle.groovy.scripts.DelegatingScriptSource;
+import org.gradle.groovy.scripts.ScriptSource;
+
+public class ImportsScriptSource extends DelegatingScriptSource {
+    private final ImportsReader importsReader;
+
+    public ImportsScriptSource(ScriptSource source, ImportsReader importsReader) {
+        super(source);
+        this.importsReader = importsReader;
+    }
+
+    @Override
+    public Resource getResource() {
+        return new ImportsResource(super.getResource());
+    }
+
+    private class ImportsResource extends DelegatingResource {
+        private ImportsResource(Resource resource) {
+            super(resource);
+        }
+
+        @Override
+        public String getText() {
+            String text = getResource().getText();
+            assert text != null;
+
+            String imports;
+            if (text.length() > 0) {
+                imports = '\n' + importsReader.getImports();
+            } else {
+                imports = "";
+            }
+
+            return text + imports;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/InitScriptProcessor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/InitScriptProcessor.java
new file mode 100644
index 0000000..f172434
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/InitScriptProcessor.java
@@ -0,0 +1,23 @@
+/*
+ * 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.configuration;
+
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.api.internal.GradleInternal;
+
+public interface InitScriptProcessor {
+    void process(ScriptSource initScript, GradleInternal gradle);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/ProjectDependencies2TaskResolver.java b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/ProjectDependencies2TaskResolver.java
new file mode 100644
index 0000000..0f58513
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/ProjectDependencies2TaskResolver.java
@@ -0,0 +1,49 @@
+/*
+ * 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.configuration;
+
+import org.gradle.api.Action;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author Hans Dockter
+ */
+public class ProjectDependencies2TaskResolver {
+    private static Logger logger = LoggerFactory.getLogger(ProjectDependencies2TaskResolver.class);
+
+    public void resolve(Project rootProject) {
+        Action<Project> projectAction = new Action<Project>() {
+            public void execute(Project project) {
+                for (Project dependsOnProject : project.getDependsOnProjects()) {
+                    logger.debug("Checking task dependencies for project: {} dependsOn: {}", project, dependsOnProject);
+                    for (Task task : project.getTasks()) {
+                        String taskName = task.getName();
+                        Task dependentTask = dependsOnProject.getTasks().findByName(taskName);
+                        if (dependentTask != null) {
+                            logger.debug("Setting task dependencies for task: {}", taskName);
+                            task.dependsOn(dependentTask);
+                        }
+                    }
+                }
+            }
+        };
+        rootProject.allprojects(projectAction);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/ProjectEvaluator.java b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/ProjectEvaluator.java
new file mode 100644
index 0000000..677cc61
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/ProjectEvaluator.java
@@ -0,0 +1,23 @@
+/*
+ * 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.configuration;
+
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ProjectStateInternal;
+
+public interface ProjectEvaluator {
+    void evaluate(ProjectInternal project, ProjectStateInternal state);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/ScriptPlugin.java b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/ScriptPlugin.java
new file mode 100644
index 0000000..fc79f09
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/ScriptPlugin.java
@@ -0,0 +1,42 @@
+/*
+ * 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.configuration;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.internal.initialization.ScriptClassLoaderProvider;
+import org.gradle.groovy.scripts.BasicScript;
+import org.gradle.groovy.scripts.ScriptSource;
+
+public interface ScriptPlugin extends Plugin<Object> {
+    ScriptSource getSource();
+
+    ScriptPlugin setClasspathClosureName(String name);
+
+    ScriptPlugin setScriptBaseClass(Class<? extends BasicScript> type);
+
+    ScriptPlugin setClassLoader(ClassLoader classLoader);
+
+    ScriptPlugin setClassLoaderProvider(ScriptClassLoaderProvider classLoaderProvider);
+
+    /**
+     * Applies the script to the given object. If target object implements {@link org.gradle.groovy.scripts.ScriptAware},
+     * will call {@link org.gradle.groovy.scripts.ScriptAware#beforeCompile(ScriptPlugin)} and {@link
+     * org.gradle.groovy.scripts.ScriptAware#afterCompile(ScriptPlugin , org.gradle.groovy.scripts.Script)}.
+     *
+     * @param target The target object to configure.
+     */
+    void apply(Object target);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/ScriptPluginFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/ScriptPluginFactory.java
new file mode 100644
index 0000000..0ad9f4f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/configuration/ScriptPluginFactory.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.configuration;
+
+import org.gradle.groovy.scripts.ScriptSource;
+
+public interface ScriptPluginFactory {
+    ScriptPlugin create(ScriptSource scriptSource);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/execution/BuildExecuter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/BuildExecuter.java
new file mode 100644
index 0000000..5b91cb1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/BuildExecuter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 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.execution;
+
+import org.gradle.api.internal.GradleInternal;
+
+/**
+ * Selects and executes the tasks requested for a build.
+ */
+public interface BuildExecuter {
+
+    /**
+     * Selects the tasks to execute, if any. This method is called before any other methods on this executer.
+     */
+    void select(GradleInternal gradle);
+
+    /**
+     * Returns the description of this executer. The result is used for log and error messages. Called after {@link
+     * #select(org.gradle.api.internal.GradleInternal)}.
+     */
+    String getDisplayName();
+
+    /**
+     * Executes the selected tasks. Called after {@link #select(org.gradle.api.internal.GradleInternal)}.
+     */
+    void execute();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/execution/BuiltInTaskBuildExecuter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/BuiltInTaskBuildExecuter.java
new file mode 100644
index 0000000..ec789e3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/BuiltInTaskBuildExecuter.java
@@ -0,0 +1,75 @@
+/*
+ * 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.execution;
+
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.project.taskfactory.ITaskFactory;
+import org.gradle.api.tasks.diagnostics.AbstractReportTask;
+import org.gradle.util.WrapUtil;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A {@link BuildExecuter} which executes the built-in tasks which are executable from the command-line.
+ */
+public abstract class BuiltInTaskBuildExecuter<T extends AbstractReportTask> implements BuildExecuter {
+    private GradleInternal gradle;
+    public static final String ALL_PROJECTS_WILDCARD = "?";
+
+    private T task;
+    private String path;
+
+    public BuiltInTaskBuildExecuter(String path) {
+        this.path = path;
+    }
+
+    protected abstract Class<T> getTaskType();
+
+    public void select(GradleInternal gradle) {
+        this.gradle = gradle;
+        ITaskFactory taskFactory = gradle.getServiceRegistryFactory().get(ITaskFactory.class);
+        Map<String, Object> args = new HashMap<String, Object>();
+        args.put(Task.TASK_NAME, "report");
+        args.put(Task.TASK_TYPE, getTaskType());
+        task = getTaskType().cast(taskFactory.createTask(gradle.getDefaultProject(), args));
+        task.setProjects(getProjectsForReport(path));
+        afterConfigure(task);
+    }
+
+    protected void afterConfigure(T task) {
+    }
+
+    private Set<Project> getProjectsForReport(String path) {
+        if (path != null) {
+            return path.equals(ALL_PROJECTS_WILDCARD) ? gradle.getRootProject().getAllprojects() : WrapUtil.toSet(
+                    gradle.getRootProject().project(path));
+        }
+        return WrapUtil.<Project>toSet(gradle.getDefaultProject());
+    }
+
+    public T getTask() {
+        return task;
+    }
+
+    public void execute() {
+        gradle.getTaskGraph().execute(Collections.singleton(task));
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/execution/DefaultBuildExecuter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/DefaultBuildExecuter.java
new file mode 100644
index 0000000..eb509e9
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/DefaultBuildExecuter.java
@@ -0,0 +1,54 @@
+/*
+ * 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.execution;
+
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.Task;
+import org.gradle.api.specs.Spec;
+import org.gradle.util.GUtil;
+
+import java.util.*;
+
+/**
+ * <p>The standard {@link BuildExecuter} implementation.</p>
+ */
+public class DefaultBuildExecuter extends DelegatingBuildExecuter {
+    private final Set<String> excludedTaskNames;
+
+    public DefaultBuildExecuter(Collection<String> includedTaskNames, Collection<String> excludedTaskNames) {
+        this.excludedTaskNames = new HashSet<String>(excludedTaskNames);
+        if (includedTaskNames.isEmpty()) {
+            setDelegate(new ProjectDefaultsBuildExecuter());
+        } else {
+            setDelegate(new TaskNameResolvingBuildExecuter(includedTaskNames));
+        }
+    }
+
+    @Override
+    public void select(GradleInternal gradle) {
+        if (!excludedTaskNames.isEmpty()) {
+            final Set<Task> excludedTasks = new HashSet<Task>();
+            GUtil.flatten(TaskNameResolvingBuildExecuter.select(gradle, excludedTaskNames), excludedTasks);
+            gradle.getTaskGraph().useFilter(new Spec<Task>() {
+                public boolean isSatisfiedBy(Task task) {
+                    return !excludedTasks.contains(task);
+                }
+            });
+        }
+
+        super.select(gradle);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/execution/DefaultTaskGraphExecuter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/DefaultTaskGraphExecuter.java
new file mode 100644
index 0000000..e8b7a7c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/DefaultTaskGraphExecuter.java
@@ -0,0 +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.execution;
+
+import groovy.lang.Closure;
+import org.gradle.api.CircularReferenceException;
+import org.gradle.api.Task;
+import org.gradle.api.execution.TaskExecutionGraphListener;
+import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.tasks.CachingTaskDependencyResolveContext;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.listener.ListenerBroadcast;
+import org.gradle.listener.ListenerManager;
+import org.gradle.util.Clock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultTaskGraphExecuter implements TaskGraphExecuter {
+    private static Logger logger = LoggerFactory.getLogger(DefaultTaskGraphExecuter.class);
+
+    private final ListenerBroadcast<TaskExecutionGraphListener> graphListeners;
+    private final ListenerBroadcast<TaskExecutionListener> taskListeners;
+    private final Set<Task> executionPlan = new LinkedHashSet<Task>();
+    private boolean populated;
+    private Spec<? super Task> filter = Specs.satisfyAll();
+
+    public DefaultTaskGraphExecuter(ListenerManager listenerManager) {
+        graphListeners = listenerManager.createAnonymousBroadcaster(TaskExecutionGraphListener.class);
+        taskListeners = listenerManager.createAnonymousBroadcaster(TaskExecutionListener.class);
+    }
+
+    public void useFilter(Spec<? super Task> filter) {
+        this.filter = filter;
+    }
+
+    public void addTasks(Iterable<? extends Task> tasks) {
+        assert tasks != null;
+
+        Clock clock = new Clock();
+
+        Set<Task> sortedTasks = new TreeSet<Task>();
+        for (Task task : tasks) {
+            sortedTasks.add(task);
+        }
+        fillDag(sortedTasks);
+        populated = true;
+
+        logger.debug("Timing: Creating the DAG took " + clock.getTime());
+    }
+
+    public void execute() {
+        Clock clock = new Clock();
+
+        graphListeners.getSource().graphPopulated(this);
+
+        try {
+            doExecute(executionPlan);
+            logger.debug("Timing: Executing the DAG took " + clock.getTime());
+        } finally {
+            executionPlan.clear();
+        }
+    }
+
+    public void execute(Iterable<? extends Task> tasks) {
+        addTasks(tasks);
+        execute();
+    }
+
+    private void fillDag(Collection<? extends Task> tasks) {
+        Set<Task> visiting = new HashSet<Task>();
+        List<Task> queue = new ArrayList<Task>();
+        queue.addAll(tasks);
+        CachingTaskDependencyResolveContext context = new CachingTaskDependencyResolveContext();
+
+        while (!queue.isEmpty()) {
+            Task task = queue.get(0);
+            if (!filter.isSatisfiedBy(task)) {
+                // Filtered - skip
+                queue.remove(0);
+                continue;
+            }
+            if (executionPlan.contains(task)) {
+                // Already in plan - skip
+                queue.remove(0);
+                continue;
+            }
+
+            if (visiting.add(task)) {
+                // Have not seen this task before - add its dependencies to the head of the queue and leave this
+                // task in the queue
+                Set<Task> dependsOnTasks = new TreeSet<Task>(Collections.reverseOrder());
+                dependsOnTasks.addAll(context.getDependencies(task));
+                for (Task dependsOnTask : dependsOnTasks) {
+                    if (visiting.contains(dependsOnTask)) {
+                        throw new CircularReferenceException(String.format(
+                                "Circular dependency between tasks. Cycle includes [%s, %s].", task, dependsOnTask));
+                    }
+                    queue.add(0, dependsOnTask);
+                }
+            } else {
+                // Have visited this task's dependencies - add it to the end of the plan
+                queue.remove(0);
+                visiting.remove(task);
+                executionPlan.add(task);
+            }
+        }
+    }
+
+    public void addTaskExecutionGraphListener(TaskExecutionGraphListener listener) {
+        graphListeners.add(listener);
+    }
+
+    public void removeTaskExecutionGraphListener(TaskExecutionGraphListener listener) {
+        graphListeners.remove(listener);
+    }
+
+    public void whenReady(final Closure closure) {
+        graphListeners.add("graphPopulated", closure);
+    }
+
+    public void addTaskExecutionListener(TaskExecutionListener listener) {
+        taskListeners.add(listener);
+    }
+
+    public void removeTaskExecutionListener(TaskExecutionListener listener) {
+        taskListeners.remove(listener);
+    }
+
+    public void beforeTask(final Closure closure) {
+        taskListeners.add("beforeExecute", closure);
+    }
+
+    public void afterTask(final Closure closure) {
+        taskListeners.add("afterExecute", closure);
+    }
+
+    private void doExecute(Iterable<? extends Task> tasks) {
+        for (Task task : tasks) {
+            executeTask(task);
+        }
+    }
+
+    private void executeTask(Task task) {
+        taskListeners.getSource().beforeExecute(task);
+        try {
+            ((TaskInternal) task).execute();
+        } finally {
+            taskListeners.getSource().afterExecute(task, task.getState());
+        }
+    }
+
+    public boolean hasTask(Task task) {
+        assertPopulated();
+        return executionPlan.contains(task);
+    }
+
+    public boolean hasTask(String path) {
+        assertPopulated();
+        assert path != null && path.length() > 0;
+        for (Task task : getAllTasks()) {
+            if (task.getPath().equals(path)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public List<Task> getAllTasks() {
+        assertPopulated();
+        return new ArrayList<Task>(executionPlan);
+    }
+
+    private void assertPopulated() {
+        if (!populated) {
+            throw new IllegalStateException(
+                    "Task information is not available, as this task execution graph has not been populated.");
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/execution/DelegatingBuildExecuter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/DelegatingBuildExecuter.java
new file mode 100644
index 0000000..fe1410e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/DelegatingBuildExecuter.java
@@ -0,0 +1,56 @@
+/*
+ * 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.execution;
+
+import org.gradle.api.internal.GradleInternal;
+
+public class DelegatingBuildExecuter implements BuildExecuter {
+    private BuildExecuter delegate;
+    private GradleInternal gradle;
+
+    public DelegatingBuildExecuter(BuildExecuter delegate) {
+        this.delegate = delegate;
+    }
+
+    public DelegatingBuildExecuter() {
+    }
+
+    protected GradleInternal getBuild() {
+        return gradle;
+    }
+
+    protected BuildExecuter getDelegate() {
+        return delegate;
+    }
+
+    protected void setDelegate(BuildExecuter delegate) {
+        this.delegate = delegate;
+    }
+
+    public void select(GradleInternal gradle) {
+        this.gradle = gradle;
+        delegate.select(gradle);
+    }
+
+    public String getDisplayName() {
+        return delegate.getDisplayName();
+    }
+
+    public void execute() {
+        delegate.execute();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/execution/DependencyReportBuildExecuter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/DependencyReportBuildExecuter.java
new file mode 100644
index 0000000..94c79b8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/DependencyReportBuildExecuter.java
@@ -0,0 +1,33 @@
+/*
+ * 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.execution;
+
+import org.gradle.api.tasks.diagnostics.DependencyReportTask;
+
+public class DependencyReportBuildExecuter extends BuiltInTaskBuildExecuter<DependencyReportTask> {
+    public DependencyReportBuildExecuter(String path) {
+        super(path);
+    }
+
+    @Override
+    protected Class<DependencyReportTask> getTaskType() {
+        return DependencyReportTask.class;
+    }
+
+    public String getDisplayName() {
+        return "dependency list";
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/execution/DryRunBuildExecuter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/DryRunBuildExecuter.java
new file mode 100644
index 0000000..036a119
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/DryRunBuildExecuter.java
@@ -0,0 +1,35 @@
+/*
+ * 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.execution;
+
+import org.gradle.api.Task;
+
+/**
+ * A {@link org.gradle.execution.BuildExecuter} which disables all selected tasks before they are executed.
+ */
+public class DryRunBuildExecuter extends DelegatingBuildExecuter {
+    public DryRunBuildExecuter(BuildExecuter delegate) {
+        super(delegate);
+    }
+
+    @Override
+    public void execute() {
+        for (Task task : getBuild().getTaskGraph().getAllTasks()) {
+            task.setEnabled(false);
+        }
+        super.execute();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/execution/ProjectDefaultsBuildExecuter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/ProjectDefaultsBuildExecuter.java
new file mode 100644
index 0000000..9a15dcd
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/ProjectDefaultsBuildExecuter.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 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.execution;
+
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.util.GUtil;
+
+import java.util.List;
+
+/**
+ * A {@link BuildExecuter} which selects the default tasks for a project.
+ */
+public class ProjectDefaultsBuildExecuter extends DelegatingBuildExecuter {
+    private List<String> defaultTasks;
+
+    public void select(GradleInternal gradle) {
+        if (getDelegate() == null) {
+            // Gather the default tasks from this first group project
+            ProjectInternal project = gradle.getDefaultProject();
+            defaultTasks = project.getDefaultTasks();
+            if (defaultTasks.size() == 0) {
+                throw new TaskSelectionException(String.format(
+                        "No tasks have been specified and %s has not defined any default tasks.", project));
+            }
+            setDelegate(new TaskNameResolvingBuildExecuter(defaultTasks));
+        }
+
+        super.select(gradle);
+    }
+
+    @Override
+    public String getDisplayName() {
+        return String.format("project default tasks %s", GUtil.toString(defaultTasks));
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/execution/PropertyReportBuildExecuter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/PropertyReportBuildExecuter.java
new file mode 100644
index 0000000..5fc26d7
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/PropertyReportBuildExecuter.java
@@ -0,0 +1,33 @@
+/*
+ * 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.execution;
+
+import org.gradle.api.tasks.diagnostics.PropertyReportTask;
+
+public class PropertyReportBuildExecuter extends BuiltInTaskBuildExecuter<PropertyReportTask> {
+    public PropertyReportBuildExecuter(String path) {
+        super(path);
+    }
+
+    @Override
+    protected Class<PropertyReportTask> getTaskType() {
+        return PropertyReportTask.class;
+    }
+
+    public String getDisplayName() {
+        return "property list";
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/execution/TaskGraphExecuter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/TaskGraphExecuter.java
new file mode 100644
index 0000000..7764306
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/TaskGraphExecuter.java
@@ -0,0 +1,45 @@
+/*
+ * 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.execution;
+
+import org.gradle.api.Task;
+import org.gradle.api.execution.TaskExecutionGraph;
+import org.gradle.api.specs.Spec;
+
+public interface TaskGraphExecuter extends TaskExecutionGraph {
+    /**
+     * Sets the filter to use when adding tasks to this graph. Only those tasks which are accepted by the given filter
+     * will be added to this graph.
+     */
+    void useFilter(Spec<? super Task> filter);
+
+    /**
+     * Adds the given tasks and their dependencies to this graph. Tasks are executed in an arbitrary order. The tasks
+     * are executed before any tasks from a subsequent call to this method are executed.
+     */
+    void addTasks(Iterable<? extends Task> tasks);
+
+    /**
+     * Executes the tasks in this graph. Discards the contents of this graph when completed.
+     */
+    void execute();
+
+    /**
+     * Adds the given tasks and their dependencies to this graph, then executes all the tasks in this graph. Discards
+     * the contents of this graph when completed.
+     */
+    void execute(Iterable<? extends Task> tasks);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/execution/TaskNameResolvingBuildExecuter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/TaskNameResolvingBuildExecuter.java
new file mode 100644
index 0000000..86e6cfe
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/TaskNameResolvingBuildExecuter.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.execution;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.util.GUtil;
+import org.gradle.util.NameMatcher;
+
+import java.util.*;
+
+/**
+ * A {@link BuildExecuter} which selects tasks which match the provided names. For each name, selects all tasks in all
+ * projects whose name is the given name.
+ */
+public class TaskNameResolvingBuildExecuter implements BuildExecuter {
+    private final List<String> names;
+    private String description;
+    private TaskGraphExecuter executer;
+
+    public TaskNameResolvingBuildExecuter(Collection<String> names) {
+        this.names = new ArrayList<String>(names);
+    }
+
+    public void select(GradleInternal gradle) {
+        Map<String, Collection<Task>> selectedTasks = doSelect(gradle, names);
+
+        this.executer = gradle.getTaskGraph();
+        for (Collection<Task> tasksForName : selectedTasks.values()) {
+            executer.addTasks(tasksForName);
+        }
+        if (selectedTasks.size() == 1) {
+            description = String.format("primary task %s", GUtil.toString(selectedTasks.keySet()));
+        } else {
+            description = String.format("primary tasks %s", GUtil.toString(selectedTasks.keySet()));
+        }
+    }
+
+    static List<Collection<Task>> select(GradleInternal gradle, Iterable<String> names) {
+        return new ArrayList<Collection<Task>>(doSelect(gradle, names).values());
+    }
+
+    private static Map<String, Collection<Task>> doSelect(GradleInternal gradle, Iterable<String> paths) {
+
+        Map<String, Collection<Task>> allProjectsTasksByName = null;
+
+        Map<String, Collection<Task>> matches = new LinkedHashMap<String, Collection<Task>>();
+        for (String path : paths) {
+            Map<String, Collection<Task>> tasksByName;
+            String baseName;
+            String prefix;
+
+            Project project = gradle.getDefaultProject();
+
+            if (path.contains(Project.PATH_SEPARATOR)) {
+                String projectPath = StringUtils.substringBeforeLast(path, Project.PATH_SEPARATOR);
+                projectPath = projectPath.length() == 0 ? Project.PATH_SEPARATOR : projectPath;
+                project = findProject(project, projectPath);
+                baseName = StringUtils.substringAfterLast(path, Project.PATH_SEPARATOR);
+                Task match = project.getTasks().findByName(baseName);
+                if (match != null) {
+                    matches.put(path, Collections.singleton(match));
+                    continue;
+                }
+
+                tasksByName = new HashMap<String, Collection<Task>>();
+                for (Task task : project.getTasks().getAll()) {
+                    tasksByName.put(task.getName(), Collections.singleton(task));
+                }
+
+                prefix = project.getPath() + Project.PATH_SEPARATOR;
+            }
+            else {
+                Set<Task> tasks = project.getTasksByName(path, true);
+                if (!tasks.isEmpty()) {
+                    matches.put(path, tasks);
+                    continue;
+                }
+                if (allProjectsTasksByName == null) {
+                    allProjectsTasksByName = buildTaskMap(project);
+                }
+                tasksByName = allProjectsTasksByName;
+                baseName = path;
+                prefix = "";
+            }
+
+            NameMatcher matcher = new NameMatcher();
+            String actualName = matcher.find(baseName, tasksByName.keySet());
+
+            if (actualName != null) {
+                matches.put(prefix + actualName, tasksByName.get(actualName));
+                continue;
+            }
+
+            throw new TaskSelectionException(matcher.formatErrorMessage("task", project));
+        }
+
+        return matches;
+    }
+
+    private static Project findProject(Project startFrom, String path) {
+        if (path.equals(Project.PATH_SEPARATOR)) {
+            return startFrom.getRootProject();
+        }
+        Project current = startFrom;
+        if (path.startsWith(Project.PATH_SEPARATOR)) {
+            current = current.getRootProject();
+            path = path.substring(1);
+        }
+        for (String pattern : path.split(Project.PATH_SEPARATOR)) {
+            Map<String, Project> children = current.getChildProjects();
+
+            NameMatcher matcher = new NameMatcher();
+            Project child = matcher.find(pattern, children);
+            if (child != null) {
+                current = child;
+                continue;
+            }
+
+            throw new TaskSelectionException(matcher.formatErrorMessage("project", current));
+        }
+
+        return current;
+    }
+
+    private static Map<String, Collection<Task>> buildTaskMap(Project defaultProject) {
+        Map<String, Collection<Task>> tasksByName = new HashMap<String, Collection<Task>>();
+        for (Project project : defaultProject.getAllprojects()) {
+            for (Task task : project.getTasks().getAll()) {
+                Collection<Task> tasksForName = tasksByName.get(task.getName());
+                if (tasksForName == null) {
+                    tasksForName = new HashSet<Task>();
+                    tasksByName.put(task.getName(), tasksForName);
+                }
+                tasksForName.add(task);
+            }
+        }
+        return tasksByName;
+    }
+
+    public String getDisplayName() {
+        return description;
+    }
+
+    public void execute() {
+        executer.execute();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/execution/TaskReportBuildExecuter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/TaskReportBuildExecuter.java
new file mode 100644
index 0000000..c241790
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/TaskReportBuildExecuter.java
@@ -0,0 +1,41 @@
+/*
+ * 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.execution;
+
+import org.gradle.api.tasks.diagnostics.TaskReportTask;
+
+public class TaskReportBuildExecuter extends BuiltInTaskBuildExecuter<TaskReportTask> {
+    private final boolean showDetail;
+
+    public TaskReportBuildExecuter(String path, boolean showDetail) {
+        super(path);
+        this.showDetail = showDetail;
+    }
+
+    @Override
+    protected Class<TaskReportTask> getTaskType() {
+        return TaskReportTask.class;
+    }
+
+    public String getDisplayName() {
+        return "task list";
+    }
+
+    @Override
+    protected void afterConfigure(TaskReportTask task) {
+        task.setShowDetail(showDetail);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/execution/TaskSelectionException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/TaskSelectionException.java
new file mode 100644
index 0000000..f6fdfc4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/execution/TaskSelectionException.java
@@ -0,0 +1,28 @@
+/*
+ * 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.execution;
+
+import org.gradle.api.InvalidUserDataException;
+
+/**
+ * A {@code TaskSelectionException} is thrown when the tasks to execute cannot be selected due to some user input
+ * problem.
+ */
+public class TaskSelectionException extends InvalidUserDataException {
+    public TaskSelectionException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/AsmBackedEmptyScriptGenerator.java b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/AsmBackedEmptyScriptGenerator.java
new file mode 100644
index 0000000..0b985f7
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/AsmBackedEmptyScriptGenerator.java
@@ -0,0 +1,84 @@
+/*
+ * 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.Script;
+import org.gradle.util.ReflectionUtil;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class AsmBackedEmptyScriptGenerator {
+    private static final Map<Class<?>, Class<?>> CACHED_CLASSES = new HashMap<Class<?>, Class<?>>();
+
+    <T extends Script> Class<? extends T> generate(Class<T> type) {
+        Class<?> subclass = CACHED_CLASSES.get(type);
+        if (subclass == null) {
+            subclass = generateEmptyScriptClass(type);
+            CACHED_CLASSES.put(type, subclass);
+        }
+        return subclass.asSubclass(type);
+    }
+
+    private <T extends Script> Class<? extends T> generateEmptyScriptClass(Class<T> type) {
+        ClassWriter visitor = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+        String typeName = type.getName() + "_Decorated";
+        Type generatedType = Type.getType("L" + typeName.replaceAll("\\.", "/") + ";");
+        Type superclassType = Type.getType(type);
+        visitor.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, generatedType.getInternalName(), null,
+                superclassType.getInternalName(), new String[0]);
+
+        // Constructor
+
+        String constructorDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[0]);
+        MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, "<init>", constructorDescriptor, null,
+                new String[0]);
+        methodVisitor.visitCode();
+
+        // super()
+        methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+        methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superclassType.getInternalName(), "<init>",
+                constructorDescriptor);
+
+        methodVisitor.visitInsn(Opcodes.RETURN);
+        methodVisitor.visitMaxs(0, 0);
+        methodVisitor.visitEnd();
+
+        // run() method
+
+        String runDesciptor = Type.getMethodDescriptor(Type.getType(Object.class), new Type[0]);
+        methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, "run", runDesciptor, null, new String[0]);
+        methodVisitor.visitCode();
+
+        // return null
+        methodVisitor.visitInsn(Opcodes.ACONST_NULL);
+
+        methodVisitor.visitInsn(Opcodes.ARETURN);
+        methodVisitor.visitMaxs(0, 0);
+        methodVisitor.visitEnd();
+
+        visitor.visitEnd();
+
+        byte[] bytecode = visitor.toByteArray();
+        return (Class<T>) ReflectionUtil.invoke(type.getClassLoader(), "defineClass", new Object[]{
+                typeName, bytecode, 0, bytecode.length
+        });
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/BasicScript.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/BasicScript.groovy
new file mode 100644
index 0000000..7a59de6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/BasicScript.groovy
@@ -0,0 +1,72 @@
+/*
+ * 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.internal.project.ServiceRegistry
+import org.gradle.api.internal.file.FileOperations
+import org.gradle.logging.StandardOutputCapture
+
+/**
+ * @author Hans Dockter
+ *
+ */
+abstract class BasicScript extends org.gradle.groovy.scripts.Script implements org.gradle.api.Script, FileOperations {
+    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".equals(property)) {
+            setMetaClass((MetaClass) newValue)
+        } else if ("scriptTarget".equals(property)) {
+            target = newValue
+        } else {
+            target."$property" = newValue
+        }
+    }
+
+    def propertyMissing(String property) {
+        if ('out'.equals(property)) {
+            System.out
+        } else {
+            target."$property"
+        }
+    }
+
+    def hasProperty(String property) {
+        target.hasProperty(property)
+    }
+
+    def methodMissing(String name, Object params) {
+        return target.invokeMethod(name, params)
+    }
+}
+
+
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/CachingScriptCompilationHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/CachingScriptCompilationHandler.java
new file mode 100644
index 0000000..6704f75
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/CachingScriptCompilationHandler.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.groovy.scripts;
+
+import groovy.lang.Script;
+
+import java.io.File;
+import java.util.*;
+
+public class CachingScriptCompilationHandler implements ScriptCompilationHandler {
+    private final ScriptCompilationHandler handler;
+    private final Map<Collection<Object>, Class<?>> cachedClasses = new HashMap<Collection<Object>, Class<?>>();
+
+    public CachingScriptCompilationHandler(ScriptCompilationHandler handler) {
+        this.handler = handler;
+    }
+
+    public void compileToDir(ScriptSource source, ClassLoader classLoader, File scriptCacheDir, Transformer transformer,
+                             Class<? extends Script> scriptBaseClass) {
+        handler.compileToDir(source, classLoader, scriptCacheDir, transformer, scriptBaseClass);
+    }
+
+    public <T extends Script> Class<? extends T> loadFromDir(ScriptSource source, ClassLoader classLoader,
+                                                             File scriptCacheDir, Class<T> scriptBaseClass) {
+        List<Object> key = Arrays.asList(source.getClassName(), classLoader, scriptCacheDir);
+        Class<?> c = cachedClasses.get(key);
+        if (c == null) {
+            c = handler.loadFromDir(source, classLoader, scriptCacheDir, scriptBaseClass);
+            cachedClasses.put(key, c);
+        }
+        return c.asSubclass(scriptBaseClass);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/CachingScriptSource.java b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/CachingScriptSource.java
new file mode 100644
index 0000000..8ee4c5d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/CachingScriptSource.java
@@ -0,0 +1,51 @@
+/*
+ * 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.internal.resource.CachingResource;
+import org.gradle.api.internal.resource.Resource;
+
+public class CachingScriptSource extends DelegatingScriptSource {
+    private Resource resource;
+
+    public CachingScriptSource(ScriptSource source) {
+        super(source);
+        resource = new CachingResource(source.getResource());
+    }
+
+    @Override
+    public Resource getResource() {
+        return resource;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+
+        CachingScriptSource other = (CachingScriptSource) obj;
+        return other.getSource().equals(getSource());
+    }
+
+    @Override
+    public int hashCode() {
+        return getSource().hashCode();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/DefaultScript.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/DefaultScript.groovy
new file mode 100644
index 0000000..05abd83
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/DefaultScript.groovy
@@ -0,0 +1,172 @@
+/*
+ * 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.plugins.DefaultObjectConfigurationAction
+import org.gradle.api.internal.project.ServiceRegistry
+import org.gradle.api.logging.LogLevel
+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.tasks.WorkResult
+import org.gradle.configuration.ScriptPluginFactory
+import org.gradle.util.ConfigureUtil
+import org.gradle.process.ExecResult
+import org.gradle.api.internal.file.*
+import org.gradle.util.DeprecationLogger
+
+abstract class DefaultScript extends BasicScript {
+    private static final Logger LOGGER = Logging.getLogger(Script.class)
+    private ServiceRegistry services
+    private FileOperations fileOperations
+    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 BaseDirConverter(scriptSource.resource.file.parentFile), null, null)
+        } else {
+            fileOperations = new DefaultFileOperations(new IdentityFileResolver(), null, null)
+        }
+    }
+
+    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(baseDir)
+    }
+
+    ConfigurableFileTree fileTree(Map args) {
+        fileOperations.fileTree(args)
+    }
+
+    ConfigurableFileTree fileTree(Closure closure) {
+        fileOperations.fileTree(closure)
+    }
+
+    FileTree zipTree(Object zipPath) {
+        fileOperations.zipTree(zipPath)
+    }
+
+    FileTree tarTree(Object tarPath) {
+        fileOperations.tarTree(tarPath)
+    }
+
+    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 fileOperations.javaexec(closure);
+    }
+
+    ExecResult exec(Closure closure) {
+        return fileOperations.exec(closure);
+    }
+
+    LoggingManager getLogging() {
+        return loggingManager
+    }
+
+    public void captureStandardOutput(LogLevel level) {
+        DeprecationLogger.nagUser('captureStandardOutput()', 'getLogging().captureStandardOutput()')
+        logging.captureStandardOutput(level)
+    }
+
+    public void disableStandardOutputCapture() {
+        DeprecationLogger.nagUser('disableStandardOutputCapture')
+        logging.disableStandardOutputCapture()
+    }
+
+    public Logger getLogger() {
+        return LOGGER;
+    }
+
+    def String toString() {
+        return "script"
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilationHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilationHandler.java
new file mode 100644
index 0000000..22878b9
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilationHandler.java
@@ -0,0 +1,196 @@
+/*
+ * 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.GroovyClassLoader;
+import groovy.lang.GroovyCodeSource;
+import groovy.lang.Script;
+import groovyjarjarasm.asm.ClassWriter;
+import org.apache.commons.lang.StringUtils;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.stmt.ReturnStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.*;
+import org.codehaus.groovy.syntax.SyntaxException;
+import org.gradle.api.GradleException;
+import org.gradle.api.ScriptCompilationException;
+import org.gradle.util.Clock;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.WrapUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.net.URLClassLoader;
+import java.security.CodeSource;
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultScriptCompilationHandler implements ScriptCompilationHandler {
+    private Logger logger = LoggerFactory.getLogger(DefaultScriptCompilationHandler.class);
+    private static final String EMPTY_SCRIPT_MARKER_FILE_NAME = "emptyScript.txt";
+
+    public void compileToDir(ScriptSource source, ClassLoader classLoader, File classesDir,
+                             Transformer transformer, Class<? extends Script> scriptBaseClass) {
+        Clock clock = new Clock();
+        GFileUtils.deleteDirectory(classesDir);
+        classesDir.mkdirs();
+        CompilerConfiguration configuration = createBaseCompilerConfiguration(scriptBaseClass);
+        configuration.setTargetDirectory(classesDir);
+        try {
+            compileScript(source, classLoader, configuration, classesDir, transformer);
+        } catch (GradleException e) {
+            GFileUtils.deleteDirectory(classesDir);
+            throw e;
+        }
+
+        logger.debug("Timing: Writing script to cache at {} took: {}", classesDir.getAbsolutePath(),
+                clock.getTime());
+    }
+
+    private void compileScript(final ScriptSource source, ClassLoader classLoader, CompilerConfiguration configuration,
+                               File classesDir, final Transformer transformer) {
+        logger.info("Compiling {} using {}.", source.getDisplayName(), transformer != null ? transformer.getClass().getSimpleName() : "no transformer");
+
+        final EmptyScriptDetector emptyScriptDetector = new EmptyScriptDetector();
+        final PackageStatementDetector packageDetector = new PackageStatementDetector();
+        GroovyClassLoader groovyClassLoader = new GroovyClassLoader(classLoader, configuration, false) {
+            @Override
+            protected CompilationUnit createCompilationUnit(CompilerConfiguration compilerConfiguration,
+                                                            CodeSource codeSource) {
+                CompilationUnit compilationUnit = new CompilationUnit(compilerConfiguration, codeSource, this) {
+                    // This creepy bit of code is here to put the full source path of the script into the debug info for
+                    // the class.  This makes it possible for a debugger to find the source file for the class.  By default
+                    // Groovy will only put the filename into the class, but that does not help a debugger for Gradle
+                    // because it does not know where Gradle scripts might live.
+                    @Override
+                    protected groovyjarjarasm.asm.ClassVisitor createClassVisitor() {
+                        return new ClassWriter(ClassWriter.COMPUTE_MAXS) {
+                            // ignore the sourcePath that is given by Groovy (this is only the filename) and instead
+                            // insert the full path if our script source has a source file
+                            @Override
+                            public void visitSource(String sourcePath, String debugInfo) {
+                                super.visitSource(source.getFileName(), debugInfo);
+                            }
+                        };
+                    }
+                };
+
+                if (transformer != null) {
+                    transformer.register(compilationUnit);
+                }
+
+                compilationUnit.addPhaseOperation(packageDetector, Phases.CANONICALIZATION);
+                compilationUnit.addPhaseOperation(emptyScriptDetector, Phases.CANONICALIZATION);
+                return compilationUnit;
+            }
+        };
+        String scriptText = source.getResource().getText();
+        String scriptName = source.getClassName();
+        GroovyCodeSource codeSource = new GroovyCodeSource(scriptText == null ? "" : scriptText, scriptName, "/groovy/script");
+        try {
+            groovyClassLoader.parseClass(codeSource, false);
+        } catch (MultipleCompilationErrorsException e) {
+            SyntaxException syntaxError = e.getErrorCollector().getSyntaxError(0);
+            Integer lineNumber = syntaxError == null ? null : syntaxError.getLine();
+            throw new ScriptCompilationException(String.format("Could not compile %s.", source.getDisplayName()), e, source,
+                    lineNumber);
+        } catch (CompilationFailedException e) {
+            throw new GradleException(String.format("Could not compile %s.", source.getDisplayName()), e);
+        }
+
+        if (packageDetector.hasPackageStatement) {
+            throw new UnsupportedOperationException(String.format("%s should not contain a package statement.",
+                    StringUtils.capitalize(source.getDisplayName())));
+        }
+        if (emptyScriptDetector.isEmptyScript()) {
+            GFileUtils.touch(new File(classesDir, EMPTY_SCRIPT_MARKER_FILE_NAME));
+        }
+    }
+
+    private CompilerConfiguration createBaseCompilerConfiguration(Class<? extends Script> scriptBaseClass) {
+        CompilerConfiguration configuration = new CompilerConfiguration();
+        configuration.setScriptBaseClass(scriptBaseClass.getName());
+        return configuration;
+    }
+
+    public <T extends Script> Class<? extends T> loadFromDir(ScriptSource source, ClassLoader classLoader, File scriptCacheDir,
+                                              Class<T> scriptBaseClass) {
+        if (new File(scriptCacheDir, EMPTY_SCRIPT_MARKER_FILE_NAME).isFile()) {
+            return new AsmBackedEmptyScriptGenerator().generate(scriptBaseClass);
+        }
+        
+        try {
+            URLClassLoader urlClassLoader = new URLClassLoader(WrapUtil.toArray(scriptCacheDir.toURI().toURL()),
+                    classLoader);
+            return urlClassLoader.loadClass(source.getClassName()).asSubclass(scriptBaseClass);
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not load compiled classes for %s from cache.",
+                    source.getDisplayName()), e);
+        }
+    }
+
+    private static class PackageStatementDetector extends CompilationUnit.SourceUnitOperation {
+        private boolean hasPackageStatement;
+
+        @Override
+        public void call(SourceUnit source) throws CompilationFailedException {
+            hasPackageStatement = source.getAST().getPackageName() != null;
+        }
+    }
+
+    private static class EmptyScriptDetector extends CompilationUnit.SourceUnitOperation {
+        private boolean emptyScript;
+
+        @Override
+        public void call(SourceUnit source) throws CompilationFailedException {
+            emptyScript = isEmpty(source);
+        }
+
+        private boolean isEmpty(SourceUnit source) {
+            if (!source.getAST().getMethods().isEmpty()) {
+                return false;
+            }
+            List<Statement> statements = source.getAST().getStatementBlock().getStatements();
+            if (statements.size() > 1) {
+                return false;
+            }
+            if (statements.isEmpty()) {
+                return true;
+            }
+
+            Statement statement = statements.get(0);
+            if (statement instanceof ReturnStatement) {
+                ReturnStatement returnStatement = (ReturnStatement) statement;
+                if (returnStatement.getExpression() instanceof ConstantExpression) {
+                    ConstantExpression constantExpression = (ConstantExpression) returnStatement.getExpression();
+                    if (constantExpression.getValue() == null) {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        public boolean isEmptyScript() {
+            return emptyScript;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactory.java
new file mode 100644
index 0000000..4409b20
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactory.java
@@ -0,0 +1,98 @@
+/*
+ * 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.cache.CacheRepository;
+import org.gradle.cache.PersistentCache;
+import org.gradle.util.HashUtil;
+import org.gradle.util.ReflectionUtil;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultScriptCompilerFactory implements ScriptCompilerFactory {
+    private final ScriptCompilationHandler scriptCompilationHandler;
+    private final CacheRepository cacheRepository;
+    private final ScriptRunnerFactory scriptRunnerFactory;
+
+    public DefaultScriptCompilerFactory(ScriptCompilationHandler scriptCompilationHandler,
+                                        ScriptRunnerFactory scriptRunnerFactory, CacheRepository cacheRepository) {
+        this.scriptCompilationHandler = scriptCompilationHandler;
+        this.cacheRepository = cacheRepository;
+        this.scriptRunnerFactory = scriptRunnerFactory;
+    }
+
+    public ScriptCompiler createCompiler(ScriptSource source) {
+        return new ScriptCompilerImpl(source);
+    }
+
+    private class ScriptCompilerImpl implements ScriptCompiler {
+        private final ScriptSource source;
+        private ClassLoader classloader;
+        private Transformer transformer;
+
+        public ScriptCompilerImpl(ScriptSource source) {
+            this.source = new CachingScriptSource(source);
+        }
+
+        public ScriptCompiler setClassloader(ClassLoader classloader) {
+            this.classloader = classloader;
+            return this;
+        }
+
+        public ScriptCompiler setTransformer(Transformer transformer) {
+            this.transformer = transformer;
+            return this;
+        }
+
+        public <T extends Script> ScriptRunner<T> compile(Class<T> scriptType) {
+            ClassLoader classloader = this.classloader != null ? this.classloader
+                    : Thread.currentThread().getContextClassLoader();
+
+            T script = loadViaCache(classloader, scriptType);
+            script.setScriptSource(source);
+            script.setContextClassloader(classloader);
+            return scriptRunnerFactory.create(script);
+        }
+
+        private <T extends Script> T loadViaCache(ClassLoader classLoader, Class<T> scriptBaseClass) {
+            Map<String, Object> properties = new HashMap<String, Object>();
+            properties.put("source.filename", source.getFileName());
+            properties.put("source.hash", HashUtil.createHash(source.getResource().getText()));
+
+            PersistentCache cache = cacheRepository.cache(String.format("scripts/%s", source.getClassName())).withProperties(properties).open();
+            File classesDir;
+            if (transformer != null) {
+                String subdirName = String.format("%s_%s", transformer.getId(), scriptBaseClass.getSimpleName());
+                classesDir = new File(cache.getBaseDir(), subdirName);
+            } else {
+                classesDir = new File(cache.getBaseDir(), scriptBaseClass.getSimpleName());
+            }
+
+            if (!cache.isValid() || !classesDir.exists()) {
+                scriptCompilationHandler.compileToDir(source, classLoader, classesDir, transformer, scriptBaseClass);
+                cache.markValid();
+            }
+            Class<? extends T> scriptClass = scriptCompilationHandler.loadFromDir(source, classLoader, classesDir,
+                    scriptBaseClass);
+            return scriptBaseClass.cast(ReflectionUtil.newInstance(scriptClass, new Object[0]));
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptRunnerFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptRunnerFactory.java
new file mode 100644
index 0000000..97857c3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/DefaultScriptRunnerFactory.java
@@ -0,0 +1,61 @@
+/*
+ * 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.GradleScriptException;
+
+public class DefaultScriptRunnerFactory implements ScriptRunnerFactory {
+    private final ScriptExecutionListener listener;
+
+    public DefaultScriptRunnerFactory(ScriptExecutionListener listener) {
+        this.listener = listener;
+    }
+
+    public <T extends Script> ScriptRunner<T> create(T script) {
+        return new ScriptRunnerImpl<T>(script);
+    }
+
+    private class ScriptRunnerImpl<T extends Script> implements ScriptRunner<T> {
+        private final T script;
+
+        public ScriptRunnerImpl(T script) {
+            this.script = script;
+        }
+
+        public T getScript() {
+            return script;
+        }
+
+        public void run() throws GradleScriptException {
+            ClassLoader originalLoader = Thread.currentThread().getContextClassLoader();
+            listener.beforeScript(script);
+            GradleScriptException failure = null;
+            Thread.currentThread().setContextClassLoader(script.getContextClassloader());
+            script.getStandardOutputCapture().start();
+            try {
+                script.run();
+            } catch (Throwable e) {
+                failure = new GradleScriptException(String.format("A problem occurred evaluating %s.", script), e);
+            }
+            script.getStandardOutputCapture().stop();
+            Thread.currentThread().setContextClassLoader(originalLoader);
+            listener.afterScript(script, failure);
+            if (failure != null) {
+                throw failure;
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/DelegatingScriptSource.java b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/DelegatingScriptSource.java
new file mode 100644
index 0000000..47592c9
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/DelegatingScriptSource.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.groovy.scripts;
+
+import org.gradle.api.internal.resource.Resource;
+
+public class DelegatingScriptSource implements ScriptSource {
+    private final ScriptSource source;
+
+    public DelegatingScriptSource(ScriptSource source) {
+        this.source = source;
+    }
+
+    public ScriptSource getSource() {
+        return source;
+    }
+    
+    public String getClassName() {
+        return source.getClassName();
+    }
+
+    public String getDisplayName() {
+        return source.getDisplayName();
+    }
+
+    public String getFileName() {
+        return source.getFileName();
+    }
+
+    public Resource getResource() {
+        return source.getResource();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/Script.java b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/Script.java
new file mode 100644
index 0000000..4d62766
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/Script.java
@@ -0,0 +1,48 @@
+/*
+ * 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.internal.project.ServiceRegistry;
+import org.gradle.logging.StandardOutputCapture;
+
+/**
+ * The base class for all scripts executed by Gradle.
+ */
+public abstract class Script extends groovy.lang.Script {
+    private ScriptSource source;
+    private ClassLoader contextClassloader;
+
+    public ScriptSource getScriptSource() {
+        return source;
+    }
+
+    public void setScriptSource(ScriptSource source) {
+        this.source = source;
+    }
+
+    public void setContextClassloader(ClassLoader contextClassloader) {
+        this.contextClassloader = contextClassloader;
+    }
+
+    public ClassLoader getContextClassloader() {
+        return contextClassloader;
+    }
+
+    public abstract void init(Object target, ServiceRegistry services);
+
+    public abstract StandardOutputCapture getStandardOutputCapture();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptAware.java b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptAware.java
new file mode 100644
index 0000000..a3db4a0
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptAware.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+import org.gradle.configuration.ScriptPlugin;
+
+public interface ScriptAware {
+    void beforeCompile(ScriptPlugin configurer);
+
+    void afterCompile(ScriptPlugin configurer, Script script);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompilationHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompilationHandler.java
new file mode 100644
index 0000000..08434d6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompilationHandler.java
@@ -0,0 +1,31 @@
+/*
+ * 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.groovy.scripts;
+
+import groovy.lang.Script;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ScriptCompilationHandler {
+    void compileToDir(ScriptSource source, ClassLoader classLoader, File scriptCacheDir, Transformer transformer,
+                      Class<? extends Script> scriptBaseClass);
+
+    <T extends Script> Class<? extends T> loadFromDir(ScriptSource source, ClassLoader classLoader, File scriptCacheDir,
+                                       Class<T> scriptBaseClass);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompiler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompiler.java
new file mode 100644
index 0000000..0ecb560
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompiler.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+import org.gradle.api.ScriptCompilationException;
+
+/**
+ * Compiles a script into a {@code Script} object.
+ */
+public interface ScriptCompiler {
+    /**
+     * Sets the parent classloader for the script. Can be null, defaults to the context classloader.
+     */
+    ScriptCompiler setClassloader(ClassLoader classloader);
+
+    /**
+     * Sets the transformer to use to compile the script. Can be null, in which case no transformations are applied to
+     * the script.
+     */
+    ScriptCompiler setTransformer(Transformer transformer);
+
+    /**
+     * Compiles the script into a {@code Script} object of the given type. 
+     *
+     * @returns a {@code ScriptRunner} for the script.
+     * @throws ScriptCompilationException On compilation failure.
+     */
+    <T extends Script> ScriptRunner<T> compile(Class<T> scriptType) throws ScriptCompilationException;
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompilerFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompilerFactory.java
new file mode 100644
index 0000000..ea4fb9c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptCompilerFactory.java
@@ -0,0 +1,32 @@
+/*
+ * 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.groovy.scripts;
+
+/**
+ * A factory for script compilers.
+ *
+ * @author Hans Dockter
+ */
+public interface ScriptCompilerFactory {
+    /**
+     * Creates a compiler for the given source. The returned compiler can be used to compile the script into various
+     * different forms.
+     *
+     * @param source The script source.
+     * @return a compiler which can be used to compiler the script.
+     */
+    ScriptCompiler createCompiler(ScriptSource source);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptExecutionListener.java b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptExecutionListener.java
new file mode 100644
index 0000000..5ab3ebc
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptExecutionListener.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.groovy.scripts;
+
+public interface ScriptExecutionListener {
+    void beforeScript(Script script);
+
+    void afterScript(Script script, Throwable result);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptMetaData.java b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptMetaData.java
new file mode 100644
index 0000000..9066f0d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptMetaData.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.groovy.scripts;
+
+import groovy.lang.Script;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ScriptMetaData {
+    void applyMetaData(Script script, Object delegate);
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptRunner.java b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptRunner.java
new file mode 100644
index 0000000..2ff62f7
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptRunner.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;
+
+import groovy.lang.Script;
+import org.gradle.api.GradleScriptException;
+
+/**
+ * Executes a script of type T.
+ */
+public interface ScriptRunner<T extends Script> extends Runnable {
+    /**
+     * Returns the script which will be executed by this runner.
+     *
+     * @return the script.
+     */
+    T getScript();
+
+    /**
+     * Executes the script.
+     *
+     * @throws GradleScriptException On execution failure.
+     */
+    void run() throws GradleScriptException;
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptRunnerFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptRunnerFactory.java
new file mode 100644
index 0000000..af93917
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptRunnerFactory.java
@@ -0,0 +1,20 @@
+/*
+ * 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;
+
+public interface ScriptRunnerFactory {
+    <T extends Script> ScriptRunner<T> create(T script);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptSource.java b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptSource.java
new file mode 100644
index 0000000..30890a1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/ScriptSource.java
@@ -0,0 +1,44 @@
+/*
+ * 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.internal.resource.Resource;
+
+/**
+ * The source for the text of a script, with some meta-info about the script.
+ */
+public interface ScriptSource {
+    /**
+     * Returns the name to use for the compiled class for this script. Never returns null.
+     */
+    String getClassName();
+
+    /**
+     * Returns the source for this script. Never returns null.
+     */
+    Resource getResource();
+
+    /**
+     * Returns the file name that is inserted into the class during compilation.  For a script with a source
+     * file this is the path to the file.  Never returns null.
+     */
+    String getFileName();
+
+    /**
+     * Returns the description for this script. Never returns null.
+     */
+    String getDisplayName();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/StringScriptSource.java b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/StringScriptSource.java
new file mode 100644
index 0000000..a471833
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/StringScriptSource.java
@@ -0,0 +1,44 @@
+/*
+ * 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.internal.resource.Resource;
+import org.gradle.api.internal.resource.StringResource;
+import org.gradle.util.HashUtil;
+
+public class StringScriptSource implements ScriptSource {
+    private final Resource resource;
+
+    public StringScriptSource(String description, String content) {
+        resource = new StringResource(description, content == null ? "" : content);
+    }
+
+    public String getClassName() {
+        return "script_" + HashUtil.createHash(resource.getText());
+    }
+
+    public Resource getResource() {
+        return resource;
+    }
+
+    public String getFileName() {
+        return getClassName();
+    }
+
+    public String getDisplayName() {
+        return resource.getDisplayName();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/Transformer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/Transformer.java
new file mode 100644
index 0000000..78264cc
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/Transformer.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+import org.codehaus.groovy.control.CompilationUnit;
+
+public interface Transformer {
+    /**
+     * A unique id for this transformer. Used to distinguish between the classes compiled from the same script with
+     * different transformers, so should be a valid java identifier.
+     */
+    String getId();
+
+    void register(CompilationUnit compilationUnit);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/UriScriptSource.java b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/UriScriptSource.java
new file mode 100644
index 0000000..d56eb03
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/groovy/scripts/UriScriptSource.java
@@ -0,0 +1,84 @@
+/*
+ * 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.apache.commons.lang.StringUtils;
+import org.gradle.api.internal.resource.Resource;
+import org.gradle.api.internal.resource.UriResource;
+import org.gradle.util.HashUtil;
+
+import java.io.File;
+import java.net.URI;
+
+/**
+ * A {@link ScriptSource} which loads the script from a URI.
+ */
+public class UriScriptSource implements ScriptSource {
+    private final Resource resource;
+    private String className;
+
+    public UriScriptSource(String description, File sourceFile) {
+        resource = new UriResource(description, sourceFile);
+    }
+
+    public UriScriptSource(String description, URI sourceUri) {
+        resource = new UriResource(description, sourceUri);
+    }
+
+    /**
+     * Returns the class name for use for this script source.  The name is intended to be unique to support mapping
+     * class names to source files even if many sources have the same file name (e.g. build.gradle).
+     */
+    public String getClassName() {
+        if (className == null) {
+            URI sourceUri = resource.getURI();
+            String name = StringUtils.substringAfterLast(sourceUri.getPath(), "/");
+            StringBuilder className = new StringBuilder(name.length());
+            for (int i = 0; i < name.length(); i++) {
+                char ch = name.charAt(i);
+                if (Character.isJavaIdentifierPart(ch)) {
+                    className.append(ch);
+                } else {
+                    className.append('_');
+                }
+            }
+            if (!Character.isJavaIdentifierStart(className.charAt(0))) {
+                className.insert(0, '_');
+            }
+            className.append('_');
+            String path = sourceUri.toString();
+            className.append(HashUtil.createHash(path));
+
+            this.className = className.toString();
+        }
+
+        return className;
+    }
+
+    public Resource getResource() {
+        return resource;
+    }
+
+    public String getFileName() {
+        File sourceFile = resource.getFile();
+        URI sourceUri = resource.getURI();
+        return sourceFile != null ? sourceFile.getPath() : sourceUri.toString();
+    }
+
+    public String getDisplayName() {
+        return resource.getDisplayName();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/AbstractProjectSpec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/AbstractProjectSpec.java
new file mode 100644
index 0000000..ba20320
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/AbstractProjectSpec.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.initialization;
+
+import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.api.internal.project.ProjectIdentifier;
+import org.gradle.api.InvalidUserDataException;
+
+import java.util.List;
+import java.util.ArrayList;
+
+public abstract class AbstractProjectSpec implements ProjectSpec {
+    public boolean containsProject(IProjectRegistry<?> registry) {
+        checkPreconditions(registry);
+        for (ProjectIdentifier project : registry.getAllProjects()) {
+            if (select(project)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public <T extends ProjectIdentifier> T selectProject(IProjectRegistry<? extends T> registry) {
+        checkPreconditions(registry);
+        List<T> matches = new ArrayList<T>();
+        for (T project : registry.getAllProjects()) {
+            if (select(project)) {
+                matches.add(project);
+            }
+        }
+        if (matches.isEmpty()) {
+            throw new InvalidUserDataException(formatNoMatchesMessage());
+        }
+        if (matches.size() != 1) {
+            throw new InvalidUserDataException(formatMultipleMatchesMessage(matches));
+        }
+        return matches.get(0);
+    }
+
+    protected void checkPreconditions(IProjectRegistry<?> registry) {
+    }
+
+    protected abstract String formatMultipleMatchesMessage(Iterable<? extends ProjectIdentifier> matches);
+
+    protected abstract String formatNoMatchesMessage();
+
+    protected abstract boolean select(ProjectIdentifier project);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/AbstractSettingsFileSearchStrategyTemplate.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/AbstractSettingsFileSearchStrategyTemplate.java
new file mode 100644
index 0000000..f739081
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/AbstractSettingsFileSearchStrategyTemplate.java
@@ -0,0 +1,37 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+import org.gradle.api.initialization.Settings;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractSettingsFileSearchStrategyTemplate implements ISettingsFileSearchStrategy {
+    public File find(StartParameter startParameter) {
+        File currentDirSettingsFile = new File(startParameter.getCurrentDir(), Settings.DEFAULT_SETTINGS_FILE);
+        if (currentDirSettingsFile.isFile()) {
+            return currentDirSettingsFile;
+        }
+        return findBeyondCurrentDir(startParameter);
+    }
+
+    protected abstract File findBeyondCurrentDir(StartParameter startParameter);
+
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BaseSettings.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BaseSettings.java
new file mode 100644
index 0000000..e2fdef4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BaseSettings.java
@@ -0,0 +1,201 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+import org.gradle.api.UnknownProjectException;
+import org.gradle.api.initialization.ProjectDescriptor;
+import org.gradle.api.initialization.Settings;
+import org.gradle.api.internal.DynamicObjectHelper;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.SettingsInternal;
+import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.groovy.scripts.ScriptSource;
+
+import java.io.File;
+import java.net.URLClassLoader;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class BaseSettings implements SettingsInternal {
+    public static final String DEFAULT_BUILD_SRC_DIR = "buildSrc";
+
+    private ScriptSource settingsScript;
+
+    private StartParameter startParameter;
+
+    private URLClassLoader classloader;
+
+    private File settingsDir;
+
+    private DefaultProjectDescriptor rootProjectDescriptor;
+
+    private DynamicObjectHelper dynamicObjectHelper;
+
+    private GradleInternal gradle;
+    private IProjectDescriptorRegistry projectDescriptorRegistry;
+
+    protected BaseSettings() {
+    }
+
+    public BaseSettings(GradleInternal gradle,
+                        IProjectDescriptorRegistry projectDescriptorRegistry,
+                        URLClassLoader classloader, File settingsDir, ScriptSource settingsScript,
+                        StartParameter startParameter) {
+        this.gradle = gradle;
+        this.projectDescriptorRegistry = projectDescriptorRegistry;
+        this.settingsDir = settingsDir;
+        this.settingsScript = settingsScript;
+        this.startParameter = startParameter;
+        this.classloader = classloader;
+        rootProjectDescriptor = createProjectDescriptor(null, settingsDir.getName(), settingsDir);
+        dynamicObjectHelper = new DynamicObjectHelper(this);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("settings '%s'", rootProjectDescriptor.getName());
+    }
+
+    public GradleInternal getGradle() {
+        return gradle;
+    }
+
+    public Settings getSettings() {
+        return this;
+    }
+
+    public DefaultProjectDescriptor createProjectDescriptor(DefaultProjectDescriptor parent, String name, File dir) {
+        return new DefaultProjectDescriptor(parent, name, dir, projectDescriptorRegistry);
+    }
+
+    public DefaultProjectDescriptor findProject(String path) {
+        return projectDescriptorRegistry.getProject(path);
+    }
+
+    public DefaultProjectDescriptor findProject(File projectDir) {
+        return projectDescriptorRegistry.getProject(projectDir);
+    }
+
+    public DefaultProjectDescriptor project(String path) {
+        DefaultProjectDescriptor projectDescriptor = projectDescriptorRegistry.getProject(path);
+        if (projectDescriptor == null) {
+            throw new UnknownProjectException(String.format("Project with path '%s' could not be found.", path));
+        }
+        return projectDescriptor;
+    }
+
+    public DefaultProjectDescriptor project(File projectDir) {
+        DefaultProjectDescriptor projectDescriptor = projectDescriptorRegistry.getProject(projectDir);
+        if (projectDescriptor == null) {
+            throw new UnknownProjectException(String.format("Project with path '%s' could not be found.", projectDir));
+        }
+        return projectDescriptor;
+    }
+
+    public void include(String[] projectPaths) {
+        for (String projectPath : projectPaths) {
+            String subPath = "";
+            String[] pathElements = removeTrailingColon(projectPath).split(":");
+            DefaultProjectDescriptor parentProjectDescriptor = rootProjectDescriptor;
+            for (String pathElement : pathElements) {
+                subPath = subPath + ":" + pathElement;
+                DefaultProjectDescriptor projectDescriptor = projectDescriptorRegistry.getProject(subPath);
+                if (projectDescriptor == null) {
+                    parentProjectDescriptor = createProjectDescriptor(parentProjectDescriptor, pathElement, new File(parentProjectDescriptor.getProjectDir(), pathElement));
+                } else {
+                    parentProjectDescriptor = projectDescriptor;
+                }
+            }
+        }
+    }
+
+    public void includeFlat(String[] projectNames) {
+        for (String projectName : projectNames) {
+            createProjectDescriptor(rootProjectDescriptor, projectName,
+                    new File(rootProjectDescriptor.getProjectDir().getParentFile(), projectName));
+        }
+    }
+
+    private String removeTrailingColon(String projectPath) {
+        if (projectPath.startsWith(":")) {
+            return projectPath.substring(1);
+        }
+        return projectPath;
+    }
+
+    public URLClassLoader getClassLoader() {
+        return classloader;
+    }
+
+    public ProjectDescriptor getRootProject() {
+        return rootProjectDescriptor;
+    }
+
+    public void setRootProjectDescriptor(DefaultProjectDescriptor rootProjectDescriptor) {
+        this.rootProjectDescriptor = rootProjectDescriptor;
+    }
+
+    public File getRootDir() {
+        return rootProjectDescriptor.getProjectDir();
+    }
+
+    public StartParameter getStartParameter() {
+        return startParameter;
+    }
+
+    public void setStartParameter(StartParameter startParameter) {
+        this.startParameter = startParameter;
+    }
+
+    public File getSettingsDir() {
+        return settingsDir;
+    }
+
+    public void setSettingsDir(File settingsDir) {
+        this.settingsDir = settingsDir;
+    }
+
+    public ScriptSource getSettingsScript() {
+        return settingsScript;
+    }
+
+    public void setSettingsScript(ScriptSource settingsScript) {
+        this.settingsScript = settingsScript;
+    }
+
+    public IProjectDescriptorRegistry getProjectDescriptorRegistry() {
+        return projectDescriptorRegistry;
+    }
+
+    public void setProjectDescriptorRegistry(IProjectDescriptorRegistry projectDescriptorRegistry) {
+        this.projectDescriptorRegistry = projectDescriptorRegistry;
+    }
+
+    public Map<String, Object> getAdditionalProperties() {
+        return dynamicObjectHelper.getAdditionalProperties();
+    }
+
+    protected DynamicObjectHelper getDynamicObjectHelper() {
+        return dynamicObjectHelper;
+    }
+
+    public IProjectRegistry<DefaultProjectDescriptor> getProjectRegistry() {
+        return projectDescriptorRegistry;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BuildFileProjectSpec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BuildFileProjectSpec.java
new file mode 100644
index 0000000..10d4687
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BuildFileProjectSpec.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.initialization;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.gradle.api.internal.project.ProjectIdentifier;
+import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.api.InvalidUserDataException;
+
+import java.io.File;
+
+public class BuildFileProjectSpec extends AbstractProjectSpec {
+    private final File buildFile;
+
+    public BuildFileProjectSpec(File buildFile) {
+        this.buildFile = buildFile;
+    }
+
+    public String getDisplayName() {
+        return String.format("project has build file '%s'", buildFile);
+    }
+
+    protected String formatNoMatchesMessage() {
+        return String.format("No projects in this build have build file '%s'.", buildFile);
+    }
+
+    protected String formatMultipleMatchesMessage(Iterable<? extends ProjectIdentifier> matches) {
+        return String.format("Multiple projects in this build have build file '%s': %s", buildFile, matches);
+    }
+
+    protected boolean select(ProjectIdentifier project) {
+        return buildFile.equals(project.getBuildFile());
+    }
+
+    @Override
+    protected void checkPreconditions(IProjectRegistry<?> registry) {
+        if (!buildFile.exists()) {
+            throw new InvalidUserDataException(String.format("Build file '%s' does not exist.", buildFile));
+        }
+        if (!buildFile.isFile()) {
+            throw new InvalidUserDataException(String.format("Build file '%s' is not a file.", buildFile));
+        }
+    }
+
+    public boolean equals(Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj);
+    }
+
+    public int hashCode() {
+        return HashCodeBuilder.reflectionHashCode(this);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BuildLoader.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BuildLoader.java
new file mode 100644
index 0000000..5e45c69
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BuildLoader.java
@@ -0,0 +1,106 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Project;
+import org.gradle.api.initialization.ProjectDescriptor;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.project.IProjectFactory;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.util.Clock;
+import org.gradle.util.GUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * @author Hans Dockter
+ */
+public class BuildLoader {
+    private static final Logger LOGGER = LoggerFactory.getLogger(BuildLoader.class);
+
+    private final IProjectFactory projectFactory;
+
+    public BuildLoader(IProjectFactory projectFactory) {
+        this.projectFactory = projectFactory;
+    }
+
+    /**
+     * Creates the {@link org.gradle.api.internal.GradleInternal} and {@link ProjectInternal} instances for the given root project,
+     * ready for the projects to be evaluated.
+     */
+    public void load(ProjectDescriptor rootProjectDescriptor, GradleInternal gradle,
+                     Map<String, String> externalProjectProperties) {
+        LOGGER.debug("Loading Project objects");
+        Clock clock = new Clock();
+        createProjects(rootProjectDescriptor, gradle, externalProjectProperties);
+        attachDefaultProject(gradle);
+        LOGGER.debug("Timing: Loading projects took: " + clock.getTime());
+    }
+
+    private void attachDefaultProject(GradleInternal gradle) {
+        ProjectSpec selector = gradle.getStartParameter().getDefaultProjectSelector();
+        ProjectInternal defaultProject;
+        try {
+            defaultProject = selector.selectProject(gradle.getRootProject().getProjectRegistry());
+        } catch (InvalidUserDataException e) {
+            throw new GradleException(String.format("Could not select the default project for this build. %s",
+                    e.getMessage()), e);
+        }
+        gradle.setDefaultProject(defaultProject);
+    }
+
+    private void createProjects(ProjectDescriptor rootProjectDescriptor, GradleInternal gradle,
+                                Map<String, String> externalProjectProperties) {
+        ProjectInternal rootProject = projectFactory.createProject(rootProjectDescriptor, null, gradle);
+        gradle.setRootProject(rootProject);
+
+        addPropertiesToProject(externalProjectProperties, rootProject);
+        addProjects(rootProject, rootProjectDescriptor, gradle, externalProjectProperties);
+    }
+
+    private void addProjects(ProjectInternal parent, ProjectDescriptor parentProjectDescriptor, GradleInternal gradle,
+                             Map<String, String> externalProjectProperties) {
+        for (ProjectDescriptor childProjectDescriptor : parentProjectDescriptor.getChildren()) {
+            ProjectInternal childProject = projectFactory.createProject(childProjectDescriptor, parent, gradle);
+            addPropertiesToProject(externalProjectProperties, childProject);
+            addProjects(childProject, childProjectDescriptor, gradle, externalProjectProperties);
+        }
+    }
+
+    private void addPropertiesToProject(Map<String, String> externalProperties, ProjectInternal project) {
+        Properties projectProperties = new Properties();
+        File projectPropertiesFile = new File(project.getProjectDir(), Project.GRADLE_PROPERTIES);
+        LOGGER.debug("Looking for project properties from: {}", projectPropertiesFile);
+        if (projectPropertiesFile.isFile()) {
+            projectProperties = GUtil.loadProperties(projectPropertiesFile);
+            LOGGER.debug("Adding project properties (if not overwritten by user properties): {}",
+                    projectProperties.keySet());
+        } else {
+            LOGGER.debug("project property file does not exists. We continue!");
+        }
+        projectProperties.putAll(externalProperties);
+        for (Object key : projectProperties.keySet()) {
+            project.setProperty((String) key, projectProperties.get(key));
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BuildProgressLogger.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BuildProgressLogger.java
new file mode 100644
index 0000000..f7197e0
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BuildProgressLogger.java
@@ -0,0 +1,59 @@
+/*
+ * 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.initialization;
+
+import org.gradle.BuildAdapter;
+import org.gradle.BuildResult;
+import org.gradle.api.execution.TaskExecutionGraph;
+import org.gradle.api.execution.TaskExecutionGraphListener;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.logging.ProgressLogger;
+import org.gradle.logging.ProgressLoggerFactory;
+
+class BuildProgressLogger extends BuildAdapter implements TaskExecutionGraphListener {
+    private ProgressLogger progressLogger;
+    private final ProgressLoggerFactory progressLoggerFactory;
+    private Gradle gradle;
+
+    public BuildProgressLogger(ProgressLoggerFactory progressLoggerFactory) {
+        this.progressLoggerFactory = progressLoggerFactory;
+    }
+
+    @Override
+    public void buildStarted(Gradle gradle) {
+        if (gradle.getParent() == null) {
+            progressLogger = progressLoggerFactory.start();
+            progressLogger.progress("Loading");
+            this.gradle = gradle;
+        }
+    }
+
+    public void graphPopulated(TaskExecutionGraph graph) {
+        if (graph == gradle.getTaskGraph()) {
+            progressLogger.progress("Building");
+        }
+    }
+
+    @Override
+    public void buildFinished(BuildResult result) {
+        if (result.getGradle() == gradle) {
+            progressLogger.completed();
+            progressLogger = null;
+            gradle = null;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BuildSourceBuilder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BuildSourceBuilder.java
new file mode 100644
index 0000000..562ec86
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/BuildSourceBuilder.java
@@ -0,0 +1,145 @@
+/*
+ * 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.initialization;
+
+import org.apache.commons.io.IOUtils;
+import org.gradle.*;
+import org.gradle.api.Project;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.plugins.EmbeddableJavaProject;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.cache.CacheRepository;
+import org.gradle.cache.PersistentStateCache;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.groovy.scripts.StringScriptSource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class BuildSourceBuilder {
+    private static final Logger LOGGER = LoggerFactory.getLogger(BuildSourceBuilder.class);
+
+    private final GradleLauncherFactory gradleLauncherFactory;
+    private final ClassLoaderFactory classLoaderFactory;
+    private final CacheRepository cacheRepository;
+
+    private static final String DEFAULT_BUILD_SOURCE_SCRIPT_RESOURCE = "defaultBuildSourceScript.txt";
+
+    public BuildSourceBuilder(GradleLauncherFactory gradleLauncherFactory, ClassLoaderFactory classLoaderFactory, CacheRepository cacheRepository) {
+        this.gradleLauncherFactory = gradleLauncherFactory;
+        this.classLoaderFactory = classLoaderFactory;
+        this.cacheRepository = cacheRepository;
+    }
+
+    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, classLoaderFactory.getRootClassLoader());
+    }
+
+    public Set<File> 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>();
+        }
+        LOGGER.info("================================================" + " Start building buildSrc");
+        StartParameter startParameterArg = startParameter.newInstance();
+        startParameterArg.setProjectProperties(startParameter.getProjectProperties());
+        startParameterArg.setSearchUpwards(false);
+
+        // 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.cache("buildSrc").forObject(
+                startParameter.getCurrentDir()).invalidateOnVersionChange().open().openStateCache();
+        boolean rebuild = stateCache.get() == null;
+
+        if (!new File(startParameter.getCurrentDir(), Project.DEFAULT_BUILD_FILE).isFile()) {
+            LOGGER.debug("Gradle script file does not exist. Using default one.");
+            ScriptSource source = new StringScriptSource("default buildSrc build script", getDefaultScript());
+            startParameterArg.setBuildScriptSource(source);
+        }
+
+        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");
+
+        return buildSourceClasspath;
+    }
+
+    static String getDefaultScript() {
+        try {
+            return IOUtils.toString(BuildSourceBuilder.class.getResourceAsStream(DEFAULT_BUILD_SOURCE_SCRIPT_RESOURCE));
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static class BuildSrcBuildListener extends BuildAdapter {
+        private EmbeddableJavaProject projectInfo;
+        private Set<File> classpath;
+        private final boolean rebuild;
+
+        public BuildSrcBuildListener(boolean rebuild) {
+            this.rebuild = rebuild;
+        }
+
+        @Override
+        public void projectsEvaluated(Gradle gradle) {
+            projectInfo = gradle.getRootProject().getConvention().getPlugin(
+                    EmbeddableJavaProject.class);
+            gradle.getStartParameter().setTaskNames(rebuild ? projectInfo.getRebuildTasks() : projectInfo.getBuildTasks());
+            classpath = projectInfo.getRuntimeClasspath().getFiles();
+        }
+
+        public Collection<File> getRuntimeClasspath() {
+            return classpath;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ClassLoaderFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ClassLoaderFactory.java
new file mode 100644
index 0000000..1314e7c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ClassLoaderFactory.java
@@ -0,0 +1,30 @@
+/*
+ * 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.initialization;
+
+import org.gradle.util.MultiParentClassLoader;
+
+public interface ClassLoaderFactory {
+    /**
+     * Returns the root ClassLoader shared by all builds.
+     */
+    ClassLoader getRootClassLoader();
+
+    /**
+     * Creates the script ClassLoader for a build.
+     */
+    MultiParentClassLoader createScriptClassLoader();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/CommandLine2StartParameterConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/CommandLine2StartParameterConverter.java
new file mode 100644
index 0000000..4e86a8f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/CommandLine2StartParameterConverter.java
@@ -0,0 +1,31 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+
+import java.io.OutputStream;
+
+/**
+ * @author Hans Dockter
+ */
+public interface CommandLine2StartParameterConverter {
+    StartParameter convert(String[] args);
+
+    void convert(String[] args, StartParameter parameter);
+
+    void showHelp(OutputStream out);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderFactory.java
new file mode 100644
index 0000000..596c8ef
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultClassLoaderFactory.java
@@ -0,0 +1,53 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.util.ClasspathUtil;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.Jvm;
+import org.gradle.util.MultiParentClassLoader;
+
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collections;
+
+public class DefaultClassLoaderFactory implements ClassLoaderFactory {
+    private final URLClassLoader rootClassLoader;
+
+    public DefaultClassLoaderFactory(ClassPathRegistry classPathRegistry) {
+        // Add in tools.jar
+        ClassLoader parent = getClass().getClassLoader();
+        File toolsJar = Jvm.current().getToolsJar();
+        if (toolsJar != null) {
+            ClasspathUtil.addUrl((URLClassLoader) parent, GFileUtils.toURLs(Collections.singleton(toolsJar)));
+        }
+
+        // Add in libs for plugins
+        URL[] classPath = classPathRegistry.getClassPathUrls("GRADLE_PLUGINS");
+        rootClassLoader = new URLClassLoader(classPath, parent);
+    }
+
+    public ClassLoader getRootClassLoader() {
+        return rootClassLoader;
+    }
+
+    public MultiParentClassLoader createScriptClassLoader() {
+        return new MultiParentClassLoader(rootClassLoader);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultCommandLine2StartParameterConverter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultCommandLine2StartParameterConverter.java
new file mode 100644
index 0000000..5f2773d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultCommandLine2StartParameterConverter.java
@@ -0,0 +1,415 @@
+/*
+ * 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.initialization;
+
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import joptsimple.OptionException;
+import joptsimple.OptionParser;
+import joptsimple.OptionSet;
+import org.gradle.CacheUsage;
+import org.gradle.CommandLineArgumentException;
+import org.gradle.StartParameter;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.artifacts.ProjectDependenciesBuildInstruction;
+import org.gradle.api.initialization.Settings;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.execution.DependencyReportBuildExecuter;
+import org.gradle.execution.PropertyReportBuildExecuter;
+import org.gradle.execution.TaskReportBuildExecuter;
+import org.gradle.util.WrapUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultCommandLine2StartParameterConverter implements CommandLine2StartParameterConverter {
+    private static final String NO_SEARCH_UPWARDS = "u";
+    private static final String PROJECT_DIR = "p";
+    private static final String PROJECT_DEPENDENCY_TASK_NAMES = "A";
+    private static final String NO_PROJECT_DEPENDENCY_REBUILD = "a";
+    private static final String BUILD_FILE = "b";
+    public static final String INIT_SCRIPT = "I";
+    private static final String SETTINGS_FILE = "c";
+    public static final String TASKS = "t";
+    private static final String PROPERTIES = "r";
+    private static final String DEPENDENCIES = "n";
+    public static final String DEBUG = "d";
+    public static final String INFO = "i";
+    public static final String QUIET = "q";
+    public static final String FULL_STACKTRACE = "S";
+    public static final String STACKTRACE = "s";
+    private static final String SYSTEM_PROP = "D";
+    private static final String PROJECT_PROP = "P";
+    private static final String GRADLE_USER_HOME = "g";
+    private static final String EMBEDDED_SCRIPT = "e";
+    private static final String VERSION = "v";
+    private static final String CACHE = "C";
+    private static final String DRY_RUN = "m";
+    private static final String NO_OPT = "no-opt";
+    private static final String EXCLUDE_TASK = "x";
+    private static final String HELP = "h";
+    private static final String GUI = "gui";
+    private static final String ALL = "all";
+
+    private final OptionParser parser = new OptionParser() {
+        {
+            acceptsAll(WrapUtil.toList(NO_SEARCH_UPWARDS, "no-search-upward"), String.format(
+                    "Don't search in parent folders for a %s file.", Settings.DEFAULT_SETTINGS_FILE));
+            acceptsAll(WrapUtil.toList(CACHE, "cache"),
+                    "Specifies how compiled build scripts should be cached. Possible values are: 'rebuild' and 'on'. Default value is 'on'")
+                    .withRequiredArg().ofType(String.class);
+            acceptsAll(WrapUtil.toList(VERSION, "version"), "Print version info.");
+            acceptsAll(WrapUtil.toList(DEBUG, "debug"), "Log in debug mode (includes normal stacktrace).");
+            acceptsAll(WrapUtil.toList(QUIET, "quiet"), "Log errors only.");
+            acceptsAll(WrapUtil.toList(DRY_RUN, "dry-run"), "Runs the builds with all task actions disabled.");
+            acceptsAll(WrapUtil.toList(INFO, "info"), "Set log level to info.");
+            acceptsAll(WrapUtil.toList(STACKTRACE, "stacktrace"),
+                    "Print out the stacktrace also for user exceptions (e.g. compile error).");
+            acceptsAll(WrapUtil.toList(FULL_STACKTRACE, "full-stacktrace"),
+                    "Print out the full (very verbose) stacktrace for any exceptions.");
+            acceptsAll(WrapUtil.toList(TASKS, "tasks"), "Show list of all available tasks.").
+                    withOptionalArg().ofType(String.class);
+            acceptsAll(WrapUtil.toList(ALL), "Show additional details in the task listing.");
+            acceptsAll(WrapUtil.toList(PROPERTIES, "properties"), "Show list of all available project properties.").
+                    withOptionalArg().ofType(String.class);
+            acceptsAll(WrapUtil.toList(DEPENDENCIES, "dependencies"), "Show list of all project dependencies.").
+                    withOptionalArg().ofType(String.class);
+            acceptsAll(WrapUtil.toList(GUI), "Launches a GUI application");
+            acceptsAll(WrapUtil.toList(PROJECT_DIR, "project-dir"),
+                    "Specifies the start directory for Gradle. Defaults to current directory.").withRequiredArg()
+                    .ofType(String.class);
+            acceptsAll(WrapUtil.toList(GRADLE_USER_HOME, "gradle-user-home"),
+                    "Specifies the gradle user home directory.").withRequiredArg().ofType(String.class);
+            acceptsAll(WrapUtil.toList(INIT_SCRIPT, "init-script"), "Specifies an initialization script.")
+                    .withRequiredArg().ofType(String.class);
+            acceptsAll(WrapUtil.toList(SETTINGS_FILE, "settings-file"), "Specifies the settings file.")
+                    .withRequiredArg().ofType(String.class);
+            acceptsAll(WrapUtil.toList(BUILD_FILE, "build-file"), "Specifies the build file.").withRequiredArg().ofType(
+                    String.class);
+            acceptsAll(WrapUtil.toList(SYSTEM_PROP, "system-prop"),
+                    "Set system property of the JVM (e.g. -Dmyprop=myvalue).").withRequiredArg().ofType(String.class);
+            acceptsAll(WrapUtil.toList(PROJECT_PROP, "project-prop"),
+                    "Set project property for the build script (e.g. -Pmyprop=myvalue).").withRequiredArg().ofType(
+                    String.class);
+            acceptsAll(WrapUtil.toList(EMBEDDED_SCRIPT, "embedded"), "Specify an embedded build script.")
+                    .withRequiredArg().ofType(String.class);
+            acceptsAll(WrapUtil.toList(PROJECT_DEPENDENCY_TASK_NAMES, "dep-tasks"),
+                    "Specify additional tasks for building project dependencies.").withRequiredArg().ofType(
+                    String.class);
+            acceptsAll(WrapUtil.toList(NO_PROJECT_DEPENDENCY_REBUILD, "no-rebuild"),
+                    "Do not rebuild project dependencies.");
+            acceptsAll(WrapUtil.toList(NO_OPT), "Ignore any task optimization.");
+            acceptsAll(WrapUtil.toList(EXCLUDE_TASK, "exclude-task"), "Specify a task to be excluded from execution.")
+                    .withRequiredArg().ofType(String.class);
+            acceptsAll(WrapUtil.toList(HELP, "?", "help"), "Shows this help message");
+        }};
+
+    private static BiMap<String, LogLevel> logLevelMap = HashBiMap.create();
+    private static BiMap<String, StartParameter.ShowStacktrace> showStacktraceMap = HashBiMap.create();
+
+    //Initialize bi-directional maps so you can convert these back and forth from their command line options to their
+    //object representation.
+
+    static {
+        logLevelMap.put(QUIET, LogLevel.QUIET);
+        logLevelMap.put(INFO, LogLevel.INFO);
+        logLevelMap.put(DEBUG, LogLevel.DEBUG);
+        logLevelMap.put("", LogLevel.LIFECYCLE);
+        //there are also other log levels that gradle doesn't support command-line-wise.
+
+        showStacktraceMap.put(FULL_STACKTRACE, StartParameter.ShowStacktrace.ALWAYS_FULL);
+        showStacktraceMap.put(STACKTRACE, StartParameter.ShowStacktrace.ALWAYS);
+        //showStacktraceMap.put( , StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS ); there is no command argument for this. Rather, the lack of an argument means 'default to this'.
+    }
+
+    public StartParameter convert(String[] args) {
+        StartParameter startParameter = new StartParameter();
+        convert(args, startParameter);
+        return startParameter;
+    }
+
+    public void convert(String[] args, StartParameter startParameter) {
+        OptionSet options;
+        try {
+            options = parser.parse(args);
+        } catch (OptionException e) {
+            throw new CommandLineArgumentException(e.getMessage());
+        }
+
+        if (options.has(HELP)) {
+            startParameter.setShowHelp(true);
+            return;
+        }
+
+        if (options.has(VERSION)) {
+            startParameter.setShowVersion(true);
+            return;
+        }
+
+        if (options.has(GUI)) {
+            startParameter.setLaunchGUI(true);
+        }
+
+        if (options.has(SYSTEM_PROP)) {
+            List<String> props = (List<String>) options.valuesOf(SYSTEM_PROP);
+            for (String keyValueExpression : props) {
+                String[] elements = keyValueExpression.split("=");
+                startParameter.getSystemPropertiesArgs().put(elements[0], elements.length == 1 ? "" : elements[1]);
+            }
+        }
+
+        if (options.has(PROJECT_PROP)) {
+            List<String> props = (List<String>) options.valuesOf(PROJECT_PROP);
+            for (String keyValueExpression : props) {
+                String[] elements = keyValueExpression.split("=");
+                startParameter.getProjectProperties().put(elements[0], elements.length == 1 ? "" : elements[1]);
+            }
+        }
+
+        if (options.has(NO_SEARCH_UPWARDS)) {
+            startParameter.setSearchUpwards(false);
+        }
+
+        if (options.has(PROJECT_DIR)) {
+            startParameter.setProjectDir(new File((String) options.valueOf(PROJECT_DIR)));
+        }
+        if (options.hasArgument(GRADLE_USER_HOME)) {
+            startParameter.setGradleUserHomeDir(new File((String) options.valueOf(GRADLE_USER_HOME)));
+        }
+        if (options.hasArgument(BUILD_FILE)) {
+            startParameter.setBuildFile(new File((String) options.valueOf(BUILD_FILE)));
+        }
+        if (options.hasArgument(SETTINGS_FILE)) {
+            startParameter.setSettingsFile(new File((String) options.valueOf(SETTINGS_FILE)));
+        }
+
+        for (String script : (List<String>) options.valuesOf(INIT_SCRIPT)) {
+            startParameter.addInitScript(new File(script));
+        }
+
+        if (options.has(CACHE)) {
+            try {
+                startParameter.setCacheUsage(CacheUsage.fromString(options.valueOf(CACHE).toString()));
+            } catch (InvalidUserDataException e) {
+                throw new CommandLineArgumentException(e.getMessage());
+            }
+        }
+
+        if (options.has(EMBEDDED_SCRIPT)) {
+            if (options.has(BUILD_FILE) || options.has(NO_SEARCH_UPWARDS) || options.has(SETTINGS_FILE)) {
+                System.err.println(String.format(
+                        "Error: The -%s option can't be used together with the -%s, -%s or -%s options.",
+                        EMBEDDED_SCRIPT, BUILD_FILE, SETTINGS_FILE, NO_SEARCH_UPWARDS));
+                throw new CommandLineArgumentException(String.format(
+                        "Error: The -%s option can't be used together with the -%s, -%s or -%s options.",
+                        EMBEDDED_SCRIPT, BUILD_FILE, SETTINGS_FILE, NO_SEARCH_UPWARDS));
+            }
+            startParameter.useEmbeddedBuildFile((String) options.valueOf(EMBEDDED_SCRIPT));
+        }
+
+        if (options.has(FULL_STACKTRACE)) {
+            if (options.has(STACKTRACE)) {
+                throw new CommandLineArgumentException(String.format(
+                        "Error: The -%s option can't be used together with the -%s option.", FULL_STACKTRACE,
+                        STACKTRACE));
+            }
+            startParameter.setShowStacktrace(StartParameter.ShowStacktrace.ALWAYS_FULL);
+        } else if (options.has(STACKTRACE)) {
+            startParameter.setShowStacktrace(StartParameter.ShowStacktrace.ALWAYS);
+        }
+
+        if (options.has(TASKS) && options.has(PROPERTIES)) {
+            throw new CommandLineArgumentException(String.format(
+                    "Error: The -%s and -%s options cannot be used together.", TASKS, PROPERTIES));
+        }
+
+        if (options.has(PROJECT_DEPENDENCY_TASK_NAMES) && options.has(NO_PROJECT_DEPENDENCY_REBUILD)) {
+            throw new CommandLineArgumentException(String.format(
+                    "Error: The -%s and -%s options cannot be used together.", PROJECT_DEPENDENCY_TASK_NAMES,
+                    NO_PROJECT_DEPENDENCY_REBUILD));
+        } else if (options.has(NO_PROJECT_DEPENDENCY_REBUILD)) {
+            startParameter.setProjectDependenciesBuildInstruction(new ProjectDependenciesBuildInstruction(null));
+        } else if (options.has(PROJECT_DEPENDENCY_TASK_NAMES)) {
+            List<String> normalizedTaskNames = new ArrayList<String>();
+            for (Object o : options.valuesOf(PROJECT_DEPENDENCY_TASK_NAMES)) {
+                String taskName = (String) o;
+                normalizedTaskNames.add(taskName.trim());
+            }
+            startParameter.setProjectDependenciesBuildInstruction(new ProjectDependenciesBuildInstruction(
+                    normalizedTaskNames));
+        }
+
+        if (options.has(TASKS)) {
+            startParameter.setBuildExecuter(new TaskReportBuildExecuter((String) options.valueOf(TASKS), options.has(ALL)));
+        } else if (options.has(PROPERTIES)) {
+            startParameter.setBuildExecuter(new PropertyReportBuildExecuter((String) options.valueOf(PROPERTIES)));
+        } else if (options.has(DEPENDENCIES)) {
+            startParameter.setBuildExecuter(new DependencyReportBuildExecuter((String) options.valueOf(DEPENDENCIES)));
+        } else if (!options.nonOptionArguments().isEmpty()) {
+            startParameter.setTaskNames(options.nonOptionArguments());
+        }
+
+        if (options.has(DRY_RUN)) {
+            startParameter.setDryRun(true);
+        }
+
+        if (options.has(NO_OPT)) {
+            startParameter.setNoOpt(true);
+        }
+
+        if (options.has(EXCLUDE_TASK)) {
+            startParameter.setExcludedTaskNames((List<String>) options.valuesOf(EXCLUDE_TASK));
+        }
+
+        startParameter.setLogLevel(getLogLevel(options));
+    }
+
+    public void showHelp(OutputStream out) {
+        try {
+            parser.printHelpOn(out);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    private LogLevel getLogLevel(OptionSet options) {
+        LogLevel logLevel = null;
+        if (options.has(QUIET)) {
+            logLevel = LogLevel.QUIET;
+        }
+        if (options.has(INFO)) {
+            quitWithErrorIfLogLevelAlreadyDefined(logLevel, INFO);
+            logLevel = LogLevel.INFO;
+        }
+        if (options.has(DEBUG)) {
+            quitWithErrorIfLogLevelAlreadyDefined(logLevel, DEBUG);
+            logLevel = LogLevel.DEBUG;
+        }
+        if (logLevel == null) {
+            logLevel = LogLevel.LIFECYCLE;
+        }
+        return logLevel;
+    }
+
+    /*
+       This returns the log level object represented by the command line argument
+       @param  commandLineArgument a single command line argument (with no '-')
+       @return the corresponding log level or null if it doesn't match any.
+       @author mhunsicker
+    */
+
+    public LogLevel getLogLevel(String commandLineArgument) {
+        LogLevel logLevel = logLevelMap.get(commandLineArgument);
+        if (logLevel == null) {
+            return null;
+        }
+
+        return logLevel;
+    }
+
+    /*
+       This returns the command line argument that represents the specified
+       log level.
+       @param  logLevel       the log level.
+       @return the command line argument or null if this level cannot be
+                represented on the command line.
+       @author mhunsicker
+    */
+
+    public String getLogLevelCommandLine(LogLevel logLevel) {
+        String commandLine = logLevelMap.inverse().get(logLevel);
+        if (commandLine == null) {
+            return null;
+        }
+
+        return commandLine;
+    }
+
+    /*
+       This returns the log levels that are supported on the command line.
+       @return a collection of available log levels
+       @author mhunsicker
+    */
+
+    public Collection<LogLevel> getLogLevels() {
+        return Collections.unmodifiableCollection(logLevelMap.values());
+    }
+
+    /*
+       This returns the stack trace level object represented by the command
+       line argument
+       @param  commandLineArgument a single command line argument (with no '-')
+       @return the corresponding stack trace level or null if it doesn't match any.
+       @author mhunsicker
+    */
+
+    public StartParameter.ShowStacktrace getShowStacktrace(String commandLineArgument) {
+        StartParameter.ShowStacktrace showStacktrace = showStacktraceMap.get(commandLineArgument);
+        if (showStacktrace == null) {
+            return null;
+        }
+
+        return showStacktrace;
+    }
+
+    /*
+       This returns the command line argument that represents the specified
+       stack trace level.
+
+       @param  showStacktrace the stack trace level.
+       @return the command line argument or null if this level cannot be
+                represented on the command line.
+       @author mhunsicker
+    */
+
+    public String getShowStacktraceCommandLine(StartParameter.ShowStacktrace showStacktrace) {
+        String commandLine = showStacktraceMap.inverse().get(showStacktrace);
+        if (commandLine == null) {
+            return null;
+        }
+
+        return commandLine;
+    }
+
+    /*
+       This returns the ShowStacktrace levels that are supported on the command
+       line.
+       @return a collection of available ShowStacktrace levels
+       @author mhunsicker
+    */
+
+    public Collection<StartParameter.ShowStacktrace> getShowStacktrace() {
+        return Collections.unmodifiableCollection(showStacktraceMap.values());
+    }
+
+    private void quitWithErrorIfLogLevelAlreadyDefined(LogLevel logLevel, String option) {
+        if (logLevel != null) {
+            System.err.println(String.format(
+                    "Error: The log level is already defined by another option. Therefore the option %s is invalid.",
+                    option));
+            throw new InvalidUserDataException();
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultExceptionAnalyser.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultExceptionAnalyser.java
new file mode 100644
index 0000000..cee2873
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultExceptionAnalyser.java
@@ -0,0 +1,105 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.GradleScriptException;
+import org.gradle.api.LocationAwareException;
+import org.gradle.api.ScriptCompilationException;
+import org.gradle.api.internal.Contextual;
+import org.gradle.api.internal.ExceptionAnalyser;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.tasks.TaskExecutionException;
+import org.gradle.groovy.scripts.Script;
+import org.gradle.groovy.scripts.ScriptExecutionListener;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.listener.ListenerManager;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class DefaultExceptionAnalyser implements ExceptionAnalyser, ScriptExecutionListener {
+    private final Map<String, ScriptSource> scripts = new HashMap<String, ScriptSource>();
+    private final ExceptionDecoratingClassGenerator generator = new ExceptionDecoratingClassGenerator();
+
+    public DefaultExceptionAnalyser(ListenerManager listenerManager) {
+        listenerManager.addListener(this);
+    }
+
+    public void beforeScript(Script script) {
+        ScriptSource source = script.getScriptSource();
+        scripts.put(source.getFileName(), source);
+    }
+
+    public void afterScript(Script script, Throwable result) {
+    }
+
+    public Throwable transform(Throwable exception) {
+        Throwable actualException = findDeepest(exception);
+        if (actualException == null) {
+            return exception;
+        }
+        if (actualException instanceof LocationAwareException) {
+            return actualException;
+        }
+
+        ScriptSource source = null;
+        Integer lineNumber = null;
+
+        // todo - remove this special case
+        if (actualException instanceof ScriptCompilationException) {
+            ScriptCompilationException scriptCompilationException = (ScriptCompilationException) actualException;
+            source = scriptCompilationException.getScriptSource();
+            lineNumber = scriptCompilationException.getLineNumber();
+        }
+
+        if (source == null) {
+            for (
+                    Throwable currentException = actualException; currentException != null;
+                    currentException = currentException.getCause()) {
+                for (StackTraceElement element : currentException.getStackTrace()) {
+                    if (scripts.containsKey(element.getFileName())) {
+                        source = scripts.get(element.getFileName());
+                        lineNumber = element.getLineNumber() >= 0 ? element.getLineNumber() : null;
+                        break;
+                    }
+                }
+            }
+        }
+
+        if (source == null) {
+            if (actualException instanceof TaskExecutionException) {
+                TaskExecutionException taskExecutionException = (TaskExecutionException) actualException;
+                source = ((ProjectInternal) taskExecutionException.getTask().getProject()).getBuildScriptSource();
+            }
+        }
+
+        return generator.newInstance(actualException.getClass(), actualException, source, lineNumber);
+    }
+
+    private Throwable findDeepest(Throwable exception) {
+        Throwable result = null;
+        Throwable contextMatch = null;
+        for (Throwable current = exception; current != null; current = current.getCause()) {
+            if (current instanceof GradleScriptException || current instanceof TaskExecutionException) {
+                result = current;
+            }
+            if (current.getClass().getAnnotation(Contextual.class) != null) {
+                contextMatch = current;
+            }
+        }
+        return result != null ? result : contextMatch;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java
new file mode 100644
index 0000000..12db113
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncher.java
@@ -0,0 +1,203 @@
+/*
+ * 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.initialization;
+
+import org.gradle.BuildListener;
+import org.gradle.BuildResult;
+import org.gradle.GradleLauncher;
+import org.gradle.api.internal.ExceptionAnalyser;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.SettingsInternal;
+import org.gradle.api.logging.StandardOutputListener;
+import org.gradle.configuration.BuildConfigurer;
+import org.gradle.execution.BuildExecuter;
+import org.gradle.logging.LoggingManagerInternal;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DefaultGradleLauncher extends GradleLauncher {
+    private enum Stage {
+        Configure, PopulateTaskGraph, Build
+    }
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultGradleLauncher.class);
+
+    private final GradleInternal gradle;
+    private final SettingsHandler settingsHandler;
+    private final IGradlePropertiesLoader gradlePropertiesLoader;
+    private final BuildLoader buildLoader;
+    private final BuildConfigurer buildConfigurer;
+    private final ExceptionAnalyser exceptionAnalyser;
+    private final BuildListener buildListener;
+    private final InitScriptHandler initScriptHandler;
+    private final LoggingManagerInternal loggingManager;
+
+    /**
+     * Creates a new instance.  Don't call this directly, use {@link #newInstance(org.gradle.StartParameter)} or {@link
+     * #newInstance(String...)} instead.  Note that this method is package-protected to discourage it's direct use.
+     */
+    public DefaultGradleLauncher(GradleInternal gradle, InitScriptHandler initScriptHandler, SettingsHandler settingsHandler,
+                                 IGradlePropertiesLoader gradlePropertiesLoader, BuildLoader buildLoader,
+                                 BuildConfigurer buildConfigurer, BuildListener buildListener,
+                                 ExceptionAnalyser exceptionAnalyser, LoggingManagerInternal loggingManager) {
+        this.gradle = gradle;
+        this.initScriptHandler = initScriptHandler;
+        this.settingsHandler = settingsHandler;
+        this.gradlePropertiesLoader = gradlePropertiesLoader;
+        this.buildLoader = buildLoader;
+        this.buildConfigurer = buildConfigurer;
+        this.exceptionAnalyser = exceptionAnalyser;
+        this.buildListener = buildListener;
+        this.loggingManager = loggingManager;
+    }
+
+    /**
+     * <p>Executes the build for this GradleLauncher instance and returns the result. Note that when the build fails,
+     * the exception is available using {@link org.gradle.BuildResult#getFailure()}.</p>
+     *
+     * @return The result. Never returns null.
+     */
+    @Override
+    public BuildResult run() {
+        return doBuild(Stage.Build);
+    }
+
+    /**
+     * Evaluates the settings and all the projects. The information about available tasks and projects is accessible via
+     * the {@link org.gradle.api.invocation.Gradle#getRootProject()} object.
+     *
+     * @return A BuildResult object. Never returns null.
+     */
+    @Override
+    public BuildResult getBuildAnalysis() {
+        return doBuild(Stage.Configure);
+    }
+
+    /**
+     * Evaluates the settings and all the projects. The information about available tasks and projects is accessible via
+     * the {@link org.gradle.api.invocation.Gradle#getRootProject()} object. Fills the execution plan without running
+     * the build. The tasks to be executed tasks are available via {@link org.gradle.api.invocation.Gradle#getTaskGraph()}.
+     *
+     * @return A BuildResult object. Never returns null.
+     */
+    @Override
+    public BuildResult getBuildAndRunAnalysis() {
+        return doBuild(Stage.PopulateTaskGraph);
+    }
+
+    private BuildResult doBuild(Stage upTo) {
+        loggingManager.start();
+        buildListener.buildStarted(gradle);
+
+        Throwable failure = null;
+        try {
+            doBuildStages(upTo);
+        } catch (Throwable t) {
+            failure = exceptionAnalyser.transform(t);
+        }
+        BuildResult buildResult = new BuildResult(gradle, failure);
+        buildListener.buildFinished(buildResult);
+
+        // Switching Logging off is important if the Gradle factory is used to
+        // run multiple Gradle builds (each one requiring a new instances of GradleLauncher).
+        // Switching it off shouldn't be strictly necessary as StandardOutput capturing should
+        // always be closed. But as we expose this functionality to the builds, we can't
+        // guarantee this.
+        loggingManager.stop();
+        return buildResult;
+    }
+
+    private void doBuildStages(Stage upTo) {
+        // Evaluate init scripts
+        initScriptHandler.executeScripts(gradle);
+
+        // Evaluate settings script
+        SettingsInternal settings = settingsHandler.findAndLoadSettings(gradle, gradlePropertiesLoader);
+        buildListener.settingsEvaluated(settings);
+
+        // Load build
+        buildLoader.load(settings.getRootProject(), gradle, gradlePropertiesLoader.getGradleProperties());
+        buildListener.projectsLoaded(gradle);
+
+        // Configure build
+        buildConfigurer.process(gradle.getRootProject());
+        buildListener.projectsEvaluated(gradle);
+
+        if (upTo == Stage.Configure) {
+            return;
+        }
+
+        // Populate task graph
+        BuildExecuter executer = gradle.getStartParameter().getBuildExecuter();
+        executer.select(gradle);
+
+        if (upTo == Stage.PopulateTaskGraph) {
+            return;
+        }
+
+        // Execute build
+        LOGGER.info(String.format("Starting build for %s.", executer.getDisplayName()));
+        executer.execute();
+
+        assert upTo == Stage.Build;
+    }
+
+    // This is used for mocking
+
+    /**
+     * <p>Adds a listener to this build instance. The listener is notified of events which occur during the
+     * execution of the build. See {@link org.gradle.api.invocation.Gradle#addListener(Object)} for supported listener
+     * types.</p>
+     *
+     * @param listener The listener to add. Has no effect if the listener has already been added.
+     */
+    @Override
+    public void addListener(Object listener) {
+        gradle.addListener(listener);
+    }
+
+    /**
+     * Use the given listener. See {@link org.gradle.api.invocation.Gradle#useLogger(Object)} for details.
+     *
+     * @param logger The logger to use.
+     */
+    @Override
+    public void useLogger(Object logger) {
+        gradle.useLogger(logger);
+    }
+    
+    /**
+     * <p>Adds a {@link StandardOutputListener} to this build instance. The listener is notified of any text written to
+     * standard output by Gradle's logging system
+     *
+     * @param listener The listener to add. Has no effect if the listener has already been added.
+     */
+    @Override
+    public void addStandardOutputListener(StandardOutputListener listener) {
+        loggingManager.addStandardOutputListener(listener);
+    }
+
+    /**
+     * <p>Adds a {@link StandardOutputListener} to this build instance. The listener is notified of any text written to
+     * standard error by Gradle's logging system
+     *
+     * @param listener The listener to add. Has no effect if the listener has already been added.
+     */
+    @Override
+    public void addStandardErrorListener(StandardOutputListener listener) {
+        loggingManager.addStandardErrorListener(listener);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java
new file mode 100644
index 0000000..4a4bb23
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultGradleLauncherFactory.java
@@ -0,0 +1,125 @@
+/*
+ * 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.initialization;
+
+import org.gradle.*;
+import org.gradle.api.internal.ExceptionAnalyser;
+import org.gradle.api.internal.project.GlobalServicesRegistry;
+import org.gradle.api.internal.project.IProjectFactory;
+import org.gradle.api.internal.project.ServiceRegistry;
+import org.gradle.api.internal.project.TopLevelBuildServiceRegistry;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.StandardOutputListener;
+import org.gradle.cache.CacheRepository;
+import org.gradle.configuration.BuildConfigurer;
+import org.gradle.configuration.ProjectDependencies2TaskResolver;
+import org.gradle.invocation.DefaultGradle;
+import org.gradle.listener.ListenerManager;
+import org.gradle.logging.LoggingManagerFactory;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.logging.ProgressLoggingBridge;
+import org.gradle.util.WrapUtil;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultGradleLauncherFactory implements GradleLauncherFactory {
+    private final ServiceRegistry sharedServices;
+    private final NestedBuildTracker tracker;
+    private CommandLine2StartParameterConverter commandLine2StartParameterConverter;
+
+    public DefaultGradleLauncherFactory() {
+        sharedServices = new GlobalServicesRegistry();
+
+        // Start logging system
+        sharedServices.get(LoggingManagerFactory.class).create().setLevel(LogLevel.LIFECYCLE).start();
+
+        commandLine2StartParameterConverter = sharedServices.get(CommandLine2StartParameterConverter.class);
+        tracker = new NestedBuildTracker();
+
+        // Register default loggers 
+        ListenerManager listenerManager = sharedServices.get(ListenerManager.class);
+        listenerManager.useLogger(new ProgressLoggingBridge());
+        listenerManager.addListener(new BuildProgressLogger(sharedServices.get(ProgressLoggerFactory.class)));
+    }
+
+    public StartParameter createStartParameter(String[] commandLineArgs) {
+        return commandLine2StartParameterConverter.convert(commandLineArgs);
+    }
+
+    public GradleLauncher newInstance(String[] commandLineArgs) {
+        return newInstance(commandLine2StartParameterConverter.convert(commandLineArgs));
+    }
+
+    public GradleLauncher newInstance(StartParameter startParameter) {
+        TopLevelBuildServiceRegistry serviceRegistry = new TopLevelBuildServiceRegistry(sharedServices, startParameter);
+        ListenerManager listenerManager = serviceRegistry.get(ListenerManager.class);
+        LoggingManagerInternal loggingManager = serviceRegistry.get(LoggingManagerFactory.class).create();
+        loggingManager.setLevel(startParameter.getLogLevel());
+
+        //this hooks up the ListenerManager and LoggingConfigurer so you can call Gradle.addListener() with a StandardOutputListener.
+        loggingManager.addStandardOutputListener(listenerManager.getBroadcaster(StandardOutputListener.class));
+        loggingManager.addStandardErrorListener(listenerManager.getBroadcaster(StandardOutputListener.class));
+
+        listenerManager.useLogger(new TaskExecutionLogger(serviceRegistry.get(ProgressLoggerFactory.class)));
+        listenerManager.addListener(tracker);
+        listenerManager.addListener(new BuildCleanupListener(serviceRegistry));
+
+        DefaultGradle gradle = new DefaultGradle(
+                tracker.getCurrentBuild(),
+                startParameter, serviceRegistry);
+        return new DefaultGradleLauncher(
+                gradle,
+                serviceRegistry.get(InitScriptHandler.class),
+                new SettingsHandler(
+                        new EmbeddedScriptSettingsFinder(
+                                new DefaultSettingsFinder(WrapUtil.<ISettingsFileSearchStrategy>toList(
+                                        new MasterDirSettingsFinderStrategy(),
+                                        new ParentDirSettingsFinderStrategy()))),
+                        serviceRegistry.get(SettingsProcessor.class),
+                        new BuildSourceBuilder(
+                                this,
+                                serviceRegistry.get(ClassLoaderFactory.class),
+                                serviceRegistry.get(CacheRepository.class))),
+                new DefaultGradlePropertiesLoader(),
+                new BuildLoader(
+                        serviceRegistry.get(IProjectFactory.class)
+                ),
+                new BuildConfigurer(new ProjectDependencies2TaskResolver()), gradle.getBuildListenerBroadcaster(),
+                serviceRegistry.get(ExceptionAnalyser.class),
+                loggingManager);
+    }
+
+    public void setCommandLine2StartParameterConverter(
+            CommandLine2StartParameterConverter commandLine2StartParameterConverter) {
+        this.commandLine2StartParameterConverter = commandLine2StartParameterConverter;
+    }
+
+    private static class BuildCleanupListener extends BuildAdapter {
+        private final TopLevelBuildServiceRegistry services;
+
+        private BuildCleanupListener(TopLevelBuildServiceRegistry services) {
+            this.services = services;
+        }
+
+        @Override
+        public void buildFinished(BuildResult result) {
+            services.close();
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultGradlePropertiesLoader.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultGradlePropertiesLoader.java
new file mode 100644
index 0000000..53a9237
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultGradlePropertiesLoader.java
@@ -0,0 +1,128 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+import org.gradle.util.GUtil;
+import org.gradle.api.Project;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultGradlePropertiesLoader implements IGradlePropertiesLoader {
+    private static Logger logger = LoggerFactory.getLogger(DefaultGradlePropertiesLoader.class);
+
+    private Map<String, String> gradleProperties = new HashMap<String, String>();
+
+    public void loadProperties(File settingsDir, StartParameter startParameter) {
+        loadProperties(settingsDir, startParameter, getAllSystemProperties(), getAllEnvProperties());
+    }
+
+    void loadProperties(File settingsDir, StartParameter startParameter, Map<String, String> systemProperties,
+                        Map<String, String> envProperties) {
+        gradleProperties.clear();
+        addGradleProperties(
+                new File(settingsDir, Project.GRADLE_PROPERTIES),
+                new File(startParameter.getGradleUserHomeDir(), Project.GRADLE_PROPERTIES));
+        setSystemProperties(startParameter.getSystemPropertiesArgs());
+        gradleProperties.putAll(getEnvProjectProperties(envProperties));
+        gradleProperties.putAll(getSystemProjectProperties(systemProperties));
+        gradleProperties.putAll(startParameter.getProjectProperties());
+    }
+
+    Map getAllSystemProperties() {
+        return System.getProperties();
+    }
+
+    Map<String, String> getAllEnvProperties() {
+        // The reason why we have an try-catch block here is for JDK 1.4 compatibility. We use the retrotranslator to produce
+        // a 1.4 compatible version. But the retrotranslator is not capable of translating System.getenv to 1.4.
+        // The System.getenv call is only available in 1.5. In fact 1.4 does not offer any API to read
+        // environment variables. Therefore this call leads to an exception when used with 1.4. We ignore the exception in this
+        // case and simply return an empty hashmap.
+        try {
+            return System.getenv();
+        } catch (Throwable e) {
+            logger.debug("The System.getenv() call has lead to an exception. Probably you are running on Java 1.4.", e);
+            return Collections.emptyMap();
+        }
+    }
+
+    private void addGradleProperties(File... files) {
+        for (File propertyFile : files) {
+            if (propertyFile.isFile()) {
+                Properties properties = GUtil.loadProperties(propertyFile);
+                gradleProperties.putAll(new HashMap(properties));
+            }
+        }
+    }
+
+    public Map<String, String> getGradleProperties() {
+        return gradleProperties;
+    }
+
+    public void setGradleProperties(Map<String, String> gradleProperties) {
+        this.gradleProperties = gradleProperties;
+    }
+
+    private Map<String, String> getSystemProjectProperties(Map<String, String> systemProperties) {
+        Map<String, String> systemProjectProperties = new HashMap<String, String>();
+        for (Map.Entry<String, String> entry : systemProperties.entrySet()) {
+            if (entry.getKey().startsWith(SYSTEM_PROJECT_PROPERTIES_PREFIX) &&
+                    entry.getKey().length() > SYSTEM_PROJECT_PROPERTIES_PREFIX.length()) {
+                systemProjectProperties.put(entry.getKey().substring(SYSTEM_PROJECT_PROPERTIES_PREFIX.length()),
+                        entry.getValue());
+            }
+        }
+        logger.debug("Found system project properties: {}", systemProjectProperties.keySet());
+        return systemProjectProperties;
+    }
+
+    private Map<String, String> getEnvProjectProperties(Map<String, String> envProperties) {
+        Map<String, String> envProjectProperties = new HashMap<String, String>();
+        for (Map.Entry<String, String> entry : envProperties.entrySet()) {
+            if (entry.getKey().startsWith(ENV_PROJECT_PROPERTIES_PREFIX) &&
+                    entry.getKey().length() > ENV_PROJECT_PROPERTIES_PREFIX.length()) {
+                envProjectProperties.put(entry.getKey().substring(ENV_PROJECT_PROPERTIES_PREFIX.length()),
+                        entry.getValue());
+            }
+        }
+        logger.debug("Found env project properties: {}", envProjectProperties.keySet());
+        return envProjectProperties;
+    }
+
+    private void setSystemProperties(Map<String, String> properties) {
+        System.getProperties().putAll(properties);
+        addSystemPropertiesFromGradleProperties();
+    }
+
+    private void addSystemPropertiesFromGradleProperties() {
+        for (String key : gradleProperties.keySet()) {
+            if (key.startsWith(Project.SYSTEM_PROP_PREFIX + '.')) {
+                System.setProperty(key.substring((Project.SYSTEM_PROP_PREFIX + '.').length()), gradleProperties.get(
+                        key));
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultInitScriptFinder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultInitScriptFinder.java
new file mode 100644
index 0000000..5ae15ed
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultInitScriptFinder.java
@@ -0,0 +1,42 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.groovy.scripts.UriScriptSource;
+
+import java.io.File;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Simple finder that "finds" all the init scripts that were explicitly added
+ * to the start parameters.
+ */
+public class DefaultInitScriptFinder implements InitScriptFinder
+{
+    public List<ScriptSource> findScripts(GradleInternal gradle) {
+        List<File> scriptFiles = gradle.getStartParameter().getInitScripts();
+        List<ScriptSource> scripts = new ArrayList<ScriptSource>(scriptFiles.size());
+        for (File file : scriptFiles) {
+            scripts.add(new UriScriptSource("initialization script", file));
+        }
+
+        return scripts;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultProjectDescriptor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultProjectDescriptor.java
new file mode 100644
index 0000000..5edc668
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultProjectDescriptor.java
@@ -0,0 +1,151 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.initialization.ProjectDescriptor;
+import org.gradle.api.Project;
+import org.gradle.api.internal.project.ProjectIdentifier;
+import org.gradle.util.PathHelper;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.util.Set;
+import java.util.LinkedHashSet;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultProjectDescriptor implements ProjectDescriptor, ProjectIdentifier {
+    private String name;
+    private File dir;
+    private DefaultProjectDescriptor parent;
+    private Set<ProjectDescriptor> children = new LinkedHashSet<ProjectDescriptor>();
+    private IProjectDescriptorRegistry projectDescriptorRegistry;
+    private String path;
+    private String buildFileName = Project.DEFAULT_BUILD_FILE;
+
+    public DefaultProjectDescriptor(DefaultProjectDescriptor parent, String name, File dir,
+                                    IProjectDescriptorRegistry projectDescriptorRegistry) {
+        this.parent = parent;
+        this.name = name;
+        this.dir = GFileUtils.canonicalise(dir);
+        this.projectDescriptorRegistry = projectDescriptorRegistry;
+        this.path = path(name);
+        projectDescriptorRegistry.addProject(this);
+        if (parent != null) {
+            parent.getChildren().add(this);
+        }
+    }
+
+    private String path(String name) {
+        if (isRootDescriptor()) {
+            return path = Project.PATH_SEPARATOR;
+        } else {
+            return parent.absolutePath(name);
+        }
+    }
+
+    private String absolutePath(String path) {
+        if (!PathHelper.isAbsolutePath(path)) {
+            String prefix = isRootDescriptor() ? "" : Project.PATH_SEPARATOR;
+            return this.path + prefix + path;
+        }
+        return path;
+    }
+
+    private boolean isRootDescriptor() {
+        return parent == null;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        projectDescriptorRegistry.changeDescriptorPath(getPath(), path(name));
+        this.name = name;
+    }
+
+    public File getProjectDir() {
+        return dir;
+    }
+
+    public void setProjectDir(File dir) {
+        this.dir = GFileUtils.canonicalise(dir);
+    }
+
+    public DefaultProjectDescriptor getParent() {
+        return parent;
+    }
+
+    public ProjectIdentifier getParentIdentifier() {
+        return parent;
+    }
+
+    public Set<ProjectDescriptor> getChildren() {
+        return children;
+    }
+
+    public String getPath() {
+        return path;
+    }
+
+    void setPath(String path) {
+        this.path = path;
+    }
+
+    public String getBuildFileName() {
+        return buildFileName;
+    }
+
+    public void setBuildFileName(String name) {
+        this.buildFileName = name;
+    }
+
+    public File getBuildFile() {
+        return GFileUtils.canonicalise(new File(dir, buildFileName));
+    }
+
+    public IProjectDescriptorRegistry getProjectDescriptorRegistry() {
+        return projectDescriptorRegistry;
+    }
+
+    public void setProjectDescriptorRegistry(IProjectDescriptorRegistry projectDescriptorRegistry) {
+        this.projectDescriptorRegistry = projectDescriptorRegistry;
+    }
+
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultProjectDescriptor that = (DefaultProjectDescriptor) o;
+
+        return this.getPath().equals(that.getPath());
+    }
+
+    public int hashCode() {
+        return this.getPath().hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return getPath();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultProjectDescriptorRegistry.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultProjectDescriptorRegistry.java
new file mode 100644
index 0000000..3f54ae8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultProjectDescriptorRegistry.java
@@ -0,0 +1,30 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.internal.project.DefaultProjectRegistry;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultProjectDescriptorRegistry extends DefaultProjectRegistry<DefaultProjectDescriptor> implements IProjectDescriptorRegistry {
+
+    public void changeDescriptorPath(String oldPath, String newPath) {
+        DefaultProjectDescriptor projectDescriptor = removeProject(oldPath);
+        projectDescriptor.setPath(newPath);
+        addProject(projectDescriptor);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultProjectSpec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultProjectSpec.java
new file mode 100644
index 0000000..1996eb0
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultProjectSpec.java
@@ -0,0 +1,31 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.internal.project.IProjectRegistry;
+
+import java.io.File;
+
+public class DefaultProjectSpec extends ProjectDirectoryProjectSpec {
+    public DefaultProjectSpec(File dir) {
+        super(dir);
+    }
+
+    @Override
+    protected void checkPreconditions(IProjectRegistry<?> registry) {
+        // Ignore
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultSettings.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultSettings.groovy
new file mode 100644
index 0000000..dfcfb45
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultSettings.groovy
@@ -0,0 +1,43 @@
+/*
+ * 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.initialization
+
+import org.gradle.StartParameter
+import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.api.internal.GradleInternal
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultSettings extends BaseSettings {
+    public DefaultSettings() {}
+
+    DefaultSettings(GradleInternal gradle,
+                    IProjectDescriptorRegistry projectDescriptorRegistry,
+                    URLClassLoader classloader, File settingsDir,
+                    ScriptSource settingsScript, StartParameter startParameter) {
+      super(gradle, projectDescriptorRegistry, classloader, settingsDir, settingsScript, startParameter)
+    }
+
+    def propertyMissing(String property) {
+        return dynamicObjectHelper.getProperty(property)
+    }
+
+    void setProperty(String name, value) {
+        dynamicObjectHelper.setProperty(name, value) 
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultSettingsFinder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultSettingsFinder.java
new file mode 100644
index 0000000..0a244cd
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/DefaultSettingsFinder.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.initialization;
+
+import org.gradle.StartParameter;
+import org.gradle.groovy.scripts.UriScriptSource;
+import org.gradle.groovy.scripts.StringScriptSource;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultSettingsFinder implements ISettingsFinder {
+    private List<ISettingsFileSearchStrategy> settingsFileSearchStrategies;
+
+    public DefaultSettingsFinder(List<ISettingsFileSearchStrategy> settingsFileSearchStrategies) {
+        this.settingsFileSearchStrategies = settingsFileSearchStrategies;
+    }
+
+    public SettingsLocation find(StartParameter startParameter) {
+        File settingsFile = null;
+        for (ISettingsFileSearchStrategy settingsFileSearchStrategy : settingsFileSearchStrategies) {
+            settingsFile = settingsFileSearchStrategy.find(startParameter);
+            if (settingsFile != null) {
+                break;
+            }
+        }
+        if (settingsFile == null) {
+            return new SettingsLocation(startParameter.getCurrentDir(),
+                                       new StringScriptSource("empty settings file", ""));
+        } else {
+            return new SettingsLocation(settingsFile.getParentFile(),
+                                       new UriScriptSource("settings file", settingsFile));
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/EmbeddedScriptSettingsFinder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/EmbeddedScriptSettingsFinder.java
new file mode 100644
index 0000000..9e82480
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/EmbeddedScriptSettingsFinder.java
@@ -0,0 +1,36 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+
+public class EmbeddedScriptSettingsFinder implements ISettingsFinder {
+    private final ISettingsFinder finder;
+
+    public EmbeddedScriptSettingsFinder(ISettingsFinder finder) {
+        this.finder = finder;
+    }
+
+    public SettingsLocation find(StartParameter startParameter) {
+        if (startParameter.getSettingsScriptSource() != null) {
+            return new SettingsLocation(startParameter.getCurrentDir(),
+                                        startParameter.getSettingsScriptSource());
+        } else {
+            return finder.find(startParameter);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ExceptionDecoratingClassGenerator.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ExceptionDecoratingClassGenerator.java
new file mode 100644
index 0000000..3ff6e0a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ExceptionDecoratingClassGenerator.java
@@ -0,0 +1,230 @@
+/*
+ * 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.initialization;
+
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.LocationAwareException;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.Contextual;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.util.ReflectionUtil;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A {@link ClassGenerator} which mixes {@link org.gradle.api.LocationAwareException} into the supplied exception
+ * types. Uses {@link ExceptionHelper} to do the work.
+ */
+public class ExceptionDecoratingClassGenerator implements ClassGenerator {
+    private static final Map<Class<?>, Class<?>> GENERATED_CLASSES = new HashMap<Class<?>, Class<?>>();
+
+    public <T> T newInstance(Class<T> type, Object... parameters) {
+        Throwable throwable = ReflectionUtil.newInstance(generate(type), parameters);
+        throwable.setStackTrace(((Throwable) parameters[0]).getStackTrace());
+        return type.cast(throwable);
+    }
+
+    public <T> Class<? extends T> generate(Class<T> type) {
+        Class generated = GENERATED_CLASSES.get(type);
+        if (generated == null) {
+            generated = doGenerate(type);
+            GENERATED_CLASSES.put(type, generated);
+        }
+        return generated;
+    }
+
+    private <T> Class<? extends T> doGenerate(Class<T> type) {
+        ClassWriter visitor = new ClassWriter(ClassWriter.COMPUTE_MAXS);
+        String typeName = StringUtils.substringBeforeLast(type.getName(), ".") + ".LocationAware" + type.getSimpleName();
+        Type generatedType = Type.getType("L" + typeName.replaceAll("\\.", "/") + ";");
+        Type superclassType = Type.getType(type);
+
+        visitor.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, generatedType.getInternalName(), null,
+                superclassType.getInternalName(), new String[]{Type.getType(LocationAwareException.class).getInternalName()});
+
+        Type helperType = Type.getType(ExceptionHelper.class);
+        Type throwableType = Type.getType(Throwable.class);
+        Type scriptSourceType = Type.getType(ScriptSource.class);
+        Type integerType = Type.getType(Integer.class);
+
+        // GENERATE private ExceptionHelper helper;
+        visitor.visitField(Opcodes.ACC_PRIVATE, "helper", helperType.getDescriptor(), null, null);
+
+        // GENERATE <init>(<type> target, ScriptSource source, Integer lineNumber)
+
+        String methodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE,
+                new Type[]{superclassType, scriptSourceType, integerType});
+        MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, "<init>", methodDescriptor, null,
+                new String[0]);
+        methodVisitor.visitCode();
+
+        boolean noArgsConstructor;
+        try {
+            type.getConstructor(type);
+            noArgsConstructor = false;
+        } catch (NoSuchMethodException e) {
+            try {
+                type.getConstructor();
+                noArgsConstructor = true;
+            } catch (NoSuchMethodException e1) {
+                throw new IllegalArgumentException(String.format(
+                        "Cannot create subtype for exception '%s'. It needs a zero-args or copy constructor.",
+                        type.getName()));
+            }
+        }
+
+        if (noArgsConstructor) {
+            // GENERATE super()
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superclassType.getInternalName(), "<init>",
+                    Type.getMethodDescriptor(Type.VOID_TYPE, new Type[0]));
+            // END super()
+        } else {
+            // GENERATE super(target)
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
+            methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superclassType.getInternalName(), "<init>",
+                    Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{superclassType}));
+            // END super(target)
+        }
+
+        // GENERATE helper = new ExceptionHelper(this, target, source, lineNumber)
+        methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+
+        methodVisitor.visitTypeInsn(Opcodes.NEW, helperType.getInternalName());
+        methodVisitor.visitInsn(Opcodes.DUP);
+        methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+        methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
+        methodVisitor.visitVarInsn(Opcodes.ALOAD, 2);
+        methodVisitor.visitVarInsn(Opcodes.ALOAD, 3);
+        methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, helperType.getInternalName(), "<init>",
+                Type.getMethodDescriptor(Type.VOID_TYPE, new Type[]{throwableType, throwableType, scriptSourceType, integerType}));
+
+        methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, generatedType.getInternalName(), "helper",
+                helperType.getDescriptor());
+
+        // END helper = new ExceptionHelper(target)
+
+        methodVisitor.visitInsn(Opcodes.RETURN);
+        methodVisitor.visitMaxs(0, 0);
+        methodVisitor.visitEnd();
+
+        // END <init>(<type> target, ScriptSource source, Integer lineNumber)
+
+        for (Method method : ExceptionHelper.class.getDeclaredMethods()) {
+            // GENERATE public <type> <method>() { return helper.<method>(); }
+            methodDescriptor = Type.getMethodDescriptor(Type.getType(method.getReturnType()), new Type[0]);
+            methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, method.getName(), methodDescriptor, null,
+                    new String[0]);
+            methodVisitor.visitCode();
+
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            methodVisitor.visitFieldInsn(Opcodes.GETFIELD, generatedType.getInternalName(), "helper",
+                    helperType.getDescriptor());
+            methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, helperType.getInternalName(), method.getName(),
+                    methodDescriptor);
+
+            methodVisitor.visitInsn(Opcodes.ARETURN);
+            methodVisitor.visitMaxs(0, 0);
+            methodVisitor.visitEnd();
+            // END public <type> <method>() { return helper.<method>(); }
+        }
+
+        visitor.visitEnd();
+
+        byte[] bytecode = visitor.toByteArray();
+        return (Class<T>) ReflectionUtil.invoke(type.getClassLoader(), "defineClass", new Object[]{
+                typeName, bytecode, 0, bytecode.length
+        });
+    }
+
+    public static class ExceptionHelper {
+        private final Throwable target;
+        private final ScriptSource source;
+        private final Integer lineNumber;
+
+        public ExceptionHelper(Throwable owner, Throwable target, ScriptSource source, Integer lineNumber) {
+            if (owner.getCause() == null) {
+                owner.initCause(target.getCause());
+            }
+            this.target = target;
+            this.source = source;
+            this.lineNumber = lineNumber;
+        }
+
+        public String getOriginalMessage() {
+            return target.getMessage();
+        }
+
+        public Throwable getOriginalException() {
+            return target;
+        }
+
+        public String getLocation() {
+            if (source == null) {
+                return null;
+            }
+            String sourceMsg = StringUtils.capitalize(source.getDisplayName());
+            if (lineNumber == null) {
+                return sourceMsg;
+            }
+            return String.format("%s line: %d", sourceMsg, lineNumber);
+        }
+
+        public String getMessage() {
+            String location = getLocation();
+            String message = target.getMessage();
+            if (location == null && message == null) {
+                return null;
+            }
+            if (location == null) {
+                return message;
+            }
+            if (message == null) {
+                return location;
+            }
+            return String.format("%s%n%s", location, message);
+        }
+
+        public ScriptSource getScriptSource() {
+            return source;
+        }
+
+        public Integer getLineNumber() {
+            return lineNumber;
+        }
+
+        public List<Throwable> getReportableCauses() {
+            ArrayList<Throwable> causes = new ArrayList<Throwable>();
+            for (Throwable t = target.getCause(); t != null; t = t.getCause()) {
+                causes.add(t);
+                if (t.getClass().getAnnotation(Contextual.class) == null) {
+                    break;
+                }
+            }
+            return causes;
+        }
+
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/IGradlePropertiesLoader.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/IGradlePropertiesLoader.java
new file mode 100644
index 0000000..39c8239
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/IGradlePropertiesLoader.java
@@ -0,0 +1,34 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public interface IGradlePropertiesLoader {
+    public static final String SYSTEM_PROJECT_PROPERTIES_PREFIX = "org.gradle.project.";
+
+    public static final String ENV_PROJECT_PROPERTIES_PREFIX = "ORG_GRADLE_PROJECT_";
+    
+    Map<String, String> getGradleProperties();
+
+    void loadProperties(File rootDir, StartParameter startParameter);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/IProjectDescriptorRegistry.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/IProjectDescriptorRegistry.java
new file mode 100644
index 0000000..1ee52f4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/IProjectDescriptorRegistry.java
@@ -0,0 +1,25 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.internal.project.IProjectRegistry;
+
+/**
+ * @author Hans Dockter
+ */
+public interface IProjectDescriptorRegistry extends IProjectRegistry<DefaultProjectDescriptor> {
+    void changeDescriptorPath(String oldPath, String newPath);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ISettingsFileSearchStrategy.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ISettingsFileSearchStrategy.java
new file mode 100644
index 0000000..82fdc97
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ISettingsFileSearchStrategy.java
@@ -0,0 +1,27 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ISettingsFileSearchStrategy {
+    File find(StartParameter startParameter);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ISettingsFinder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ISettingsFinder.java
new file mode 100644
index 0000000..62074f6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ISettingsFinder.java
@@ -0,0 +1,25 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ISettingsFinder {
+    SettingsLocation find(StartParameter startParameter);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/InitScript.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/InitScript.groovy
new file mode 100644
index 0000000..5985db6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/InitScript.groovy
@@ -0,0 +1,33 @@
+/*
+ * 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.initialization
+
+import org.gradle.groovy.scripts.DefaultScript
+import org.gradle.api.initialization.dsl.ScriptHandler
+
+abstract class InitScript extends DefaultScript {
+    ScriptHandler getInitscript() {
+        getBuildscript()
+    }
+
+    void initscript(Closure configureClosure) {
+        buildscript(configureClosure)
+    }
+    
+    def String toString() {
+        return "initialization script"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/InitScriptFinder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/InitScriptFinder.java
new file mode 100644
index 0000000..1ccc78f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/InitScriptFinder.java
@@ -0,0 +1,29 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.groovy.scripts.ScriptSource;
+
+import java.util.List;
+
+/**
+ * Interface for objects that can find init scripts for a given build.
+ */
+public interface InitScriptFinder
+{
+    public List<ScriptSource> findScripts(GradleInternal gradle);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/InitScriptHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/InitScriptHandler.java
new file mode 100644
index 0000000..960453f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/InitScriptHandler.java
@@ -0,0 +1,44 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.configuration.InitScriptProcessor;
+
+import java.util.List;
+
+/**
+ * Finds and executes all init scripts for a given build.
+ */
+public class InitScriptHandler
+{
+    private final InitScriptFinder finder;
+    private final InitScriptProcessor processor;
+
+    public InitScriptHandler(InitScriptFinder finder, InitScriptProcessor processor) {
+        this.finder = finder;
+        this.processor = processor;
+    }
+
+    public void executeScripts(GradleInternal gradle) {
+        List<ScriptSource> scriptSources = finder.findScripts(gradle);
+        for (ScriptSource source : scriptSources) {
+            processor.process(source, gradle);
+        }
+    }
+}
+
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/MasterDirSettingsFinderStrategy.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/MasterDirSettingsFinderStrategy.java
new file mode 100644
index 0000000..84b0d90
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/MasterDirSettingsFinderStrategy.java
@@ -0,0 +1,43 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+import org.gradle.api.initialization.Settings;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class MasterDirSettingsFinderStrategy extends AbstractSettingsFileSearchStrategyTemplate {
+    public static final String MASTER_DIR_NAME = "master";
+
+    protected File findBeyondCurrentDir(StartParameter startParameter) {
+        File searchDir = startParameter.getCurrentDir().getParentFile();
+        if (searchDir != null && startParameter.isSearchUpwards()) {
+            for (File file : searchDir.listFiles()) {
+                if (file.isDirectory() && file.getName().equals(MASTER_DIR_NAME)) {
+                    File settingsFile = new File(file, Settings.DEFAULT_SETTINGS_FILE);
+                    if (settingsFile.isFile()) {
+                        return settingsFile;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/NestedBuildTracker.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/NestedBuildTracker.java
new file mode 100644
index 0000000..5d4094a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/NestedBuildTracker.java
@@ -0,0 +1,41 @@
+/*
+ * 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.initialization;
+
+import org.gradle.BuildAdapter;
+import org.gradle.BuildResult;
+import org.gradle.api.invocation.Gradle;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class NestedBuildTracker extends BuildAdapter {
+    private final List<Gradle> buildStack = new CopyOnWriteArrayList<Gradle>();
+
+    @Override
+    public void buildStarted(Gradle gradle) {
+        buildStack.add(0, gradle);
+    }
+
+    @Override
+    public void buildFinished(BuildResult result) {
+        buildStack.remove(result.getGradle());
+    }
+
+    public Gradle getCurrentBuild() {
+        return buildStack.isEmpty() ? null : buildStack.get(0);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ParentDirSettingsFinderStrategy.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ParentDirSettingsFinderStrategy.java
new file mode 100644
index 0000000..bca369f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ParentDirSettingsFinderStrategy.java
@@ -0,0 +1,39 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+import org.gradle.api.initialization.Settings;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class ParentDirSettingsFinderStrategy extends AbstractSettingsFileSearchStrategyTemplate {
+    protected File findBeyondCurrentDir(StartParameter startParameter) {
+        File searchDir = startParameter.getCurrentDir().getParentFile();
+        while (searchDir != null && startParameter.isSearchUpwards()) {
+            File settingsFile = new File(searchDir, Settings.DEFAULT_SETTINGS_FILE);
+            if (settingsFile.isFile()) {
+                return settingsFile;
+            }
+            searchDir = searchDir.getParentFile();
+        }
+        return null;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ProjectDirectoryProjectSpec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ProjectDirectoryProjectSpec.java
new file mode 100644
index 0000000..6d783ac
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ProjectDirectoryProjectSpec.java
@@ -0,0 +1,66 @@
+/*
+ * 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.initialization;
+
+import org.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.HashCodeBuilder;
+import org.gradle.api.internal.project.ProjectIdentifier;
+import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.api.InvalidUserDataException;
+
+import java.io.File;
+
+public class ProjectDirectoryProjectSpec extends AbstractProjectSpec {
+    private final File dir;
+
+    public ProjectDirectoryProjectSpec(File dir) {
+        this.dir = dir;
+    }
+
+    public String getDisplayName() {
+        return String.format("with project directory '%s'", dir);
+    }
+
+    protected String formatNoMatchesMessage() {
+        return String.format("No projects in this build have project directory '%s'.", dir);
+    }
+
+    protected String formatMultipleMatchesMessage(Iterable<? extends ProjectIdentifier> matches) {
+        return String.format("Multiple projects in this build have project directory '%s': %s", dir, matches);
+    }
+
+    protected boolean select(ProjectIdentifier project) {
+        return project.getProjectDir().equals(dir);
+    }
+
+    @Override
+    protected void checkPreconditions(IProjectRegistry<?> registry) {
+        if (!dir.exists()) {
+            throw new InvalidUserDataException(String.format("Project directory '%s' does not exist.", dir));
+        }
+        if (!dir.isDirectory()) {
+            throw new InvalidUserDataException(String.format("Project directory '%s' is not a directory.", dir));
+        }
+    }
+
+    public boolean equals(Object obj) {
+        return EqualsBuilder.reflectionEquals(this, obj);
+    }
+
+    public int hashCode() {
+        return HashCodeBuilder.reflectionHashCode(this);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ProjectSpec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ProjectSpec.java
new file mode 100644
index 0000000..b8a73a6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ProjectSpec.java
@@ -0,0 +1,40 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.internal.project.ProjectIdentifier;
+import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.api.InvalidUserDataException;
+
+public interface ProjectSpec {
+    /**
+     * Determines whether the given registry contains at least 1 project which meets this spec.
+     */
+    boolean containsProject(IProjectRegistry<?> registry);
+
+    /**
+     * Returns the single project in the given registry which meets this spec.
+     * @return the project. Never returns null.
+     * @throws InvalidUserDataException When project cannot be selected due to some user input mismatch.
+     */
+    <T extends ProjectIdentifier> T selectProject(IProjectRegistry<? extends T> registry) throws
+            InvalidUserDataException;
+
+    /**
+     * Returns the display name of this spec. Used for logging and error messages.
+     */
+    String getDisplayName();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessor.java
new file mode 100644
index 0000000..6ea9511
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessor.java
@@ -0,0 +1,39 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.SettingsInternal;
+import org.gradle.StartParameter;
+
+import java.net.URLClassLoader;
+
+public class PropertiesLoadingSettingsProcessor implements SettingsProcessor {
+    private final SettingsProcessor processor;
+
+    public PropertiesLoadingSettingsProcessor(SettingsProcessor processor) {
+        this.processor = processor;
+    }
+
+    public SettingsInternal process(GradleInternal gradle,
+                                    SettingsLocation settingsLocation,
+                                    URLClassLoader buildSourceClassLoader,
+                                    StartParameter startParameter,
+                                    IGradlePropertiesLoader propertiesLoader) {
+        propertiesLoader.loadProperties(settingsLocation.getSettingsDir(), startParameter);
+        return processor.process(gradle, settingsLocation, buildSourceClassLoader, startParameter, propertiesLoader);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/SameLevelDirSettingsFinderStrategy.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/SameLevelDirSettingsFinderStrategy.java
new file mode 100644
index 0000000..b1456a2
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/SameLevelDirSettingsFinderStrategy.java
@@ -0,0 +1,42 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+import org.gradle.api.initialization.Settings;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class SameLevelDirSettingsFinderStrategy extends AbstractSettingsFileSearchStrategyTemplate {
+    protected File findBeyondCurrentDir(StartParameter startParameter) {
+        File parentDir = startParameter.getCurrentDir().getParentFile();
+        if (parentDir != null && startParameter.isSearchUpwards()) {
+            for (File potentialSameLevelDir : parentDir.listFiles()) {
+                if (potentialSameLevelDir.isDirectory()) {
+                    File settingsFile = new File(potentialSameLevelDir, Settings.DEFAULT_SETTINGS_FILE);
+                    if (settingsFile.isFile()) {
+                        return settingsFile;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessor.java
new file mode 100644
index 0000000..c3a7f42
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessor.java
@@ -0,0 +1,70 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.SettingsInternal;
+import org.gradle.configuration.ScriptPlugin;
+import org.gradle.configuration.ScriptPluginFactory;
+import org.gradle.util.Clock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.URLClassLoader;
+
+
+/**
+ * @author Hans Dockter
+ */
+public class ScriptEvaluatingSettingsProcessor implements SettingsProcessor {
+    private static Logger logger = LoggerFactory.getLogger(ScriptEvaluatingSettingsProcessor.class);
+
+    private SettingsFactory settingsFactory;
+
+    private ScriptPluginFactory configurerFactory;
+
+    public ScriptEvaluatingSettingsProcessor() {
+
+    }
+
+    public ScriptEvaluatingSettingsProcessor(ScriptPluginFactory configurerFactory,
+                                             SettingsFactory settingsFactory) {
+        this.configurerFactory = configurerFactory;
+        this.settingsFactory = settingsFactory;
+    }
+
+    public SettingsInternal process(GradleInternal gradle,
+                                    SettingsLocation settingsLocation,
+                                    URLClassLoader buildSourceClassLoader,
+                                    StartParameter startParameter,
+                                    IGradlePropertiesLoader propertiesLoader) {
+        Clock settingsProcessingClock = new Clock();
+        SettingsInternal settings = settingsFactory.createSettings(gradle, settingsLocation.getSettingsDir(),
+                settingsLocation.getSettingsScriptSource(), propertiesLoader.getGradleProperties(), startParameter, buildSourceClassLoader);
+        applySettingsScript(settingsLocation, buildSourceClassLoader, settings);
+        logger.debug("Timing: Processing settings took: {}", settingsProcessingClock.getTime());
+        return settings;
+    }
+
+    private void applySettingsScript(SettingsLocation settingsLocation, ClassLoader buildSourceClassLoader, SettingsInternal settings) {
+        ScriptPlugin configurer = configurerFactory.create(settingsLocation.getSettingsScriptSource());
+        configurer.setClassLoader(buildSourceClassLoader);
+        configurer.setScriptBaseClass(SettingsScript.class);
+        configurer.apply(settings);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/SettingsFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/SettingsFactory.java
new file mode 100644
index 0000000..9373680
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/SettingsFactory.java
@@ -0,0 +1,45 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.SettingsInternal;
+import org.gradle.groovy.scripts.ScriptSource;
+
+import java.io.File;
+import java.net.URLClassLoader;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class SettingsFactory {
+    private IProjectDescriptorRegistry projectDescriptorRegistry;
+
+    public SettingsFactory(IProjectDescriptorRegistry projectDescriptorRegistry) {
+        this.projectDescriptorRegistry = projectDescriptorRegistry;
+    }
+
+    public SettingsInternal createSettings(GradleInternal gradle, File settingsDir, ScriptSource settingsScript,
+                                           Map<String, String> gradleProperties, StartParameter startParameter,
+                                           URLClassLoader classloader) {
+        DefaultSettings settings = new DefaultSettings(gradle, projectDescriptorRegistry, classloader, settingsDir, settingsScript, startParameter);
+        settings.getAdditionalProperties().putAll(gradleProperties);
+        return settings;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/SettingsHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/SettingsHandler.java
new file mode 100644
index 0000000..e02ae98
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/SettingsHandler.java
@@ -0,0 +1,101 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.internal.SettingsInternal;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.initialization.ProjectDescriptor;
+import org.gradle.StartParameter;
+import org.gradle.groovy.scripts.StringScriptSource;
+
+import java.io.File;
+import java.net.URLClassLoader;
+
+/**
+ * Handles locating and processing setting.gradle files.  Also deals with the buildSrc module, since that modules is
+ * found after settings is located, but needs to be built before settings is processed.
+ */
+public class SettingsHandler {
+    private ISettingsFinder settingsFinder;
+    private SettingsProcessor settingsProcessor;
+    private BuildSourceBuilder buildSourceBuilder;
+
+    public SettingsHandler(ISettingsFinder settingsFinder, SettingsProcessor settingsProcessor,
+                           BuildSourceBuilder buildSourceBuilder) {
+        this.settingsFinder = settingsFinder;
+        this.settingsProcessor = settingsProcessor;
+        this.buildSourceBuilder = buildSourceBuilder;
+    }
+
+    public SettingsInternal findAndLoadSettings(GradleInternal gradle, IGradlePropertiesLoader gradlePropertiesLoader) {
+        StartParameter startParameter = gradle.getStartParameter();
+        SettingsInternal settings = findSettingsAndLoadIfAppropriate(gradle, startParameter, gradlePropertiesLoader);
+        if (!startParameter.getDefaultProjectSelector().containsProject(settings.getProjectRegistry())) {
+            // The settings we found did not include the desired default project. Try again with an empty settings file.
+
+            StartParameter noSearchParameter = startParameter.newInstance();
+            noSearchParameter.setSettingsScriptSource(new StringScriptSource("empty settings file", ""));
+            settings = findSettingsAndLoadIfAppropriate(gradle, noSearchParameter, gradlePropertiesLoader);
+            if (settings == null) // not using an assert to make sure it is not disabled
+            {
+                throw new InternalError("Empty settings file does not contain expected project.");
+            }
+
+            // Set explicit build file, if required
+            if (noSearchParameter.getBuildFile() != null) {
+                ProjectDescriptor rootProject = settings.getRootProject();
+                assert noSearchParameter.getBuildFile().getParentFile().equals(rootProject.getProjectDir());
+                rootProject.setBuildFileName(noSearchParameter.getBuildFile().getName());
+            }
+        }
+
+        gradle.getScriptClassLoader().addParent(settings.getClassLoader());
+        return settings;
+    }
+
+    /**
+     * Finds the settings.gradle for the given startParameter, and loads it if contains the project selected by the
+     * startParameter, or if the startParameter explicity specifies a settings script.  If the settings file is not
+     * loaded (executed), then a null is returned.
+     */
+    private SettingsInternal findSettingsAndLoadIfAppropriate(GradleInternal gradle,
+                                                              StartParameter startParameter,
+                                                              IGradlePropertiesLoader gradlePropertiesLoader) {
+        SettingsLocation settingsLocation = findSettings(startParameter);
+
+        // We found the desired settings file, now build the associated buildSrc before loading settings.  This allows
+        // the settings script to reference classes in the buildSrc.
+        StartParameter buildSrcStartParameter = startParameter.newBuild();
+        buildSrcStartParameter.setCurrentDir(new File(settingsLocation.getSettingsDir(),
+                BaseSettings.DEFAULT_BUILD_SRC_DIR));
+        URLClassLoader buildSourceClassLoader = buildSourceBuilder.buildAndCreateClassLoader(buildSrcStartParameter);
+
+        return loadSettings(gradle, settingsLocation, buildSourceClassLoader, startParameter, gradlePropertiesLoader);
+    }
+
+    private SettingsLocation findSettings(StartParameter startParameter) {
+        return settingsFinder.find(startParameter);
+    }
+
+    private SettingsInternal loadSettings(GradleInternal gradle, SettingsLocation settingsLocation,
+                                          URLClassLoader buildSourceClassLoader, StartParameter startParameter,
+                                          IGradlePropertiesLoader gradlePropertiesLoader) {
+        return settingsProcessor.process(gradle, settingsLocation, buildSourceClassLoader, startParameter,
+                gradlePropertiesLoader);
+    }
+}
+
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/SettingsLocation.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/SettingsLocation.java
new file mode 100644
index 0000000..3519a6d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/SettingsLocation.java
@@ -0,0 +1,35 @@
+/*
+ * 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.initialization;
+
+import org.gradle.groovy.scripts.ScriptSource;
+
+import java.io.File;
+
+public class SettingsLocation
+{
+    private File settingsDir;
+    private ScriptSource settingsScriptSource;
+
+    public SettingsLocation(File settingsDir, ScriptSource settingsScriptSource) {
+        this.settingsDir = settingsDir;
+        this.settingsScriptSource = settingsScriptSource;
+    }
+
+    public File getSettingsDir() { return settingsDir; }
+    public ScriptSource getSettingsScriptSource() { return settingsScriptSource; }
+}
+
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/SettingsProcessor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/SettingsProcessor.java
new file mode 100644
index 0000000..44629d0
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/SettingsProcessor.java
@@ -0,0 +1,33 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.SettingsInternal;
+import org.gradle.StartParameter;
+
+import java.net.URLClassLoader;
+
+/**
+ * Responsible for locating, constructing, and evaluating the {@link SettingsInternal} for a build.
+ */
+public interface SettingsProcessor {
+    SettingsInternal process(GradleInternal gradle,
+                             SettingsLocation settingsLocation,
+                             URLClassLoader buildSourceClassLoader,
+                             StartParameter startParameter,
+                             IGradlePropertiesLoader propertiesLoader);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/SettingsScript.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/SettingsScript.groovy
new file mode 100644
index 0000000..fc41dd4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/SettingsScript.groovy
@@ -0,0 +1,24 @@
+/*
+ * 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.initialization
+
+import org.gradle.groovy.scripts.DefaultScript
+
+abstract class SettingsScript extends DefaultScript {
+    def String toString() {
+        return scriptTarget.toString()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/UserHomeInitScriptFinder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/UserHomeInitScriptFinder.java
new file mode 100644
index 0000000..679c72d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/initialization/UserHomeInitScriptFinder.java
@@ -0,0 +1,47 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.groovy.scripts.UriScriptSource;
+
+import java.io.File;
+import java.util.List;
+
+public class UserHomeInitScriptFinder implements InitScriptFinder
+{
+    public static final String DEFAULT_INIT_SCRIPT_NAME = "init.gradle";
+
+    private final InitScriptFinder finder;
+
+    public UserHomeInitScriptFinder(InitScriptFinder finder) {
+        this.finder = finder;
+    }
+
+    public List<ScriptSource> findScripts(GradleInternal gradle) {
+        List<ScriptSource> scripts = finder.findScripts(gradle);
+
+        File userHomeDir = gradle.getStartParameter().getGradleUserHomeDir();
+        File userInitScript = new File(userHomeDir, DEFAULT_INIT_SCRIPT_NAME);
+        if (userInitScript.isFile()) {
+            scripts.add(new UriScriptSource("initialization script", userInitScript));
+        }
+
+        return scripts;
+    }
+}
+
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/invocation/DefaultGradle.java b/subprojects/gradle-core/src/main/groovy/org/gradle/invocation/DefaultGradle.java
new file mode 100644
index 0000000..9a9fee1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/invocation/DefaultGradle.java
@@ -0,0 +1,161 @@
+/*
+ * 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.invocation;
+
+import groovy.lang.Closure;
+import org.gradle.BuildListener;
+import org.gradle.StartParameter;
+import org.gradle.api.ProjectEvaluationListener;
+import org.gradle.api.internal.GradleDistributionLocator;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ServiceRegistryFactory;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.execution.TaskGraphExecuter;
+import org.gradle.listener.ListenerManager;
+import org.gradle.util.DeprecationLogger;
+import org.gradle.util.GradleVersion;
+import org.gradle.util.MultiParentClassLoader;
+
+import java.io.File;
+
+public class DefaultGradle implements GradleInternal {
+    private ProjectInternal rootProject;
+    private ProjectInternal defaultProject;
+    private TaskGraphExecuter taskGraph;
+    private final Gradle parent;
+    private StartParameter startParameter;
+    private MultiParentClassLoader scriptClassLoader;
+    private IProjectRegistry<ProjectInternal> projectRegistry;
+    private final ListenerManager listenerManager;
+    private final ServiceRegistryFactory services;
+    private final GradleDistributionLocator distributionLocator;
+
+    public DefaultGradle(Gradle parent, StartParameter startParameter, ServiceRegistryFactory parentRegistry) {
+        this.parent = parent;
+        this.startParameter = startParameter;
+        this.services = parentRegistry.createFor(this);
+        this.listenerManager = services.get(ListenerManager.class);
+        projectRegistry = services.get(IProjectRegistry.class);
+        taskGraph = services.get(TaskGraphExecuter.class);
+        scriptClassLoader = services.get(MultiParentClassLoader.class);
+        distributionLocator = services.get(GradleDistributionLocator.class);
+    }
+
+    public Gradle getParent() {
+        return parent;
+    }
+
+    public String getGradleVersion() {
+        return new GradleVersion().getVersion();
+    }
+
+    public File getGradleHomeDir() {
+        DeprecationLogger.nagUser("Gradle.getGradleHomeDir()");
+        return distributionLocator.getGradleHome();
+    }
+
+    public File getGradleUserHomeDir() {
+        return startParameter.getGradleUserHomeDir();
+    }
+
+    public StartParameter getStartParameter() {
+        return startParameter;
+    }
+
+    public ProjectInternal getRootProject() {
+        return rootProject;
+    }
+
+    public void setRootProject(ProjectInternal rootProject) {
+        this.rootProject = rootProject;
+    }
+
+    public ProjectInternal getDefaultProject() {
+        return defaultProject;
+    }
+
+    public void setDefaultProject(ProjectInternal defaultProject) {
+        this.defaultProject = defaultProject;
+    }
+
+    public TaskGraphExecuter getTaskGraph() {
+        return taskGraph;
+    }
+
+    public void setTaskGraph(TaskGraphExecuter taskGraph) {
+        this.taskGraph = taskGraph;
+    }
+
+    public IProjectRegistry<ProjectInternal> getProjectRegistry() {
+        return projectRegistry;
+    }
+
+    public MultiParentClassLoader getScriptClassLoader() {
+        return scriptClassLoader;
+    }
+
+    public ProjectEvaluationListener addProjectEvaluationListener(ProjectEvaluationListener listener) {
+        addListener(listener);
+        return listener;
+    }
+
+    public void removeProjectEvaluationListener(ProjectEvaluationListener listener) {
+        removeListener(listener);
+    }
+
+    public void beforeProject(Closure closure) {
+        listenerManager.addListener(ProjectEvaluationListener.class, "beforeEvaluate", closure);
+    }
+
+    public void afterProject(Closure closure) {
+        listenerManager.addListener(ProjectEvaluationListener.class, "afterEvaluate", closure);
+    }
+
+    public void addListener(Object listener) {
+        listenerManager.addListener(listener);
+    }
+
+    public void removeListener(Object listener) {
+        listenerManager.removeListener(listener);
+    }
+
+    public void useLogger(Object logger) {
+        listenerManager.useLogger(logger);
+    }
+
+    public ProjectEvaluationListener getProjectEvaluationBroadcaster() {
+        return listenerManager.getBroadcaster(ProjectEvaluationListener.class);
+    }
+
+    public void addBuildListener(BuildListener buildListener) {
+        addListener(buildListener);
+    }
+
+    public BuildListener getBuildListenerBroadcaster() {
+        return listenerManager.getBroadcaster(BuildListener.class);
+    }
+
+    public Gradle getGradle() {
+        return this;
+    }
+
+    public ServiceRegistryFactory getServiceRegistryFactory() {
+        return services;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/listener/AsyncListenerBroadcast.java b/subprojects/gradle-core/src/main/groovy/org/gradle/listener/AsyncListenerBroadcast.java
new file mode 100644
index 0000000..210c961
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/listener/AsyncListenerBroadcast.java
@@ -0,0 +1,38 @@
+/*
+ * 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>>() {
+            public StoppableDispatch<MethodInvocation> transform(StoppableDispatch<MethodInvocation> original) {
+                return new AsyncDispatch<MethodInvocation>(executor, original);
+            }
+        });
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/listener/ContextClassLoaderProxy.java b/subprojects/gradle-core/src/main/groovy/org/gradle/listener/ContextClassLoaderProxy.java
new file mode 100644
index 0000000..cff261b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/listener/ContextClassLoaderProxy.java
@@ -0,0 +1,42 @@
+/*
+ * 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.messaging.dispatch.ContextClassLoaderDispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.dispatch.ProxyDispatchAdapter;
+import org.gradle.messaging.dispatch.ReflectionDispatch;
+
+/**
+ * Creates a proxy object which sets the context ClassLoader when invoking methods on the target object.
+ *
+ * @param <T>
+ */
+public class ContextClassLoaderProxy<T> {
+    private final ProxyDispatchAdapter<T> adapter;
+
+    /**
+     * Creates a proxy which dispatches to the given target object.
+     */
+    public ContextClassLoaderProxy(Class<T> type, T target, ClassLoader contextClassLoader) {
+        adapter = new ProxyDispatchAdapter<T>(type, new ContextClassLoaderDispatch<MethodInvocation>(new ReflectionDispatch(target), contextClassLoader));
+    }
+
+    public T getSource() {
+        return adapter.getSource();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/listener/DefaultListenerManager.java b/subprojects/gradle-core/src/main/groovy/org/gradle/listener/DefaultListenerManager.java
new file mode 100644
index 0000000..c54d983
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/listener/DefaultListenerManager.java
@@ -0,0 +1,182 @@
+/*
+ * 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.messaging.dispatch.BroadcastDispatch;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.dispatch.ReflectionDispatch;
+
+import java.util.*;
+
+ at SuppressWarnings({"unchecked"})
+public class DefaultListenerManager implements ListenerManager {
+    private final Set<Object> allListeners = new LinkedHashSet<Object>();
+    private final Set<Object> allLoggers = new LinkedHashSet<Object>();
+    private final Map<Class<?>, ListenerBroadcast> broadcasters = new HashMap<Class<?>, ListenerBroadcast>();
+    private final Map<Class<?>, LoggerDispatch> loggers = new HashMap<Class<?>, LoggerDispatch>();
+    private final Map<Class<?>, BroadcastDispatch> dispatchers = new HashMap<Class<?>, BroadcastDispatch>();
+    private final Object lock = new Object();
+    private final DefaultListenerManager parent;
+
+    public DefaultListenerManager() {
+        this(null);
+    }
+
+    private DefaultListenerManager(DefaultListenerManager parent) {
+        this.parent = parent;
+    }
+
+    public void addListener(Object listener) {
+        synchronized (lock) {
+            if (allListeners.add(listener)) {
+                for (BroadcastDispatch<?> broadcaster : dispatchers.values()) {
+                    maybeAddToDispatcher(broadcaster, listener);
+                }
+            }
+        }
+    }
+
+    public void addListener(Class<?> listenerType, String method, Closure listenerClosure) {
+        addListener(new ClosureListener(listenerType, method, listenerClosure));
+    }
+
+    public void removeListener(Object listener) {
+        synchronized (lock) {
+            if (allListeners.remove(listener)) {
+                for (BroadcastDispatch<?> broadcaster : dispatchers.values()) {
+                    broadcaster.remove(listener);
+                }
+            }
+        }
+    }
+
+    public void useLogger(Object logger) {
+        synchronized (lock) {
+            if (allLoggers.add(logger)) {
+                for (LoggerDispatch dispatch : loggers.values()) {
+                    dispatch.maybeSetLogger(logger);
+                }
+            }
+        }
+    }
+
+    public <T> T getBroadcaster(Class<T> listenerClass) {
+        return getBroadcasterInternal(listenerClass).getSource();
+    }
+
+    public <T> ListenerBroadcast<T> createAnonymousBroadcaster(Class<T> listenerClass) {
+        ListenerBroadcast<T> broadcast = new ListenerBroadcast(listenerClass);
+        broadcast.add(getBroadcasterInternal(listenerClass).getSource());
+        return broadcast;
+    }
+
+    private <T> ListenerBroadcast<T> getBroadcasterInternal(Class<T> listenerClass) {
+        synchronized (lock) {
+            ListenerBroadcast<T> broadcaster = broadcasters.get(listenerClass);
+            if (broadcaster == null) {
+                broadcaster = new ListenerBroadcast<T>(listenerClass);
+                broadcaster.add(getLogger(listenerClass));
+                broadcaster.add(getDispatcher(listenerClass));
+                if (parent != null) {
+                    broadcaster.add(parent.getDispatcher(listenerClass));
+                }
+                broadcasters.put(listenerClass, broadcaster);
+            }
+
+            return broadcaster;
+        }
+    }
+
+    private <T> BroadcastDispatch<T> getDispatcher(Class<T> listenerClass) {
+        synchronized (lock) {
+            BroadcastDispatch<T> dispatcher = dispatchers.get(listenerClass);
+            if (dispatcher == null) {
+                dispatcher = new BroadcastDispatch<T>(listenerClass);
+                dispatchers.put(listenerClass, dispatcher);
+                for (Object listener : allListeners) {
+                    maybeAddToDispatcher(dispatcher, listener);
+                }
+            }
+            return dispatcher;
+        }
+    }
+
+    private LoggerDispatch getLogger(Class<?> listenerClass) {
+        synchronized (lock) {
+            LoggerDispatch dispatch = loggers.get(listenerClass);
+            if (dispatch == null) {
+                dispatch = new LoggerDispatch(listenerClass, parent == null ? null : parent.getLogger(listenerClass));
+                for (Object logger : allLoggers) {
+                    dispatch.maybeSetLogger(logger);
+                }
+                loggers.put(listenerClass, dispatch);
+            }
+            return dispatch;
+        }
+    }
+
+    private void maybeAddToDispatcher(BroadcastDispatch broadcaster, Object listener) {
+        if (listener instanceof ClosureListener) {
+            ClosureListener closureListener = (ClosureListener) listener;
+            if (broadcaster.getType().isAssignableFrom(closureListener.listenerType)) {
+                broadcaster.add(closureListener.method, closureListener.closure);
+            }
+        } else if (broadcaster.getType().isInstance(listener)) {
+            broadcaster.add(listener);
+        }
+    }
+
+    public ListenerManager createChild() {
+        return new DefaultListenerManager(this);
+    }
+
+    private static class ClosureListener {
+        final Class<?> listenerType;
+        final String method;
+        final Closure closure;
+
+        private ClosureListener(Class<?> listenerType, String method, Closure closure) {
+            this.listenerType = listenerType;
+            this.method = method;
+            this.closure = closure;
+        }
+    }
+
+    private static class LoggerDispatch implements Dispatch<MethodInvocation> {
+        private final Class<?> type;
+        private Dispatch<MethodInvocation> dispatch;
+
+        private LoggerDispatch(Class<?> type, LoggerDispatch parentDispatch) {
+            this.type = type;
+            this.dispatch = parentDispatch;
+        }
+
+        public void dispatch(MethodInvocation message) {
+            if (dispatch != null) {
+                dispatch.dispatch(message);
+            }
+        }
+
+        public void maybeSetLogger(Object logger) {
+            if (type.isInstance(logger)) {
+                dispatch = new ReflectionDispatch(logger);
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/listener/ListenerBroadcast.java b/subprojects/gradle-core/src/main/groovy/org/gradle/listener/ListenerBroadcast.java
new file mode 100644
index 0000000..33861e8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/listener/ListenerBroadcast.java
@@ -0,0 +1,156 @@
+/*
+ * 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.api.Transformer;
+import org.gradle.messaging.dispatch.*;
+
+/**
+ * <p>Manages a set of listeners of type T. Provides an implementation of T which can be used to broadcast to all
+ * registered listeners.</p>
+ *
+ * <p>Ordering is maintained for events, so that events are delivered to listeners in the order they are generated.
+ * Events are delivered to listeners in the order that listeners are added to this broadcaster.</p>
+ *
+ * @param <T> The listener type.
+ */
+public class ListenerBroadcast<T> implements StoppableDispatch<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>>() {
+            public StoppableDispatch<MethodInvocation> transform(StoppableDispatch<MethodInvocation> original) {
+                return original;
+            }
+        });
+    }
+
+    protected ListenerBroadcast(Class<T> type, Transformer<StoppableDispatch<MethodInvocation>> transformer) {
+        this.type = type;
+        broadcast = new BroadcastDispatch<T>(type);
+        dispatch = transformer.transform(broadcast);
+        source = new ProxyDispatchAdapter<T>(type, dispatch);
+    }
+
+    /**
+     * Returns the broadcaster. Any method call on this object is broadcast to all listeners.
+     *
+     * @return The broadcaster.
+     */
+    public T getSource() {
+        return source.getSource();
+    }
+
+    /**
+     * Returns the type of listener to which this class broadcasts.
+     *
+     * @return The type of the broadcaster.
+     */
+    public Class<T> getType() {
+        return type;
+    }
+
+    /**
+     * Adds a listener.
+     *
+     * @param listener The listener.
+     */
+    public void add(T listener) {
+        broadcast.add(listener);
+    }
+
+    /**
+     * Adds the given listeners.
+     *
+     * @param listeners The listeners
+     */
+    public void addAll(Iterable<? extends T> listeners) {
+        for (T listener : listeners) {
+            broadcast.add(listener);
+        }
+    }
+    
+    /**
+     * Adds the given listener if it is an instance of the listener type.
+     *
+     * @param listener The listener
+     */
+    public void maybeAdd(Object listener) {
+        if (type.isInstance(listener)) {
+            add(type.cast(listener));
+        }
+    }
+
+    /**
+     * Adds a {@link org.gradle.messaging.dispatch.Dispatch} to receive events from this broadcast.
+     */
+    public void add(Dispatch<MethodInvocation> dispatch) {
+        broadcast.add(dispatch);
+    }
+    
+    /**
+     * Adds a closure to be notified when the given method is called.
+     */
+    public void add(String methodName, Closure closure) {
+        broadcast.add(methodName, closure);
+    }
+
+    /**
+     * Adds an action to be executed when the given method is called.
+     */
+    public void add(String methodName, Action<?> action) {
+        broadcast.add(methodName, action);
+    }
+
+    /**
+     * Removes the given listener.
+     *
+     * @param listener The listener.
+     */
+    public void remove(Object listener) {
+        broadcast.remove(listener);
+    }
+
+    /**
+     * Removes the given listeners.
+     *
+     * @param listeners The listeners
+     */
+    public void removeAll(Iterable<?> listeners) {
+        for (Object listener : listeners) {
+            remove(listener);
+        }
+    }
+
+    /**
+     * Broadcasts the given event to all listeners.
+     *
+     * @param event The event
+     */
+    public void dispatch(MethodInvocation event) {
+        dispatch.dispatch(event);
+    }
+
+    public void stop() {
+        dispatch.stop();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/listener/ListenerManager.java b/subprojects/gradle-core/src/main/groovy/org/gradle/listener/ListenerManager.java
new file mode 100644
index 0000000..bcb20d9
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/listener/ListenerManager.java
@@ -0,0 +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.listener;
+
+import groovy.lang.Closure;
+
+/**
+ * Unified manager for all listeners for Gradle.  Provides a simple way to find all listeners of a given type in the
+ * system.
+ *
+ * While the methods work with any Object, in general only interfaces should be used as listeners.  Also, due to
+ * implementation details, any listener method with a non-void return type will return a null.
+ */
+public interface ListenerManager {
+    /**
+     * Added a listener.  A single object can implement multiple interfaces, and all interfaces are registered by a
+     * single invocation of this method.  There is no order dependency: if a broadcaster has already been made for type
+     * T, the listener will be registered with it if <code>(listener instanceof T)</code> returns true.
+     *
+     * @param listener the listener to add.
+     */
+    void addListener(Object listener);
+
+    /**
+     * Added a listener implemented via a Groovy closure.  The closure can implement a single method of the given
+     * listenerType class.  There is no order dependency: if a broadcaster has already been made for listenerType, the
+     * closure will be registered with it.
+     *
+     * @param listenerType The class of the listener type for which the closure is a method implementation.
+     * @param method The name of the method in the listenerType class for which the closure is an implementation.
+     * @param listenerClosure The closure containing the implementation of the listener method.
+     */
+    void addListener(Class<?> listenerType, String method, Closure listenerClosure);
+
+    /**
+     * Removes a listener.  A single object can implement multiple interfaces, and all interfaces are unregistered by a
+     * single invocation of this method.  There is no order dependency: if a broadcaster has already been made for type
+     * T, the listener will be unregistered with it if <code>(listener instanceof T)</code> returns true.
+     *
+     * @param listener the listener to remove.
+     */
+    void removeListener(Object listener);
+
+    /**
+     * Returns a broadcaster for the given listenerClass.  If there are no registered listeners for that type, a
+     * broadcaster is returned which does not forward method calls to any listeners.  The returned broadcasters are
+     * live, that is their list of listeners can be updated by calls to {@link #addListener(Object)} and {@link
+     * #removeListener(Object)} after they have been returned.  Broadcasters are also cached, so that repeatedly calling
+     * this method with the same listenerClass returns the same broadcaster object.
+     *
+     * @param listenerClass The type of listener for which to return a broadcaster.
+     * @return The broadcaster that forwards method calls to all listeners of the same type that have been (or will be)
+     *         registered with this manager.
+     */
+    <T> T getBroadcaster(Class<T> listenerClass);
+
+    /**
+     * Returns a broadcaster for the given listenerClass.  The returned broadcaster will delegate to the canonical
+     * broadcaster returned by {@link #getBroadcaster(Class)} for the given listener type.  However, it can also have
+     * listeners assigned/removed directly to/from it.  This allows these "anonymous" broadcasters to specialize what
+     * listeners receive messages.  Each call creates a new broadcaster, so that client code can create as many "facets"
+     * of the listener as they need.  The client code must provide some way for its users to register listeners on the
+     * specialized broadcasters.
+     *
+     * @param listenerClass The type of listener for which to create a broadcaster.
+     * @return A broadcaster that forwards method calls to all listeners assigned to it, or of the same type that have
+     *         been (or will be) registered with this manager.
+     */
+    <T> ListenerBroadcast<T> createAnonymousBroadcaster(Class<T> listenerClass);
+
+    /**
+     * Uses the given object as a logger. Each listener class has exactly one logger associated with it. Any existing
+     * logger for the listener class is discarded.
+     *
+     * @param logger The new logger to use.
+     */
+    void useLogger(Object logger);
+
+    /**
+     * Creates a child {@code ListenerManager}. All events broadcast in the child will be received by the listeners
+     * registered in the parent. However, the reverse is not true: events broadcast in the parent are not received
+     * by the listeners in the children. The child inherits the loggers of its parent, though these can be replaced.
+     *
+     * @return The child
+     */
+    ListenerManager createChild();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/listener/ListenerNotificationException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/listener/ListenerNotificationException.java
new file mode 100644
index 0000000..dfbf075
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/listener/ListenerNotificationException.java
@@ -0,0 +1,33 @@
+/*
+ * 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.listener;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.Contextual;
+
+/**
+ * A {@code ListenerNotificationException} is thrown when a listener cannot be notified of an event.
+ */
+ at Contextual
+public class ListenerNotificationException extends GradleException {
+    // Required for @Contextual
+    public ListenerNotificationException() {
+    }
+
+    public ListenerNotificationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/AbstractProgressLoggingAwareFormatter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/AbstractProgressLoggingAwareFormatter.java
new file mode 100644
index 0000000..9183ffc
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/AbstractProgressLoggingAwareFormatter.java
@@ -0,0 +1,93 @@
+/*
+ * 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;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.PatternLayout;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Context;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.logging.Logging;
+
+import java.io.IOException;
+import java.util.LinkedList;
+
+public abstract class AbstractProgressLoggingAwareFormatter implements LogEventFormatter {
+    public static final String EOL = System.getProperty("line.separator");
+    private final PatternLayout layout;
+    private final LinkedList<Operation> pendingOperations = new LinkedList<Operation>();
+
+    protected AbstractProgressLoggingAwareFormatter(Context context) {
+        this.layout = new PatternLayout();
+        layout.setContext(context);
+        layout.setPattern("%msg%n%ex");
+        layout.start();
+    }
+
+    public void format(ILoggingEvent event) {
+        try {
+            if (event.getMarker() == Logging.PROGRESS_STARTED) {
+                Operation operation = new Operation();
+                operation.description = event.getFormattedMessage();
+                operation.status = "";
+                pendingOperations.addFirst(operation);
+                onStart(operation);
+            } else if (event.getMarker() == Logging.PROGRESS) {
+                assert !pendingOperations.isEmpty();
+                Operation operation = pendingOperations.getFirst();
+                operation.status = event.getFormattedMessage();
+                onStatusChange(operation);
+            } else if (event.getMarker() == Logging.PROGRESS_COMPLETE) {
+                assert !pendingOperations.isEmpty();
+                Operation operation = pendingOperations.removeFirst();
+                operation.status = event.getFormattedMessage();
+                onComplete(operation);
+            } else if (event.getLevel() == Level.ERROR) {
+                String message = layout.doLayout(event);
+                onErrorMessage(message);
+            } else {
+                String message = layout.doLayout(event);
+                onInfoMessage(message);
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    protected abstract void onStart(Operation operation) throws IOException;
+
+    protected abstract void onStatusChange(Operation operation) throws IOException;
+
+    protected abstract void onComplete(Operation operation) throws IOException;
+
+    protected abstract void onInfoMessage(String message) throws IOException;
+
+    protected abstract void onErrorMessage(String message) throws IOException;
+
+    protected class Operation {
+        private String description;
+        private String status;
+
+        public String getDescription() {
+            return description;
+        }
+
+        public String getStatus() {
+            return status;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/AnsiConsole.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/AnsiConsole.java
new file mode 100644
index 0000000..d15e1de
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/AnsiConsole.java
@@ -0,0 +1,296 @@
+/*
+ * 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;
+
+import org.apache.commons.lang.StringUtils;
+import org.fusesource.jansi.Ansi;
+import org.gradle.api.Action;
+import org.gradle.api.UncheckedIOException;
+
+import java.io.Flushable;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+public class AnsiConsole implements Console {
+    private final static String EOL = System.getProperty("line.separator");
+    private final Appendable target;
+    private final Flushable flushable;
+    private final LinkedList<LabelImpl> statusBars = new LinkedList<LabelImpl>();
+    private final TextAreaImpl textArea;
+    private Widget bottomWidget;
+    private final Screen container;
+
+    public AnsiConsole(Appendable target, Flushable flushable) {
+        this.target = target;
+        this.flushable = flushable;
+        container = new Screen();
+        textArea = new TextAreaImpl(container);
+        bottomWidget = textArea;
+    }
+
+    public Label addStatusBar() {
+        final LabelImpl statusBar = new LabelImpl(container);
+        render(new Action<Ansi>() {
+            public void execute(Ansi ansi) {
+                bottomWidget.removeFromLastLine(ansi);
+                statusBar.draw(ansi);
+            }
+        });
+        statusBars.addFirst(statusBar);
+        bottomWidget = statusBar;
+        return statusBar;
+    }
+
+    public TextArea getMainArea() {
+        return textArea;
+    }
+
+    private void render(Action<Ansi> action) {
+        Ansi ansi = createAnsi();
+        action.execute(ansi);
+        try {
+            target.append(ansi.toString());
+            flushable.flush();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    Ansi createAnsi() {
+        return Ansi.ansi();
+    }
+
+    private interface Container {
+        void redraw(Widget widget, Action<Ansi> drawOperation);
+
+        void close(Widget widget);
+    }
+
+    private interface Widget {
+        /**
+         * Removes content of this widget from the last line of the screen. Leaves cursor at left edge of bottom-most
+         * line.
+         */
+        void removeFromLastLine(Ansi ansi);
+    }
+
+    private class Screen implements Container {
+        public void redraw(Widget widget, final Action<Ansi> drawOperation) {
+            final LabelImpl currentStatusBar = statusBars.peek();
+            if (widget == textArea) {
+                render(new Action<Ansi>() {
+                    public void execute(Ansi ansi) {
+                        if (currentStatusBar != null) {
+                            currentStatusBar.removeFromLastLine(ansi);
+                        }
+                        drawOperation.execute(ansi);
+                        if (currentStatusBar != null) {
+                            textArea.removeFromLastLine(ansi);
+                            currentStatusBar.draw(ansi);
+                        }
+                    }
+                });
+            } else {
+                final LabelImpl statusBar = (LabelImpl) widget;
+                if (statusBar != currentStatusBar) {
+                    return;
+                }
+                render(new Action<Ansi>() {
+                    public void execute(Ansi ansi) {
+                        drawOperation.execute(ansi);
+                    }
+                });
+            }
+        }
+
+        public void close(Widget widget) {
+            if (widget == textArea) {
+                throw new UnsupportedOperationException();
+            }
+            final LabelImpl statusBar = (LabelImpl) widget;
+            statusBars.remove(statusBar);
+            if (statusBar == bottomWidget) {
+                render(new Action<Ansi>() {
+                    public void execute(Ansi ansi) {
+                        statusBar.removeFromLastLine(ansi);
+                        LabelImpl current = statusBars.peek();
+                        if (current != null) {
+                            current.draw(ansi);
+                            bottomWidget = current;
+                        } else {
+                            bottomWidget = textArea;
+                        }
+                    }
+                });
+            }
+        }
+    }
+
+    private class LabelImpl implements Label, Widget {
+        private final Container container;
+        private String text = "";
+        private String displayedText = "";
+
+        public LabelImpl(Container container) {
+            this.container = container;
+        }
+
+        public void setText(String text) {
+            if (text.equals(this.text)) {
+                return;
+            }
+            this.text = text;
+            container.redraw(this, new Action<Ansi>() {
+                public void execute(Ansi ansi) {
+                    draw(ansi);
+                }
+            });
+        }
+
+        public void close() {
+            container.close(this);
+        }
+
+        public void removeFromLastLine(Ansi ansi) {
+            if (displayedText.length() > 0) {
+                ansi.cursorLeft(displayedText.length());
+                ansi.eraseLine(Ansi.Erase.FORWARD);
+                displayedText = "";
+            }
+        }
+
+        public void draw(Ansi ansi) {
+            String prefix = StringUtils.getCommonPrefix(new String[]{text, displayedText});
+            if (prefix.length() < displayedText.length()) {
+                ansi.cursorLeft(displayedText.length() - prefix.length());
+            }
+            if (prefix.length() < text.length()) {
+                ansi.a(text.substring(prefix.length()));
+            }
+            if (displayedText.length() > text.length()) {
+                ansi.eraseLine(Ansi.Erase.FORWARD);
+            }
+            displayedText = text;
+        }
+    }
+
+    private class TextAreaImpl implements TextArea, Widget {
+        private final Container container;
+        private int width;
+        boolean extraEol;
+
+        private TextAreaImpl(Container container) {
+            this.container = container;
+        }
+
+        public void removeFromLastLine(Ansi ansi) {
+            if (width > 0) {
+                ansi.newline();
+                extraEol = true;
+            }
+        }
+
+        public void append(final CharSequence text) {
+            if (text.length() == 0) {
+                return;
+            }
+            container.redraw(this, new Action<Ansi>() {
+                public void execute(Ansi ansi) {
+                    if (extraEol) {
+                        ansi.cursorUp(1);
+                        ansi.cursorRight(width);
+                        extraEol = false;
+                    }
+
+                    Iterator<String> tokenizer = new LineSplitter(text);
+                    while (tokenizer.hasNext()) {
+                        String token = tokenizer.next();
+                        if (token.equals(EOL)) {
+                            width = 0;
+                            extraEol = false;
+                        } else {
+                            width += token.length();
+                        }
+                        ansi.a(token);
+                    }
+                }
+            });
+        }
+    }
+
+    private static class LineSplitter implements Iterator<String> {
+        private final CharSequence text;
+        private int start;
+        private int end;
+
+        private LineSplitter(CharSequence text) {
+            this.text = text;
+            findNext();
+        }
+
+        public boolean findNext() {
+            if (end == text.length()) {
+                start = -1;
+                return false;
+            }
+            if (startsWithEol(text, end)) {
+                start = end;
+                end = start + EOL.length();
+                return true;
+            }
+            int pos = end;
+            while (pos < text.length()) {
+                if (startsWithEol(text, pos)) {
+                    start = end;
+                    end = pos;
+                    return true;
+                }
+                pos++;
+            }
+            start = end;
+            end = text.length();
+            return true;
+        }
+
+        private boolean startsWithEol(CharSequence text, int startAt) {
+            if (startAt + EOL.length() > text.length()) {
+                return false;
+            }
+            for (int i = 0; i < EOL.length(); i++) {
+                if (EOL.charAt(i) != text.charAt(startAt + i)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public boolean hasNext() {
+            return start >= 0;
+        }
+
+        public String next() {
+            CharSequence next = text.subSequence(start, end);
+            findNext();
+            return next.toString();
+        }
+
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/BasicProgressLoggingAwareFormatter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/BasicProgressLoggingAwareFormatter.java
new file mode 100644
index 0000000..4f6d083
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/BasicProgressLoggingAwareFormatter.java
@@ -0,0 +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.logging;
+
+import ch.qos.logback.core.Context;
+import org.gradle.api.logging.StandardOutputListener;
+
+import java.io.IOException;
+
+public class BasicProgressLoggingAwareFormatter extends AbstractProgressLoggingAwareFormatter {
+
+    private enum State {
+        StartOfLine {
+            @Override
+            public State startNewLine(StandardOutputListener target) throws IOException {
+                return this;
+            }
+            @Override
+            public State addCompletion(StandardOutputListener target, String status) throws IOException {
+                if (status.length() == 0) {
+                    return this;
+                }
+                target.onOutput(status);
+                target.onOutput(EOL);
+                return this;
+            }},
+        Description {
+            @Override
+            public State startNewLine(StandardOutputListener target) throws IOException {
+                target.onOutput(EOL);
+                return StartOfLine;
+            }
+            @Override
+            public State addCompletion(StandardOutputListener target, String status) throws IOException {
+                if (status.length() > 0) {
+                    target.onOutput(" ");
+                    target.onOutput(status);
+                }
+                target.onOutput(EOL);
+                return StartOfLine;
+            }};
+
+        public abstract State startNewLine(StandardOutputListener target) throws IOException;
+
+        public abstract State addCompletion(StandardOutputListener target, String status) throws IOException;
+    }
+
+    private State state = State.StartOfLine;
+    private final StandardOutputListener infoTarget;
+    private final StandardOutputListener errorTarget;
+
+    public BasicProgressLoggingAwareFormatter(Context context, StandardOutputListener infoTarget, StandardOutputListener errorTarget) {
+        super(context);
+        this.infoTarget = infoTarget;
+        this.errorTarget = errorTarget;
+    }
+
+    @Override
+    protected void onStart(Operation operation) throws IOException {
+        if (operation.getDescription().length() > 0) {
+            state.startNewLine(infoTarget);
+            infoTarget.onOutput(operation.getDescription());
+            state = State.Description;
+        }
+    }
+
+    @Override
+    protected void onStatusChange(Operation operation) throws IOException {
+    }
+
+    @Override
+    protected void onComplete(Operation operation) throws IOException {
+        state = state.addCompletion(infoTarget, operation.getStatus());
+    }
+
+    @Override
+    protected void onInfoMessage(String message) throws IOException {
+        state = state.startNewLine(infoTarget);
+        infoTarget.onOutput(message);
+    }
+
+    @Override
+    protected void onErrorMessage(String message) throws IOException {
+        state = state.startNewLine(infoTarget);
+        errorTarget.onOutput(message);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/Console.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/Console.java
new file mode 100644
index 0000000..4a85483
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/Console.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+public interface Console {
+    TextArea getMainArea();
+
+    Label addStatusBar();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/ConsoleBackedFormatter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/ConsoleBackedFormatter.java
new file mode 100644
index 0000000..da80fe4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/ConsoleBackedFormatter.java
@@ -0,0 +1,107 @@
+/*
+ * 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;
+
+import ch.qos.logback.core.Context;
+
+import java.io.IOException;
+import java.util.*;
+
+public class ConsoleBackedFormatter extends AbstractProgressLoggingAwareFormatter {
+    private final Console console;
+    private final Set<Operation> currentOperations = new LinkedHashSet<Operation>();
+    private final Set<Operation> noHeader = new LinkedHashSet<Operation>();
+    private final Label statusBar;
+
+    public ConsoleBackedFormatter(Context context, Console console) {
+        super(context);
+        this.console = console;
+        statusBar = console.addStatusBar();
+    }
+
+    @Override
+    protected void onStart(Operation operation) throws IOException {
+        writeHeaders();
+        currentOperations.add(operation);
+        noHeader.add(operation);
+        updateText();
+    }
+
+    @Override
+    protected void onComplete(Operation operation) throws IOException {
+        currentOperations.remove(operation);
+        boolean hasCompletionStatus = operation.getStatus().length() > 0;
+        boolean hasDescription = operation.getDescription().length() > 0;
+        if (noHeader.remove(operation) || hasCompletionStatus) {
+            StringBuilder builder = new StringBuilder();
+            if (hasDescription) {
+                builder.append(operation.getDescription());
+            }
+            if (hasCompletionStatus) {
+                if (hasDescription) {
+                    builder.append(' ');
+                }
+                builder.append(operation.getStatus());
+            }
+            if (builder.length() > 0) {
+                builder.append(EOL);
+                console.getMainArea().append(builder.toString());
+            }
+        }
+        updateText();
+    }
+
+    @Override
+    protected void onStatusChange(Operation operation) throws IOException {
+        updateText();
+    }
+
+    @Override
+    protected void onInfoMessage(String message) throws IOException {
+        writeHeaders();
+        console.getMainArea().append(message);
+    }
+
+    @Override
+    protected void onErrorMessage(String message) throws IOException {
+        onInfoMessage(message);
+    }
+
+    private void updateText() {
+        StringBuilder builder = new StringBuilder();
+        for (Operation operation : currentOperations) {
+            if (operation.getStatus().length() == 0) {
+                continue;
+            }
+            if (builder.length() > 0) {
+                builder.append(' ');
+            }
+            builder.append("> ");
+            builder.append(operation.getStatus());
+        }
+        statusBar.setText(builder.toString());
+    }
+
+    private void writeHeaders() {
+        for (Operation operation : noHeader) {
+            if (operation.getDescription().length() > 0) {
+                console.getMainArea().append(operation.getDescription() + EOL);
+            }
+        }
+        noHeader.clear();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/DefaultLoggingConfigurer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/DefaultLoggingConfigurer.java
new file mode 100644
index 0000000..1ebdbd5
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/DefaultLoggingConfigurer.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.logging;
+
+import org.gradle.api.logging.LogLevel;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class DefaultLoggingConfigurer implements LoggingConfigurer {
+    private final List<LoggingConfigurer> configurers = new ArrayList<LoggingConfigurer>();
+
+    public DefaultLoggingConfigurer(LoggingConfigurer... configurers) {
+        this.configurers.addAll(Arrays.asList(configurers));
+    }
+
+    public void configure(LogLevel logLevel) {
+        for (LoggingConfigurer configurer : configurers) {
+            configurer.configure(logLevel);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/DefaultLoggingManager.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/DefaultLoggingManager.java
new file mode 100644
index 0000000..3ce9ab3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/DefaultLoggingManager.java
@@ -0,0 +1,192 @@
+/*
+ * 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;
+
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.LoggingOutput;
+import org.gradle.api.logging.StandardOutputListener;
+import org.gradle.messaging.concurrent.CompositeStoppable;
+import org.gradle.messaging.concurrent.Stoppable;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultLoggingManager implements LoggingManagerInternal {
+    private boolean started;
+    private final StartableLoggingSystem loggingSystem;
+    private final StartableLoggingSystem stdOutLoggingSystem;
+    private final StartableLoggingSystem stdErrLoggingSystem;
+    private final LoggingOutput loggingOutput;
+    private final Set<StandardOutputListener> stdoutListeners = new LinkedHashSet<StandardOutputListener>();
+    private final Set<StandardOutputListener> stderrListeners = new LinkedHashSet<StandardOutputListener>();
+
+    public DefaultLoggingManager(LoggingSystem loggingSystem, LoggingSystem stdOutLoggingSystem,
+                                 LoggingSystem stdErrLoggingSystem, LoggingOutput loggingOutput) {
+        this.loggingOutput = loggingOutput;
+        this.loggingSystem = new StartableLoggingSystem(loggingSystem, null);
+        this.stdOutLoggingSystem = new StartableLoggingSystem(stdOutLoggingSystem, LogLevel.QUIET);
+        this.stdErrLoggingSystem = new StartableLoggingSystem(stdErrLoggingSystem, LogLevel.ERROR);
+    }
+
+    public DefaultLoggingManager start() {
+        started = true;
+        for (StandardOutputListener stdoutListener : stdoutListeners) {
+            loggingOutput.addStandardOutputListener(stdoutListener);
+        }
+        for (StandardOutputListener stderrListener : stderrListeners) {
+            loggingOutput.addStandardErrorListener(stderrListener);
+        }
+        loggingSystem.start();
+        stdOutLoggingSystem.start();
+        stdErrLoggingSystem.start();
+
+        return this;
+    }
+
+    public DefaultLoggingManager stop() {
+        try {
+            new CompositeStoppable(loggingSystem, stdOutLoggingSystem, stdErrLoggingSystem).stop();
+            for (StandardOutputListener stdoutListener : stdoutListeners) {
+                loggingOutput.removeStandardOutputListener(stdoutListener);
+            }
+            for (StandardOutputListener stderrListener : stderrListeners) {
+                loggingOutput.removeStandardErrorListener(stderrListener);
+            }
+        } finally {
+            started = false;
+        }
+        return this;
+    }
+
+    public DefaultLoggingManager setLevel(LogLevel logLevel) {
+        loggingSystem.setLevel(logLevel);
+        return this;
+    }
+
+    public LogLevel getLevel() {
+        return loggingSystem.level;
+    }
+
+    public LogLevel getStandardOutputCaptureLevel() {
+        return stdOutLoggingSystem.level;
+    }
+
+    public boolean isStandardOutputCaptureEnabled() {
+        return getStandardOutputCaptureLevel() != null;
+    }
+
+    public DefaultLoggingManager captureStandardOutput(LogLevel level) {
+        stdOutLoggingSystem.setLevel(level);
+        return this;
+    }
+
+    public DefaultLoggingManager captureStandardError(LogLevel level) {
+        stdErrLoggingSystem.setLevel(level);
+        return this;
+    }
+
+    public DefaultLoggingManager disableStandardOutputCapture() {
+        stdOutLoggingSystem.disable();
+        stdErrLoggingSystem.disable();
+        return this;
+    }
+
+    public LogLevel getStandardErrorCaptureLevel() {
+        return stdErrLoggingSystem.level;
+    }
+
+    public void addStandardOutputListener(StandardOutputListener listener) {
+        if (stdoutListeners.add(listener) && started) {
+            loggingOutput.addStandardOutputListener(listener);
+        }
+    }
+
+    public void addStandardErrorListener(StandardOutputListener listener) {
+        if (stderrListeners.add(listener) && started) {
+            loggingOutput.addStandardErrorListener(listener);
+        }
+    }
+
+    public void removeStandardOutputListener(StandardOutputListener listener) {
+        if (stdoutListeners.remove(listener) && started) {
+            loggingOutput.removeStandardOutputListener(listener);
+        }
+    }
+
+    public void removeStandardErrorListener(StandardOutputListener listener) {
+        if (stderrListeners.remove(listener) && started) {
+            loggingOutput.removeStandardErrorListener(listener);
+        }
+    }
+
+    private static class StartableLoggingSystem implements Stoppable {
+        private final LoggingSystem loggingSystem;
+        private LogLevel level;
+        private boolean disable;
+        private LoggingSystem.Snapshot originalState;
+
+        private StartableLoggingSystem(LoggingSystem loggingSystem, LogLevel level) {
+            this.loggingSystem = loggingSystem;
+            this.level = level;
+        }
+
+        public void start() {
+            if (disable) {
+                originalState = loggingSystem.off();
+            } else if (level != null) {
+                originalState = loggingSystem.on(level);
+            } else {
+                originalState = loggingSystem.snapshot();
+            }
+        }
+
+        public void setLevel(LogLevel logLevel) {
+            if (this.level == logLevel) {
+                return;
+            }
+
+            this.level = logLevel;
+            disable = false;
+
+            if (originalState == null) {
+                return;
+            }
+            loggingSystem.on(logLevel);
+        }
+
+        public void disable() {
+            level = null;
+            disable = true;
+            if (originalState != null) {
+                loggingSystem.off();
+            }
+        }
+
+        public void stop() {
+            try {
+                if (originalState != null) {
+                    loggingSystem.restore(originalState);
+                }
+            } finally {
+                originalState = null;
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/DefaultLoggingManagerFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/DefaultLoggingManagerFactory.java
new file mode 100644
index 0000000..355a05e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/DefaultLoggingManagerFactory.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.logging;
+
+import org.gradle.api.logging.LoggingOutput;
+
+public class DefaultLoggingManagerFactory implements LoggingManagerFactory {
+    private final LoggingSystem slfLoggingSystem;
+    private final LoggingSystem stdOutLoggingSystem;
+    private final LoggingSystem stdErrLoggingSystem;
+    private final LoggingOutput loggingOutput;
+
+    public DefaultLoggingManagerFactory(LoggingConfigurer loggingConfigurer, LoggingOutput loggingOutput) {
+        this.loggingOutput = loggingOutput;
+        slfLoggingSystem = new LoggingSystemAdapter(loggingConfigurer);
+        stdOutLoggingSystem = new StdOutLoggingSystem();
+        stdErrLoggingSystem = new StdErrLoggingSystem();
+    }
+
+    public LoggingManagerInternal create() {
+        return new DefaultLoggingManager(slfLoggingSystem, stdOutLoggingSystem, stdErrLoggingSystem, loggingOutput);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/DefaultProgressLoggerFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/DefaultProgressLoggerFactory.java
new file mode 100644
index 0000000..e05fb02
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/DefaultProgressLoggerFactory.java
@@ -0,0 +1,84 @@
+/*
+ * 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;
+
+import org.gradle.listener.ListenerManager;
+
+public class DefaultProgressLoggerFactory implements ProgressLoggerFactory {
+    private final ListenerManager listenerManager;
+
+    public DefaultProgressLoggerFactory(ListenerManager listenerManager) {
+        this.listenerManager = listenerManager;
+    }
+
+    public ProgressLogger start() {
+        return start("");
+    }
+
+    public ProgressLogger start(String description) {
+        ProgressListener listener = listenerManager.getBroadcaster(ProgressListener.class);
+        ProgressLoggerImpl logger = new ProgressLoggerImpl(description, listener);
+        logger.started();
+        return logger;
+    }
+
+    private static class ProgressLoggerImpl implements ProgressLogger {
+        private final String description;
+        private final ProgressListener listener;
+        private String status = "";
+        private boolean completed;
+
+        public ProgressLoggerImpl(String description, ProgressListener listener) {
+            this.description = description;
+            this.listener = listener;
+        }
+
+        public String getDescription() {
+            return description;
+        }
+
+        public String getStatus() {
+            return status;
+        }
+
+        public void started() {
+            listener.started(this);
+        }
+
+        public void progress(String status) {
+            assertNotCompleted();
+            this.status = status;
+            listener.progress(this);
+        }
+
+        public void completed() {
+            completed("");
+        }
+
+        public void completed(String status) {
+            this.status = status;
+            completed = true;
+            listener.completed(this);
+        }
+
+        private void assertNotCompleted() {
+            if (completed) {
+                throw new IllegalStateException("This ProgressLogger has been completed.");
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/DefaultStandardOutputRedirector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/DefaultStandardOutputRedirector.java
new file mode 100644
index 0000000..48e05c6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/DefaultStandardOutputRedirector.java
@@ -0,0 +1,88 @@
+/*
+ * 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;
+
+import org.gradle.api.Action;
+import org.gradle.api.logging.StandardOutputListener;
+import org.gradle.util.LinePerThreadBufferingOutputStream;
+
+import java.io.PrintStream;
+
+public class DefaultStandardOutputRedirector implements StandardOutputRedirector {
+    private PrintStream originalStdOut;
+    private PrintStream originalStdErr;
+    private final WriteAction stdOut = new WriteAction();
+    private final WriteAction stdErr = new WriteAction();
+    private final PrintStream redirectedStdOut = new LinePerThreadBufferingOutputStream(stdOut, true);
+    private final PrintStream redirectedStdErr = new LinePerThreadBufferingOutputStream(stdErr, true);
+
+    public void redirectStandardOutputTo(StandardOutputListener stdOutDestination) {
+        stdOut.setDestination(stdOutDestination);
+    }
+
+    public void redirectStandardErrorTo(StandardOutputListener stdErrDestination) {
+        stdErr.setDestination(stdErrDestination);
+    }
+
+    public StandardOutputCapture start() {
+        if (stdOut.destination != null) {
+            originalStdOut = System.out;
+            System.setOut(redirectedStdOut);
+        }
+        if (stdErr.destination != null) {
+            originalStdErr = System.err;
+            System.setErr(redirectedStdErr);
+        }
+        return this;
+    }
+
+    public StandardOutputCapture stop() {
+        try {
+            if (originalStdOut != null) {
+                System.setOut(originalStdOut);
+            }
+            if (originalStdErr != null) {
+                System.setErr(originalStdErr);
+            }
+            redirectedStdOut.flush();
+            redirectedStdErr.flush();
+        } finally {
+            originalStdOut = null;
+            originalStdErr = null;
+            stdOut.setDestination(new DiscardAction());
+            stdErr.setDestination(new DiscardAction());
+        }
+        return this;
+    }
+
+    private static class DiscardAction implements StandardOutputListener {
+        public void onOutput(CharSequence output) {
+        }
+    }
+
+    private static class WriteAction implements Action<String> {
+        private StandardOutputListener destination;
+
+        public void execute(String message) {
+            destination.onOutput(message);
+        }
+
+        public void setDestination(StandardOutputListener destination) {
+            this.destination = destination;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/IvyLoggingAdaper.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/IvyLoggingAdaper.java
new file mode 100644
index 0000000..4a205d9
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/IvyLoggingAdaper.java
@@ -0,0 +1,47 @@
+/*
+ * 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.logging;
+
+import org.apache.ivy.util.AbstractMessageLogger;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+/**
+ * This class is for integrating Ivy log statements into our logging system. We don't want to have a dependency on
+ * logback. This would be bad for embedded usage. We only want one on slf4j. But slf4j has no constants for log levels.
+ * As we want to avoid the execution of if statements for each Ivy request, we use Map which delegates Ivy log
+ * statements to Sl4j action classes.
+ *
+ * @author Hans Dockter
+ */
+public class IvyLoggingAdaper extends AbstractMessageLogger {
+    private final Logger logger = Logging.getLogger(IvyLoggingAdaper.class);
+
+    public void log(String msg, int level) {
+        logger.log(Logging.ANT_IVY_2_SLF4J_LEVEL_MAPPER.get(level), msg);
+    }
+
+    public void rawlog(String msg, int level) {
+        log(msg, level);
+    }
+
+    public void doProgress() {
+    }
+
+    public void doEndProgress(String msg) {
+        logger.info(msg);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/JavaUtilLoggingConfigurer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/JavaUtilLoggingConfigurer.java
new file mode 100644
index 0000000..6283a9e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/JavaUtilLoggingConfigurer.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+import org.gradle.api.logging.LogLevel;
+import org.slf4j.bridge.SLF4JBridgeHandler;
+
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+
+public class JavaUtilLoggingConfigurer implements LoggingConfigurer {
+    private boolean configured;
+
+    public void configure(LogLevel logLevel) {
+        if (configured) {
+            return;
+        }
+
+        LogManager.getLogManager().reset();
+        SLF4JBridgeHandler.install();
+        Logger.getLogger("").setLevel(java.util.logging.Level.FINE);
+        configured = true; 
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/Label.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/Label.java
new file mode 100644
index 0000000..ff42037
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/Label.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+public interface Label {
+    void setText(String text);
+
+    void close();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LayoutBasedFormatter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LayoutBasedFormatter.java
new file mode 100644
index 0000000..f28a179
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LayoutBasedFormatter.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.logging;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Layout;
+import org.gradle.api.logging.StandardOutputListener;
+
+public class LayoutBasedFormatter implements LogEventFormatter {
+    private final Layout<ILoggingEvent> layout;
+    private final StandardOutputListener target;
+
+    public LayoutBasedFormatter(Layout<ILoggingEvent> layout, StandardOutputListener target) {
+        this.layout = layout;
+        this.target = target;
+    }
+
+    public void format(ILoggingEvent event) {
+        target.onOutput(layout.doLayout(event));
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LogEventFormatter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LogEventFormatter.java
new file mode 100644
index 0000000..62ebeb9
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LogEventFormatter.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+import ch.qos.logback.classic.spi.ILoggingEvent;
+
+public interface LogEventFormatter {
+    void format(ILoggingEvent event);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LoggingConfigurer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LoggingConfigurer.java
new file mode 100644
index 0000000..134d4a2
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LoggingConfigurer.java
@@ -0,0 +1,25 @@
+/*
+ * 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.logging;
+
+import org.gradle.api.logging.LogLevel;
+
+/**
+ * @author Hans Dockter
+ */
+public interface LoggingConfigurer {
+    void configure(LogLevel logLevel);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LoggingManagerFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LoggingManagerFactory.java
new file mode 100644
index 0000000..1d399b5
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LoggingManagerFactory.java
@@ -0,0 +1,21 @@
+/*
+ * 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;
+
+public interface LoggingManagerFactory {
+    LoggingManagerInternal create();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LoggingManagerInternal.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LoggingManagerInternal.java
new file mode 100644
index 0000000..9890006
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LoggingManagerInternal.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.logging;
+
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.LoggingManager;
+
+public interface LoggingManagerInternal extends LoggingManager, StandardOutputCapture {
+    @Override
+    LoggingManagerInternal start();
+
+    @Override
+    LoggingManagerInternal stop();
+
+    @Override
+    LoggingManagerInternal captureStandardOutput(LogLevel level);
+
+    @Override
+    LoggingManagerInternal captureStandardError(LogLevel level);
+
+    @Override
+    LoggingManagerInternal setLevel(LogLevel logLevel);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LoggingServiceRegistry.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LoggingServiceRegistry.java
new file mode 100644
index 0000000..293fe37
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LoggingServiceRegistry.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+import org.gradle.api.internal.project.DefaultServiceRegistry;
+
+/**
+ * A {@link org.gradle.api.internal.project.ServiceRegistry} implementation which provides the logging services.
+ */
+public class LoggingServiceRegistry extends DefaultServiceRegistry {
+    protected LoggingManagerFactory createLoggingManagerFactory() {
+        Slf4jLoggingConfigurer slf4jLoggingConfigurer = new Slf4jLoggingConfigurer();
+        LoggingConfigurer loggingConfigurer = new DefaultLoggingConfigurer(slf4jLoggingConfigurer,
+                new JavaUtilLoggingConfigurer());
+        return new DefaultLoggingManagerFactory(loggingConfigurer, slf4jLoggingConfigurer);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LoggingSystem.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LoggingSystem.java
new file mode 100644
index 0000000..625a915
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LoggingSystem.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.logging;
+
+import org.gradle.api.logging.LogLevel;
+
+public interface LoggingSystem {
+    Snapshot snapshot();
+
+    /**
+     * Enables logging for this logging system at the given level.
+     *
+     * @param level The new level.
+     * @return the state of this logging system immediately before the changes are applied.
+     */
+    Snapshot on(LogLevel level);
+
+    /**
+     * Disables logging for this logging system
+     *
+     * @return the state of this logging system immediately before the changes are applied.
+     */
+    Snapshot off();
+
+    void restore(Snapshot state);
+
+    interface Snapshot {
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LoggingSystemAdapter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LoggingSystemAdapter.java
new file mode 100644
index 0000000..9a9a43f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/LoggingSystemAdapter.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.logging;
+
+import org.gradle.api.logging.LogLevel;
+
+/**
+ * Adapts a {@link org.gradle.logging.LoggingConfigurer} to a {@link org.gradle.logging.LoggingSystem}.
+ */
+public class LoggingSystemAdapter implements LoggingSystem {
+    private final LoggingConfigurer configurer;
+    private LogLevel logLevel = LogLevel.LIFECYCLE;
+
+    public LoggingSystemAdapter(LoggingConfigurer configurer) {
+        this.configurer = configurer;
+    }
+
+    public Snapshot snapshot() {
+        return new SnapshotImpl(logLevel);
+    }
+
+    public Snapshot off() {
+        return new SnapshotImpl(logLevel);
+    }
+
+    public Snapshot on(LogLevel level) {
+        SnapshotImpl snapshot = new SnapshotImpl(logLevel);
+        setLevel(level);
+        return snapshot;
+    }
+
+    public void restore(Snapshot state) {
+        LogLevel oldLevel = ((SnapshotImpl) state).level;
+        this.logLevel = oldLevel;
+        if (oldLevel != null) {
+            configurer.configure(oldLevel);
+        }
+    }
+
+    private void setLevel(LogLevel level) {
+        configurer.configure(level);
+        this.logLevel = level;
+    }
+
+    private class SnapshotImpl implements Snapshot {
+        private final LogLevel level;
+
+        public SnapshotImpl(LogLevel level) {
+            this.level = level;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/MarkerFilter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/MarkerFilter.java
new file mode 100644
index 0000000..2713ecb
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/MarkerFilter.java
@@ -0,0 +1,67 @@
+/*
+ * 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;
+
+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/gradle-core/src/main/groovy/org/gradle/logging/OutputStreamStandardOutputListenerAdapter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/OutputStreamStandardOutputListenerAdapter.java
new file mode 100644
index 0000000..b610f37
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/OutputStreamStandardOutputListenerAdapter.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.logging.StandardOutputListener;
+
+import java.io.Flushable;
+import java.io.IOException;
+import java.io.PrintStream;
+
+public class OutputStreamStandardOutputListenerAdapter implements StandardOutputListener {
+    private final Appendable appendable;
+    private final Flushable flushable;
+
+    public OutputStreamStandardOutputListenerAdapter(PrintStream printStream) {
+        appendable = printStream;
+        flushable = printStream;
+    }
+
+    public void onOutput(CharSequence output) {
+        try {
+            appendable.append(output);
+            flushable.flush();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/PrintStreamLoggingSystem.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/PrintStreamLoggingSystem.java
new file mode 100644
index 0000000..4d59fb5
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/PrintStreamLoggingSystem.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.logging;
+
+import org.gradle.api.Action;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.StandardOutputListener;
+import org.gradle.util.LinePerThreadBufferingOutputStream;
+
+import java.io.PrintStream;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A {@link org.gradle.logging.LoggingSystem} which routes content written to a PrintStream to the logging system.
+ */
+abstract class PrintStreamLoggingSystem implements LoggingSystem {
+    private final AtomicReference<StandardOutputListener> destination
+            = new AtomicReference<StandardOutputListener>();
+    private final PrintStream outstr = new LinePerThreadBufferingOutputStream(new Action<String>() {
+        public void execute(String output) {
+            destination.get().onOutput(output);
+        }
+    });
+    private final Logger logger;
+    private StandardOutputListener original;
+
+    protected PrintStreamLoggingSystem(Logger logger) {
+        this.logger = logger;
+    }
+
+    /**
+     * Returns the current value of the PrintStream
+     */
+    protected abstract PrintStream get();
+
+    /**
+     * Sets the current value of the PrintStream
+     */
+    protected abstract void set(PrintStream printStream);
+
+    public Snapshot snapshot() {
+        return new SnapshotImpl(destination.get());
+    }
+
+    public void restore(Snapshot state) {
+        SnapshotImpl snapshot = (SnapshotImpl) state;
+        install();
+        if (snapshot.listener == null) {
+            destination.set(original);
+        } else {
+            destination.set(snapshot.listener);
+        }
+    }
+
+    public Snapshot on(final LogLevel level) {
+        Snapshot snapshot = snapshot();
+        install();
+        destination.set(new LoggerDestination(level));
+        return snapshot;
+    }
+
+    public Snapshot off() {
+        Snapshot snapshot = snapshot();
+        if (original != null) {
+            outstr.flush();
+            destination.set(original);
+        }
+        return snapshot;
+    }
+
+    private void install() {
+        if (original == null) {
+            PrintStream originalStream = get();
+            original = new PrintStreamDestination(originalStream);
+        }
+        outstr.flush();
+        if (get() != outstr) {
+            set(outstr);
+        }
+    }
+
+    private static class PrintStreamDestination implements StandardOutputListener {
+        private final PrintStream originalStream;
+
+        public PrintStreamDestination(PrintStream originalStream) {
+            this.originalStream = originalStream;
+        }
+
+        public void onOutput(CharSequence output) {
+            originalStream.println(output);
+        }
+    }
+
+    private class LoggerDestination implements StandardOutputListener {
+        private final LogLevel level;
+
+        public LoggerDestination(LogLevel level) {
+            this.level = level;
+        }
+
+        public void onOutput(CharSequence output) {
+            logger.log(level, output.toString());
+        }
+    }
+
+    private static class SnapshotImpl implements Snapshot {
+        private final StandardOutputListener listener;
+
+        public SnapshotImpl(StandardOutputListener listener) {
+            this.listener = listener;
+        }
+    }
+
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/ProgressListener.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/ProgressListener.java
new file mode 100644
index 0000000..fbe8793
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/ProgressListener.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+public interface ProgressListener {
+    void started(ProgressLogger logger);
+
+    void progress(ProgressLogger logger);
+
+    void completed(ProgressLogger logger);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/ProgressLogger.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/ProgressLogger.java
new file mode 100644
index 0000000..7339fea
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/ProgressLogger.java
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+/**
+ * Used to log the progress of a potentially long running operation.
+ */
+public interface ProgressLogger {
+    /**
+     * Returns the description of the operation.
+     *
+     * @return the description, possibly empty.
+     */
+    String getDescription();
+
+    /**
+     * Logs some progress.
+     *
+     * @param status The new status message
+     */
+    void progress(String status);
+
+    /**
+     * Logs the completion of the operation
+     */
+    void completed();
+
+    /**
+     * Logs the completion of the operation
+     *
+     * @param status The final status message
+     */
+    void completed(String status);
+
+    /**
+     * Returns the current status of the operation.
+     *
+     * @return The status, possibly empty.
+     */
+    String getStatus();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/ProgressLoggerFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/ProgressLoggerFactory.java
new file mode 100644
index 0000000..8df71c8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/ProgressLoggerFactory.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+public interface ProgressLoggerFactory {
+    ProgressLogger start();
+    
+    ProgressLogger start(String description);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/ProgressLoggingBridge.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/ProgressLoggingBridge.java
new file mode 100644
index 0000000..984af25
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/ProgressLoggingBridge.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.logging;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+public class ProgressLoggingBridge implements ProgressListener {
+    private static final Logger LOGGER = Logging.getLogger(ProgressLoggingBridge.class);
+
+    public void started(ProgressLogger logger) {
+        LOGGER.info(Logging.PROGRESS_STARTED, logger.getDescription());
+    }
+
+    public void completed(ProgressLogger logger) {
+        LOGGER.info(Logging.PROGRESS_COMPLETE, logger.getStatus());
+    }
+
+    public void progress(ProgressLogger logger) {
+        LOGGER.info(Logging.PROGRESS, logger.getStatus());
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/Slf4jLoggingConfigurer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/Slf4jLoggingConfigurer.java
new file mode 100644
index 0000000..98f95e1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/Slf4jLoggingConfigurer.java
@@ -0,0 +1,244 @@
+/*
+ * 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;
+
+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.core.AppenderBase;
+import ch.qos.logback.core.Layout;
+import ch.qos.logback.core.filter.Filter;
+import ch.qos.logback.core.spi.FilterReply;
+import org.fusesource.jansi.AnsiConsole;
+import org.gradle.api.logging.*;
+import org.gradle.api.specs.Spec;
+import org.gradle.listener.ListenerBroadcast;
+import org.slf4j.LoggerFactory;
+
+import java.io.FileDescriptor;
+import java.io.PrintStream;
+
+/**
+ * @author Hans Dockter
+ */
+public class Slf4jLoggingConfigurer implements LoggingConfigurer, LoggingOutput {
+    private final LoggingDestination stdout = new LoggingDestination();
+    private final LoggingDestination stderr = new LoggingDestination();
+    private final Appender errorAppender = new Appender();
+    private final Appender infoAppender = new Appender();
+    private final Spec<FileDescriptor> terminalDetector;
+    private LogEventFormatter consoleFormatter;
+    private LogEventFormatter nonConsoleFormatter;
+    private LogLevel currentLevel;
+    private final PrintStream defaultStdOut;
+
+    public Slf4jLoggingConfigurer() {
+        this(new TerminalDetector());
+    }
+
+    Slf4jLoggingConfigurer(Spec<FileDescriptor> terminalDetector) {
+        this.terminalDetector = terminalDetector;
+        defaultStdOut = System.out;
+    }
+
+    Console createConsole() {
+        if (stdout.terminal) {
+            return new org.gradle.logging.AnsiConsole(AnsiConsole.out(), AnsiConsole.out());
+        }
+        if (stderr.terminal) {
+            return new org.gradle.logging.AnsiConsole(AnsiConsole.err(), AnsiConsole.err());
+        }
+        return null;
+    }
+
+    public void addStandardErrorListener(StandardOutputListener listener) {
+        stderr.addListener(listener);
+    }
+
+    public void removeStandardErrorListener(StandardOutputListener listener) {
+        stderr.removeListener(listener);
+    }
+
+    public void addStandardOutputListener(StandardOutputListener listener) {
+        stdout.addListener(listener);
+    }
+
+    public void removeStandardOutputListener(StandardOutputListener listener) {
+        stdout.removeListener(listener);
+    }
+
+    public void configure(LogLevel logLevel) {
+        if (currentLevel == logLevel) {
+            return;
+        }
+
+        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
+        ch.qos.logback.classic.Logger rootLogger;
+        if (currentLevel == null) {
+            lc.reset();
+
+            stdout.init(FileDescriptor.out, System.out);
+            stderr.init(FileDescriptor.err, System.err);
+
+            Console console = createConsole();
+            consoleFormatter = console == null ? null : new ConsoleBackedFormatter(lc, console);
+            nonConsoleFormatter = new BasicProgressLoggingAwareFormatter(lc, stdout.getBroadcast(),
+                    stderr.getBroadcast());
+
+            errorAppender.setContext(lc);
+            infoAppender.setContext(lc);
+
+            rootLogger = lc.getLogger("ROOT");
+            rootLogger.addAppender(infoAppender);
+            rootLogger.addAppender(errorAppender);
+        } else {
+            rootLogger = lc.getLogger("ROOT");
+        }
+
+        currentLevel = logLevel;
+        errorAppender.stop();
+        infoAppender.stop();
+        errorAppender.clearAllFilters();
+        infoAppender.clearAllFilters();
+
+        errorAppender.addFilter(createLevelFilter(lc, Level.ERROR, FilterReply.ACCEPT, FilterReply.DENY));
+        Level level = Level.INFO;
+
+        setLayouts(logLevel, errorAppender, infoAppender, lc);
+
+        MarkerFilter quietFilter = new MarkerFilter(FilterReply.DENY, Logging.QUIET);
+        infoAppender.addFilter(quietFilter);
+        if (!(logLevel == LogLevel.QUIET)) {
+            quietFilter.setOnMismatch(FilterReply.NEUTRAL);
+            if (logLevel == LogLevel.DEBUG) {
+                level = Level.DEBUG;
+                infoAppender.addFilter(createLevelFilter(lc, Level.INFO, FilterReply.ACCEPT, FilterReply.NEUTRAL));
+                infoAppender.addFilter(createLevelFilter(lc, Level.DEBUG, FilterReply.ACCEPT, FilterReply.NEUTRAL));
+            } else {
+                if (logLevel == LogLevel.INFO) {
+                    level = Level.INFO;
+                    infoAppender.addFilter(createLevelFilter(lc, Level.INFO, FilterReply.ACCEPT, FilterReply.NEUTRAL));
+                } else {
+                    infoAppender.addFilter(new MarkerFilter(Logging.LIFECYCLE, Logging.PROGRESS));
+                }
+            }
+            infoAppender.addFilter(createLevelFilter(lc, Level.WARN, FilterReply.ACCEPT, FilterReply.DENY));
+        }
+        rootLogger.setLevel(level);
+        infoAppender.start();
+        errorAppender.start();
+    }
+
+    private void setLayouts(LogLevel logLevel, Appender errorAppender, Appender nonErrorAppender, LoggerContext lc) {
+        nonErrorAppender.setFormatter(stdout.createFormatter(lc, logLevel));
+        errorAppender.setFormatter(stderr.createFormatter(lc, logLevel));
+    }
+
+    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 static class DebugLayout extends PatternLayout {
+        private DebugLayout() {
+            setPattern("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n%ex");
+        }
+    }
+
+    private class LoggingDestination {
+        private final ListenerBroadcast<StandardOutputListener> listeners
+                = new ListenerBroadcast<StandardOutputListener>(StandardOutputListener.class);
+        private boolean terminal;
+        private PrintStream target;
+
+        private void init(FileDescriptor fileDescriptor, PrintStream target) {
+            this.target = target;
+            terminal = terminalDetector.isSatisfiedBy(fileDescriptor);
+        }
+
+        private LogEventFormatter createFormatter(LoggerContext loggerContext, LogLevel logLevel) {
+            if (logLevel == LogLevel.DEBUG) {
+                Layout<ILoggingEvent> layout = new DebugLayout();
+                layout.setContext(loggerContext);
+                layout.start();
+                return new LayoutBasedFormatter(layout, getBroadcastWithTarget());
+            }
+            if (terminal) {
+                return new LogEventFormatter() {
+                    public void format(ILoggingEvent event) {
+                        consoleFormatter.format(event);
+                        nonConsoleFormatter.format(event);
+                    }
+                };
+            } else {
+                return nonConsoleFormatter;
+            }
+        }
+
+        public void removeListener(StandardOutputListener listener) {
+            listeners.remove(listener);
+        }
+
+        public void addListener(StandardOutputListener listener) {
+            listeners.add(listener);
+        }
+
+        public StandardOutputListener getBroadcast() {
+            if (terminal) {
+                return listeners.getSource();
+            } else {
+                return getBroadcastWithTarget();
+            }
+        }
+
+        private StandardOutputListener getBroadcastWithTarget() {
+            final StandardOutputListener targetListener = new OutputStreamStandardOutputListenerAdapter(target);
+            return new StandardOutputListener() {
+                public void onOutput(CharSequence output) {
+                    targetListener.onOutput(output);
+                    listeners.getSource().onOutput(output);
+                }
+            };
+        }
+    }
+
+    private class Appender extends AppenderBase<ILoggingEvent> {
+        private LogEventFormatter formatter;
+
+        public void setFormatter(LogEventFormatter formatter) {
+            this.formatter = formatter;
+        }
+
+        @Override
+        protected void append(ILoggingEvent event) {
+            try {
+                formatter.format(event);
+            } catch (Throwable t) {
+                // Give up and try stdout
+                t.printStackTrace(defaultStdOut);
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/StandardOutputCapture.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/StandardOutputCapture.java
new file mode 100644
index 0000000..8392d98
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/StandardOutputCapture.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.logging;
+
+/**
+ * @author Hans Dockter
+ */
+public interface StandardOutputCapture {
+    /**
+     * Starts redirection of System.out and System.err to the Gradle logging system.
+     *
+     * @return this
+     */
+    StandardOutputCapture start();
+
+    /**
+     * Restores System.out and System.err to the values they had before {@link #start()} has been called.
+     *
+     * @return this
+     */
+    StandardOutputCapture stop();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/StandardOutputRedirector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/StandardOutputRedirector.java
new file mode 100644
index 0000000..048d8bd
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/StandardOutputRedirector.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+import org.gradle.api.logging.StandardOutputListener;
+
+public interface StandardOutputRedirector extends StandardOutputCapture {
+    void redirectStandardOutputTo(StandardOutputListener stdOutDestination);
+
+    void redirectStandardErrorTo(StandardOutputListener stdErrDestination);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/StdErrLoggingSystem.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/StdErrLoggingSystem.java
new file mode 100644
index 0000000..d8c6f89
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/StdErrLoggingSystem.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.logging;
+
+import org.gradle.api.logging.Logging;
+
+import java.io.PrintStream;
+
+class StdErrLoggingSystem extends PrintStreamLoggingSystem {
+    public StdErrLoggingSystem() {
+        super(Logging.getLogger("system.err"));
+    }
+
+    @Override
+    protected PrintStream get() {
+        return System.err;
+    }
+
+    @Override
+    protected void set(PrintStream printStream) {
+        System.setErr(printStream);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/StdOutLoggingSystem.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/StdOutLoggingSystem.java
new file mode 100644
index 0000000..91533fa
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/StdOutLoggingSystem.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.logging;
+
+import org.gradle.api.logging.Logging;
+
+import java.io.PrintStream;
+
+class StdOutLoggingSystem extends PrintStreamLoggingSystem {
+    public StdOutLoggingSystem() {
+        super(Logging.getLogger("system.out"));
+    }
+
+    @Override
+    protected PrintStream get() {
+        return System.out;
+    }
+
+    @Override
+    protected void set(PrintStream printStream) {
+        System.setOut(printStream);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/TerminalDetector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/TerminalDetector.java
new file mode 100644
index 0000000..6a23677
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/TerminalDetector.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.logging;
+
+import org.fusesource.jansi.WindowsAnsiOutputStream;
+import org.gradle.api.specs.Spec;
+import org.gradle.util.OperatingSystem;
+import org.gradle.util.PosixUtil;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+public class TerminalDetector implements Spec<FileDescriptor> {
+    public boolean isSatisfiedBy(FileDescriptor element) {
+        if (OperatingSystem.current().isWindows()) {
+            // Use Jansi's detection mechanism
+            try {
+                new WindowsAnsiOutputStream(new ByteArrayOutputStream());
+            } catch (IOException ignore) {
+                // Not attached to a console
+                return false;
+            }
+        }
+
+        // Use jna-posix
+        return PosixUtil.current().isatty(element);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/logging/TextArea.java b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/TextArea.java
new file mode 100644
index 0000000..654fdcd
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/logging/TextArea.java
@@ -0,0 +1,21 @@
+/*
+ * 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;
+
+public interface TextArea {
+    void append(CharSequence text);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/actor/Actor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/actor/Actor.java
new file mode 100644
index 0000000..d1b671b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/actor/Actor.java
@@ -0,0 +1,50 @@
+/*
+ * 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.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. In this way, the target object does not need to
+ * perform any synchronisation.</p>
+ *
+ * <p>An actor delivers method calls to the target object asynchronously, so that method dispatch does not block waiting
+ * for the method call to be delivered.</p>
+ *
+ * <p>All implementations of this interface must be thread-safe.</p>
+ */
+public interface Actor extends StoppableDispatch<MethodInvocation> {
+    /**
+     * 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);
+
+    /**
+     * Blocks until all method calls have been delivered to the target object. Stops accepting new method calls.
+     *
+     * @throws DispatchException When there were any failures dispatching method calls to the target object.
+     */
+    void stop() throws DispatchException;
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/actor/ActorFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/actor/ActorFactory.java
new file mode 100644
index 0000000..7ab9d8f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/actor/ActorFactory.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.messaging.actor;
+
+public interface ActorFactory {
+    /**
+     * Creates an asynchronous actor for the given target object.
+     *
+     * @param target The target object.
+     * @return The actor.
+     */
+    Actor createActor(Object target);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/actor/internal/DefaultActorFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/actor/internal/DefaultActorFactory.java
new file mode 100644
index 0000000..13084dc
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/actor/internal/DefaultActorFactory.java
@@ -0,0 +1,98 @@
+/*
+ * 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.messaging.actor.Actor;
+import org.gradle.messaging.actor.ActorFactory;
+import org.gradle.messaging.concurrent.CompositeStoppable;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.gradle.messaging.concurrent.Stoppable;
+import org.gradle.messaging.concurrent.StoppableExecutor;
+import org.gradle.messaging.dispatch.*;
+
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+public class DefaultActorFactory implements ActorFactory, Stoppable {
+    private final Map<Object, ActorImpl> actors = new IdentityHashMap<Object, ActorImpl>();
+    private final Object lock = new Object();
+    private final ExecutorFactory executorFactory;
+
+    public DefaultActorFactory(ExecutorFactory executorFactory) {
+        this.executorFactory = executorFactory;
+    }
+
+    public void stop() {
+        synchronized (lock) {
+            try {
+                new CompositeStoppable(actors.values()).stop();
+            } finally {
+                actors.clear();
+            }
+        }
+    }
+
+    public Actor createActor(Object target) {
+        if (target instanceof ActorImpl) {
+            return (ActorImpl) target;
+        }
+        synchronized (lock) {
+            ActorImpl actor = actors.get(target);
+            if (actor == null) {
+                actor = new ActorImpl(target);
+                actors.put(target, actor);
+            }
+            return actor;
+        }
+    }
+
+    private void stopped(ActorImpl actor) {
+        synchronized (lock) {
+            actors.values().remove(actor);
+        }
+    }
+
+    private class ActorImpl implements Actor {
+        private final StoppableDispatch<MethodInvocation> dispatch;
+        private final StoppableExecutor executor;
+        private final ExceptionTrackingListener exceptionListener;
+
+        public ActorImpl(Object targetObject) {
+            executor = executorFactory.create(String.format("Dispatch %s", targetObject));
+            exceptionListener = new ExceptionTrackingListener(Logging.getLogger(ActorImpl.class));
+            dispatch = new AsyncDispatch<MethodInvocation>(executor, new ExceptionTrackingDispatch<MethodInvocation>(
+                    new ReflectionDispatch(targetObject), exceptionListener));
+        }
+
+        public <T> T getProxy(Class<T> type) {
+            return new ProxyDispatchAdapter<T>(type, this).getSource();
+        }
+
+        public void stop() {
+            try {
+                new CompositeStoppable(dispatch, executor, exceptionListener).stop();
+            } finally {
+                stopped(this);
+            }
+        }
+
+        public void dispatch(MethodInvocation message) {
+            dispatch.dispatch(message);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/concurrent/AsyncStoppable.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/concurrent/AsyncStoppable.java
new file mode 100644
index 0000000..e34a78c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/concurrent/AsyncStoppable.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+/**
+ * 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
+     * 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>
+     */
+    void requestStop();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/concurrent/CompositeStoppable.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/concurrent/CompositeStoppable.java
new file mode 100644
index 0000000..3b4630b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/concurrent/CompositeStoppable.java
@@ -0,0 +1,108 @@
+/*
+ * 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.UncheckedIOException;
+import org.gradle.util.GUtil;
+import org.gradle.util.UncheckedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class CompositeStoppable implements Stoppable {
+    private static final Logger LOGGER = LoggerFactory.getLogger(CompositeStoppable.class);
+    private final List<Stoppable> elements = new CopyOnWriteArrayList<Stoppable>();
+
+    public CompositeStoppable() {
+    }
+
+    public CompositeStoppable(Stoppable... elements) {
+        this(Arrays.asList(elements));
+    }
+
+    public CompositeStoppable(Iterable<? extends Stoppable> elements) {
+        GUtil.addToCollection(this.elements, elements);
+    }
+
+    public CompositeStoppable(Closeable... elements) {
+        for (Closeable element : elements) {
+            add(element);
+        }
+    }
+
+    public CompositeStoppable add(Iterable<? extends Stoppable> elements) {
+        GUtil.addToCollection(this.elements, elements);
+        return this;
+    }
+
+    public CompositeStoppable add(Stoppable stoppable) {
+        elements.add(stoppable);
+        return this;
+    }
+
+    public CompositeStoppable add(Closeable closeable) {
+        elements.add(toStoppable(closeable));
+        return this;
+    }
+
+    public CompositeStoppable addCloseables(Iterable<? extends Closeable> closeables) {
+        for (Closeable closeable : closeables) {
+            add(closeable);
+        }
+        return this;
+    }
+
+    private Stoppable toStoppable(final Closeable closeable) {
+        return new Stoppable() {
+            public void stop() {
+                try {
+                    closeable.close();
+                } catch (IOException e) {
+                    throw new UncheckedIOException(e);
+                }
+            }
+        };
+    }
+
+    public void stop() {
+        Throwable failure = null;
+        try {
+            for (Stoppable element : elements) {
+                try {
+                    element.stop();
+                } catch (Throwable throwable) {
+                    if (failure == null) {
+                        failure = throwable;
+                    } else {
+                        LOGGER.error(String.format("Could not stop %s.", element), throwable);
+                    }
+                }
+            }
+        } finally {
+            elements.clear();
+        }
+
+        if (failure != null) {
+            throw UncheckedException.asUncheckedException(failure);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/concurrent/DefaultExecutorFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/concurrent/DefaultExecutorFactory.java
new file mode 100644
index 0000000..ea6bd24
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/concurrent/DefaultExecutorFactory.java
@@ -0,0 +1,121 @@
+/*
+ * 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.messaging.dispatch.ExceptionTrackingListener;
+import org.gradle.util.UncheckedException;
+
+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 ExceptionTrackingListener exceptionListener;
+        private final ThreadLocal<Runnable> executing = new ThreadLocal<Runnable>();
+
+        public StoppableExecutorImpl(ExecutorService executor) {
+            this.executor = executor;
+            exceptionListener = new ExceptionTrackingListener(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) {
+                        exceptionListener.execute(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);
+                }
+                exceptionListener.stop();
+            } 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/gradle-core/src/main/groovy/org/gradle/messaging/concurrent/ExecutorFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/concurrent/ExecutorFactory.java
new file mode 100644
index 0000000..1598206
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/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.messaging.concurrent;
+
+public interface ExecutorFactory {
+    /**
+     * Creates an executor. 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/gradle-core/src/main/groovy/org/gradle/messaging/concurrent/Stoppable.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/concurrent/Stoppable.java
new file mode 100644
index 0000000..e1c9f59
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/concurrent/Stoppable.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+/**
+ * Represents an object which performs concurrent activity.
+ */
+public interface Stoppable {
+    /**
+     * <p>Requests a graceful stop of this object. Blocks until all concurrent activity has been completed.</p>
+     *
+     * <p>If this object has already been stopped, this method does nothing.</p>
+     */
+    void stop();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/concurrent/StoppableExecutor.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/concurrent/StoppableExecutor.java
new file mode 100644
index 0000000..c095804
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/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.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/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/Addressable.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/Addressable.java
new file mode 100644
index 0000000..373a3e6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/Addressable.java
@@ -0,0 +1,24 @@
+/*
+ * 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 java.net.URI;
+
+public interface Addressable {
+    URI getLocalAddress();
+
+    URI getRemoteAddress();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/AsyncDispatch.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/AsyncDispatch.java
new file mode 100644
index 0000000..3d9dde1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/AsyncDispatch.java
@@ -0,0 +1,189 @@
+/*
+ * 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.messaging.concurrent.AsyncStoppable;
+import org.gradle.util.UncheckedException;
+
+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 delivers the messages in the order they have been received to one
+ * of a pool of delegate {@link org.gradle.messaging.dispatch.Dispatch} instances.</p>
+ *
+ * <p>One or more {@link org.gradle.messaging.dispatch.Receive} instances can use used as a source of messages.</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);
+        }
+    }
+
+    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("This message dispatch has been stopped.");
+            }
+            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);
+    }
+
+    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/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/AsyncReceive.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/AsyncReceive.java
new file mode 100644
index 0000000..c48b2c7
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/AsyncReceive.java
@@ -0,0 +1,141 @@
+/*
+ * 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.messaging.concurrent.AsyncStoppable;
+import org.gradle.util.UncheckedException;
+
+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>
+ */
+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 Dispatch<? super T> dispatch;
+    private int receivers;
+    private State state = State.Init;
+
+    public AsyncReceive(Executor executor, final Dispatch<? super T> dispatch) {
+        this.executor = executor;
+        this.dispatch = dispatch;
+    }
+
+    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--;
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void receiveMessages(Receive<? extends T> receive) {
+        while (true) {
+            T message = receive.receive();
+            if (message == null) {
+                return;
+            }
+
+            dispatch.dispatch(message);
+
+            lock.lock();
+            try {
+                if (state != State.Init) {
+                    return;
+                }
+            } finally {
+                lock.unlock();
+            }
+        }
+    }
+
+    private void setState(State state) {
+        this.state = state;
+        condition.signalAll();
+    }
+
+    public void requestStop() {
+        lock.lock();
+        try {
+            doRequestStop();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void doRequestStop() {
+        if (receivers > 0) {
+            setState(State.Stopping);
+        } else {
+            setState(State.Stopped);
+        }
+    }
+
+    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/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/BroadcastDispatch.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/BroadcastDispatch.java
new file mode 100644
index 0000000..6574e07
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/BroadcastDispatch.java
@@ -0,0 +1,139 @@
+/*
+ * 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.listener.ListenerNotificationException;
+import org.gradle.util.UncheckedException;
+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 {
+            ExceptionTrackingListener tracker = new ExceptionTrackingListener(LOGGER);
+            for (Dispatch<MethodInvocation> handler : new ArrayList<Dispatch<MethodInvocation>>(handlers.values())) {
+                try {
+                    handler.dispatch(invocation);
+                } catch (UncheckedException e) {
+                    tracker.execute(e.getCause());
+                } catch (Throwable t) {
+                    tracker.execute(t);
+                }
+            }
+            tracker.stop();
+        } catch (Throwable t) {
+            throw new ListenerNotificationException(getErrorMessage(), t);
+        }
+    }
+
+    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/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/ContextClassLoaderDispatch.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/ContextClassLoaderDispatch.java
new file mode 100644
index 0000000..8ac4129
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/ContextClassLoaderDispatch.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+public class ContextClassLoaderDispatch<T> implements Dispatch<T> {
+    private final Dispatch<? super T> dispatch;
+    private final ClassLoader contextClassLoader;
+
+    public ContextClassLoaderDispatch(Dispatch<? super T> dispatch, ClassLoader contextClassLoader) {
+        this.dispatch = dispatch;
+        this.contextClassLoader = contextClassLoader;
+    }
+
+    public void dispatch(T message) {
+        ClassLoader original = Thread.currentThread().getContextClassLoader();
+        Thread.currentThread().setContextClassLoader(contextClassLoader);
+        try {
+            dispatch.dispatch(message);
+        }
+        finally {
+            Thread.currentThread().setContextClassLoader(original);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/DiscardOnFailureDispatch.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/DiscardOnFailureDispatch.java
new file mode 100644
index 0000000..e14a5a6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/DiscardOnFailureDispatch.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.dispatch;
+
+import org.slf4j.Logger;
+
+public class DiscardOnFailureDispatch<T> implements Dispatch<T> {
+    private final Dispatch<? super T> dispatch;
+    private final Logger logger;
+
+    public DiscardOnFailureDispatch(Dispatch<? super T> dispatch, Logger logger) {
+        this.dispatch = dispatch;
+        this.logger = logger;
+    }
+
+    public void dispatch(T message) {
+        try {
+            dispatch.dispatch(message);
+        } catch (Throwable e) {
+            logger.error(String.format("Could not dispatch message %s to %s. Discarding message.", message, dispatch), e);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/Dispatch.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/Dispatch.java
new file mode 100644
index 0000000..ed71bf0
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/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/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/DispatchException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/DispatchException.java
new file mode 100644
index 0000000..fd70820
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/DispatchException.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+public class DispatchException extends RuntimeException {
+    public DispatchException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/ExceptionTrackingDispatch.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/ExceptionTrackingDispatch.java
new file mode 100644
index 0000000..77a3644
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/ExceptionTrackingDispatch.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.dispatch;
+
+import org.gradle.api.Action;
+
+public class ExceptionTrackingDispatch<T> implements Dispatch<T> {
+    private final Dispatch<T> dispatch;
+    private final Action<? super DispatchException> action;
+
+    public ExceptionTrackingDispatch(Dispatch<T> dispatch, Action<? super DispatchException> action) {
+        this.dispatch = dispatch;
+        this.action = action;
+    }
+
+    public void dispatch(T message) {
+        try {
+            dispatch.dispatch(message);
+        } catch (Throwable t) {
+            action.execute(new DispatchException(String.format("Failed to dispatch message %s.", message), t));
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/ExceptionTrackingListener.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/ExceptionTrackingListener.java
new file mode 100644
index 0000000..26112ec
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/ExceptionTrackingListener.java
@@ -0,0 +1,49 @@
+/*
+ * 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.api.Action;
+import org.gradle.messaging.concurrent.Stoppable;
+import org.gradle.util.UncheckedException;
+import org.slf4j.Logger;
+
+public class ExceptionTrackingListener implements Action<Throwable>, Stoppable {
+    private final Logger logger;
+    private RuntimeException failure;
+
+    public ExceptionTrackingListener(Logger logger) {
+        this.logger = logger;
+    }
+
+    public void execute(Throwable failure) {
+        if (this.failure != null) {
+            logger.error(failure.getMessage(), failure);
+        } else {
+            this.failure = UncheckedException.asUncheckedException(failure);
+        }
+    }
+
+    public void stop() {
+        if (failure != null) {
+            try {
+                throw failure;
+            } finally {
+                failure = null;
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/MethodInvocation.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/MethodInvocation.java
new file mode 100644
index 0000000..564b4d2
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/MethodInvocation.java
@@ -0,0 +1,60 @@
+/*
+ * 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 java.lang.reflect.Method;
+import java.util.Arrays;
+
+public class MethodInvocation {
+    private final Method method;
+    private final Object[] arguments;
+
+    public MethodInvocation(Method method, Object[] args) {
+        this.method = method;
+        arguments = args;
+    }
+
+    public Object[] getArguments() {
+        return arguments;
+    }
+
+    public Method getMethod() {
+        return method;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+
+        MethodInvocation other = (MethodInvocation) obj;
+        if (!method.equals(other.method)) {
+            return false;
+        }
+
+        return Arrays.equals(arguments, other.arguments);
+    }
+
+    @Override
+    public int hashCode() {
+        return method.hashCode();
+    }
+}
+
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/ProxyDispatchAdapter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/ProxyDispatchAdapter.java
new file mode 100644
index 0000000..c0c3a54
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/ProxyDispatchAdapter.java
@@ -0,0 +1,79 @@
+/*
+ * 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 java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * Adapts from interface T to a {@link Dispatch}
+ *
+ * @param <T>
+ */
+public class ProxyDispatchAdapter<T> {
+    private final Class<T> type;
+    private final T source;
+
+    public ProxyDispatchAdapter(Class<T> type, Dispatch<? super MethodInvocation> dispatch) {
+        this.type = type;
+        source = type.cast(Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[]{type},
+                new DispatchingInvocationHandler(type, dispatch)));
+    }
+
+    public Class<T> getType() {
+        return type;
+    }
+
+    public T getSource() {
+        return source;
+    }
+
+    private static class DispatchingInvocationHandler implements InvocationHandler {
+        private final Class<?> type;
+        private final Dispatch<? super MethodInvocation> dispatch;
+
+        private DispatchingInvocationHandler(Class<?> type, Dispatch<? super MethodInvocation> dispatch) {
+            this.type = type;
+            this.dispatch = dispatch;
+        }
+
+        public Object invoke(Object target, Method method, Object[] parameters) throws Throwable {
+            if (method.getName().equals("equals")) {
+                Object parameter = parameters[0];
+                if (parameter == null || !Proxy.isProxyClass(parameter.getClass())) {
+                    return false;
+                }
+                Object handler = Proxy.getInvocationHandler(parameter);
+                if (!DispatchingInvocationHandler.class.isInstance(handler)) {
+                    return false;
+                }
+
+                DispatchingInvocationHandler otherHandler = (DispatchingInvocationHandler) handler;
+                return otherHandler.type.equals(type) && otherHandler.dispatch == dispatch;
+            }
+
+            if (method.getName().equals("hashCode")) {
+                return dispatch.hashCode();
+            }
+            if (method.getName().equals("toString")) {
+                return String.format("%s broadcast", type.getSimpleName());
+            }
+            dispatch.dispatch(new MethodInvocation(method, parameters));
+            return null;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/Receive.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/Receive.java
new file mode 100644
index 0000000..f4e75f0
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/Receive.java
@@ -0,0 +1,28 @@
+/*
+ * 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 source for messages. Implementations do not have to be thread-safe.
+ */
+public interface Receive<T> {
+    /**
+     * Blocks until the next message is available. Returns null when the end of the message stream has been reached.
+     *
+     * @return The next message.
+     */
+    T receive();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/ReflectionDispatch.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/ReflectionDispatch.java
new file mode 100644
index 0000000..cb44a7c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/ReflectionDispatch.java
@@ -0,0 +1,39 @@
+/*
+ * 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.util.UncheckedException;
+
+import java.lang.reflect.InvocationTargetException;
+
+public class ReflectionDispatch implements Dispatch<MethodInvocation> {
+    private final Object target;
+
+    public ReflectionDispatch(Object target) {
+        this.target = target;
+    }
+
+    public void dispatch(MethodInvocation message) {
+        try {
+            message.getMethod().invoke(target, message.getArguments());
+        } catch (InvocationTargetException e) {
+            throw UncheckedException.asUncheckedException(e.getCause());
+        } catch (Throwable throwable) {
+            throw UncheckedException.asUncheckedException(throwable);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/StoppableDispatch.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/StoppableDispatch.java
new file mode 100644
index 0000000..b8f9ce4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/StoppableDispatch.java
@@ -0,0 +1,25 @@
+/*
+ * 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.messaging.concurrent.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/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/ThreadSafeDispatch.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/ThreadSafeDispatch.java
new file mode 100644
index 0000000..7432d6c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/dispatch/ThreadSafeDispatch.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+public class ThreadSafeDispatch<T> implements Dispatch<T> {
+    private final Object lock = new Object();
+    private final Dispatch<T> dispatch;
+
+    public ThreadSafeDispatch(Dispatch<T> dispatch) {
+        this.dispatch = dispatch;
+    }
+
+    public void dispatch(T message) {
+        synchronized (lock) {
+            dispatch.dispatch(message);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/ConnectEvent.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/ConnectEvent.java
new file mode 100644
index 0000000..47cf05e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/ConnectEvent.java
@@ -0,0 +1,45 @@
+/*
+ * 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.dispatch.Addressable;
+
+import java.net.URI;
+
+public class ConnectEvent<T> implements Addressable {
+    private final T connection;
+    private final URI localAddress;
+    private final URI remoteAddress;
+
+    public ConnectEvent(T connection, URI localAddress, URI remoteAddress) {
+        this.connection = connection;
+        this.localAddress = localAddress;
+        this.remoteAddress = remoteAddress;
+    }
+
+    public T getConnection() {
+        return connection;
+    }
+
+    public URI getLocalAddress() {
+        return localAddress;
+    }
+
+    public URI getRemoteAddress() {
+        return remoteAddress;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/MessagingClient.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/MessagingClient.java
new file mode 100644
index 0000000..0f33a06
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/MessagingClient.java
@@ -0,0 +1,33 @@
+/*
+ * 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.Stoppable;
+
+/**
+ * A {@code MessagingClient} maintains a single bi-directional uni-cast object connection with some peer.
+ */
+public interface MessagingClient extends Stoppable {
+    /**
+     * Returns the connection for this client.
+     */
+    ObjectConnection getConnection();
+
+    /**
+     * Performs a graceful stop of this client. Stops the client's connection.
+     */
+    void stop();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/MessagingServer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/MessagingServer.java
new file mode 100644
index 0000000..0ff2758
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/MessagingServer.java
@@ -0,0 +1,40 @@
+/*
+ * 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.api.Action;
+import org.gradle.messaging.concurrent.Stoppable;
+
+import java.net.URI;
+
+/**
+ * A {@code MessagingServer} allows the creation of multiple bi-direction uni-cast connections with some peer.
+ */
+public interface MessagingServer extends Stoppable {
+    /**
+     * Creates an endpoint which a single peer can connect to.
+     *
+     * @param action The action to execute when the connection has been established.
+     * @return The local address of the endpoint, for the peer to connect to
+     */
+    URI accept(Action<ConnectEvent<ObjectConnection>> action);
+
+    /**
+     * Performs a graceful stop of this server. Blocks until connections have been stopped.
+     */
+    void stop();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/ObjectConnection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/ObjectConnection.java
new file mode 100644
index 0000000..936be45
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/ObjectConnection.java
@@ -0,0 +1,64 @@
+/*
+ * 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.Addressable;
+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.
+     *
+     * @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.
+     *
+     * @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/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessage.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessage.java
new file mode 100644
index 0000000..809bbc3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessage.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.messaging.remote.internal;
+
+public class ChannelMessage extends Message {
+    private final Object channel;
+    private final Message payload;
+
+    public ChannelMessage(Object channel, Message payload) {
+        this.channel = channel;
+        this.payload = payload;
+    }
+
+    public Object getChannel() {
+        return channel;
+    }
+
+    public Message getPayload() {
+        return payload;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || !getClass().equals(obj.getClass())) {
+            return false;
+        }
+
+        ChannelMessage other = (ChannelMessage) obj;
+        return channel.equals(other.channel) && payload.equals(other.payload);
+    }
+
+    @Override
+    public int hashCode() {
+        return channel.hashCode() ^ payload.hashCode();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessageMarshallingDispatch.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessageMarshallingDispatch.java
new file mode 100644
index 0000000..184a39e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessageMarshallingDispatch.java
@@ -0,0 +1,47 @@
+/*
+ * 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.dispatch.Dispatch;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ChannelMessageMarshallingDispatch implements Dispatch<Message> {
+    private final Dispatch<Message> dispatch;
+    private final Map<Object, Integer> channels = new HashMap<Object, Integer>();
+
+    public ChannelMessageMarshallingDispatch(Dispatch<Message> dispatch) {
+        this.dispatch = dispatch;
+    }
+
+    public void dispatch(Message message) {
+        if (message instanceof ChannelMessage) {
+            ChannelMessage channelMessage = (ChannelMessage) message;
+            Object key = channelMessage.getChannel();
+            Integer id = channels.get(key);
+            if (id == null) {
+                id = channels.size();
+                channels.put(key, id);
+                dispatch.dispatch(new ChannelMetaInfo(key, id));
+            }
+            dispatch.dispatch(new ChannelMessage(id, channelMessage.getPayload()));
+        } else {
+            dispatch.dispatch(message);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessageUnmarshallingDispatch.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessageUnmarshallingDispatch.java
new file mode 100644
index 0000000..d1d1d6f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMessageUnmarshallingDispatch.java
@@ -0,0 +1,47 @@
+/*
+ * 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.dispatch.Dispatch;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class ChannelMessageUnmarshallingDispatch implements Dispatch<Message> {
+    private final Dispatch<Message> dispatch;
+    private final Map<Integer, Object> channels = new HashMap<Integer, Object>();
+
+    public ChannelMessageUnmarshallingDispatch(Dispatch<Message> dispatch) {
+        this.dispatch = dispatch;
+    }
+
+    public void dispatch(Message message) {
+        if (message instanceof ChannelMetaInfo) {
+            ChannelMetaInfo metaInfo = (ChannelMetaInfo) message;
+            channels.put(metaInfo.getChannelId(), metaInfo.getChannelKey());
+            return;
+        }
+        if (message instanceof ChannelMessage) {
+            ChannelMessage channelMessage = (ChannelMessage) message;
+            dispatch.dispatch(new ChannelMessage(channels.get(channelMessage.getChannel()),
+                    channelMessage.getPayload()));
+            return;
+        }
+        
+        dispatch.dispatch(message);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMetaInfo.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMetaInfo.java
new file mode 100644
index 0000000..618e0cb
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ChannelMetaInfo.java
@@ -0,0 +1,53 @@
+/*
+ * 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 ChannelMetaInfo extends Message {
+    private final Object channelKey;
+    private final int channelId;
+
+    public ChannelMetaInfo(Object channelKey, int channelId) {
+        this.channelKey = channelKey;
+        this.channelId = channelId;
+    }
+
+    public int getChannelId() {
+        return channelId;
+    }
+
+    public Object getChannelKey() {
+        return channelKey;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || !getClass().equals(o.getClass())) {
+            return false;
+        }
+
+        ChannelMetaInfo other = (ChannelMetaInfo) o;
+        return other.channelKey.equals(channelKey) && other.channelId == channelId;
+    }
+
+    @Override
+    public int hashCode() {
+        return channelKey.hashCode();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ConnectRequest.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ConnectRequest.java
new file mode 100644
index 0000000..3dda186
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/ConnectRequest.java
@@ -0,0 +1,31 @@
+/*
+ * 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 java.net.URI;
+
+public class ConnectRequest extends Message {
+    private final URI destinationAddress;
+
+    public ConnectRequest(URI destinationAddress) {
+        this.destinationAddress = destinationAddress;
+    }
+
+    public URI getDestinationAddress() {
+        return destinationAddress;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/Connection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/Connection.java
new file mode 100644
index 0000000..3ff0b18
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/Connection.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+public interface Connection<T> extends Dispatch<T>, Receive<T>, AsyncStoppable {
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClient.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClient.java
new file mode 100644
index 0000000..6e8f711
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClient.java
@@ -0,0 +1,40 @@
+/*
+ * 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.remote.MessagingClient;
+import org.gradle.messaging.remote.ObjectConnection;
+
+import java.net.URI;
+
+public class DefaultMessagingClient implements MessagingClient {
+    private final ObjectConnection connection;
+
+    public DefaultMessagingClient(MultiChannelConnector connector, ClassLoader classLoader, URI serverAddress) {
+        MultiChannelConnection<Message> connection = connector.connect(serverAddress);
+        IncomingMethodInvocationHandler incoming = new IncomingMethodInvocationHandler(classLoader, connection);
+        OutgoingMethodInvocationHandler outgoing = new OutgoingMethodInvocationHandler(connection);
+        this.connection = new DefaultObjectConnection(connection, connection, outgoing, incoming);
+    }
+
+    public ObjectConnection getConnection() {
+        return connection;
+    }
+
+    public void stop() {
+        connection.stop();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServer.java
new file mode 100644
index 0000000..b6dc37a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServer.java
@@ -0,0 +1,96 @@
+/*
+ * 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.messaging.concurrent.AsyncStoppable;
+import org.gradle.messaging.concurrent.CompositeStoppable;
+import org.gradle.messaging.remote.ConnectEvent;
+import org.gradle.messaging.remote.MessagingServer;
+import org.gradle.messaging.remote.ObjectConnection;
+
+import java.net.URI;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class DefaultMessagingServer implements MessagingServer {
+    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 URI accept(final Action<ConnectEvent<ObjectConnection>> action) {
+        return connector.accept(new Action<ConnectEvent<MultiChannelConnection<Message>>>() {
+            public void execute(ConnectEvent<MultiChannelConnection<Message>> connectEvent) {
+                finishConnect(connectEvent, action);
+            }
+        });
+    }
+
+    private void finishConnect(ConnectEvent<MultiChannelConnection<Message>> connectEvent,
+                               Action<ConnectEvent<ObjectConnection>> action) {
+        MultiChannelConnection<Message> messageConnection = connectEvent.getConnection();
+        IncomingMethodInvocationHandler incoming = new IncomingMethodInvocationHandler(classLoader, 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<Message> messageConnection;
+        private final AtomicReference<ObjectConnection> connectionRef;
+
+        public ConnectionAsyncStoppable(MultiChannelConnection<Message> 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/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnection.java
new file mode 100644
index 0000000..c8feabb
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnection.java
@@ -0,0 +1,174 @@
+/*
+ * 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;
+import org.gradle.messaging.concurrent.*;
+import org.gradle.messaging.dispatch.*;
+import org.slf4j.LoggerFactory;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.*;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+class DefaultMultiChannelConnection implements MultiChannelConnection<Message> {
+    private final URI sourceAddress;
+    private final URI destinationAddress;
+    private final EndOfStreamDispatch outgoingDispatch;
+    private final AsyncDispatch<Message> outgoingQueue;
+    private final AsyncReceive<Message> incomingReceive;
+    private final EndOfStreamFilter incomingDispatch;
+    private final IncomingDemultiplex incomingDemux;
+    private final StoppableExecutor executor;
+    private final Connection<Message> connection;
+
+    DefaultMultiChannelConnection(ExecutorFactory executorFactory, String displayName, final Connection<Message> connection, URI sourceAddress, URI destinationAddress) {
+        this.connection = connection;
+        this.executor = executorFactory.create(displayName);
+
+        this.sourceAddress = sourceAddress;
+        this.destinationAddress = destinationAddress;
+
+        // Outgoing pipeline: <source> -> <channel-mux> -> <end-of-stream-dispatch> -> <async-queue> -> <ignore-failures> -> <connection>
+        outgoingQueue = new AsyncDispatch<Message>(executor);
+        outgoingQueue.dispatchTo(wrapFailures(connection));
+        outgoingDispatch = new EndOfStreamDispatch(new ChannelMessageMarshallingDispatch(outgoingQueue));
+
+        // Incoming pipeline: <connection> -> <async-receive> -> <ignore-failures> -> <end-of-stream-filter> -> <channel-demux> -> <channel-async-queue> -> <ignore-failures> -> <handler>
+        incomingDemux = new IncomingDemultiplex();
+        incomingDispatch = new EndOfStreamFilter(incomingDemux, new Runnable() {
+            public void run() {
+                requestStop();
+            }
+        });
+        incomingReceive = new AsyncReceive<Message>(executor, wrapFailures(new ChannelMessageUnmarshallingDispatch(incomingDispatch)));
+        incomingReceive.receiveFrom(new EndOfStreamReceive(connection));
+    }
+
+    private Dispatch<Message> wrapFailures(Dispatch<Message> dispatch) {
+        return new DiscardOnFailureDispatch<Message>(dispatch, LoggerFactory.getLogger(
+                DefaultMultiChannelConnector.class));
+    }
+
+    public URI getLocalAddress() {
+        if (sourceAddress == null) {
+            throw new UnsupportedOperationException();
+        }
+        return sourceAddress;
+    }
+
+    public URI getRemoteAddress() {
+        if (destinationAddress == null) {
+            throw new UnsupportedOperationException();
+        }
+        return destinationAddress;
+    }
+
+    public void requestStop() {
+        outgoingDispatch.stop();
+    }
+
+    public void addIncomingChannel(Object channelKey, Dispatch<Message> dispatch) {
+        incomingDemux.addIncomingChannel(channelKey, wrapFailures(dispatch));
+    }
+
+    public Dispatch<Message> addOutgoingChannel(Object channelKey) {
+        return new OutgoingMultiplex(channelKey, outgoingDispatch);
+    }
+
+    public void stop() {
+        executor.execute(new Runnable() {
+            public void run() {
+                // End-of-stream handshake
+                requestStop();
+                incomingDispatch.stop();
+
+                // Flush queues (should be empty)
+                incomingReceive.requestStop();
+                outgoingQueue.requestStop();
+                new CompositeStoppable(outgoingQueue, connection, incomingReceive, incomingDemux).stop();
+            }
+        });
+        try {
+            executor.stop(120, TimeUnit.SECONDS);
+        } catch (Throwable e) {
+            throw new GradleException("Could not stop connection.", e);
+        }
+    }
+
+    private class IncomingDemultiplex implements Dispatch<Message>, Stoppable {
+        private final Lock queueLock = new ReentrantLock();
+        private final Map<Object, AsyncDispatch<Message>> incomingQueues
+                = new HashMap<Object, AsyncDispatch<Message>>();
+
+        public void dispatch(Message message) {
+            ChannelMessage channelMessage = (ChannelMessage) message;
+            Dispatch<Message> channel = findChannel(channelMessage.getChannel());
+            channel.dispatch(channelMessage.getPayload());
+        }
+
+        public void addIncomingChannel(Object channel, Dispatch<Message> dispatch) {
+            AsyncDispatch<Message> queue = findChannel(channel);
+            queue.dispatchTo(dispatch);
+        }
+
+        private AsyncDispatch<Message> findChannel(Object channel) {
+            AsyncDispatch<Message> queue;
+            queueLock.lock();
+            try {
+                queue = incomingQueues.get(channel);
+                if (queue == null) {
+                    queue = new AsyncDispatch<Message>(executor);
+                    incomingQueues.put(channel, queue);
+                }
+            } finally {
+                queueLock.unlock();
+            }
+            return queue;
+        }
+
+        public void stop() {
+            Stoppable stopper;
+            queueLock.lock();
+            try {
+                stopper = new CompositeStoppable(incomingQueues.values());
+                incomingQueues.clear();
+            } finally {
+                queueLock.unlock();
+            }
+
+            stopper.stop();
+        }
+    }
+
+    private static class OutgoingMultiplex implements Dispatch<Message> {
+        private final Dispatch<Message> dispatch;
+        private final Object channelKey;
+
+        private OutgoingMultiplex(Object channelKey, Dispatch<Message> dispatch) {
+            this.channelKey = channelKey;
+            this.dispatch = dispatch;
+        }
+
+        public void dispatch(Message message) {
+            dispatch.dispatch(new ChannelMessage(channelKey, message));
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnector.java
new file mode 100644
index 0000000..9171c75
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnector.java
@@ -0,0 +1,68 @@
+/*
+ * 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.messaging.concurrent.ExecutorFactory;
+import org.gradle.messaging.concurrent.Stoppable;
+import org.gradle.messaging.concurrent.StoppableExecutor;
+import org.gradle.messaging.remote.ConnectEvent;
+
+import java.net.URI;
+
+public class DefaultMultiChannelConnector implements MultiChannelConnector, Stoppable {
+    private final OutgoingConnector outgoingConnector;
+    private final ExecutorFactory executorFactory;
+    private final StoppableExecutor executorService;
+    private final HandshakeIncomingConnector incomingConnector;
+
+    public DefaultMultiChannelConnector(OutgoingConnector outgoingConnector, IncomingConnector incomingConnector,
+                                        ExecutorFactory executorFactory) {
+        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 URI accept(final Action<ConnectEvent<MultiChannelConnection<Message>>> action) {
+        return incomingConnector.accept(new Action<ConnectEvent<Connection<Message>>>() {
+            public void execute(ConnectEvent<Connection<Message>> event) {
+                finishConnect(event, action);
+            }
+        });
+    }
+
+    private void finishConnect(ConnectEvent<Connection<Message>> event,
+                               Action<ConnectEvent<MultiChannelConnection<Message>>> action) {
+        URI localAddress = event.getLocalAddress();
+        URI remoteAddress = event.getRemoteAddress();
+        DefaultMultiChannelConnection channelConnection = new DefaultMultiChannelConnection(executorFactory,
+                String.format("Incoming Connection %s", localAddress), event.getConnection(), localAddress, remoteAddress);
+        action.execute(new ConnectEvent<MultiChannelConnection<Message>>(channelConnection, localAddress, remoteAddress));
+    }
+
+    public MultiChannelConnection<Message> connect(URI destinationAddress) {
+        Connection<Message> connection = outgoingConnector.connect(destinationAddress);
+        DefaultMultiChannelConnection channelConnection = new DefaultMultiChannelConnection(executorFactory,
+                String.format("Outgoing Connection %s", destinationAddress), connection, null, destinationAddress);
+        return channelConnection;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnection.java
new file mode 100644
index 0000000..cbe7248
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnection.java
@@ -0,0 +1,67 @@
+/*
+ * 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.Addressable;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.remote.ObjectConnection;
+
+import java.net.URI;
+
+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 URI getRemoteAddress() {
+        return addressable.getRemoteAddress();
+    }
+
+    public URI 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/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamDispatch.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamDispatch.java
new file mode 100644
index 0000000..98b5021
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamDispatch.java
@@ -0,0 +1,59 @@
+/*
+ * 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.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.StoppableDispatch;
+
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+public class EndOfStreamDispatch implements StoppableDispatch<Message> {
+    private final Dispatch<? super Message> dispatch;
+    private boolean stopped;
+    private final ReadWriteLock lock = new ReentrantReadWriteLock();
+
+    public EndOfStreamDispatch(Dispatch<? super Message> dispatch) {
+        this.dispatch = dispatch;
+    }
+
+    public void dispatch(Message message) {
+        lock.readLock().lock();
+        try {
+            if (stopped) {
+                throw new IllegalStateException(String.format(
+                        "Cannot dispatch message %s, as this dispatch has been stopped.", message));
+            }
+            dispatch.dispatch(message);
+        } finally {
+            lock.readLock().unlock();
+        }
+    }
+
+    public void stop() {
+        lock.writeLock().lock();
+        try {
+            if (stopped) {
+                return;
+            }
+            stopped = true;
+            dispatch.dispatch(new EndOfStreamEvent());
+        } finally {
+            lock.writeLock().unlock();
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamEvent.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamEvent.java
new file mode 100644
index 0000000..4fa4f2d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamEvent.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.remote.internal;
+
+public class EndOfStreamEvent extends Message {
+    @Override
+    public boolean equals(Object obj) {
+        return obj instanceof EndOfStreamEvent;
+    }
+
+    @Override
+    public int hashCode() {
+        return 0;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamFilter.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamFilter.java
new file mode 100644
index 0000000..de6b857
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamFilter.java
@@ -0,0 +1,72 @@
+/*
+ * 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.Stoppable;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.util.UncheckedException;
+
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class EndOfStreamFilter implements Dispatch<Message>, Stoppable {
+    private final Dispatch<Message> dispatch;
+    private final Runnable endOfStreamAction;
+    private final Lock lock = new ReentrantLock();
+    private final Condition condition = lock.newCondition();
+    private boolean endOfStreamReached;
+
+    public EndOfStreamFilter(Dispatch<Message> dispatch, Runnable endOfStreamAction) {
+        this.dispatch = dispatch;
+        this.endOfStreamAction = endOfStreamAction;
+    }
+
+    public void dispatch(Message message) {
+        lock.lock();
+        try {
+            if (endOfStreamReached) {
+                throw new IllegalStateException(String.format(
+                        "Cannot dispatch message %s, as this dispatch has been stopped.", message));
+            }
+            if (message instanceof EndOfStreamEvent) {
+                endOfStreamReached = true;
+                condition.signalAll();
+                endOfStreamAction.run();
+                return;
+            }
+        } finally {
+            lock.unlock();
+        }
+        dispatch.dispatch(message);
+    }
+
+    public void stop() {
+        lock.lock();
+        try {
+            while (!endOfStreamReached) {
+                try {
+                    condition.await();
+                } catch (InterruptedException e) {
+                    throw new UncheckedException(e);
+                }
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamReceive.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamReceive.java
new file mode 100644
index 0000000..25a720a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/EndOfStreamReceive.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.messaging.remote.internal;
+
+import org.gradle.messaging.dispatch.Receive;
+
+class EndOfStreamReceive implements Receive<Message> {
+    private final Receive<Message> receive;
+    private boolean ended;
+
+    public EndOfStreamReceive(Receive<Message> receive) {
+        this.receive = receive;
+    }
+
+    public Message receive() {
+        if (ended) {
+            return null;
+        }
+        Message message = receive.receive();
+        if (message == null) {
+            ended = true;
+            return new EndOfStreamEvent();
+        }
+        if (message instanceof EndOfStreamEvent) {
+            ended = true;
+        }
+        return message;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnector.java
new file mode 100644
index 0000000..79fec45
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnector.java
@@ -0,0 +1,85 @@
+/*
+ * 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.messaging.remote.ConnectEvent;
+import org.gradle.util.UncheckedException;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+public class HandshakeIncomingConnector implements IncomingConnector {
+    private final IncomingConnector connector;
+    private final Executor executor;
+    private final Object lock = new Object();
+    private URI localAddress;
+    private long nextId;
+    private final Map<URI, Action<ConnectEvent<Connection<Message>>>> pendingActions = new HashMap<URI, Action<ConnectEvent<Connection<Message>>>>();
+
+    public HandshakeIncomingConnector(IncomingConnector connector, Executor executor) {
+        this.connector = connector;
+        this.executor = executor;
+    }
+
+    public URI accept(Action<ConnectEvent<Connection<Message>>> action) {
+        synchronized (lock) {
+            if (localAddress == null) {
+                localAddress = connector.accept(handShakeAction());
+            }
+            
+            URI localAddress;
+            try {
+                localAddress = new URI(String.format("channel:%s!%d", this.localAddress, nextId++));
+            } catch (URISyntaxException e) {
+                throw UncheckedException.asUncheckedException(e);
+            }
+            pendingActions.put(localAddress, action);
+            return localAddress;
+        }
+    }
+
+    private Action<ConnectEvent<Connection<Message>>> handShakeAction() {
+        return new Action<ConnectEvent<Connection<Message>>>() {
+            public void execute(final ConnectEvent<Connection<Message>> connectEvent) {
+                executor.execute(new Runnable() {
+                    public void run() {
+                        handshake(connectEvent);
+                    }
+                });
+            }
+        };
+    }
+
+    private void handshake(ConnectEvent<Connection<Message>> connectEvent) {
+        Connection<Message> connection = connectEvent.getConnection();
+        ConnectRequest request = (ConnectRequest) connection.receive();
+        URI localAddress = request.getDestinationAddress();
+        Action<ConnectEvent<Connection<Message>>> channelConnection;
+        synchronized (lock) {
+            channelConnection = pendingActions.remove(localAddress);
+        }
+        if (channelConnection == null) {
+            throw new IllegalStateException(String.format(
+                    "Request to connect received for unknown address '%s'.", localAddress));
+        }
+        channelConnection.execute(new ConnectEvent<Connection<Message>>(connection, localAddress, connectEvent.getRemoteAddress()));
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnector.java
new file mode 100644
index 0000000..89b1569
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnector.java
@@ -0,0 +1,59 @@
+/*
+ * 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.apache.commons.lang.StringUtils;
+import org.gradle.util.UncheckedException;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+public class HandshakeOutgoingConnector implements OutgoingConnector {
+    private final OutgoingConnector connector;
+
+    public HandshakeOutgoingConnector(OutgoingConnector connector) {
+        this.connector = connector;
+    }
+
+    public Connection<Message> connect(URI destinationAddress) {
+        if (!destinationAddress.getScheme().equals("channel")) {
+            throw new IllegalArgumentException(String.format("Cannot create a connection to destination URI with unknown scheme: %s.",
+                    destinationAddress));
+        }
+        URI connectionAddress = toConnectionAddress(destinationAddress);
+        Connection<Message> connection = connector.connect(connectionAddress);
+        try {
+            connection.dispatch(new ConnectRequest(destinationAddress));
+        } catch (Throwable e) {
+            connection.stop();
+            throw UncheckedException.asUncheckedException(e);
+        }
+
+        return connection;
+    }
+
+    private URI toConnectionAddress(URI destinationAddress) {
+        String content = destinationAddress.getSchemeSpecificPart();
+        URI connectionAddress;
+        try {
+            connectionAddress = new URI(StringUtils.substringBeforeLast(content, "!"));
+        } catch (URISyntaxException e) {
+            throw new UncheckedException(e);
+        }
+        return connectionAddress;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingConnector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingConnector.java
new file mode 100644
index 0000000..7553624
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingConnector.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.messaging.remote.internal;
+
+import org.gradle.api.Action;
+import org.gradle.messaging.remote.ConnectEvent;
+
+import java.net.URI;
+
+public interface IncomingConnector {
+    /**
+     * Allocates a new incoming endpoint.
+     *
+     * @param action the action to execute on incoming connection.
+     * @return the address of the endpoint which the connector is listening on.
+     */
+    URI accept(Action<ConnectEvent<Connection<Message>>> action);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingMethodInvocationHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingMethodInvocationHandler.java
new file mode 100644
index 0000000..a3d0065
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/IncomingMethodInvocationHandler.java
@@ -0,0 +1,57 @@
+/*
+ * 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.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.dispatch.ReflectionDispatch;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+public class IncomingMethodInvocationHandler {
+    private final ClassLoader classLoader;
+    private final MultiChannelConnection<Message> connection;
+    private final Set<Class<?>> classes = new CopyOnWriteArraySet<Class<?>>();
+
+    public IncomingMethodInvocationHandler(ClassLoader classLoader, MultiChannelConnection<Message> connection) {
+        this.classLoader = classLoader;
+        this.connection = connection;
+    }
+
+    public <T> void addIncoming(Class<T> type, T instance) {
+        addIncoming(type, new ReflectionDispatch(instance));
+    }
+
+    public void addIncoming(Class<?> type, Dispatch<? super MethodInvocation> dispatch) {
+        Set<Class<?>> incomingTypes = new HashSet<Class<?>>();
+        addInterfaces(type, incomingTypes);
+        for (Class<?> incomingType : incomingTypes) {
+            if (!classes.add(incomingType)) {
+                throw new IllegalArgumentException(String.format("A handler has already been added for type '%s'.", incomingType.getName()));
+            }
+            connection.addIncomingChannel(incomingType.getName(), new MethodInvocationUnmarshallingDispatch(dispatch, classLoader));
+        }
+    }
+
+    private void addInterfaces(Class<?> type, Set<Class<?>> superInterfaces) {
+        superInterfaces.add(type);
+        for (Class<?> superType : type.getInterfaces()) {
+            addInterfaces(superType, superInterfaces);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/Message.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/Message.java
new file mode 100644
index 0000000..d938d9c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/Message.java
@@ -0,0 +1,137 @@
+/*
+ * 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.util.ClassLoaderObjectInputStream;
+import org.gradle.util.UncheckedException;
+
+import java.io.*;
+import java.lang.reflect.Constructor;
+
+public abstract class Message implements Serializable {
+    public void send(OutputStream outputSteam) throws IOException {
+        ObjectOutputStream oos = new ExceptionReplacingObjectOutputStream(outputSteam);
+        try {
+            oos.writeObject(this);
+        } finally {
+            oos.flush();
+        }
+    }
+
+    public static Message receive(InputStream inputSteam, ClassLoader classLoader)
+            throws IOException, ClassNotFoundException {
+        ObjectInputStream ois = new ExceptionReplacingObjectInputStream(inputSteam, classLoader);
+        return (Message) 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(Throwable throwable) throws IOException {
+            ByteArrayOutputStream outstr = new ByteArrayOutputStream();
+            ObjectOutputStream oos = new ObjectOutputStream(outstr);
+            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 {
+            if (serializedException != null) {
+                try {
+                    return (Throwable) new ClassLoaderObjectInputStream(new ByteArrayInputStream(serializedException),
+                            classLoader).readObject();
+                } catch (ClassNotFoundException e) {
+                    // Ignore
+                } catch (InvalidClassException e) {
+                    try {
+                        Constructor<?> constructor = classLoader.loadClass(type).getConstructor(String.class);
+                        Throwable throwable = (Throwable) constructor.newInstance(message);
+                        throwable.initCause(getCause(classLoader));
+                        throwable.setStackTrace(stackTrace);
+                        return throwable;
+                    } catch (ClassNotFoundException e1) {
+                        // Ignore
+                    } catch (NoSuchMethodException e1) {
+                        // Ignore
+                    } catch (Throwable t) {
+                        throw UncheckedException.asUncheckedException(t);
+                    }
+                }
+            }
+
+            PlaceholderException exception = new PlaceholderException(String.format("%s: %s", type, message), getCause(
+                    classLoader));
+            exception.setStackTrace(stackTrace);
+            return exception;
+        }
+
+        private Throwable getCause(ClassLoader classLoader) throws IOException {
+            return cause != null ? cause.read(classLoader) : null;
+        }
+    }
+
+    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/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatch.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatch.java
new file mode 100644
index 0000000..47511ae
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatch.java
@@ -0,0 +1,44 @@
+/*
+ * 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.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MethodInvocationMarshallingDispatch implements Dispatch<MethodInvocation> {
+    private final Dispatch<? super Message> dispatch;
+    private final Map<Method, Integer> methods = new HashMap<Method, Integer>();
+    private int nextKey;
+
+    public MethodInvocationMarshallingDispatch(Dispatch<? super Message> dispatch) {
+        this.dispatch = dispatch;
+    }
+
+    public void dispatch(MethodInvocation methodInvocation) {
+        Method method = methodInvocation.getMethod();
+        Integer key = methods.get(method);
+        if (key == null) {
+            key = nextKey++;
+            methods.put(method, key);
+            dispatch.dispatch(new MethodMetaInfo(key, method));
+        }
+        dispatch.dispatch(new RemoteMethodInvocation(key, methodInvocation.getArguments()));
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatch.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatch.java
new file mode 100644
index 0000000..231aabb
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatch.java
@@ -0,0 +1,53 @@
+/*
+ * 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.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MethodInvocationUnmarshallingDispatch implements Dispatch<Message> {
+    private final Dispatch<? super MethodInvocation> dispatch;
+    private final ClassLoader classLoader;
+    private final Map<Object, Method> methods = new HashMap<Object, Method>();
+
+    public MethodInvocationUnmarshallingDispatch(Dispatch<? super MethodInvocation> dispatch, ClassLoader classLoader) {
+        this.dispatch = dispatch;
+        this.classLoader = classLoader;
+    }
+
+    public void dispatch(Message message) {
+        if (message instanceof MethodMetaInfo) {
+            MethodMetaInfo methodMetaInfo = (MethodMetaInfo) message;
+            Method method = methodMetaInfo.findMethod(classLoader);
+            methods.put(methodMetaInfo.getKey(), method);
+        } else if (message instanceof RemoteMethodInvocation) {
+            RemoteMethodInvocation remoteMethodInvocation = (RemoteMethodInvocation) message;
+            Method method = methods.get(remoteMethodInvocation.getKey());
+            if (method == null) {
+                throw new IllegalStateException("Received a method invocation message for an unknown method.");
+            }
+            MethodInvocation methodInvocation = new MethodInvocation(method,
+                    remoteMethodInvocation.getArguments());
+            dispatch.dispatch(methodInvocation);
+        } else {
+            throw new IllegalStateException(String.format("Received an unknown message %s.", message));
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MethodMetaInfo.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MethodMetaInfo.java
new file mode 100644
index 0000000..ec9483d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MethodMetaInfo.java
@@ -0,0 +1,114 @@
+/*
+ * 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.util.UncheckedException;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+public class MethodMetaInfo extends Message {
+    private final Type type;
+    private final String methodName;
+    private final Type[] paramTypes;
+    private final Object key;
+
+    public MethodMetaInfo(Object key, Method method) {
+        this.key = key;
+        type = new Type(method.getDeclaringClass());
+        methodName = method.getName();
+        paramTypes = new Type[method.getParameterTypes().length];
+        for (int i = 0; i < method.getParameterTypes().length; i++) {
+            Class<?> paramType = method.getParameterTypes()[i];
+            paramTypes[i] = new Type(paramType);
+        }
+    }
+
+    public Object getKey() {
+        return key;
+    }
+
+    public Method findMethod(ClassLoader classLoader) {
+        try {
+            Class<?> declaringClass = this.type.load(classLoader);
+            Class<?>[] paramTypes = new Class[this.paramTypes.length];
+            for (int i = 0; i < this.paramTypes.length; i++) {
+                Type paramType = this.paramTypes[i];
+                paramTypes[i] = paramType.load(classLoader);
+            }
+            return declaringClass.getMethod(methodName, paramTypes);
+        } catch (Exception e) {
+            throw UncheckedException.asUncheckedException(e);
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || obj.getClass() != getClass()) {
+            return false;
+        }
+
+        MethodMetaInfo other = (MethodMetaInfo) obj;
+        if (!key.equals(other.key)) {
+            return false;
+        }
+        if (!type.equals(other.type)) {
+            return false;
+        }
+        if (!methodName.equals(other.methodName)) {
+            return false;
+        }
+        return Arrays.equals(paramTypes, other.paramTypes);
+    }
+
+    @Override
+    public int hashCode() {
+        return key.hashCode();
+    }
+
+    private static class Type implements Serializable {
+        private String typeName;
+        private Class<?> type;
+
+        public Type(Class<?> type) {
+            this.typeName = type.getName();
+            if (type.isPrimitive()) {
+                this.type = type;
+            }
+        }
+
+        Class<?> load(ClassLoader classLoader) throws ClassNotFoundException {
+            if (type != null) {
+                return type;
+            }
+            return classLoader.loadClass(typeName);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return ((Type) obj).typeName.equals(typeName);
+        }
+
+        @Override
+        public int hashCode() {
+            return typeName.hashCode();
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnection.java
new file mode 100644
index 0000000..3c6de05
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/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.messaging.concurrent.AsyncStoppable;
+import org.gradle.messaging.dispatch.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(Object 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(Object 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/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnector.java
new file mode 100644
index 0000000..6f73440
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/MultiChannelConnector.java
@@ -0,0 +1,28 @@
+/*
+ * 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.messaging.remote.ConnectEvent;
+
+import java.net.URI;
+
+public interface MultiChannelConnector {
+    URI accept(Action<ConnectEvent<MultiChannelConnection<Message>>> action);
+
+    MultiChannelConnection<Message> connect(URI destinationAddress);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java
new file mode 100644
index 0000000..0647de2
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingConnector.java
@@ -0,0 +1,25 @@
+/*
+ * 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 java.net.URI;
+
+public interface OutgoingConnector {
+    /**
+     * Creates a connection to the given address.
+     */
+    Connection<Message> connect(URI destinationUri);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingMethodInvocationHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingMethodInvocationHandler.java
new file mode 100644
index 0000000..9ffec32
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/OutgoingMethodInvocationHandler.java
@@ -0,0 +1,47 @@
+/*
+ * 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.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.messaging.dispatch.ProxyDispatchAdapter;
+import org.gradle.messaging.dispatch.ThreadSafeDispatch;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class OutgoingMethodInvocationHandler {
+    private final Map<Class<?>, ProxyDispatchAdapter<?>> outgoing = new ConcurrentHashMap<Class<?>, ProxyDispatchAdapter<?>>();
+    private final MultiChannelConnection<Message> connection;
+
+    public OutgoingMethodInvocationHandler(MultiChannelConnection<Message> connection) {
+        this.connection = connection;
+    }
+
+    public <T> T addOutgoing(Class<T> type) {
+        ProxyDispatchAdapter<?> existing = outgoing.get(type);
+        if (existing != null) {
+            return type.cast(outgoing.get(type).getSource());
+        }
+
+        Dispatch<MethodInvocation> dispatch = new ThreadSafeDispatch<MethodInvocation>(
+                new MethodInvocationMarshallingDispatch(connection.addOutgoingChannel(type.getName())));
+        ProxyDispatchAdapter<T> adapter = new ProxyDispatchAdapter<T>(type, dispatch);
+        outgoing.put(type, adapter);
+        return adapter.getSource();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/PlaceholderException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/PlaceholderException.java
new file mode 100644
index 0000000..2376233
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/PlaceholderException.java
@@ -0,0 +1,25 @@
+/*
+ * 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 {
+    public PlaceholderException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/RemoteMethodInvocation.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/RemoteMethodInvocation.java
new file mode 100644
index 0000000..87e3490
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/RemoteMethodInvocation.java
@@ -0,0 +1,54 @@
+/*
+ * 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 java.util.Arrays;
+
+public class RemoteMethodInvocation extends Message {
+    private final Object key;
+    private final Object[] arguments;
+
+    public RemoteMethodInvocation(Object key, Object[] arguments) {
+        this.key = key;
+        this.arguments = arguments;
+    }
+
+    public Object getKey() {
+        return key;
+    }
+
+    public Object[] getArguments() {
+        return arguments;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+
+        RemoteMethodInvocation other = (RemoteMethodInvocation) obj;
+        return key.equals(other.key) && Arrays.equals(arguments, other.arguments);
+    }
+
+    @Override
+    public int hashCode() {
+        return key.hashCode();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/SocketConnection.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/SocketConnection.java
new file mode 100644
index 0000000..7e2fa3b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/SocketConnection.java
@@ -0,0 +1,209 @@
+/*
+ * 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;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.messaging.concurrent.CompositeStoppable;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.SocketChannel;
+
+public class SocketConnection implements Connection<Message> {
+    private final SocketChannel socket;
+    private final URI localAddress;
+    private final URI remoteAddress;
+    private final ClassLoader classLoader;
+    private final InputStream instr;
+    private final OutputStream outstr;
+
+    public SocketConnection(SocketChannel socket, URI localAddress, URI remoteAddress, ClassLoader classLoader) {
+        this.socket = socket;
+        this.localAddress = localAddress;
+        this.remoteAddress = remoteAddress;
+        this.classLoader = classLoader;
+        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 SocketOutputStream(socket);
+            instr = new SocketInputStream(socket);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return String.format("socket connection at %s with %s", localAddress, remoteAddress);
+    }
+
+    public Message receive() {
+        try {
+            return Message.receive(instr, classLoader);
+        } catch (Exception e) {
+            if (isEndOfStream(e)) {
+                return null;
+            }
+            throw new GradleException(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 && e.getMessage().equals("An existing connection was forcibly closed by the remote host")) {
+            return true;
+        }
+        return false;
+    }
+
+    public void dispatch(Message message) {
+        try {
+            message.send(outstr);
+            outstr.flush();
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not write message to '%s'.", 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) {
+                selector.select();
+                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/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpIncomingConnector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpIncomingConnector.java
new file mode 100644
index 0000000..bedc339
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpIncomingConnector.java
@@ -0,0 +1,108 @@
+/*
+ * 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.messaging.concurrent.AsyncStoppable;
+import org.gradle.messaging.concurrent.CompositeStoppable;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.gradle.messaging.concurrent.StoppableExecutor;
+import org.gradle.messaging.remote.ConnectEvent;
+import org.gradle.util.UncheckedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.URI;
+import java.nio.channels.AsynchronousCloseException;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class TcpIncomingConnector implements IncomingConnector, AsyncStoppable {
+    private static final Logger LOGGER = LoggerFactory.getLogger(TcpIncomingConnector.class);
+    private final StoppableExecutor executor;
+    private final ClassLoader classLoader;
+    private final List<InetAddress> localAddresses;
+    private final List<ServerSocketChannel> serverSockets = new CopyOnWriteArrayList<ServerSocketChannel>();
+
+    public TcpIncomingConnector(ExecutorFactory executorFactory, ClassLoader classLoader) {
+        this.executor = executorFactory.create("Incoming TCP Connector");
+        this.classLoader = classLoader;
+
+        localAddresses = TcpOutgoingConnector.findLocalAddresses();
+    }
+
+    public URI accept(Action<ConnectEvent<Connection<Message>>> action) {
+        ServerSocketChannel serverSocket;
+        URI localAddress;
+        try {
+            serverSocket = ServerSocketChannel.open();
+            serverSockets.add(serverSocket);
+            serverSocket.socket().bind(new InetSocketAddress(0));
+            localAddress = new URI(String.format("tcp://localhost:%d", serverSocket.socket().getLocalPort()));
+            LOGGER.debug("Listening on {}.", localAddress);
+        } catch (Exception e) {
+            throw UncheckedException.asUncheckedException(e);
+        }
+
+        executor.execute(new Receiver(serverSocket, localAddress, action));
+        return localAddress;
+    }
+
+    public void requestStop() {
+        new CompositeStoppable().addCloseables(serverSockets).stop();
+    }
+
+    public void stop() {
+        requestStop();
+        executor.stop();
+    }
+
+    private class Receiver implements Runnable {
+        private final ServerSocketChannel serverSocket;
+        private final URI localAddress;
+        private final Action<ConnectEvent<Connection<Message>>> action;
+
+        public Receiver(ServerSocketChannel serverSocket, URI localAddress, Action<ConnectEvent<Connection<Message>>> action) {
+            this.serverSocket = serverSocket;
+            this.localAddress = localAddress;
+            this.action = action;
+        }
+
+        public void run() {
+            try {
+                while (true) {
+                    SocketChannel socket = serverSocket.accept();
+                    InetSocketAddress remoteAddress = (InetSocketAddress) socket.socket().getRemoteSocketAddress();
+                    if (!localAddresses.contains(remoteAddress.getAddress())) {
+                        LOGGER.error("Cannot accept connection from remote address {}.", remoteAddress.getAddress());
+                    }
+                    URI remoteUri = new URI(String.format("tcp://localhost:%d", remoteAddress.getPort()));
+                    LOGGER.debug("Accepted connection from {}.", remoteUri);
+                    action.execute(new ConnectEvent<Connection<Message>>(new SocketConnection(socket, localAddress, remoteUri, classLoader), localAddress, remoteUri));
+                }
+            } catch (AsynchronousCloseException e) {
+                // Ignore
+            } catch (Exception e) {
+                LOGGER.error("Could not accept remote connection.", e);
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpMessagingClient.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpMessagingClient.java
new file mode 100644
index 0000000..2c1de4a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpMessagingClient.java
@@ -0,0 +1,55 @@
+/*
+ * 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.messaging.concurrent.CompositeStoppable;
+import org.gradle.messaging.concurrent.DefaultExecutorFactory;
+import org.gradle.messaging.remote.ConnectEvent;
+import org.gradle.messaging.remote.MessagingClient;
+import org.gradle.messaging.remote.ObjectConnection;
+
+import java.net.URI;
+
+/**
+ * A {@link org.gradle.messaging.remote.MessagingClient} which uses a single TCP connection with a server.
+ */
+public class TcpMessagingClient implements MessagingClient {
+    private final DefaultMultiChannelConnector connector;
+    private final DefaultMessagingClient client;
+    private final DefaultExecutorFactory executorFactory;
+
+    public TcpMessagingClient(ClassLoader messagingClassLoader, URI serverAddress) {
+        executorFactory = new DefaultExecutorFactory();
+        connector = new DefaultMultiChannelConnector(new TcpOutgoingConnector(messagingClassLoader), new NoOpIncomingConnector(), executorFactory);
+        client = new DefaultMessagingClient(connector, messagingClassLoader, serverAddress);
+    }
+
+    public ObjectConnection getConnection() {
+        return client.getConnection();
+    }
+
+    public void stop() {
+        new CompositeStoppable(client, connector, executorFactory).stop();
+    }
+
+    private static class NoOpIncomingConnector implements IncomingConnector {
+        public URI accept(Action<ConnectEvent<Connection<Message>>> action) {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpMessagingServer.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpMessagingServer.java
new file mode 100644
index 0000000..15743e5
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpMessagingServer.java
@@ -0,0 +1,58 @@
+/*
+ * 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.messaging.concurrent.CompositeStoppable;
+import org.gradle.messaging.concurrent.DefaultExecutorFactory;
+import org.gradle.messaging.remote.ConnectEvent;
+import org.gradle.messaging.remote.MessagingServer;
+import org.gradle.messaging.remote.ObjectConnection;
+
+import java.net.URI;
+
+/**
+ * A {@link org.gradle.messaging.remote.MessagingServer} implementation which uses a single incoming TCP port for all peers.
+ */
+public class TcpMessagingServer implements MessagingServer {
+    private final TcpIncomingConnector incomingConnector;
+    private final DefaultMultiChannelConnector connector;
+    private final DefaultMessagingServer server;
+    private final DefaultExecutorFactory executorFactory;
+
+    public TcpMessagingServer(ClassLoader messageClassLoader) {
+        executorFactory = new DefaultExecutorFactory();
+        incomingConnector = new TcpIncomingConnector(executorFactory, messageClassLoader);
+        connector = new DefaultMultiChannelConnector(new NoOpOutgoingConnector(), incomingConnector, executorFactory);
+        server = new DefaultMessagingServer(connector, messageClassLoader);
+    }
+
+    public URI accept(Action<ConnectEvent<ObjectConnection>> action) {
+        return server.accept(action);
+    }
+
+    public void stop() {
+        incomingConnector.requestStop();
+        new CompositeStoppable(server, connector, incomingConnector, executorFactory).stop();
+    }
+
+    private static class NoOpOutgoingConnector implements OutgoingConnector {
+        public Connection<Message> connect(URI destinationUri) {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpOutgoingConnector.java b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpOutgoingConnector.java
new file mode 100644
index 0000000..b60b8fa
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/messaging/remote/internal/TcpOutgoingConnector.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.GradleException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.*;
+import java.nio.channels.SocketChannel;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+public class TcpOutgoingConnector implements OutgoingConnector {
+    private static final Logger LOGGER = LoggerFactory.getLogger(TcpOutgoingConnector.class);
+    private ClassLoader classLoader;
+
+    public TcpOutgoingConnector(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
+
+    public Connection<Message> connect(URI destinationUri) {
+        if (!destinationUri.getScheme().equals("tcp") || !destinationUri.getHost().equals("localhost")) {
+            throw new IllegalArgumentException(String.format("Cannot create connection to destination URI '%s'.",
+                    destinationUri));
+        }
+
+        // Find all loop back addresses. 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> loopBackAddresses = findLocalAddresses();
+
+        // Now try each address
+        try {
+            SocketException lastFailure = null;
+            for (InetAddress address : loopBackAddresses) {
+                LOGGER.debug("Trying to connect to address {}.", address);
+                SocketChannel socketChannel;
+                try {
+                    socketChannel = SocketChannel.open(new InetSocketAddress(address, destinationUri.getPort()));
+                } catch (SocketException e) {
+                    LOGGER.debug("Cannot connect to address {}, skipping.", address);
+                    lastFailure = e;
+                    continue;
+                }
+                LOGGER.debug("Connected to address {}.", address);
+                URI localAddress = new URI(String.format("tcp://localhost:%d", socketChannel.socket().getLocalPort()));
+                return new SocketConnection(socketChannel, localAddress, destinationUri, classLoader);
+            }
+            throw lastFailure;
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not connect to server %s. Tried addresses: %s.",
+                    destinationUri, loopBackAddresses), e);
+        }
+    }
+
+    /**
+     * Never returns an empty list.
+     */
+    static 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 inetAddress = candidates.nextElement();
+                    if (inetAddress.isLoopbackAddress()) {
+                        addresses.add(inetAddress);
+                    }
+                }
+            }
+            if (addresses.isEmpty()) {
+                addresses.add(InetAddress.getByName(null));
+            }
+            LOGGER.debug("Found loop-back addresses: {}.", addresses);
+            return addresses;
+        } catch (Exception e) {
+            throw new GradleException("Could not determine the local loop-back addresses.", e);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/package-info.java b/subprojects/gradle-core/src/main/groovy/org/gradle/package-info.java
new file mode 100644
index 0000000..c19d4fb
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 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.
+ */
+
+/**
+ * Classes for embedding Gradle.
+ */
+package org.gradle;
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/BaseExecSpec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/BaseExecSpec.java
new file mode 100644
index 0000000..b5c4d58
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/BaseExecSpec.java
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public interface BaseExecSpec extends ProcessForkOptions {
+    /**
+     * Sets whether an exit value different from zero should be ignored. In case it is not ignored, an exception is
+     * thrown in case of such an exit value.
+     *
+     * @param ignoreExitValue whether to ignore the exit value or not
+     *
+     * @return this
+     */
+    BaseExecSpec setIgnoreExitValue(boolean ignoreExitValue);
+
+    /**
+     * Returns whether an exit value different from zero should be ignored. In case it is not ignored, an exception is
+     * thrown in case of such an exit value. Defaults to <code>false</code>.
+     */
+    boolean isIgnoreExitValue();
+
+    /**
+     * Sets the standard input stream for the process executing the command.
+     * 
+     * @param inputStream The standard input stream for the command process.
+     *
+     * @return this
+     */
+    BaseExecSpec setStandardInput(InputStream inputStream);
+
+    /**
+     * Returns the standard input stream for the process executing the command.
+     */
+    InputStream getStandardInput();
+
+    /**
+     * Sets the standard output stream for the process executing the command.
+     *
+     * @param outputStream The standard output stream for the command process.
+     *
+     * @return this
+     */
+    BaseExecSpec setStandardOutput(OutputStream outputStream);
+
+    /**
+     * Sets the error output stream for the process executing the command.
+     *
+     * @param outputStream The standard output error stream for the command process.
+     *
+     * @return this
+     */
+    BaseExecSpec setErrorOutput(OutputStream outputStream);
+
+    /**
+     * Returns the command plus its arguments.
+     */
+    List<String> getCommandLine();
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/ExecResult.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/ExecResult.java
new file mode 100644
index 0000000..fd43fa0
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/ExecResult.java
@@ -0,0 +1,47 @@
+/*
+ * 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;
+
+import org.gradle.process.internal.ExecException;
+
+/**
+ * Represent the result of running an external process.
+ *
+ * @author Hans Dockter
+ */
+public interface ExecResult {
+    /**
+     * Return the exit value of the process.
+     */
+    int getExitValue();
+
+    /**
+     * Throws an {@link org.gradle.process.internal.ExecException} if the process did not exit with a 0 exit value.
+     *
+     * @return this
+     * @throws ExecException On non-zero exit value.
+     */
+    ExecResult assertNormalExitValue() throws ExecException;
+
+    /**
+     * Re-throws any failure executing this process.
+     *
+     * @return this
+     * @throws ExecException The execution failure
+     */
+    ExecResult rethrowFailure() throws ExecException;
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/ExecSpec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/ExecSpec.java
new file mode 100644
index 0000000..7d0ada5
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/ExecSpec.java
@@ -0,0 +1,71 @@
+/*
+ * 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;
+
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ExecSpec extends BaseExecSpec {
+    /**
+     * Sets the command plus the args to be executed.
+     * @param args the command plus the args to be executed
+     *
+     * @return this
+     */
+    ExecSpec commandLine(Object... args);
+
+    /**
+     * Sets the command plus the args to be executed.
+     * @param args the command plus the args to be executed
+     *
+     * @return this
+     */
+    ExecSpec commandLine(Iterable<?> args);
+
+    /**
+     * Adds args for the command to be executed.
+     *
+     * @param args args for the command
+     *
+     * @return this
+     */
+    ExecSpec args(Object... args);
+
+    /**
+     * Adds args for the command to be executed.
+     *
+     * @param args args for the command
+     *
+     * @return this
+     */
+    ExecSpec args(Iterable<?> args);
+
+    /**
+     * Sets the args for the command to be executed.
+     *
+     * @param args args for the command
+     *
+     * @return this
+     */
+    ExecSpec setArgs(Iterable<?> args);
+
+    /**
+     * Returns the args for the command to be executed
+     */
+    List<String> getArgs();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/JavaExecSpec.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/JavaExecSpec.java
new file mode 100644
index 0000000..da302c6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/JavaExecSpec.java
@@ -0,0 +1,94 @@
+/*
+ * 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;
+
+import org.gradle.api.file.FileCollection;
+
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public interface JavaExecSpec extends JavaForkOptions, BaseExecSpec {
+    /**
+     * Returns the fully qualified name of the Main class to be executed.
+     */
+    String getMain();
+
+    /**
+     * Sets the fully qualified name of the main class to be executed.
+     *
+     * @param main the fully qualified name of the main class to be executed.
+     *
+     * @return this
+     */
+    JavaExecSpec setMain(String main);
+
+    /**
+     * Returns the arguments passed to the main class to be executed.
+     */
+    List<String> getArgs();
+
+    /**
+     * Adds args for the main class to be executed.
+     *
+     * @param args Args for the main class.
+     *
+     * @return this
+     */
+    JavaExecSpec args(Object... args);
+
+    /**
+     * Adds args for the main class to be executed.
+     *
+     * @param args Args for the main class.
+     *
+     * @return this
+     */
+    JavaExecSpec args(Iterable<?> args);
+
+    /**
+     * Sets the args for the main class to be executed.
+     *
+     * @param args Args for the main class.
+     *
+     * @return this
+     */
+    JavaExecSpec setArgs(Iterable<?> args);
+
+    /**
+     * Adds elements to the classpath for executing the main class.
+     *
+     * @param paths classpath elements
+     *
+     * @return this
+     */
+    JavaExecSpec classpath(Object... paths);
+
+    /**
+     * Returns the classpath for executing the main class.
+     */
+    FileCollection getClasspath();
+
+    /**
+     * Sets the classpath for executing the main class.
+     *
+     * @param classpath the classpath
+     *
+     * @return this
+     */
+    JavaExecSpec setClasspath(FileCollection classpath);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/JavaForkOptions.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/JavaForkOptions.java
new file mode 100644
index 0000000..42d24b2
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/JavaForkOptions.java
@@ -0,0 +1,182 @@
+/*
+ * 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;
+
+import org.gradle.api.file.FileCollection;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>Specifies the options to use to fork a Java process.</p>
+ */
+public interface JavaForkOptions extends ProcessForkOptions {
+    /**
+     * Returns the system properties which will be used for the process.
+     *
+     * @return The system properties. Returns an empty map when there are no system properties.
+     */
+    Map<String, Object> getSystemProperties();
+
+    /**
+     * Sets the system properties to use for the process.
+     *
+     * @param properties The system properties. Must not be null.
+     */
+    void setSystemProperties(Map<String, ?> properties);
+
+    /**
+     * Adds some system properties to use for the process.
+     *
+     * @param properties The system properties. Must not be null.
+     * @return this
+     */
+    JavaForkOptions systemProperties(Map<String, ?> properties);
+
+    /**
+     * Adds a system property to use for the process.
+     *
+     * @param name The name of the property
+     * @param value The value for the property. May be null.
+     * @return this
+     */
+    JavaForkOptions systemProperty(String name, Object value);
+
+    /**
+     * Returns the maximum heap size for the process, if any.
+     *
+     * @return The maximum heap size. Returns null if the default maximum heap size should be used.
+     */
+    String getMaxHeapSize();
+
+    /**
+     * Sets the maximum heap size for the process.
+     *
+     * @param heapSize The heap size. Use null for the default maximum heap size.
+     */
+    void setMaxHeapSize(String heapSize);
+
+    /**
+     * Returns the extra arguments to use to launch the JVM for the process. Does not include system properties and the
+     * maximum heap size.
+     *
+     * @return The arguments. Returns an empty list if there are no arguments.
+     */
+    List<String> getJvmArgs();
+
+    /**
+     * Sets the extra arguments to use to launch the JVM for the process. System properties and maximum heap size are
+     * updated.
+     *
+     * @param arguments The arguments. Must not be null.
+     */
+    void setJvmArgs(Iterable<?> arguments);
+
+    /**
+     * Adds some arguments to use to launch the JVM for the process.
+     *
+     * @param arguments The arguments. Must not be null.
+     * @return this
+     */
+    JavaForkOptions jvmArgs(Iterable<?> arguments);
+
+    /**
+     * Adds some arguments to use to launch the JVM for the process.
+     *
+     * @param arguments The arguments.
+     * @return this
+     */
+    JavaForkOptions jvmArgs(Object... arguments);
+
+    /**
+     * Returns the bootstrap classpath to use for the process. The default bootstrap classpath for the JVM is used when
+     * this classpath is empty.
+     *
+     * @return The bootstrap classpath. Never returns null.
+     */
+    FileCollection getBootstrapClasspath();
+
+    /**
+     * Sets the bootstrap classpath to use for the process. Set to an empty classpath to use the default bootstrap
+     * classpath for the specified JVM.
+     *
+     * @param classpath The classpath. Must not be null. Can be empty.
+     */
+    void setBootstrapClasspath(FileCollection classpath);
+
+    /**
+     * Adds the given values to the end of the bootstrap classpath for the process.
+     *
+     * @param classpath The classpath.
+     * @return this
+     */
+    JavaForkOptions bootstrapClasspath(Object... classpath);
+
+    /**
+     * Returns true if assertions are enabled for the process.
+     *
+     * @return true if assertions are enabled, false if disabled
+     */
+    boolean getEnableAssertions();
+
+    /**
+     * Enable or disable assertions for the process.
+     *
+     * @param enabled true to enable assertions, false to disable.
+     */
+    void setEnableAssertions(boolean enabled);
+
+    /**
+     * Returns true if debugging is enabled for the process. When enabled, the process is started suspended and
+     * listening on port 5005.
+     *
+     * @return true when debugging is enabled, false to disable.
+     */
+    boolean getDebug();
+
+    /**
+     * Enable or disable debugging for the process. When enabled, the process is started suspended and listening on port
+     * 5005.
+     *
+     * @param enabled true to enable debugging, false to disable.
+     */
+    void setDebug(boolean enabled);
+
+    /**
+     * Returns the full set of arguments to use to launch the JVM for the process. This includes arguments to define
+     * system properties, the maximum heap size, and the bootstrap classpath.
+     *
+     * @return The arguments. Returns an empty list if there are no arguments.
+     */
+    List<String> getAllJvmArgs();
+
+    /**
+     * Sets the full set of arguments to use to launch the JVM for the process. Overwrites any previously set system
+     * properties, maximum heap size, assertions, and bootstrap classpath.
+     *
+     * @param arguments The arguments. Must not be null.
+     */
+    void setAllJvmArgs(Iterable<?> arguments);
+
+    /**
+     * Copies these options to the given options.
+     *
+     * @param options The target options.
+     * @return this
+     */
+    JavaForkOptions copyTo(JavaForkOptions options);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/ProcessForkOptions.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/ProcessForkOptions.java
new file mode 100644
index 0000000..3b1075c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/ProcessForkOptions.java
@@ -0,0 +1,109 @@
+/*
+ * 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;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * <p>Specifies the options to use to fork a process.</p>
+ */
+public interface ProcessForkOptions {
+    /**
+     * Returns the name of the executable to use.
+     *
+     * @return The executable.
+     */
+    String getExecutable();
+
+    /**
+     * Sets the name of the executable to use.
+     *
+     * @param executable The executable. Must not be null.
+     */
+    void setExecutable(Object executable);
+
+    /**
+     * Sets the name of the executable to use.
+     *
+     * @param executable The executable. Must not be null.
+     * @return this
+     */
+    ProcessForkOptions executable(Object executable);
+
+    /**
+     * Returns the working directory for the process. Defaults to the project directory.
+     *
+     * @return The working directory. Never returns null.
+     */
+    File getWorkingDir();
+
+    /**
+     * Sets the working directory for the process. The supplied argument is evaluated as for {@link
+     * org.gradle.api.Project#file(Object)}.
+     *
+     * @param dir The working directory. Must not be null.
+     */
+    void setWorkingDir(Object dir);
+
+    /**
+     * Sets the working directory for the process. The supplied argument is evaluated as for {@link
+     * org.gradle.api.Project#file(Object)}.
+     *
+     * @param dir The working directory. Must not be null.
+     * @return this
+     */
+    ProcessForkOptions workingDir(Object dir);
+
+    /**
+     * The environment variables to use for the process. Defaults to the environment of this process.
+     *
+     * @return The environment. Returns an empty map when there are no environment variables.
+     */
+    Map<String, Object> getEnvironment();
+
+    /**
+     * Sets the environment variable to use for the process.
+     *
+     * @param environmentVariables The environment variables. Must not be null.
+     */
+    void setEnvironment(Map<String, ?> environmentVariables);
+
+    /**
+     * Adds some environment variables to the environment for this process.
+     *
+     * @param environmentVariables The environment variables. Must not be null.
+     * @return this
+     */
+    ProcessForkOptions environment(Map<String, ?> environmentVariables);
+
+    /**
+     * Adds an environment variable to the environment for this process.
+     *
+     * @param name The name of the variable.
+     * @param value The value for the variable. Must not be null.
+     * @return this
+     */
+    ProcessForkOptions environment(String name, Object value);
+
+    /**
+     * Copies these options to the given target options.
+     *
+     * @param options The target options
+     * @return this
+     */
+    ProcessForkOptions copyTo(ProcessForkOptions options);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/AbstractExecHandleBuilder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/AbstractExecHandleBuilder.java
new file mode 100644
index 0000000..ac4610d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/AbstractExecHandleBuilder.java
@@ -0,0 +1,112 @@
+/*
+ * 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.lang.StringUtils;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.process.BaseExecSpec;
+import org.gradle.util.GUtil;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractExecHandleBuilder extends DefaultProcessForkOptions implements BaseExecSpec {
+    private OutputStream standardOutput;
+    private OutputStream errorOutput;
+    private InputStream input = new ByteArrayInputStream(new byte[0]);
+    private String displayName;
+    private final List<ExecHandleListener> listeners = new ArrayList<ExecHandleListener>();
+    boolean ignoreExitValue;
+
+    public AbstractExecHandleBuilder(FileResolver fileResolver) {
+        super(fileResolver);
+        standardOutput = System.out;
+        errorOutput = System.err;
+    }
+
+    public abstract List<String> getAllArguments();
+
+    public List<String> getCommandLine() {
+        return GUtil.addLists(Collections.singleton(getExecutable()), getAllArguments());
+    }
+
+    public AbstractExecHandleBuilder setStandardInput(InputStream inputStream) {
+        this.input = inputStream;
+        return this;
+    }
+
+    public InputStream getStandardInput() {
+        return input;
+    }
+
+    public AbstractExecHandleBuilder setStandardOutput(OutputStream outputStream) {
+        if (outputStream == null) {
+            throw new IllegalArgumentException("outputStream == null!");
+        }
+        this.standardOutput = outputStream;
+        return this;
+    }
+
+    public AbstractExecHandleBuilder setErrorOutput(OutputStream outputStream) {
+        if (outputStream == null) {
+            throw new IllegalArgumentException("outputStream == null!");
+        }
+        this.errorOutput = outputStream;
+        return this;
+    }
+
+    public boolean isIgnoreExitValue() {
+        return ignoreExitValue;
+    }
+
+    public BaseExecSpec setIgnoreExitValue(boolean ignoreExitValue) {
+        this.ignoreExitValue = ignoreExitValue;
+        return this;
+    }
+
+    public String getDisplayName() {
+        return displayName == null ? String.format("command '%s'", getExecutable()) : displayName;
+    }
+
+    public void setDisplayName(String displayName) {
+        this.displayName = displayName;
+    }
+
+    public AbstractExecHandleBuilder listener(ExecHandleListener listener) {
+        if (listeners == null) {
+            throw new IllegalArgumentException("listeners == null!");
+        }
+        this.listeners.add(listener);
+        return this;
+    }
+    
+    public ExecHandle build() {
+        String executable = getExecutable();
+        if (StringUtils.isEmpty(executable)) {
+            throw new IllegalStateException("execCommand == null!");
+        }
+
+        return new DefaultExecHandle(getDisplayName(), getWorkingDir(), executable, getAllArguments(), getActualEnvironment(),
+                standardOutput, errorOutput, input, listeners);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/BadExitCodeException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/BadExitCodeException.java
new file mode 100644
index 0000000..2a2f17b
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/BadExitCodeException.java
@@ -0,0 +1,25 @@
+/*
+ * 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.process.internal;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class BadExitCodeException extends Exception {
+    public BadExitCodeException(String message) {
+        super(message);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultExecAction.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultExecAction.java
new file mode 100644
index 0000000..86ef6fc
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultExecAction.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.process.internal;
+
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.IdentityFileResolver;
+import org.gradle.process.ExecResult;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultExecAction extends ExecHandleBuilder implements ExecAction {
+    public DefaultExecAction(FileResolver fileResolver) {
+        super(fileResolver);
+    }
+
+    public DefaultExecAction() {
+        super(new IdentityFileResolver());
+    }
+
+    public ExecResult execute() {
+        ExecHandle execHandle = build();
+        ExecResult execResult = execHandle.start().waitForFinish();
+        if (!isIgnoreExitValue()) {
+            execResult.assertNormalExitValue();
+        }
+        return execResult;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultExecHandle.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultExecHandle.java
new file mode 100644
index 0000000..9e142c6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultExecHandle.java
@@ -0,0 +1,347 @@
+/*
+ * 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.lang.StringUtils;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.listener.AsyncListenerBroadcast;
+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.util.UncheckedException;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.*;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Default implementation for the ExecHandle interface.
+ *
+ * <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>
+ * </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>
+ * </ul>
+ *
+ * @author Tom Eyckmans
+ */
+public class DefaultExecHandle implements ExecHandle {
+    private static final Logger LOGGER = Logging.getLogger(DefaultExecHandle.class);
+    private final String displayName;
+    /**
+     * The working directory of the process.
+     */
+    private final File directory;
+    /**
+     * The executable to run.
+     */
+    private final String command;
+    /**
+     * Arguments to pass to the executable.
+     */
+    private final List<String> arguments;
+
+    /**
+     * 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;
+
+    /**
+     * Lock to guard all mutable state
+     */
+    private final Lock lock;
+
+    private final Condition stateChange;
+
+    private final StoppableExecutor executor;
+
+    /**
+     * State of this ExecHandle.
+     */
+    private ExecHandleState state;
+
+    /**
+     * When not null, the runnable that is waiting
+     */
+    private ExecHandleRunner execHandleRunner;
+
+    private ExecResultImpl execResult;
+
+    private final ListenerBroadcast<ExecHandleListener> broadcast;
+
+    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) {
+        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.lock = new ReentrantLock();
+        this.stateChange = 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.addAll(listeners);
+    }
+
+    public File getDirectory() {
+        return directory;
+    }
+
+    public String getCommand() {
+        return command;
+    }
+
+    @Override
+    public String toString() {
+        return displayName;
+    }
+
+    public List<String> getArguments() {
+        return Collections.unmodifiableList(arguments);
+    }
+
+    public Map<String, String> getEnvironment() {
+        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 {
+            return state;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void setState(ExecHandleState state) {
+        lock.lock();
+        try {
+            this.state = state;
+            stateChange.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private boolean stateIn(ExecHandleState... states) {
+        lock.lock();
+        try {
+            return Arrays.asList(states).contains(this.state);
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void setEndStateInfo(ExecHandleState state, int exitCode, Throwable failureCause) {
+        ShutdownHookActionRegister.removeAction(shutdownHookAction);
+
+        ExecResultImpl result;
+        lock.lock();
+        try {
+            ExecException wrappedException = null;
+            if (failureCause != null) {
+                if (this.state == ExecHandleState.STARTING) {
+                    wrappedException = new ExecException(String.format("A problem occurred starting %s.",
+                            displayName), failureCause);
+                } else {
+                    wrappedException = new ExecException(String.format(
+                            "A problem occurred waiting for %s to complete.", displayName), failureCause);
+                }
+            }
+            setState(state);
+            execResult = new ExecResultImpl(exitCode, wrappedException);
+            result = execResult;
+        } finally {
+            lock.unlock();
+        }
+
+        LOGGER.debug("Process finished for {}.", displayName);
+
+        broadcast.getSource().executionFinished(this, result);
+        broadcast.stop();
+        executor.requestStop();
+    }
+
+    public ExecHandle start() {
+        lock.lock();
+        try {
+            if (!stateIn(ExecHandleState.INIT)) {
+                throw new IllegalStateException("already started!");
+            }
+            setState(ExecHandleState.STARTING);
+
+            execResult = null;
+
+            execHandleRunner = new ExecHandleRunner(this, executor);
+
+            executor.execute(execHandleRunner);
+
+            while (getState() == ExecHandleState.STARTING) {
+                try {
+                    stateChange.await();
+                } catch (InterruptedException e) {
+                    throw new UncheckedException(e);
+                }
+            }
+
+            if (execResult != null) {
+                execResult.rethrowFailure();
+            }
+
+            LOGGER.debug("Started {}.", displayName);
+        } finally {
+            lock.unlock();
+        }
+        return this;
+    }
+
+    public void abort() {
+        lock.lock();
+        try {
+            if (state == ExecHandleState.SUCCEEDED) {
+                return;
+            }
+            if (!stateIn(ExecHandleState.STARTED)) {
+                throw new IllegalStateException("not in started state!");
+            }
+            this.execHandleRunner.stopWaiting();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public ExecResult waitForFinish() {
+        executor.stop();
+
+        lock.lock();
+        try {
+            execResult.rethrowFailure();
+            return execResult;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    void started() {
+        ShutdownHookActionRegister.addAction(shutdownHookAction);
+
+        setState(ExecHandleState.STARTED);
+        broadcast.getSource().executionStarted(this);
+    }
+
+    void finished(int exitCode) {
+        if (exitCode != 0) {
+            setEndStateInfo(ExecHandleState.FAILED, exitCode, null);
+        } else {
+            setEndStateInfo(ExecHandleState.SUCCEEDED, 0, null);
+        }
+    }
+
+    void aborted(int exitCode) {
+        if (exitCode == 0) {
+            // This can happen on windows
+            exitCode = -1;
+        }
+        setEndStateInfo(ExecHandleState.ABORTED, exitCode, null);
+    }
+
+    void failed(Throwable failureCause) {
+        setEndStateInfo(ExecHandleState.FAILED, -1, failureCause);
+    }
+
+    public void addListener(ExecHandleListener listener) {
+        broadcast.add(listener);
+    }
+
+    public void removeListener(ExecHandleListener listener) {
+        broadcast.remove(listener);
+    }
+
+    private class ExecResultImpl implements ExecResult {
+        private final int exitValue;
+        private final ExecException failure;
+
+        public ExecResultImpl(int exitValue, ExecException failure) {
+            this.exitValue = exitValue;
+            this.failure = failure;
+        }
+
+        public int getExitValue() {
+            return exitValue;
+        }
+
+        public ExecResult assertNormalExitValue() throws ExecException {
+            if (exitValue != 0) {
+                throw new ExecException(String.format("%s finished with non-zero exit value.", StringUtils.capitalize(displayName)));
+            }
+            return this;
+        }
+
+        public ExecResult rethrowFailure() throws ExecException {
+            if (failure != null) {
+                throw failure;
+            }
+            return this;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultJavaExecAction.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultJavaExecAction.java
new file mode 100644
index 0000000..0dcae9a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultJavaExecAction.java
@@ -0,0 +1,38 @@
+/*
+ * 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.process.ExecResult;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultJavaExecAction extends JavaExecHandleBuilder implements JavaExecAction {
+    public DefaultJavaExecAction(FileResolver fileResolver) {
+        super(fileResolver);
+    }
+
+    public ExecResult execute() {
+        ExecHandle execHandle = build();
+        ExecResult execResult = execHandle.start().waitForFinish();
+        if (!isIgnoreExitValue()) {
+            execResult.assertNormalExitValue();
+        }
+        return execResult;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultJavaForkOptions.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultJavaForkOptions.java
new file mode 100644
index 0000000..16b6e48
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultJavaForkOptions.java
@@ -0,0 +1,229 @@
+/*
+ * 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.file.FileCollection;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.PathResolvingFileCollection;
+import org.gradle.process.JavaForkOptions;
+import org.gradle.util.Jvm;
+
+import java.io.File;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class DefaultJavaForkOptions extends DefaultProcessForkOptions implements JavaForkOptions {
+    private final Pattern sysPropPattern = Pattern.compile("-D(.+?)=(.*)");
+    private final Pattern noArgSysPropPattern = Pattern.compile("-D([^=]+)");
+    private final Pattern maxHeapPattern = Pattern.compile("-Xmx(.+)");
+    private final Pattern bootstrapPattern = Pattern.compile("-Xbootclasspath:(.+)");
+    private final List<Object> extraJvmArgs = new ArrayList<Object>();
+    private final Map<String, Object> systemProperties = new TreeMap<String, Object>();
+    private FileCollection bootstrapClasspath;
+    private String maxHeapSize;
+    private boolean assertionsEnabled;
+    private boolean debug;
+
+    public DefaultJavaForkOptions(FileResolver resolver) {
+        this(resolver, Jvm.current());
+    }
+
+    public DefaultJavaForkOptions(FileResolver resolver, Jvm jvm) {
+        super(resolver);
+        this.bootstrapClasspath = new PathResolvingFileCollection(resolver, null);
+        setExecutable(jvm.getJavaExecutable());
+    }
+
+    public List<String> getAllJvmArgs() {
+        List<String> args = new ArrayList<String>();
+        args.addAll(getJvmArgs());
+        for (Map.Entry<String, Object> entry : getSystemProperties().entrySet()) {
+            if (entry.getValue() != null) {
+                args.add(String.format("-D%s=%s", entry.getKey(), entry.getValue().toString()));
+            } else {
+                args.add(String.format("-D%s", entry.getKey()));
+            }
+        }
+        if (maxHeapSize != null) {
+            args.add(String.format("-Xmx%s", maxHeapSize));
+        }
+        FileCollection bootstrapClasspath = getBootstrapClasspath();
+        if (!bootstrapClasspath.isEmpty()) {
+            args.add(String.format("-Xbootclasspath:%s", bootstrapClasspath.getAsPath()));
+        }
+        if (assertionsEnabled) {
+            args.add("-ea");
+        }
+        if (debug) {
+            args.add("-Xdebug");
+            args.add("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005");
+        }
+        return args;
+    }
+
+    public void setAllJvmArgs(Iterable<?> arguments) {
+        systemProperties.clear();
+        maxHeapSize = null;
+        extraJvmArgs.clear();
+        assertionsEnabled = false;
+        jvmArgs(arguments);
+    }
+
+    public List<String> getJvmArgs() {
+        List<String> args = new ArrayList<String>();
+        for (Object extraJvmArg : extraJvmArgs) {
+            args.add(extraJvmArg.toString());
+        }
+        return args;
+    }
+
+    public void setJvmArgs(Iterable<?> arguments) {
+        extraJvmArgs.clear();
+        jvmArgs(arguments);
+    }
+
+    public JavaForkOptions jvmArgs(Iterable<?> arguments) {
+        for (Object argument : arguments) {
+            String argStr = argument.toString();
+            Matcher matcher = sysPropPattern.matcher(argStr);
+            if (matcher.matches()) {
+                systemProperties.put(matcher.group(1), matcher.group(2));
+                continue;
+            }
+            matcher = noArgSysPropPattern.matcher(argStr);
+            if (matcher.matches()) {
+                systemProperties.put(matcher.group(1), null);
+                continue;
+            }
+            matcher = maxHeapPattern.matcher(argStr);
+            if (matcher.matches()) {
+                maxHeapSize = matcher.group(1);
+                continue;
+            }
+            matcher = bootstrapPattern.matcher(argStr);
+            if (matcher.matches()) {
+                setBootstrapClasspath(getResolver().resolveFiles((Object[]) matcher.group(1).split(Pattern.quote(File.pathSeparator))));
+                continue;
+            }
+            if (argStr.equals("-ea") || argStr.equals("-enableassertions")) {
+                assertionsEnabled = true;
+                continue;
+            }
+            if (argStr.equals("-da") || argStr.equals("-disableassertions")) {
+                assertionsEnabled = false;
+                continue;
+            }
+
+            extraJvmArgs.add(argument);
+        }
+        
+        boolean xdebugFound = false;
+        boolean xrunjdwpFound = false;
+        Set<Object> matches = new HashSet<Object>();
+        for (Object extraJvmArg : extraJvmArgs) {
+            if (extraJvmArg.toString().equals("-Xdebug")) {
+                xdebugFound = true;
+                matches.add(extraJvmArg);
+            }
+            else if (extraJvmArg.toString().equals("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005")) {
+                xrunjdwpFound = true;
+                matches.add(extraJvmArg);
+            }
+        }
+        if (xdebugFound && xrunjdwpFound) {
+            debug = true;
+            extraJvmArgs.removeAll(matches);
+        }else {
+            debug = false;
+        }
+
+        return this;
+    }
+
+    public JavaForkOptions jvmArgs(Object... arguments) {
+        jvmArgs(Arrays.asList(arguments));
+        return this;
+    }
+
+    public Map<String, Object> getSystemProperties() {
+        return systemProperties;
+    }
+
+    public void setSystemProperties(Map<String, ?> properties) {
+        systemProperties.clear();
+        systemProperties.putAll(properties);
+    }
+
+    public JavaForkOptions systemProperties(Map<String, ?> properties) {
+        systemProperties.putAll(properties);
+        return this;
+    }
+
+    public JavaForkOptions systemProperty(String name, Object value) {
+        systemProperties.put(name, value);
+        return this;
+    }
+
+    public FileCollection getBootstrapClasspath() {
+        return bootstrapClasspath;
+    }
+
+    public void setBootstrapClasspath(FileCollection classpath) {
+        this.bootstrapClasspath = classpath;
+    }
+
+    public JavaForkOptions bootstrapClasspath(Object... classpath) {
+        this.bootstrapClasspath = this.bootstrapClasspath.plus(getResolver().resolveFiles(classpath));
+        return this;
+    }
+
+    public String getMaxHeapSize() {
+        return maxHeapSize;
+    }
+
+    public void setMaxHeapSize(String heapSize) {
+        this.maxHeapSize = heapSize;
+    }
+
+    public boolean getEnableAssertions() {
+        return assertionsEnabled;
+    }
+
+    public void setEnableAssertions(boolean enabled) {
+        assertionsEnabled = enabled;
+    }
+
+    public boolean getDebug() {
+        return debug;
+    }
+
+    public void setDebug(boolean enabled) {
+        debug = enabled;
+    }
+
+    public JavaForkOptions copyTo(JavaForkOptions target) {
+        super.copyTo(target);
+        target.setJvmArgs(extraJvmArgs);
+        target.setSystemProperties(systemProperties);
+        target.setMaxHeapSize(maxHeapSize);
+        target.setBootstrapClasspath(bootstrapClasspath);
+        target.setEnableAssertions(assertionsEnabled);
+        target.setDebug(debug);
+        return this;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultProcessForkOptions.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultProcessForkOptions.java
new file mode 100644
index 0000000..a7dd019
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultProcessForkOptions.java
@@ -0,0 +1,100 @@
+/*
+ * 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.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>(System.getenv());
+
+    public DefaultProcessForkOptions(FileResolver resolver) {
+        this.resolver = resolver;
+        workingDir = resolver.resolveLater(new File(".").getAbsoluteFile());
+    }
+
+    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;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcess.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcess.java
new file mode 100644
index 0000000..39b3ce1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcess.java
@@ -0,0 +1,149 @@
+/*
+ * 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.Action;
+import org.gradle.messaging.remote.ConnectEvent;
+import org.gradle.messaging.remote.ObjectConnection;
+import org.gradle.process.ExecResult;
+import org.gradle.util.UncheckedException;
+
+import java.util.Date;
+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 DefaultWorkerProcess implements WorkerProcess {
+    private final Lock lock = new ReentrantLock();
+    private final Condition condition = lock.newCondition();
+    private ObjectConnection connection;
+    private ExecHandle execHandle;
+    private boolean running;
+    private Throwable processFailure;
+    private final long connectTimeout;
+
+    public DefaultWorkerProcess() {
+        this(30, TimeUnit.SECONDS);
+    }
+
+    DefaultWorkerProcess(int connectTimeoutValue, TimeUnit connectTimeoutUnits) {
+        connectTimeout = connectTimeoutUnits.toMillis(connectTimeoutValue);
+    }
+
+    public void setExecHandle(ExecHandle execHandle) {
+        this.execHandle = execHandle;
+        execHandle.addListener(new ExecHandleListener() {
+            public void executionStarted(ExecHandle execHandle) {
+            }
+
+            public void executionFinished(ExecHandle execHandle, ExecResult execResult) {
+                onProcessStop(execResult);
+            }
+        });
+    }
+
+    public Action<ConnectEvent<ObjectConnection>> getConnectAction() {
+        return new Action<ConnectEvent<ObjectConnection>>() {
+            public void execute(ConnectEvent<ObjectConnection> event) {
+                onConnect(event.getConnection());
+            }
+        };
+    }
+
+    private void onConnect(ObjectConnection connection) {
+        lock.lock();
+        try {
+            this.connection = connection;
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void onProcessStop(ExecResult execResult) {
+        lock.lock();
+        try {
+            try {
+                execResult.rethrowFailure().assertNormalExitValue();
+            } catch (ExecException e) {
+                processFailure = e;
+            }
+            running = false;
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    @Override
+    public String toString() {
+        return execHandle.toString();
+    }
+
+    public ObjectConnection getConnection() {
+        return connection;
+    }
+
+    public void start() {
+        lock.lock();
+        try {
+            running = true;
+        } finally {
+            lock.unlock();
+        }
+
+        execHandle.start();
+
+        Date connectExpiry = new Date(System.currentTimeMillis() + connectTimeout);
+        lock.lock();
+        try {
+            while (connection == null && running) {
+                try {
+                    if (!condition.awaitUntil(connectExpiry)) {
+                        throw new ExecException(String.format("Timeout waiting for %s to connect.", execHandle));
+                    }
+                } catch (InterruptedException e) {
+                    throw UncheckedException.asUncheckedException(e);
+                }
+            }
+            if (processFailure != null) {
+                throw UncheckedException.asUncheckedException(processFailure);
+            }
+            if (connection == null) {
+                throw new ExecException(String.format("Never received a connection from %s.", execHandle));
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public ExecResult waitForStop() {
+        ExecResult result = execHandle.waitForFinish();
+        ObjectConnection connection;
+        lock.lock();
+        try {
+            connection = this.connection;
+        } finally {
+            lock.unlock();
+        }
+        if (connection != null) {
+            connection.stop();
+        }
+        return result.assertNormalExitValue();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcessFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcessFactory.java
new file mode 100644
index 0000000..80bf940
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/DefaultWorkerProcessFactory.java
@@ -0,0 +1,108 @@
+/*
+ * 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.ClassPathRegistry;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.logging.LogLevel;
+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.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.net.URI;
+import java.net.URL;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+public class DefaultWorkerProcessFactory implements WorkerProcessFactory {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DefaultWorkerProcessFactory.class);
+    private final LogLevel workerLogLevel;
+    private final MessagingServer server;
+    private final ClassPathRegistry classPathRegistry;
+    private final FileResolver resolver;
+    private final IdGenerator<?> idGenerator;
+
+    public DefaultWorkerProcessFactory(LogLevel workerLogLevel, MessagingServer server,
+                                       ClassPathRegistry classPathRegistry, FileResolver resolver,
+                                       IdGenerator<?> idGenerator) {
+        this.workerLogLevel = workerLogLevel;
+        this.server = server;
+        this.classPathRegistry = classPathRegistry;
+        this.resolver = resolver;
+        this.idGenerator = idGenerator;
+    }
+
+    public WorkerProcessBuilder newProcess() {
+        return new DefaultWorkerProcessBuilder();
+    }
+
+    private class DefaultWorkerProcessBuilder extends WorkerProcessBuilder {
+        public DefaultWorkerProcessBuilder() {
+            super(resolver);
+            setLogLevel(workerLogLevel);
+            getJavaCommand().setMain(GradleWorkerMain.class.getName());
+        }
+
+        @Override
+        public WorkerProcess build() {
+            if (getWorker() == null) {
+                throw new IllegalStateException("No worker action specified for this worker process.");
+            }
+
+            final DefaultWorkerProcess workerProcess = new DefaultWorkerProcess();
+            URI localAddress = server.accept(workerProcess.getConnectAction());
+
+            List<URL> implementationClassPath = ClasspathUtil.getClasspath(getWorker().getClass().getClassLoader());
+            Object id = idGenerator.generateId();
+            String displayName = String.format("Gradle Worker %s", id);
+
+            WorkerFactory workerFactory;
+            if (isLoadApplicationInSystemClassLoader()) {
+                workerFactory = new ApplicationClassesInSystemClassLoaderWorkerFactory(id, displayName, this,
+                        implementationClassPath, localAddress, classPathRegistry);
+            } else {
+                workerFactory = new ApplicationClassesInIsolatedClassLoaderWorkerFactory(id, displayName, this,
+                        implementationClassPath, localAddress, classPathRegistry);
+            }
+            Callable<?> workerMain = workerFactory.create();
+            getJavaCommand().classpath(workerFactory.getSystemClasspath());
+
+            // Build configuration for GradleWorkerMain
+            byte[] config = GUtil.serialize(workerMain);
+
+            LOGGER.debug("Creating {}", displayName);
+            LOGGER.debug("Using application classpath {}", getApplicationClasspath());
+            LOGGER.debug("Using implementation classpath {}", implementationClassPath);
+
+            getJavaCommand().setStandardInput(new ByteArrayInputStream(config));
+            getJavaCommand().setDisplayName(displayName);
+            ExecHandle execHandle = getJavaCommand().build();
+
+            workerProcess.setExecHandle(execHandle);
+
+            return workerProcess;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecAction.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecAction.java
new file mode 100644
index 0000000..ca68e64
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecAction.java
@@ -0,0 +1,26 @@
+/*
+ * 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.ExecSpec;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ExecAction extends ExecSpec {
+    ExecResult execute() throws ExecException;
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecException.java
new file mode 100644
index 0000000..6999d85
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecException.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.process.internal;
+
+import org.gradle.api.GradleException;
+
+/**
+ * @author Hans Dockter
+ */
+public class ExecException extends GradleException {
+    public ExecException(String message) {
+        super(message);
+    }
+
+    public ExecException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecHandle.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecHandle.java
new file mode 100644
index 0000000..3e2c4c4
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecHandle.java
@@ -0,0 +1,47 @@
+/*
+ * 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 java.io.File;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Tom Eyckmans
+ */
+public interface ExecHandle {
+
+    File getDirectory();
+
+    String getCommand();
+
+    List<String> getArguments();
+
+    Map<String, String> getEnvironment();
+
+    ExecHandle start();
+
+    void abort();
+
+    ExecResult waitForFinish();
+
+    void addListener(ExecHandleListener listener);
+
+    void removeListener(ExecHandleListener listener);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecHandleBuilder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecHandleBuilder.java
new file mode 100644
index 0000000..54c46d8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecHandleBuilder.java
@@ -0,0 +1,91 @@
+/*
+ * 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.IdentityFileResolver;
+import org.gradle.process.ExecSpec;
+import org.gradle.util.GUtil;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class ExecHandleBuilder extends AbstractExecHandleBuilder implements ExecSpec {
+    private final List<Object> arguments = new ArrayList<Object>();
+
+    public ExecHandleBuilder() {
+        super(new IdentityFileResolver());
+    }
+
+    public ExecHandleBuilder(FileResolver fileResolver) {
+        super(fileResolver);
+    }
+
+    public ExecHandleBuilder commandLine(Object... arguments) {
+        commandLine(Arrays.asList(arguments));
+        return this;
+    }
+
+    public ExecSpec commandLine(Iterable<?> args) {
+        List<Object> argsList = GUtil.addLists(args);
+        executable(argsList.get(0));
+        setArgs(argsList.subList(1, argsList.size()));
+        return this;
+    }
+
+    public ExecHandleBuilder args(Object... args) {
+        if (args == null) {
+            throw new IllegalArgumentException("args == null!");
+        }
+        this.arguments.addAll(Arrays.asList(args));
+        return this;
+    }
+
+    public ExecSpec args(Iterable<?> args) {
+        GUtil.addToCollection(arguments, args);
+        return this;
+    }
+
+    public ExecHandleBuilder setArgs(Iterable<?> arguments) {
+        this.arguments.clear();
+        GUtil.addToCollection(this.arguments, arguments);
+        return this;
+    }
+
+    public List<String> getArgs() {
+        List<String> args = new ArrayList<String>();
+        for (Object argument : arguments) {
+            args.add(argument.toString());
+        }
+        return args;
+    }
+
+    @Override
+    public List<String> getAllArguments() {
+        return getArgs();
+    }
+
+    @Override
+    public ExecHandleBuilder setIgnoreExitValue(boolean ignoreExitValue) {
+        super.setIgnoreExitValue(ignoreExitValue);
+        return this;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecHandleListener.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecHandleListener.java
new file mode 100644
index 0000000..a3f8b58
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecHandleListener.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+/**
+ * @author Tom Eyckmans
+ */
+public interface ExecHandleListener {
+    void executionStarted(ExecHandle execHandle);
+
+    void executionFinished(ExecHandle execHandle, ExecResult execResult);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecHandleRunner.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecHandleRunner.java
new file mode 100644
index 0000000..2f22b3c
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecHandleRunner.java
@@ -0,0 +1,95 @@
+/*
+ * 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 java.util.concurrent.Executor;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class ExecHandleRunner implements Runnable {
+    private static final Object START_LOCK = new Object();
+    private final ProcessBuilderFactory processBuilderFactory;
+    private final DefaultExecHandle execHandle;
+    private final Executor threadPool;
+    private final Object lock;
+    private Process process;
+    private boolean aborted;
+
+    public ExecHandleRunner(DefaultExecHandle execHandle, Executor threadPool) {
+        if (execHandle == null) {
+            throw new IllegalArgumentException("execHandle == null!");
+        }
+        this.processBuilderFactory = new ProcessBuilderFactory();
+        this.execHandle = execHandle;
+        this.lock = new Object();
+        this.threadPool = threadPool;
+    }
+
+    public void stopWaiting() {
+        synchronized (lock) {
+            aborted = true;
+            if (process != null) {
+                process.destroy();
+            }
+        }
+    }
+
+    public void run() {
+        ProcessBuilder processBuilder = processBuilderFactory.createProcessBuilder(execHandle);
+        int exitCode;
+        try {
+            ExecOutputHandleRunner standardOutputRunner;
+            ExecOutputHandleRunner errorOutputRunner;
+            ExecOutputHandleRunner standardInputRunner;
+            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",
+                        execHandle.getStandardInput(), process.getOutputStream());
+            }
+            synchronized (lock) {
+                this.process = process;
+            }
+
+            threadPool.execute(standardInputRunner);
+            threadPool.execute(standardOutputRunner);
+            threadPool.execute(errorOutputRunner);
+
+            execHandle.started();
+
+            exitCode = process.waitFor();
+        } catch (Throwable t) {
+            execHandle.failed(t);
+            return;
+        }
+
+        if (aborted) {
+            execHandle.aborted(exitCode);
+        } else {
+            execHandle.finished(exitCode);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecHandleShutdownHookAction.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecHandleShutdownHookAction.java
new file mode 100644
index 0000000..6529fbf
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecHandleShutdownHookAction.java
@@ -0,0 +1,47 @@
+/*
+ * 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.process.internal;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Terminates the external running 'sub' process when the Gradle process is being cancelled.
+ *
+ * @author Tom Eyckmans
+ */
+public class ExecHandleShutdownHookAction implements Runnable {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(ExecHandleShutdownHookAction.class);
+    private final ExecHandle execHandle;
+
+    public ExecHandleShutdownHookAction(ExecHandle execHandle) {
+        if (execHandle == null) {
+            throw new IllegalArgumentException("execHandle is null!");
+        }
+
+        this.execHandle = execHandle;
+    }
+
+    public void run() {
+        try {
+            execHandle.abort();
+        }
+        catch ( Throwable t ) {
+            LOGGER.error("failed to abort " + execHandle, t);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecHandleState.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecHandleState.java
new file mode 100644
index 0000000..7bae379
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecHandleState.java
@@ -0,0 +1,29 @@
+/*
+ * 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.process.internal;
+
+/**
+ * @author Tom Eyckmans
+ */
+public enum ExecHandleState {
+    INIT,
+    STARTING,
+    STARTED,
+    ABORTED,
+    FAILED,
+    SUCCEEDED
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecOutputHandleRunner.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecOutputHandleRunner.java
new file mode 100644
index 0000000..1159148
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ExecOutputHandleRunner.java
@@ -0,0 +1,57 @@
+/*
+ * 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.messaging.concurrent.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);
+            }
+            new CompositeStoppable(inputStream, outputStream).stop();
+        } catch (Throwable t) {
+            LOGGER.error(String.format("Could not %s.", displayName), t);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/JavaExecAction.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/JavaExecAction.java
new file mode 100644
index 0000000..81f5b49
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/JavaExecAction.java
@@ -0,0 +1,26 @@
+/*
+ * 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.JavaExecSpec;
+
+/**
+ * @author Hans Dockter
+ */
+public interface JavaExecAction extends JavaExecSpec {
+    ExecResult execute();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/JavaExecHandleBuilder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/JavaExecHandleBuilder.java
new file mode 100644
index 0000000..d767d2a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/JavaExecHandleBuilder.java
@@ -0,0 +1,205 @@
+/*
+ * 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.file.FileCollection;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.PathResolvingFileCollection;
+import org.gradle.process.JavaExecSpec;
+import org.gradle.process.JavaForkOptions;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+public class JavaExecHandleBuilder extends AbstractExecHandleBuilder implements JavaExecSpec {
+    private String mainClass;
+    private final List<Object> applicationArgs = new ArrayList<Object>();
+    private FileCollection classpath;
+    private final JavaForkOptions javaOptions;
+    private final FileResolver fileResolver;
+
+    public JavaExecHandleBuilder(FileResolver fileResolver) {
+        super(fileResolver);
+        this.fileResolver = fileResolver;
+        javaOptions = new DefaultJavaForkOptions(fileResolver);
+        classpath = new PathResolvingFileCollection(fileResolver, null);
+        executable(javaOptions.getExecutable());
+    }
+
+    public List<String> getAllJvmArgs() {
+        List<String> allArgs = new ArrayList<String>();
+        allArgs.addAll(javaOptions.getAllJvmArgs());
+        if (!classpath.isEmpty()) {
+            allArgs.add("-cp");
+            allArgs.add(GUtil.join(classpath.getFiles(), File.pathSeparator));
+        }
+        return allArgs;
+    }
+
+    public void setAllJvmArgs(Iterable<?> arguments) {
+        throw new UnsupportedOperationException();
+    }
+
+    public List<String> getJvmArgs() {
+        return javaOptions.getJvmArgs();
+    }
+
+    public void setJvmArgs(Iterable<?> arguments) {
+        javaOptions.setJvmArgs(arguments);
+    }
+
+    public JavaExecHandleBuilder jvmArgs(Iterable<?> arguments) {
+        javaOptions.jvmArgs(arguments);
+        return this;
+    }
+
+    public JavaExecHandleBuilder jvmArgs(Object... arguments) {
+        javaOptions.jvmArgs(arguments);
+        return this;
+    }
+
+    public Map<String, Object> getSystemProperties() {
+        return javaOptions.getSystemProperties();
+    }
+
+    public void setSystemProperties(Map<String, ?> properties) {
+        javaOptions.setSystemProperties(properties);
+    }
+
+    public JavaExecHandleBuilder systemProperties(Map<String, ?> properties) {
+        javaOptions.systemProperties(properties);
+        return this;
+    }
+
+    public JavaExecHandleBuilder systemProperty(String name, Object value) {
+        javaOptions.systemProperty(name, value);
+        return this;
+    }
+
+    public FileCollection getBootstrapClasspath() {
+        return javaOptions.getBootstrapClasspath();
+    }
+
+    public void setBootstrapClasspath(FileCollection classpath) {
+        javaOptions.setBootstrapClasspath(classpath);
+    }
+
+    public JavaForkOptions bootstrapClasspath(Object... classpath) {
+        javaOptions.bootstrapClasspath(classpath);
+        return this;
+    }
+
+    public String getMaxHeapSize() {
+        return javaOptions.getMaxHeapSize();
+    }
+
+    public void setMaxHeapSize(String heapSize) {
+        javaOptions.setMaxHeapSize(heapSize);
+    }
+
+    public boolean getEnableAssertions() {
+        return javaOptions.getEnableAssertions();
+    }
+
+    public void setEnableAssertions(boolean enabled) {
+        javaOptions.setEnableAssertions(enabled);
+    }
+
+    public boolean getDebug() {
+        return javaOptions.getDebug();
+    }
+
+    public void setDebug(boolean enabled) {
+        javaOptions.setDebug(enabled);
+    }
+
+    public String getMain() {
+        return mainClass;
+    }
+
+    public JavaExecHandleBuilder setMain(String mainClassName) {
+        this.mainClass = mainClassName;
+        return this;
+    }
+
+    public List<String> getArgs() {
+        List<String> args = new ArrayList<String>();
+        for (Object applicationArg : applicationArgs) {
+            args.add(applicationArg.toString());
+        }
+        return args;
+    }
+
+    public JavaExecHandleBuilder setArgs(Iterable<?> applicationArgs) {
+        this.applicationArgs.clear();
+        GUtil.addToCollection(this.applicationArgs, applicationArgs);
+        return this;
+    }
+
+    public JavaExecHandleBuilder args(Object... args) {
+        applicationArgs.addAll(Arrays.asList(args));
+        return this;
+    }
+
+    public JavaExecSpec args(Iterable<?> args) {
+        GUtil.addToCollection(applicationArgs, args);
+        return this;
+    }
+
+    public JavaExecHandleBuilder setClasspath(FileCollection classpath) {
+        this.classpath = classpath;
+        return this;
+    }
+
+    public JavaExecHandleBuilder classpath(Object... paths) {
+        classpath = classpath.plus(fileResolver.resolveFiles(paths));
+        return this;
+    }
+
+    public FileCollection getClasspath() {
+        return classpath;
+    }
+
+    @Override
+    public List<String> getAllArguments() {
+        List<String> arguments = new ArrayList<String>();
+        arguments.addAll(getAllJvmArgs());
+        arguments.add(mainClass);
+        arguments.addAll(getArgs());
+        return arguments;
+    }
+
+    public JavaForkOptions copyTo(JavaForkOptions options) {
+        throw new UnsupportedOperationException();
+    }
+
+    public ExecHandle build() {
+        if (mainClass == null) {
+            throw new IllegalStateException("No main class specified");
+        }
+        return super.build();
+    }
+
+    @Override
+    public JavaExecSpec setIgnoreExitValue(boolean ignoreExitValue) {
+        super.setIgnoreExitValue(ignoreExitValue);
+        return this;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ProcessBuilderFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ProcessBuilderFactory.java
new file mode 100644
index 0000000..a753c6f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/ProcessBuilderFactory.java
@@ -0,0 +1,60 @@
+/*
+ * 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.process.internal;
+
+import org.slf4j.LoggerFactory;
+import org.slf4j.Logger;
+
+import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Creates a {@link java.lang.ProcessBuilder} based on a {@link ExecHandle}.
+ *
+ * @author Tom Eyckmans
+ */
+public class ProcessBuilderFactory {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ProcessBuilderFactory.class);
+
+    public ProcessBuilder createProcessBuilder(ExecHandle execHandle) {
+        final List<String> commandWithArguments = new ArrayList<String>();
+        final String command = execHandle.getCommand();
+        if (LOGGER.isDebugEnabled()) {
+            LOGGER.debug("creating process builder for {}", execHandle);
+        }
+        commandWithArguments.add(command);
+        final List<String> arguments = execHandle.getArguments();
+        if (LOGGER.isDebugEnabled()) {
+            int argumentIndex = 0;
+            for ( String argument : arguments ) {
+                LOGGER.debug("with argument#{} = {}", argumentIndex, argument);
+                argumentIndex++;
+            }
+        }
+        commandWithArguments.addAll(arguments);
+        
+        final ProcessBuilder processBuilder = new ProcessBuilder(commandWithArguments);
+
+        processBuilder.directory(execHandle.getDirectory());
+        final Map<String, String> environment = processBuilder.environment();
+        environment.clear();
+        environment.putAll(execHandle.getEnvironment());
+
+        return processBuilder;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/WorkerProcess.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/WorkerProcess.java
new file mode 100644
index 0000000..f839ffc
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/WorkerProcess.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.process.internal;
+
+import org.gradle.messaging.remote.ObjectConnection;
+import org.gradle.process.ExecResult;
+
+/**
+ * A Java process which performs some worker action. You can send and receive messages to/from the worker process
+ * using a supplied {@link org.gradle.messaging.remote.ObjectConnection}.
+ */
+public interface WorkerProcess {
+    ObjectConnection getConnection();
+
+    void start();
+
+    ExecResult waitForStop();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/WorkerProcessBuilder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/WorkerProcessBuilder.java
new file mode 100644
index 0000000..07cc1e6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/WorkerProcessBuilder.java
@@ -0,0 +1,105 @@
+/*
+ * 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.Action;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * <p>A builder which configures and creates a {@link WorkerProcess} instance.</p>
+ *
+ * <p>A worker process is specified using an {@link Action}. The given action instance is serialized across into the
+ * worker process and executed.</p>
+ *
+ * <p>A worker process can optionally specify an application classpath. The classes of this classpath are loaded into an
+ * isolated ClassLoader, which is made visible to the worker action ClassLoader. Only the packages specified in the set
+ * of shared packages are visible to the worker action ClassLoader.</p>
+ */
+public abstract class WorkerProcessBuilder {
+    private final JavaExecHandleBuilder javaCommand;
+    private final Set<String> packages = new HashSet<String>();
+    private final Set<File> applicationClasspath = new LinkedHashSet<File>();
+    private Action<WorkerProcessContext> action;
+    private LogLevel logLevel = LogLevel.LIFECYCLE;
+    private boolean loadApplicationInSystemClassLoader;
+
+    public WorkerProcessBuilder(FileResolver fileResolver) {
+        javaCommand = new JavaExecHandleBuilder(fileResolver);
+    }
+
+    public WorkerProcessBuilder applicationClasspath(Iterable<File> files) {
+        GUtil.addToCollection(applicationClasspath, files);
+        return this;
+    }
+
+    public Set<File> getApplicationClasspath() {
+        return applicationClasspath;
+    }
+
+    public WorkerProcessBuilder sharedPackages(String... packages) {
+        sharedPackages(Arrays.asList(packages));
+        return this;
+    }
+
+    public WorkerProcessBuilder sharedPackages(Iterable<String> packages) {
+        GUtil.addToCollection(this.packages, packages);
+        return this;
+    }
+
+    public Set<String> getSharedPackages() {
+        return packages;
+    }
+
+    public WorkerProcessBuilder worker(Action<WorkerProcessContext> action) {
+        this.action = action;
+        return this;
+    }
+
+    public Action<WorkerProcessContext> getWorker() {
+        return action;
+    }
+
+    public JavaExecHandleBuilder getJavaCommand() {
+        return javaCommand;
+    }
+
+    public LogLevel getLogLevel() {
+        return logLevel;
+    }
+
+    public void setLogLevel(LogLevel logLevel) {
+        this.logLevel = logLevel;
+    }
+
+    public boolean isLoadApplicationInSystemClassLoader() {
+        return loadApplicationInSystemClassLoader;
+    }
+
+    public void setLoadApplicationInSystemClassLoader(boolean loadApplicationInSystemClassLoader) {
+        this.loadApplicationInSystemClassLoader = loadApplicationInSystemClassLoader;
+    }
+
+    public abstract WorkerProcess build();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/WorkerProcessContext.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/WorkerProcessContext.java
new file mode 100644
index 0000000..52383ae
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/WorkerProcessContext.java
@@ -0,0 +1,38 @@
+/*
+ * 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.messaging.remote.ObjectConnection;
+
+public interface WorkerProcessContext {
+    /**
+     * Returns the unique identifier for this worker process.
+     */
+    Object getWorkerId();
+
+    /**
+     * Returns a display name for this worker process.
+     */
+    String getDisplayName();
+
+    /**
+     * Returns the connection which can be used to send/receive messages to/from the server process.
+     */
+    ObjectConnection getServerConnection();
+
+    ClassLoader getApplicationClassLoader();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/WorkerProcessFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/WorkerProcessFactory.java
new file mode 100644
index 0000000..5e70002
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/WorkerProcessFactory.java
@@ -0,0 +1,20 @@
+/*
+ * 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;
+
+public interface WorkerProcessFactory {
+    WorkerProcessBuilder newProcess();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/ActionExecutionWorker.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/ActionExecutionWorker.java
new file mode 100644
index 0000000..8ea090a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/ActionExecutionWorker.java
@@ -0,0 +1,89 @@
+/*
+ * 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.child;
+
+import org.gradle.api.Action;
+import org.gradle.messaging.remote.MessagingClient;
+import org.gradle.messaging.remote.ObjectConnection;
+import org.gradle.messaging.remote.internal.TcpMessagingClient;
+import org.gradle.process.internal.WorkerProcessContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.net.URI;
+
+/**
+ * <p>The final stage of worker start-up. Takes care of executing the worker action.</p>
+ *
+ * <p>It is instantiated in the implementation ClassLoader and called from {@link ImplementationClassLoaderWorker}.<p>
+ */
+public class ActionExecutionWorker implements Action<WorkerContext>, Serializable {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ActionExecutionWorker.class);
+    private final Action<WorkerProcessContext> action;
+    private final Object workerId;
+    private final String displayName;
+    private final URI serverAddress;
+
+    public ActionExecutionWorker(Action<WorkerProcessContext> action, Object workerId, String displayName,
+                                 URI serverAddress) {
+        this.action = action;
+        this.workerId = workerId;
+        this.displayName = displayName;
+        this.serverAddress = serverAddress;
+    }
+
+    public void execute(final WorkerContext workerContext) {
+        final MessagingClient client = createClient();
+        try {
+            LOGGER.debug("Starting {}.", displayName);
+            WorkerProcessContext context = new WorkerProcessContext() {
+                public ObjectConnection getServerConnection() {
+                    return client.getConnection();
+                }
+
+                public ClassLoader getApplicationClassLoader() {
+                    return workerContext.getApplicationClassLoader();
+                }
+
+                public Object getWorkerId() {
+                    return workerId;
+                }
+
+                public String getDisplayName() {
+                    return displayName;
+                }
+            };
+
+            ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+            Thread.currentThread().setContextClassLoader(action.getClass().getClassLoader());
+            try {
+                action.execute(context);
+            } finally {
+                Thread.currentThread().setContextClassLoader(contextClassLoader);
+            }
+            LOGGER.debug("Completed {}.", displayName);
+        } finally {
+            LOGGER.debug("Stopping client connection.");
+            client.stop();
+        }
+    }
+
+    MessagingClient createClient() {
+        return new TcpMessagingClient(getClass().getClassLoader(), serverAddress);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInIsolatedClassLoaderWorkerFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInIsolatedClassLoaderWorkerFactory.java
new file mode 100644
index 0000000..e35a803
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInIsolatedClassLoaderWorkerFactory.java
@@ -0,0 +1,83 @@
+/*
+ * 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.child;
+
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.process.internal.WorkerProcessBuilder;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URL;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * A factory for a worker process which loads application classes using an isolated ClassLoader.
+ *
+ * <p>Class loader hierarchy:</p>
+ * <pre>
+ *                              bootstrap
+ *                                 |
+ *                   +-------------+------------+
+ *                   |                          |
+ *                 system                   application
+ *  (ImplementationClassLoaderWorker, logging)           |
+ *                   |                          |
+ *                filter                     filter
+ *              (logging)               (shared packages)
+ *                   |                         |
+ *                   +-------------+-----------+
+ *                                 |
+ *                          implementation
+ *                (ActionExecutionWorker + action implementation)
+ * </pre>
+ *
+ */
+public class ApplicationClassesInIsolatedClassLoaderWorkerFactory implements WorkerFactory {
+    private final Object workerId;
+    private final String displayName;
+    private final WorkerProcessBuilder processBuilder;
+    private final Collection<URL> implementationClassPath;
+    private final URI serverAddress;
+    private final ClassPathRegistry classPathRegistry;
+
+    public ApplicationClassesInIsolatedClassLoaderWorkerFactory(Object workerId, String displayName, WorkerProcessBuilder processBuilder,
+                                            Collection<URL> implementationClassPath, URI serverAddress,
+                                            ClassPathRegistry classPathRegistry) {
+        this.workerId = workerId;
+        this.displayName = displayName;
+        this.processBuilder = processBuilder;
+        this.implementationClassPath = implementationClassPath;
+        this.serverAddress = serverAddress;
+        this.classPathRegistry = classPathRegistry;
+    }
+
+    public Collection<File> getSystemClasspath() {
+        return classPathRegistry.getClassPathFiles("WORKER_PROCESS");
+    }
+
+    public Callable<?> create() {
+        List<URL> applicationClassPath = GFileUtils.toURLs(processBuilder.getApplicationClasspath());
+        ActionExecutionWorker injectedWorker = new ActionExecutionWorker(processBuilder.getWorker(), workerId,
+                displayName, serverAddress);
+        ImplementationClassLoaderWorker worker = new ImplementationClassLoaderWorker(processBuilder.getLogLevel(),
+                processBuilder.getSharedPackages(), implementationClassPath, injectedWorker);
+        return new IsolatedApplicationClassLoaderWorker(applicationClassPath, worker);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInSystemClassLoaderWorkerFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInSystemClassLoaderWorkerFactory.java
new file mode 100644
index 0000000..2da8180
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/ApplicationClassesInSystemClassLoaderWorkerFactory.java
@@ -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.process.internal.child;
+
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.process.internal.WorkerProcessBuilder;
+import org.gradle.process.internal.launcher.BootstrapClassLoaderWorker;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URL;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * A factory for a worker process which loads the application classes using the JVM's system ClassLoader.
+ *
+ * <p>Class loader hierarchy:</p>
+ * <pre>
+ *                              bootstrap
+ *                                 |
+ *                +----------------+--------------+
+ *                |                               |
+ *              system                      worker bootstrap
+ *  (GradleWorkerMain, application) (SystemApplicationClassLoaderWorker, logging)
+ *                |                   (ImplementationClassLoaderWorker)
+ *                |                               |
+ *             filter                          filter
+ *        (shared packages)                  (logging)
+ *                |                              |
+ *                +---------------+--------------+
+ *                                |
+ *                          implementation
+ *         (ActionExecutionWorker + worker action implementation)
+ * </pre>
+ *
+ */
+public class ApplicationClassesInSystemClassLoaderWorkerFactory implements WorkerFactory {
+    private final Object workerId;
+    private final String displayName;
+    private final WorkerProcessBuilder processBuilder;
+    private final List<URL> implementationClassPath;
+    private final URI serverAddress;
+    private final ClassPathRegistry classPathRegistry;
+
+    public ApplicationClassesInSystemClassLoaderWorkerFactory(Object workerId, String displayName, WorkerProcessBuilder processBuilder,
+                                          List<URL> implementationClassPath, URI serverAddress,
+                                          ClassPathRegistry classPathRegistry) {
+        this.workerId = workerId;
+        this.displayName = displayName;
+        this.processBuilder = processBuilder;
+        this.implementationClassPath = implementationClassPath;
+        this.serverAddress = serverAddress;
+        this.classPathRegistry = classPathRegistry;
+    }
+
+    public Collection<File> getSystemClasspath() {
+        return classPathRegistry.getClassPathFiles("WORKER_MAIN");
+    }
+
+    public Callable<?> create() {
+        // Serialize the bootstrap worker, so it can be transported through the system ClassLoader
+        ActionExecutionWorker injectedWorker = new ActionExecutionWorker(processBuilder.getWorker(), workerId, displayName, serverAddress);
+        ImplementationClassLoaderWorker worker = new ImplementationClassLoaderWorker(processBuilder.getLogLevel(), processBuilder.getSharedPackages(),
+                implementationClassPath, injectedWorker);
+        byte[] serializedWorker = GUtil.serialize(worker);
+
+        return new BootstrapClassLoaderWorker(classPathRegistry.getClassPath("WORKER_PROCESS"), processBuilder.getApplicationClasspath(), serializedWorker);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorker.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorker.java
new file mode 100644
index 0000000..d095d7f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorker.java
@@ -0,0 +1,90 @@
+/*
+ * 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.child;
+
+import org.gradle.api.Action;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.logging.LoggingManagerFactory;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.logging.LoggingServiceRegistry;
+import org.gradle.util.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.net.URL;
+import java.util.Collection;
+
+/**
+ * <p>A stage of the worker process start-up. Instantiated in the worker bootstrap ClassLoader and takes care of
+ * creating the implementation ClassLoader and executing the next stage of start-up in that ClassLoader. </p>
+ */
+public class ImplementationClassLoaderWorker implements Action<WorkerContext>, Serializable {
+    private final LogLevel logLevel;
+    private final Collection<String> sharedPackages;
+    private final Collection<URL> implementationClassPath;
+    private final byte[] serializedWorkerAction;
+
+    protected ImplementationClassLoaderWorker(LogLevel logLevel, Collection<String> sharedPackages,
+                                              Collection<URL> implementationClassPath,
+                                              Action<WorkerContext> workerAction) {
+        this.logLevel = logLevel;
+        this.sharedPackages = sharedPackages;
+        this.implementationClassPath = implementationClassPath;
+        serializedWorkerAction = GUtil.serialize(workerAction);
+    }
+
+    public void execute(WorkerContext workerContext) {
+        LoggingManagerInternal loggingManager = createLoggingManager();
+        loggingManager.setLevel(logLevel).start();
+
+        FilteringClassLoader filteredWorkerClassLoader = new FilteringClassLoader(getClass().getClassLoader());
+        filteredWorkerClassLoader.allowPackage("org.slf4j");
+        filteredWorkerClassLoader.allowClass(Action.class);
+        filteredWorkerClassLoader.allowClass(WorkerContext.class);
+
+        ClassLoader applicationClassLoader = workerContext.getApplicationClassLoader();
+        FilteringClassLoader filteredApplication = new FilteringClassLoader(applicationClassLoader);
+        ObservableUrlClassLoader implementationClassLoader = createImplementationClassLoader(filteredWorkerClassLoader,
+                filteredApplication);
+
+        // Configure classpaths
+        for (String sharedPackage : sharedPackages) {
+            filteredApplication.allowPackage(sharedPackage);
+        }
+        implementationClassLoader.addURLs(implementationClassPath);
+
+        // Deserialize the worker action
+        Action<WorkerContext> action;
+        try {
+            ObjectInputStream instr = new ClassLoaderObjectInputStream(new ByteArrayInputStream(serializedWorkerAction),
+                    implementationClassLoader);
+            action = (Action<WorkerContext>) instr.readObject();
+        } catch (Exception e) {
+            throw UncheckedException.asUncheckedException(e);
+        }
+        action.execute(workerContext);
+    }
+
+    LoggingManagerInternal createLoggingManager() {
+        return new LoggingServiceRegistry().get(LoggingManagerFactory.class).create();
+    }
+
+    ObservableUrlClassLoader createImplementationClassLoader(ClassLoader system, ClassLoader application) {
+        return new ObservableUrlClassLoader(new MultiParentClassLoader(application, system));
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/IsolatedApplicationClassLoaderWorker.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/IsolatedApplicationClassLoaderWorker.java
new file mode 100644
index 0000000..3ad70d3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/IsolatedApplicationClassLoaderWorker.java
@@ -0,0 +1,56 @@
+/*
+ * 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.child;
+
+import org.gradle.api.Action;
+import org.gradle.util.ObservableUrlClassLoader;
+
+import java.io.Serializable;
+import java.net.URL;
+import java.util.Collection;
+import java.util.concurrent.Callable;
+
+/**
+ * <p>A worker which loads the application classes in an isolated ClassLoader.</p>
+ */
+public class IsolatedApplicationClassLoaderWorker implements Callable<Void>, Serializable {
+    private final Action<WorkerContext> worker;
+    private final Collection<URL> applicationClassPath;
+
+    public IsolatedApplicationClassLoaderWorker(Collection<URL> applicationClassPath, Action<WorkerContext> worker) {
+        this.applicationClassPath = applicationClassPath;
+        this.worker = worker;
+    }
+
+    public Void call() throws Exception {
+        final ObservableUrlClassLoader applicationClassLoader = createApplicationClassLoader();
+
+        WorkerContext context = new WorkerContext() {
+            public ClassLoader getApplicationClassLoader() {
+                return applicationClassLoader;
+            }
+        };
+
+        worker.execute(context);
+
+        return null;
+    }
+
+    private ObservableUrlClassLoader createApplicationClassLoader() {
+        return new ObservableUrlClassLoader(ClassLoader.getSystemClassLoader().getParent(), applicationClassPath);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/SystemApplicationClassLoaderWorker.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/SystemApplicationClassLoaderWorker.java
new file mode 100644
index 0000000..8fef9ab
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/SystemApplicationClassLoaderWorker.java
@@ -0,0 +1,64 @@
+/*
+ * 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.child;
+
+import org.gradle.api.Action;
+import org.gradle.util.ClassLoaderObjectInputStream;
+import org.gradle.util.ClasspathUtil;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.GUtil;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.net.URLClassLoader;
+import java.util.Collection;
+import java.util.concurrent.Callable;
+
+/**
+ * <p>Stage 3 of the start-up for a worker process with the application classes loaded in the system ClassLoader. Takes
+ * care of adding the application classes to the system ClassLoader and then invoking the next stage of start-up.</p>
+ *
+ * <p> Instantiated in the worker bootstrap ClassLoader and invoked from {@link org.gradle.process.internal.launcher.BootstrapClassLoaderWorker}.
+ * See {@link ApplicationClassesInSystemClassLoaderWorkerFactory} for details.</p>
+ */
+public class SystemApplicationClassLoaderWorker implements Callable<Void> {
+    private final byte[] serializedWorker;
+    private final Collection<File> applicationClassPath;
+
+    public SystemApplicationClassLoaderWorker(Collection<File> applicationClassPath, byte[] serializedWorker) {
+        this.applicationClassPath = applicationClassPath;
+        this.serializedWorker = serializedWorker;
+    }
+
+    public Void call() throws Exception {
+        final ClassLoader applicationClassLoader = ClassLoader.getSystemClassLoader();
+        ClasspathUtil.addUrl((URLClassLoader) applicationClassLoader, GFileUtils.toURLs(applicationClassPath));
+        System.setProperty("java.class.path", GUtil.join(applicationClassPath, File.pathSeparator));
+
+        ClassLoaderObjectInputStream instr = new ClassLoaderObjectInputStream(new ByteArrayInputStream(
+                serializedWorker), getClass().getClassLoader());
+        final Action<WorkerContext> action = (Action<WorkerContext>) instr.readObject();
+
+        action.execute(new WorkerContext() {
+            public ClassLoader getApplicationClassLoader() {
+                return applicationClassLoader;
+            }
+        });
+
+        return null;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/WorkerContext.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/WorkerContext.java
new file mode 100644
index 0000000..747caa9
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/WorkerContext.java
@@ -0,0 +1,21 @@
+/*
+ * 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.child;
+
+public interface WorkerContext {
+    ClassLoader getApplicationClassLoader();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/WorkerFactory.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/WorkerFactory.java
new file mode 100644
index 0000000..1bc9fda
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/WorkerFactory.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.process.internal.child;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.concurrent.Callable;
+
+public interface WorkerFactory {
+    Callable<?> create();
+
+    Collection<File> getSystemClasspath();
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProvider.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProvider.java
new file mode 100644
index 0000000..720e287
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProvider.java
@@ -0,0 +1,65 @@
+/*
+ * 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.child;
+
+import org.gradle.api.internal.AbstractClassPathProvider;
+import org.gradle.cache.CacheRepository;
+import org.gradle.cache.PersistentCache;
+import org.gradle.process.internal.launcher.BootstrapClassLoaderWorker;
+import org.gradle.process.internal.launcher.GradleWorkerMain;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+
+public class WorkerProcessClassPathProvider extends AbstractClassPathProvider {
+    private final CacheRepository cacheRepository;
+    private final Object lock = new Object();
+    private Set<File> workerClassPath;
+
+    public WorkerProcessClassPathProvider(CacheRepository cacheRepository) {
+        this.cacheRepository = cacheRepository;
+        add("WORKER_PROCESS", toPatterns("gradle-core", "slf4j-api", "logback-classic", "logback-core", "jul-to-slf4j", "jansi", "jna", "jna-posix"));
+    }
+
+    public Set<File> findClassPath(String name) {
+        if (!name.equals("WORKER_MAIN")) {
+            return super.findClassPath(name);
+        }
+
+        synchronized (lock) {
+            if (workerClassPath == null) {
+                PersistentCache cache = cacheRepository.cache("workerMain").open();
+                File classesDir = new File(cache.getBaseDir(), "classes");
+                if (!cache.isValid()) {
+                    for (Class<?> aClass : Arrays.asList(GradleWorkerMain.class, BootstrapClassLoaderWorker.class)) {
+                        String fileName = aClass.getName().replace('.', '/') + ".class";
+                        GFileUtils.copyURLToFile(WorkerProcessClassPathProvider.class.getClassLoader().getResource(fileName),
+                                new File(classesDir, fileName));
+                    }
+
+                    cache.markValid();
+                }
+
+                workerClassPath = Collections.singleton(classesDir);
+            }
+            return workerClassPath;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/launcher/BootstrapClassLoaderWorker.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/launcher/BootstrapClassLoaderWorker.java
new file mode 100644
index 0000000..254bd95
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/launcher/BootstrapClassLoaderWorker.java
@@ -0,0 +1,54 @@
+/*
+ * 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.launcher;
+
+import java.io.File;
+import java.io.Serializable;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collection;
+import java.util.concurrent.Callable;
+
+/**
+ * <p>Stage 2 of the start-up for a worker process with the application classes loaded in the system ClassLoader. Takes
+ * care of creating the worker bootstrap ClassLoader and executing the next stage of start-up in that ClassLoader.</p>
+ *
+ * <p> Instantiated in the system ClassLoader and called from {@link GradleWorkerMain}. See
+ * {@link org.gradle.process.internal.child.ApplicationClassesInSystemClassLoaderWorkerFactory} for details.</p>
+ */
+public class BootstrapClassLoaderWorker implements Callable<Void>, Serializable {
+    private final Collection<URL> bootstrapClasspath;
+    private final Collection<File> applicationClasspath;
+    private final byte[] serializedWorker;
+
+    public BootstrapClassLoaderWorker(Collection<URL> bootstrapClasspath, Collection<File> applicationClasspath,
+                                      byte[] serializedWorker) {
+        this.bootstrapClasspath = bootstrapClasspath;
+        this.applicationClasspath = applicationClasspath;
+        this.serializedWorker = serializedWorker;
+    }
+
+    public Void call() throws Exception {
+        URL[] bootstrapUrls = bootstrapClasspath.toArray(new URL[bootstrapClasspath.size()]);
+        URLClassLoader classLoader = new URLClassLoader(bootstrapUrls, ClassLoader.getSystemClassLoader().getParent());
+        Class<? extends Callable> workerClass = classLoader.loadClass(
+                "org.gradle.process.internal.child.SystemApplicationClassLoaderWorker").asSubclass(Callable.class);
+        Callable<Void> main = workerClass.getConstructor(Collection.class, byte[].class).newInstance(
+                applicationClasspath, serializedWorker);
+        return main.call();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/launcher/GradleWorkerMain.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/launcher/GradleWorkerMain.java
new file mode 100644
index 0000000..09044d2
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/launcher/GradleWorkerMain.java
@@ -0,0 +1,42 @@
+/*
+ * 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.launcher;
+
+import java.io.ObjectInputStream;
+import java.util.concurrent.Callable;
+
+/**
+ * The main entry point for a worker process. Reads a serialized Callable from stdin, and executes it.
+ */
+public class GradleWorkerMain {
+    public void run() throws Exception {
+        // Read the main action from stdin and execute it
+        ObjectInputStream instr = new ObjectInputStream(System.in);
+        Callable<?> main = (Callable<?>) instr.readObject();
+        main.call();
+    }
+
+    public static void main(String[] args) {
+        try {
+            new GradleWorkerMain().run();
+            System.exit(0);
+        } catch (Throwable throwable) {
+            throwable.printStackTrace(System.err);
+            System.exit(1);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/package.html b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/package.html
new file mode 100644
index 0000000..c78cae0
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/package.html
@@ -0,0 +1,46 @@
+<!--
+ *
+ * Copyright 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.
+ *
+-->
+<html>
+    <body>
+      Classes for running external processes.
+
+      <h3>ExecHandle</h3>
+
+      <p>The {@link org.gradle.util.exec.ExecHandle} is the class that maps to the external process and provides control
+      methods to start/abort or wait for the external process to terminate (normally or by a failure).</p>
+
+      <h3>ExecHandleBuilder</h3>
+
+      <p>Creating an instance of {@link org.gradle.util.exec.ExecHandle} is done using the {@link org.gradle.util.exec.ExecHandleBuilder}
+      that provides a bunch of usefull functions to set the arguments/... .</p>
+
+      <h3>ExecHandleListener</h3>
+
+      <p>It is also possible to add listeners {@link org.gradle.util.exec.ExecHandleListener} to the {@link org.gradle.util.exec.ExecHandle} that are called
+      when the {@link org.gradle.util.exec.ExecHandle} changes from state {@link org.gradle.util.exec.ExecHandleState}.</p>
+
+      <h3>ExecOutputHandle</h3>
+
+      <p>In order to prevent the external process from blocking (because the output buffers are full)
+      the standard output and error output are fetch continuously by two Threads until the process terminates.
+      These Threads pass the output to an {@link org.gradle.util.exec.ExecOutputHandle}. The default behaviour is to pass all the output
+      to the standard output and error output of the parent process. By passing in other {@link org.gradle.util.exec.ExecOutputHandle} this
+      behaviour can be customized.</p>
+
+    </body>
+</html>
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/shutdown/ShutdownHookActionRegister.java b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/shutdown/ShutdownHookActionRegister.java
new file mode 100644
index 0000000..24eca1f
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/process/internal/shutdown/ShutdownHookActionRegister.java
@@ -0,0 +1,68 @@
+/*
+ * 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.process.internal.shutdown;
+
+import org.gradle.api.UncheckedIOException;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class ShutdownHookActionRegister {
+    private static final ShutdownHookActionRegister INSTANCE = new ShutdownHookActionRegister();
+    private final List<Runnable> shutdownHookActions = new CopyOnWriteArrayList<Runnable>();
+
+    private ShutdownHookActionRegister() {
+        Runtime.getRuntime().addShutdownHook(new Thread(new GradleShutdownHook(), "gradle-shutdown-hook"));
+    }
+
+    public static void addAction(Runnable shutdownHookAction) {
+        INSTANCE.shutdownHookActions.add(shutdownHookAction);
+    }
+
+    public static void removeAction(Runnable shutdownHookAction) {
+        INSTANCE.shutdownHookActions.remove(shutdownHookAction);
+    }
+
+    public static void closeOnExit(final Closeable closeable) {
+        addAction(new Runnable() {
+            public void run() {
+                try {
+                    closeable.close();
+                } catch (IOException e) {
+                    throw new UncheckedIOException(e);
+                }
+            }
+        });
+    }
+
+    private class GradleShutdownHook implements Runnable {
+        public void run() {
+            for (final Runnable shutdownHookAction : shutdownHookActions) {
+                try {
+                    shutdownHookAction.run();
+                } catch (Throwable t) {
+                    System.err.println("failed to execute a shutdown action.");
+                    t.printStackTrace(System.err);
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/testfixtures/ProjectBuilder.java b/subprojects/gradle-core/src/main/groovy/org/gradle/testfixtures/ProjectBuilder.java
new file mode 100644
index 0000000..951618d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/testfixtures/ProjectBuilder.java
@@ -0,0 +1,229 @@
+/*
+ * 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.testfixtures;
+
+import org.gradle.StartParameter;
+import org.gradle.api.Project;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.DefaultClassPathRegistry;
+import org.gradle.api.internal.GradleDistributionLocator;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.project.*;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.LoggingManager;
+import org.gradle.api.logging.StandardOutputListener;
+import org.gradle.cache.AutoCloseCacheFactory;
+import org.gradle.cache.CacheFactory;
+import org.gradle.cache.DefaultCacheFactory;
+import org.gradle.initialization.ClassLoaderFactory;
+import org.gradle.initialization.DefaultClassLoaderFactory;
+import org.gradle.initialization.DefaultProjectDescriptor;
+import org.gradle.initialization.DefaultProjectDescriptorRegistry;
+import org.gradle.invocation.DefaultGradle;
+import org.gradle.listener.DefaultListenerManager;
+import org.gradle.listener.ListenerManager;
+import org.gradle.logging.DefaultProgressLoggerFactory;
+import org.gradle.logging.LoggingManagerFactory;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.util.GFileUtils;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * <p>Creates dummy instances of {@link org.gradle.api.Project} which you can use in testing custom task and plugin
+ * implementations.</p>
+ *
+ * <p>To create a project instance:</p>
+ *
+ * <ol>
+ *
+ * <li>Create a {@code ProjectBuilder} instance by calling {@link #builder()}.</li>
+ *
+ * <li>Optionally, configure the builder.</li>
+ *
+ * <li>Call {@link #build()} to create the {@code Project} instance.</li>
+ *
+ * </ol>
+ *
+ * <p>You can reuse a builder to create multiple {@code Project} instances.</p>
+ */
+public class ProjectBuilder {
+    private static final GlobalTestServices GLOBAL_SERVICES = new GlobalTestServices();
+    private File projectDir;
+
+    /**
+     * Creates a project builder.
+     *
+     * @return The builder
+     */
+    public static ProjectBuilder builder() {
+        return new ProjectBuilder();
+    }
+
+    /**
+     * Specifies the project directory for the project to build.
+     *
+     * @param dir The project directory
+     * @return A new ProjectBuilder.
+     */
+    public ProjectBuilder withProjectDir(File dir) {
+        projectDir = GFileUtils.canonicalise(dir);
+        return this;
+    }
+
+    /**
+     * Creates the project.
+     *
+     * @return The project
+     */
+    public Project build() {
+        if (projectDir == null) {
+            try {
+                projectDir = GFileUtils.canonicalise(File.createTempFile("gradle", "projectDir"));
+                projectDir.delete();
+                projectDir.mkdir();
+                projectDir.deleteOnExit();
+            } catch (IOException e) {
+                throw new UncheckedIOException(e);
+            }
+        }
+
+        final File homeDir = new File(projectDir, "gradleHome");
+
+        StartParameter startParameter = new StartParameter();
+        startParameter.setGradleUserHomeDir(new File(projectDir, "userHome"));
+
+        ServiceRegistryFactory topLevelRegistry = new TestTopLevelBuildServiceRegistry(startParameter, homeDir);
+        GradleInternal gradle = new DefaultGradle(null, startParameter, topLevelRegistry);
+
+        DefaultProjectDescriptor projectDescriptor = new DefaultProjectDescriptor(null, "test", projectDir, new DefaultProjectDescriptorRegistry());
+        ProjectInternal project = topLevelRegistry.get(IProjectFactory.class).createProject(projectDescriptor, null, gradle);
+
+        gradle.setRootProject(project);
+        gradle.setDefaultProject(project);
+
+        return project;
+    }
+
+    private static class NoOpLoggingManager implements LoggingManagerInternal {
+        private LogLevel stdoutLevel = LogLevel.LIFECYCLE;
+
+        public LoggingManagerInternal captureStandardOutput(LogLevel level) {
+            stdoutLevel = level;
+            return this;
+        }
+
+        public LoggingManager disableStandardOutputCapture() {
+            stdoutLevel = null;
+            return this;
+        }
+
+        public boolean isStandardOutputCaptureEnabled() {
+            return stdoutLevel != null;
+        }
+
+        public LogLevel getStandardOutputCaptureLevel() {
+            return stdoutLevel;
+        }
+
+        public LoggingManagerInternal captureStandardError(LogLevel level) {
+            return this;
+        }
+
+        public LoggingManagerInternal setLevel(LogLevel logLevel) {
+            return this;
+        }
+
+        @Override
+        public LogLevel getStandardErrorCaptureLevel() {
+            return LogLevel.ERROR;
+        }
+
+        public LoggingManagerInternal start() {
+            return this;
+        }
+
+        public LoggingManagerInternal stop() {
+            return this;
+        }
+
+        public void addStandardErrorListener(StandardOutputListener listener) {
+        }
+
+        public void addStandardOutputListener(StandardOutputListener listener) {
+        }
+
+        public void removeStandardOutputListener(StandardOutputListener listener) {
+        }
+
+        public void removeStandardErrorListener(StandardOutputListener listener) {
+        }
+    }
+
+    private static class GlobalTestServices extends DefaultServiceRegistry {
+        protected ListenerManager createListenerManager() {
+            return new DefaultListenerManager();
+        }
+
+        protected ClassPathRegistry createClassPathRegistry() {
+            return new DefaultClassPathRegistry();
+        }
+
+        protected ClassLoaderFactory createClassLoaderFactory() {
+            return new DefaultClassLoaderFactory(get(ClassPathRegistry.class));
+        }
+
+        protected CacheFactory createCacheFactory() {
+            return new AutoCloseCacheFactory(new DefaultCacheFactory());
+        }
+
+        protected ProgressLoggerFactory createProgressLoggerFactory() {
+            return new DefaultProgressLoggerFactory(get(ListenerManager.class));
+        }
+
+        protected LoggingManagerFactory createLoggingManagerFactory() {
+            return new LoggingManagerFactory() {
+                public LoggingManagerInternal create() {
+                    return new NoOpLoggingManager();
+                }
+            };
+        }
+
+        protected IsolatedAntBuilder createIsolatedAntBuilder() {
+            return new DefaultIsolatedAntBuilder(get(ClassPathRegistry.class));
+        }
+    }
+
+    private static class TestTopLevelBuildServiceRegistry extends TopLevelBuildServiceRegistry {
+        private final File homeDir;
+
+        public TestTopLevelBuildServiceRegistry(StartParameter startParameter, File homeDir) {
+            super(ProjectBuilder.GLOBAL_SERVICES, startParameter);
+            this.homeDir = homeDir;
+        }
+
+        protected GradleDistributionLocator createGradleDistributionLocator() {
+            return new GradleDistributionLocator() {
+                public File getGradleHome() {
+                    return homeDir;
+                }
+            };
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/testfixtures/package-info.java b/subprojects/gradle-core/src/main/groovy/org/gradle/testfixtures/package-info.java
new file mode 100644
index 0000000..70071c2
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/testfixtures/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Classes and interfaces for testing custom task and plugin implementations.
+ */
+package org.gradle.testfixtures;
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/AntUtil.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/AntUtil.java
new file mode 100644
index 0000000..fc621f3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/AntUtil.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+import org.apache.tools.ant.*;
+import org.gradle.api.internal.project.ant.AntLoggingAdapter;
+
+/**
+ * @author Hans Dockter
+ */
+public class AntUtil {
+    /**
+     * @return Factory method to create new Project instances
+     */
+    public static Project createProject() {
+        final Project project = new Project();
+
+        final ProjectHelper helper = ProjectHelper.getProjectHelper();
+        project.addReference(ProjectHelper.PROJECTHELPER_REFERENCE, helper);
+        helper.getImportStack().addElement("AntBuilder"); // import checks that stack is not empty
+
+        project.addBuildListener(new AntLoggingAdapter());
+
+        project.init();
+        project.getBaseDir();
+        return project;
+    }
+
+    public static void execute(Task task) {
+        task.setProject(createProject());
+        task.execute();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/ChangeListener.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/ChangeListener.java
new file mode 100644
index 0000000..db0669a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/ChangeListener.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+public interface ChangeListener<T> {
+    void added(T element);
+
+    void removed(T element);
+
+    void changed(T element);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/ClassLoaderObjectInputStream.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/ClassLoaderObjectInputStream.java
new file mode 100644
index 0000000..f0f1688
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/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.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/gradle-core/src/main/groovy/org/gradle/util/ClasspathUtil.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/ClasspathUtil.java
new file mode 100644
index 0000000..c65e8da
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/ClasspathUtil.java
@@ -0,0 +1,54 @@
+/*
+ * 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.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public class ClasspathUtil {
+    public static void addUrl(URLClassLoader classLoader, Iterable<URL> classpathElements) {
+        try {
+            Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
+            method.setAccessible(true);
+            for (URL classpathElement : classpathElements) {
+                method.invoke(classLoader, classpathElement);
+            }
+        }
+        catch (Throwable t) {
+            t.printStackTrace();
+            throw new RuntimeException("Error, could not add URL to system classloader", t);
+        }
+    }
+
+    public static List<URL> getClasspath(ClassLoader classLoader) {
+        List<URL> implementationClassPath = new ArrayList<URL>();
+        for ( ClassLoader cl = classLoader;
+                cl != ClassLoader.getSystemClassLoader().getParent(); cl = cl.getParent()) {
+            if (cl instanceof URLClassLoader) {
+                implementationClassPath.addAll(Arrays.asList(((URLClassLoader) cl).getURLs()));
+            }
+        }
+        return implementationClassPath;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/Clock.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/Clock.java
new file mode 100644
index 0000000..036167d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/Clock.java
@@ -0,0 +1,59 @@
+/*
+ * 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.util;
+
+/**
+ * @author Hans Dockter
+ */
+public class Clock {
+    long start;
+    private TimeProvider timeProvider;
+
+    private static final long MS_PER_MINUTE = 60000;
+    private static final long MS_PER_HOUR = MS_PER_MINUTE * 60;
+
+    public Clock() {
+        this(new TrueTimeProvider());
+    }
+
+    protected Clock(TimeProvider timeProvider) {
+        this.timeProvider = timeProvider;
+        reset();
+    }
+
+    public String getTime() {
+        StringBuffer result = new StringBuffer();
+        long timeInMs = getTimeInMs();
+        if (timeInMs > MS_PER_HOUR) {
+            result.append(timeInMs / MS_PER_HOUR).append(" hrs ");
+        }
+        if (timeInMs > MS_PER_MINUTE) {
+            result.append((timeInMs % MS_PER_HOUR) / MS_PER_MINUTE).append(" mins ");
+        }
+        result.append((timeInMs % MS_PER_MINUTE) / 1000.0).append(" secs");
+        return result.toString();
+    }
+
+    public long getTimeInMs() {
+        return timeProvider.getCurrentTime() - start;
+    }
+
+    public void reset() {
+        start = timeProvider.getCurrentTime();
+    }
+
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/CompositeIdGenerator.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/CompositeIdGenerator.java
new file mode 100644
index 0000000..e8007bf
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/CompositeIdGenerator.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.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/gradle-core/src/main/groovy/org/gradle/util/Configurable.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/Configurable.java
new file mode 100644
index 0000000..9e56b2d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/Configurable.java
@@ -0,0 +1,23 @@
+/*
+ * 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 groovy.lang.Closure;
+
+public interface Configurable<T> {
+    T configure(Closure cl);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/ConfigureUtil.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/ConfigureUtil.java
new file mode 100644
index 0000000..8e9052d
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/ConfigureUtil.java
@@ -0,0 +1,65 @@
+/*
+ * 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 groovy.lang.Closure;
+import groovy.lang.MissingMethodException;
+import groovy.lang.MissingPropertyException;
+
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class ConfigureUtil {
+
+    public static <T> T configure(Closure configureClosure, T delegate) {
+        return configure(configureClosure, delegate, Closure.DELEGATE_FIRST);
+    }
+
+    public static <T> T configureByMap(Map<String, ?> properties, T delegate) {
+        for (Map.Entry<String, ?> entry : properties.entrySet()) {
+            try {
+                ReflectionUtil.setProperty(delegate, entry.getKey(), entry.getValue());
+            } catch (MissingPropertyException e) {
+                // Try as a method
+                try {
+                    ReflectionUtil.invoke(delegate, entry.getKey(), new Object[]{entry.getValue()});
+                } catch (MissingMethodException mme) {
+                    // Throw the original MPE
+                    throw e;
+                }
+            }
+        }
+        return delegate;
+    }
+
+    private static <T> T configure(Closure configureClosure, T delegate, int resolveStrategy) {
+        if (configureClosure == null) {
+            return delegate;
+        }
+        Closure copy = (Closure) configureClosure.clone();
+        copy.setResolveStrategy(resolveStrategy);
+        copy.setDelegate(delegate);
+        if (copy.getMaximumNumberOfParameters() == 0) {
+            copy.call();
+        } else {
+            copy.call(delegate);
+        }
+        return delegate;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/DeleteOnExit.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/DeleteOnExit.java
new file mode 100644
index 0000000..a16ac23
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/DeleteOnExit.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import org.apache.commons.io.FileUtils;
+
+import java.util.ArrayList;
+import java.io.File;
+
+/**
+ * Provides a mechanism to delete files or whole directories on shutdown.
+ * File.deleteOnExit won't work on subdirectories that are not empty.
+ * There are some temporary files which are not currently well managed
+ * but we want to make sure that they are eventually removed
+ * @author Steve Appling
+ */
+public class DeleteOnExit {
+    private static final ArrayList<File> FILES = new ArrayList<File>();
+
+    static {
+        Runtime.getRuntime().addShutdownHook(new DeleteOnExitThread());
+    }
+
+    public static void addFile(File file) {
+        synchronized (FILES) {
+            FILES.add(file);
+        }
+    }
+
+    private static class DeleteOnExitThread extends Thread {
+        public void run() {
+            synchronized (FILES) {
+                for (File file : FILES) {
+                    FileUtils.deleteQuietly(file);
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/DeprecationLogger.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/DeprecationLogger.java
new file mode 100644
index 0000000..a34804e
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/DeprecationLogger.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+
+public class DeprecationLogger {
+    private static final Logger LOGGER = LoggerFactory.getLogger(DeprecationLogger.class);
+    private static final Set<String> METHODS = new CopyOnWriteArraySet<String>();
+
+    public static void nagUser(String methodName, String replacement) {
+        if (METHODS.add(methodName)) {
+            LOGGER.warn(String.format(
+                    "The %s method is deprecated and will be removed in the next version of Gradle. You should use the %s method instead.",
+                    methodName, replacement));
+        }
+    }
+
+    public static void nagUser(String methodName) {
+        if (METHODS.add(methodName)) {
+            LOGGER.warn(String.format("The %s method is deprecated and will be removed in the next version of Gradle.",
+                    methodName));
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/DiffUtil.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/DiffUtil.java
new file mode 100644
index 0000000..5dbe160
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/DiffUtil.java
@@ -0,0 +1,61 @@
+/*
+ * 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.apache.commons.lang.ObjectUtils;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class DiffUtil {
+    public static <T> void diff(Set<T> newSet, Set<T> oldSet, ChangeListener<? super T> changeListener) {
+        Set<T> added = new HashSet<T>(newSet);
+        added.removeAll(oldSet);
+        for (T t : added) {
+            changeListener.added(t);
+        }
+
+        Set<T> removed = new HashSet<T>(oldSet);
+        removed.removeAll(newSet);
+        for (T t : removed) {
+            changeListener.removed(t);
+        }
+    }
+    
+    public static <K, V> void diff(Map<K, V> newMap, Map<K, V> oldMap, ChangeListener<? super Map.Entry<K, V>> changeListener) {
+        Map<K, V> added = new HashMap<K, V>(newMap);
+        added.keySet().removeAll(oldMap.keySet());
+        for (Map.Entry<K, V> entry : added.entrySet()) {
+            changeListener.added(entry);
+        }
+
+        Map<K, V> removed = new HashMap<K, V>(oldMap);
+        removed.keySet().removeAll(newMap.keySet());
+        for (Map.Entry<K, V> entry : removed.entrySet()) {
+            changeListener.removed(entry);
+        }
+
+        Map<K, V> same = new HashMap<K, V>(newMap);
+        same.keySet().retainAll(oldMap.keySet());
+        for (Map.Entry<K, V> entry : same.entrySet()) {
+            if (!ObjectUtils.equals(entry.getValue(), oldMap.get(entry.getKey()))) {
+                changeListener.changed(entry);
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/FilteringClassLoader.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/FilteringClassLoader.java
new file mode 100644
index 0000000..852f2fb
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/FilteringClassLoader.java
@@ -0,0 +1,177 @@
+/*
+ * 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.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+
+/**
+ * A ClassLoader which hides all non-system classes, packages and resources. Allows certain non-system packages and
+ * classes to be declared as visible. By default, only the Java system classes, packages and resources are visible.
+ */
+public class FilteringClassLoader extends ClassLoader {
+    private static final Set<ClassLoader> SYSTEM_CLASS_LOADERS = new HashSet<ClassLoader>();
+    private static final ClassLoader EXT_CLASS_LOADER;
+    private static final Set<Package> SYSTEM_PACKAGES = new HashSet<Package>();
+    private final Set<String> packageNames = new HashSet<String>();
+    private final Set<String> packagePrefixes = new HashSet<String>();
+    private final Set<String> resourcePrefixes = new HashSet<String>();
+    private final Set<String> classNames = new HashSet<String>();
+
+    static {
+        EXT_CLASS_LOADER = ClassLoader.getSystemClassLoader().getParent();
+        for (ClassLoader cl = EXT_CLASS_LOADER; cl != null; cl = cl.getParent()) {
+            SYSTEM_CLASS_LOADERS.add(cl);
+        }
+        JavaMethod<ClassLoader, Package[]> method = new JavaMethod<ClassLoader, Package[]>(ClassLoader.class,
+                Package[].class, "getPackages");
+        SYSTEM_PACKAGES.addAll(Arrays.asList((Package[]) method.invoke(EXT_CLASS_LOADER)));
+    }
+
+    public FilteringClassLoader(ClassLoader parent) {
+        super(parent);
+    }
+
+    @Override
+    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+        Class<?> cl;
+        try {
+            cl = super.loadClass(name, false);
+        } catch (NoClassDefFoundError e) {
+            if (classAllowed(name)) {
+                throw e;
+            }
+            // The class isn't visible
+            throw new ClassNotFoundException(String.format("%s not found.", name));
+        }
+
+        if (!allowed(cl)) {
+            throw new ClassNotFoundException(String.format("%s not found.", cl.getName()));
+        }
+        if (resolve) {
+            resolveClass(cl);
+        }
+
+        return cl;
+    }
+
+    @Override
+    protected Package getPackage(String name) {
+        Package p = super.getPackage(name);
+        if (p == null || !allowed(p)) {
+            return null;
+        }
+        return p;
+    }
+
+    @Override
+    protected Package[] getPackages() {
+        List<Package> packages = new ArrayList<Package>();
+        for (Package p : super.getPackages()) {
+            if (allowed(p)) {
+                packages.add(p);
+            }
+        }
+        return packages.toArray(new Package[packages.size()]);
+    }
+
+    @Override
+    public URL getResource(String name) {
+        if (allowed(name)) {
+            return super.getResource(name);
+        }
+        return EXT_CLASS_LOADER.getResource(name);
+    }
+
+    @Override
+    public Enumeration<URL> getResources(String name) throws IOException {
+        if (allowed(name)) {
+            return super.getResources(name);
+        }
+        return EXT_CLASS_LOADER.getResources(name);
+    }
+
+    private boolean allowed(String resourceName) {
+        for (String resourcePrefix : resourcePrefixes) {
+            if (resourceName.startsWith(resourcePrefix)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean allowed(Package p) {
+        if (SYSTEM_PACKAGES.contains(p)) {
+            return true;
+        }
+        for (String packageName : packageNames) {
+            if (p.getName().equals(packageName)) {
+                return true;
+            }
+        }
+        for (String packagePrefix : packagePrefixes) {
+            if (p.getName().startsWith(packagePrefix)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean allowed(final Class<?> cl) {
+        boolean systemClass = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
+            public Boolean run() {
+                return cl.getClassLoader() == null || SYSTEM_CLASS_LOADERS.contains(cl.getClassLoader());
+            }
+        });
+        return systemClass || classAllowed(cl.getName());
+    }
+
+    private boolean classAllowed(String className) {
+        if (classNames.contains(className)) {
+            return true;
+        }
+        for (String packagePrefix : packagePrefixes) {
+            if (className.startsWith(packagePrefix)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Marks a package and all its sub-packages as visible.
+     *
+     * @param packageName The package name
+     */
+    public void allowPackage(String packageName) {
+        packageNames.add(packageName);
+        packagePrefixes.add(packageName + ".");
+        resourcePrefixes.add(packageName.replace('.', '/') + '/');
+    }
+
+    /**
+     * Marks a single class as visible
+     *
+     * @param aClass The class
+     */
+    public void allowClass(Class<?> aClass) {
+        classNames.add(aClass.getName());
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/GFileUtils.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/GFileUtils.java
new file mode 100644
index 0000000..0ab3f3a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/GFileUtils.java
@@ -0,0 +1,534 @@
+/*
+ * 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.apache.commons.io.FileUtils;
+import org.apache.commons.io.LineIterator;
+import org.apache.commons.io.filefilter.IOFileFilter;
+import org.gradle.api.UncheckedIOException;
+
+import java.io.*;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.*;
+import java.util.zip.Checksum;
+
+/**
+ * @author Hans Dockter
+ */
+public class GFileUtils {
+
+    public static final String FILE_SEPARATOR = System.getProperty("file.separator");
+
+    public static FileInputStream openInputStream(File file) {
+        try {
+            return FileUtils.openInputStream(file);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public static FileOutputStream openOutputStream(File file) {
+        try {
+            return FileUtils.openOutputStream(file);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public static String byteCountToDisplaySize(long size) {
+        return FileUtils.byteCountToDisplaySize(size);
+    }
+
+    public static void touch(File file) {
+        try {
+            FileUtils.touch(file);
+        } 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 boolean contentEquals(File file1, File file2) {
+        try {
+            return FileUtils.contentEquals(file1, file2);
+        } 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 File[] toFiles(URL[] urls) {
+        return FileUtils.toFiles(urls);
+    }
+
+    public static List<String> toPaths(Collection<File> files) {
+        List<String> paths = new ArrayList<String>();
+        for (File file : files) {
+            paths.add(file.getAbsolutePath());
+        }
+        return paths;
+    }
+
+    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 URL[] toURLArray(Collection<File> files) {
+        return toURLs(files.toArray(new File[files.size()]));
+    }
+
+    public static URL[] toURLs(File[] files) {
+        try {
+            return FileUtils.toURLs(files);
+        } 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);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public static void deleteDirectory(File directory) {
+        try {
+            FileUtils.deleteDirectory(directory);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public static boolean deleteQuietly(File file) {
+        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 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);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    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);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    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);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    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();
+        } catch (IOException e) {
+            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;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/GUtil.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/GUtil.java
new file mode 100644
index 0000000..a38fbc1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/GUtil.java
@@ -0,0 +1,300 @@
+/*
+ * 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.gradle.api.UncheckedIOException;
+import org.apache.commons.lang.StringUtils;
+
+import java.io.*;
+import java.net.URL;
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+/**
+ * @author Hans Dockter
+ */
+public class GUtil {
+    public static <T extends Collection> T flatten(Object[] elements, T addTo, boolean flattenMaps) {
+        return flatten(Arrays.asList(elements), addTo, flattenMaps);
+    }
+
+    public static <T extends Collection> T flatten(Object[] elements, T addTo) {
+        return flatten(Arrays.asList(elements), addTo);
+    }
+
+    public static <T extends Collection> T flatten(Collection elements, T addTo) {
+        return flatten(elements, addTo, true);
+    }
+
+    public static <T extends Collection> T flatten(Collection elements, T addTo, boolean flattenMaps) {
+        Iterator iter = elements.iterator();
+        while (iter.hasNext()) {
+            Object element = iter.next();
+            if (element instanceof Collection) {
+                flatten((Collection) element, addTo, flattenMaps);
+            } else if ((element instanceof Map) && flattenMaps) {
+                flatten(((Map) element).values(), addTo, flattenMaps);
+            } else if ((element.getClass().isArray()) && flattenMaps) {
+                flatten(Arrays.asList((Object[]) element), addTo, flattenMaps);
+            } else {
+                addTo.add(element);
+            }
+        }
+        return addTo;
+    }
+
+    public static List flatten(Collection elements, boolean flattenMaps) {
+        return flatten(elements, new ArrayList(), flattenMaps);
+    }
+
+    public static List flatten(Collection elements) {
+        return flatten(elements, new ArrayList());
+    }
+
+    public static String join(Collection self, String separator) {
+        StringBuffer buffer = new StringBuffer();
+        boolean first = true;
+
+        if (separator == null) {
+            separator = "";
+        }
+
+        for (Object value : self) {
+            if (first) {
+                first = false;
+            } else {
+                buffer.append(separator);
+            }
+            buffer.append(value.toString());
+        }
+        return buffer.toString();
+    }
+
+    public static String join(Object[] self, String separator) {
+        return join(Arrays.asList(self), separator);
+    }
+
+    public static List<String> prefix(String prefix, Collection<String> strings) {
+        List<String> prefixed = new ArrayList<String>();
+        for (String string : strings) {
+            prefixed.add(prefix + string);
+        }
+        return prefixed;
+    }
+
+    public static boolean isTrue(Object object) {
+        if (object == null) {
+            return false;
+        }
+        if (object instanceof Collection) {
+            return ((Collection) object).size() > 0;
+        } else if (object instanceof String) {
+            return ((String) object).length() > 0;
+        }
+        return true;
+    }
+
+    public static <T> T elvis(T object, T defaultValue) {
+        return isTrue(object) ? object : defaultValue;
+    }
+
+    public static <T> Set<T> addSets(Iterable<? extends T>... sets) {
+        return addToCollection(new HashSet<T>(), sets);
+    }
+
+    public static <T> List<T> addLists(Iterable<? extends T>... lists) {
+        return addToCollection(new ArrayList<T>(), lists);
+    }
+
+    public static <V, T extends Collection<? super V>> T addToCollection(T dest, Iterable<? extends V>... srcs) {
+        for (Iterable<? extends V> src : srcs) {
+            for (V v : src) {
+                dest.add(v);
+            }
+        }
+        return dest;
+    }
+
+    public static Comparator<String> caseInsensitive() {
+        return new Comparator<String>() {
+            @Override
+            public int compare(String o1, String o2) {
+                return o1.compareToIgnoreCase(o2);
+            }
+        };
+    }
+
+    public static Comparator<String> emptyLast(final Comparator<String> comparator) {
+        return new Comparator<String>() {
+            @Override
+            public int compare(String o1, String o2) {
+                boolean o1Empty = o1 == null || o1.isEmpty();
+                boolean o2Empty = o2 == null || o2.isEmpty();
+                if (o1Empty && o2Empty) {
+                    return 0;
+                }
+                if (o1Empty && !o2Empty) {
+                    return 1;
+                }
+                if (!o1Empty && o2Empty) {
+                    return -1;
+                }
+                return comparator.compare(o1, o2);
+            }
+        };
+    }
+    
+    public static Map addMaps(Map map1, Map map2) {
+        HashMap map = new HashMap();
+        map.putAll(map1);
+        map.putAll(map2);
+        return map;
+    }
+
+    public static void addToMap(Map<String, String> dest, Properties src) {
+        Enumeration<?> enumeration = src.propertyNames();
+        while (enumeration.hasMoreElements()) {
+            Object o = enumeration.nextElement();
+            dest.put(o.toString(), src.getProperty(o.toString()));
+        }
+    }
+
+    public static Properties loadProperties(File propertyFile) {
+        try {
+            return loadProperties(new FileInputStream(propertyFile));
+        } catch (FileNotFoundException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public static Properties loadProperties(URL url) {
+        try {
+            return loadProperties(url.openStream());
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public static Properties loadProperties(InputStream inputStream) {
+        Properties properties = new Properties();
+        try {
+            properties.load(inputStream);
+            inputStream.close();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        return properties;
+    }
+
+    public static void saveProperties(Properties properties, File propertyFile) {
+        try {
+            FileOutputStream propertiesFileOutputStream = new FileOutputStream(propertyFile);
+            properties.store(propertiesFileOutputStream, null);
+            propertiesFileOutputStream.close();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public static Map map(Object... objects) {
+        Map map = new HashMap();
+        assert objects.length % 2 == 0;
+        for (int i = 0; i < objects.length; i += 2) {
+            map.put(objects[i], objects[i + 1]);
+        }
+        return map;
+    }
+
+    public static String toString(Iterable<String> names) {
+        Formatter formatter = new Formatter();
+        boolean first = true;
+        for (String name : names) {
+            if (first) {
+                formatter.format("'%s'", name);
+                first = false;
+            } else {
+                formatter.format(", '%s'", name);
+            }
+        }
+        return formatter.toString();
+    }
+
+    /**
+     * Converts an arbitrary string to a camel-case string which can be used in a Java identifier. Eg, with_underscores
+     * -> withUnderscored
+     */
+    public static String toCamelCase(CharSequence string) {
+        if (string == null) {
+            return null;
+        }
+        StringBuilder builder = new StringBuilder();
+        Matcher matcher = Pattern.compile("[^\\w]+").matcher(string);
+        int pos = 0;
+        while (matcher.find()) {
+            builder.append(StringUtils.capitalize(string.subSequence(pos, matcher.start()).toString()));
+            pos = matcher.end();
+        }
+        builder.append(StringUtils.capitalize(string.subSequence(pos, string.length()).toString()));
+        return builder.toString();
+    }
+
+    /**
+     * Converts an arbitrary string to space-separated words. Eg, camelCase -> camel case, with_underscores -> with
+     * underscores
+     */
+    public static String toWords(CharSequence string) {
+        if (string == null) {
+            return null;
+        }
+        StringBuilder builder = new StringBuilder();
+        int pos = 0;
+        boolean inSeparator = false;
+        for (; pos < string.length(); pos++) {
+            char ch = string.charAt(pos);
+            if (Character.isLowerCase(ch)) {
+                if (inSeparator && builder.length() > 0) {
+                    builder.append(' ');
+                }
+                builder.append(ch);
+                inSeparator = false;
+            } else if (Character.isUpperCase(ch)) {
+                if (builder.length() > 0) {
+                    builder.append(' ');
+                }
+                builder.append(Character.toLowerCase(ch));
+                inSeparator = false;
+            } else {
+                inSeparator = true;
+            }
+        }
+
+        return builder.toString();
+    }
+
+    public static byte[] serialize(Object object) {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        try {
+            ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
+            objectOutputStream.writeObject(object);
+            objectOutputStream.close();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        return outputStream.toByteArray();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/GradleVersion.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/GradleVersion.java
new file mode 100644
index 0000000..664ecda
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/GradleVersion.java
@@ -0,0 +1,72 @@
+/*
+ * 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.codehaus.groovy.runtime.InvokerHelper ;
+import org.apache.ivy.Ivy ;
+import org.apache.tools.ant.Main ;
+
+import java.util.Properties ;
+
+/**
+ * @author Hans Dockter
+ * @author Russel Winder
+ */
+public class GradleVersion {
+  private final static String BUILD_TIME = "buildTime" ;
+  private final static String VERSION = "version" ;
+  private final static String FILE_NAME = "/org/gradle/version.properties" ;
+  public final static String URL = "http://www.gradle.org" ;
+
+  private final Properties versionProperties ;
+
+  public GradleVersion ( ) {
+    versionProperties = GUtil.loadProperties ( getClass ( ).getResourceAsStream ( FILE_NAME ) ) ;
+  }
+
+  public String getVersion ( ) {
+    return versionProperties.getProperty ( VERSION ) ;
+  }
+  
+  public String getBuildTime ( ) {
+    return versionProperties.getProperty ( BUILD_TIME ) ;
+  }
+  
+  public String prettyPrint ( ) {
+    final StringBuilder sb = new StringBuilder ( ) ;
+    sb.append ( "\n------------------------------------------------------------\nGradle " ) ;
+    sb.append ( getVersion ( ) ) ;
+    sb.append ( "\n------------------------------------------------------------\n\nGradle buildtime: " ) ;
+    sb.append ( getBuildTime ( ) ) ;
+    sb.append ( "\nGroovy: " ) ;
+    sb.append ( InvokerHelper.getVersion ( ) ) ;
+    sb.append ( "\nAnt: " ) ;
+    sb.append ( Main.getAntVersion ( ) ) ;
+    sb.append ( "\nIvy: " ) ;
+    sb.append ( Ivy.getIvyVersion ( ) ) ;
+    sb.append ( "\nJava: " ) ;
+    sb.append ( System.getProperty ( "java.version" ) ) ;
+    sb.append ( "\nJVM: " ) ;
+    sb.append ( System.getProperty ( "java.vm.version" ) ) ;
+    sb.append ( "\nJVM Vendor: " ) ;
+    sb.append ( System.getProperty ( "java.vm.vendor" ) ) ;
+    sb.append ( "\nOS Name: " ) ;
+    sb.append ( System.getProperty ( "os.name" ) ) ;
+    sb.append ( "\n" ) ;
+    return sb.toString ( ) ;
+  }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/HashUtil.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/HashUtil.java
new file mode 100644
index 0000000..2548385
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/HashUtil.java
@@ -0,0 +1,69 @@
+/*
+ * 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;
+
+import org.gradle.api.UncheckedIOException;
+
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.io.File;
+import java.io.InputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+/**
+ * @author Hans Dockter
+ */
+public class HashUtil {
+    public static String createHash(String scriptText) {
+        MessageDigest messageDigest;
+        try {
+            messageDigest = MessageDigest.getInstance("MD5");
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        }
+        messageDigest.update(scriptText.getBytes());
+        return new BigInteger(1, messageDigest.digest()).toString(16);
+    }
+
+    public static byte[] createHash(File file) {
+        MessageDigest messageDigest;
+        try {
+            messageDigest = MessageDigest.getInstance("MD5");
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        }
+        try {
+            byte[] buffer = new byte[4096];
+            InputStream instr = new FileInputStream(file);
+            try {
+                while (true) {
+                    int nread = instr.read(buffer);
+                    if (nread < 0) {
+                        break;
+                    }
+                    messageDigest.update(buffer, 0, nread);
+                }
+            } finally {
+                instr.close();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        return messageDigest.digest();
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/IdGenerator.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/IdGenerator.java
new file mode 100644
index 0000000..9c5add0
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/IdGenerator.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.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/gradle-core/src/main/groovy/org/gradle/util/IgnoreInterruptHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/IgnoreInterruptHandler.java
new file mode 100644
index 0000000..8200978
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/IgnoreInterruptHandler.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class IgnoreInterruptHandler<T> implements InterruptHandler<T> {
+    public boolean handleIterrupt(T interruptedThead, InterruptedException interruptedException) {
+        return false;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/InterruptHandler.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/InterruptHandler.java
new file mode 100644
index 0000000..84c6aaa
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/InterruptHandler.java
@@ -0,0 +1,24 @@
+/*
+ * 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;
+
+/**
+ * @author Tom Eyckmans
+ */
+public interface InterruptHandler<T> {
+    boolean handleIterrupt(T interrupted, InterruptedException interruptedException);
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/JarUtil.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/JarUtil.java
new file mode 100644
index 0000000..998c81a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/JarUtil.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.*;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipEntry;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class JarUtil {
+    public static boolean extractZipEntry(File jarFile, String entryName, File extractToFile) throws IOException {
+        boolean entryExtracted = false;
+
+        ZipInputStream zipStream = null;
+        BufferedOutputStream extractTargetStream = null;
+        try {
+            zipStream = new ZipInputStream(new FileInputStream(jarFile));
+            extractTargetStream = new BufferedOutputStream(new FileOutputStream(extractToFile));
+
+            boolean classFileExtracted = false;
+            boolean zipStreamEndReached = false;
+            while (!classFileExtracted && !zipStreamEndReached) {
+                final ZipEntry candidateZipEntry = zipStream.getNextEntry();
+
+                if (candidateZipEntry == null) {
+                    zipStreamEndReached = true;
+                } else {
+                    if (candidateZipEntry.getName().equals(entryName)) {
+                        IOUtils.copy(zipStream, extractTargetStream);
+                        classFileExtracted = true;
+                        entryExtracted = true;
+                    }
+                }
+            }
+        } finally {
+            IOUtils.closeQuietly(zipStream);
+            IOUtils.closeQuietly(extractTargetStream);
+        }
+
+        return entryExtracted;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/JavaMethod.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/JavaMethod.java
new file mode 100644
index 0000000..d6934ce
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/JavaMethod.java
@@ -0,0 +1,61 @@
+/*
+ * 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.gradle.api.GradleException;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+
+public class JavaMethod<T, R> {
+    private final Method method;
+    private final Class<R> returnType;
+
+    public JavaMethod(Class<T> target, Class<R> returnType, String name, Class<?> ... paramTypes) {
+        this.returnType = returnType;
+        method = findMethod(target, name, paramTypes);
+        method.setAccessible(true);
+    }
+
+    private Method findMethod(Class target, String name, Class<?>[] paramTypes) {
+        for (Method method : target.getDeclaredMethods()) {
+            if (Modifier.isStatic(method.getModifiers())) {
+                continue;
+            }
+            if (method.getName().equals(name) && Arrays.equals(method.getParameterTypes(), paramTypes)) {
+                return method;
+            }
+        }
+        throw new GradleException(String.format("Could not find method %s(%s) on %s", name, Arrays.toString(paramTypes),
+                target));
+    }
+
+    public R invoke(T target, Object... args) {
+        try {
+            return returnType.cast(method.invoke(target, args));
+        } catch (InvocationTargetException e) {
+            Throwable cause = e.getCause();
+            if (cause instanceof RuntimeException) {
+                throw (RuntimeException) cause;
+            }
+            throw new GradleException(String.format("Could not call %s.%s() on %s", method.getDeclaringClass().getSimpleName(), method.getName(), target), cause);
+        } catch (Exception e) {
+            throw new GradleException(String.format("Could not call %s.%s() on %s", method.getDeclaringClass().getSimpleName(), method.getName(), target), e);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/Jvm.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/Jvm.java
new file mode 100644
index 0000000..54b0bd8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/Jvm.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.util;
+
+import org.apache.tools.ant.util.JavaEnvUtils;
+
+import java.io.File;
+
+public class Jvm {
+    public static Jvm current() {
+        return new Jvm();
+    }
+
+    Jvm() {
+    }
+
+    public File getJavaExecutable() {
+        return new File(JavaEnvUtils.getJdkExecutable("java"));
+    }
+
+    public File getJavadocExecutable() {
+        return new File(JavaEnvUtils.getJdkExecutable("javadoc"));
+    }
+
+    public boolean isJava5Compatible() {
+        return System.getProperty("java.version").startsWith("1.5") || isJava6Compatible();
+    }
+
+    public boolean isJava6Compatible() {
+        return System.getProperty("java.version").startsWith("1.6");
+    }
+
+    public File getToolsJar() {
+        File javaHome = new File(System.getProperty("java.home"));
+        File toolsJar = new File(javaHome, "/lib/tools.jar");
+        if (toolsJar.exists()) {
+            return toolsJar;
+        }
+        if (javaHome.getName().equalsIgnoreCase("jre")) {
+            javaHome = javaHome.getParentFile();
+            toolsJar = new File(javaHome + "/lib/tools.jar");
+        }
+        if (!toolsJar.exists()) {
+            return null;
+        }
+        return toolsJar;
+    }
+
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/LineBufferingOutputStream.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/LineBufferingOutputStream.java
new file mode 100644
index 0000000..f9075d1
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/LineBufferingOutputStream.java
@@ -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.util;
+
+import org.gradle.api.Action;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An OutputStream which separates bytes written into lines. Uses the platform default encoding. Is not thread safe.
+ *
+ * @author Hans Dockter
+ */
+public class LineBufferingOutputStream extends OutputStream {
+    private boolean hasBeenClosed;
+    private final byte[] lineSeparator;
+    private final int bufferIncrement;
+    private byte[] buf;
+    private int count;
+    private final Output output;
+
+    public LineBufferingOutputStream(Action<String> action) {
+        this(action, false, 2048);
+    }
+
+    public LineBufferingOutputStream(Action<String> action, boolean includeEOL) {
+        this(action, includeEOL, 2048);
+    }
+
+    public LineBufferingOutputStream(Action<String> action, boolean includeEOL, int bufferLength) {
+        this(new StringOutput(includeEOL, action), bufferLength);
+    }
+
+    private LineBufferingOutputStream(Output output, int bufferLength) {
+        this.output = output;
+        bufferIncrement = bufferLength;
+        buf = new byte[bufferLength];
+        count = 0;
+        lineSeparator = System.getProperty("line.separator").getBytes();
+    }
+
+    /**
+     * Closes this output stream and releases any system resources associated with this stream. The general contract of
+     * <code>close</code> is that it closes the output stream. A closed stream cannot perform output operations and
+     * cannot be reopened.
+     */
+    public void close() throws IOException {
+        flush();
+        hasBeenClosed = true;
+    }
+
+    /**
+     * Writes the specified byte to this output stream. The general contract for <code>write</code> is that one byte is
+     * written to the output stream. The byte to be written is the eight low-order bits of the argument <code>b</code>.
+     * The 24 high-order bits of <code>b</code> are ignored.
+     *
+     * @param b the <code>byte</code> to write
+     * @throws java.io.IOException if an I/O error occurs. In particular, an <code>IOException</code> may be thrown if
+     * the output stream has been closed.
+     */
+    public void write(final int b) throws IOException {
+        if (hasBeenClosed) {
+            throw new IOException("The stream has been closed.");
+        }
+
+        if (count == buf.length) {
+            // grow the buffer
+            final int newBufLength = buf.length + bufferIncrement;
+            final byte[] newBuf = new byte[newBufLength];
+
+            System.arraycopy(buf, 0, newBuf, 0, buf.length);
+            buf = newBuf;
+        }
+
+        buf[count] = (byte) b;
+        count++;
+        if (endsWithLineSeparator()) {
+            flush();
+        }
+    }
+
+    private boolean endsWithLineSeparator() {
+        if (count < lineSeparator.length) {
+            return false;
+        }
+        for (int i = 0; i < lineSeparator.length; i++) {
+            if (buf[count - lineSeparator.length + i] != lineSeparator[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Flushes this output stream and forces any buffered output bytes to be written out. The general contract of
+     * <code>flush</code> is that calling it is an indication that, if any bytes previously written have been buffered
+     * by the implementation of the output stream, such bytes should immediately be written to their intended
+     * destination.
+     */
+    public void flush() throws IOException {
+        if (count != 0) {
+            int length = count;
+            if (endsWithLineSeparator()) {
+                length -= lineSeparator.length;
+            }
+            output.write(buf, length, count);
+        }
+        reset();
+    }
+
+    private void reset() {
+        if (buf.length > bufferIncrement) {
+            buf = new byte[bufferIncrement];
+        }
+        count = 0;
+    }
+
+    private interface Output {
+        void write(byte[] buffer, int textLength, int lineLength) throws IOException;
+    }
+
+    private static class StringOutput implements Output {
+        private final boolean includeEOL;
+        private final Action<String> action;
+
+        public StringOutput(boolean includeEOL, Action<String> action) {
+            this.includeEOL = includeEOL;
+            this.action = action;
+        }
+
+        public void write(byte[] buffer, int textLength, int lineLength) throws IOException {
+            if (includeEOL) {
+                action.execute(new String(buffer, 0, lineLength));
+            } else {
+                action.execute(new String(buffer, 0, textLength));
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/LinePerThreadBufferingOutputStream.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/LinePerThreadBufferingOutputStream.java
new file mode 100644
index 0000000..1ae192a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/LinePerThreadBufferingOutputStream.java
@@ -0,0 +1,210 @@
+/*
+ * 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.gradle.api.Action;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Locale;
+
+public class LinePerThreadBufferingOutputStream extends PrintStream {
+    private final Action<String> listener;
+    private final boolean includeEol;
+    private final ThreadLocal<PrintStream> stream = new ThreadLocal<PrintStream>(){
+        @Override
+        protected PrintStream initialValue() {
+            return AccessController.doPrivileged(new PrivilegedAction<PrintStream>() {
+                public PrintStream run() {
+                    return new PrintStream(new LineBufferingOutputStream(listener, includeEol));
+                }
+            });
+        }
+    };
+
+    public LinePerThreadBufferingOutputStream(Action<String> listener) {
+        this(listener, false);
+    }
+
+    public LinePerThreadBufferingOutputStream(Action<String> listener, boolean includeEol) {
+        super(new ByteArrayOutputStream(), true);
+        this.listener = listener;
+        this.includeEol = includeEol;
+    }
+
+    private PrintStream getStream() {
+        return stream.get();
+    }
+
+    @Override
+    public PrintStream append(CharSequence csq, int start, int end) {
+        getStream().append(csq, start, end);
+        return this;
+    }
+
+    @Override
+    public boolean checkError() {
+        return getStream().checkError();
+    }
+
+    @Override
+    public void close() {
+        getStream().close();
+    }
+
+    @Override
+    public void flush() {
+        getStream().flush();
+    }
+
+    @Override
+    public PrintStream format(String format, Object... args) {
+        getStream().format(format, args);
+        return this;
+    }
+
+    @Override
+    public PrintStream format(Locale l, String format, Object... args) {
+        getStream().format(l, format, args);
+        return this;
+    }
+
+    @Override
+    public void print(boolean b) {
+        getStream().print(b);
+    }
+
+    @Override
+    public void print(char c) {
+        getStream().print(c);
+    }
+
+    @Override
+    public void print(double d) {
+        getStream().print(d);
+    }
+
+    @Override
+    public void print(float f) {
+        getStream().print(f);
+    }
+
+    @Override
+    public void print(int i) {
+        getStream().print(i);
+    }
+
+    @Override
+    public void print(long l) {
+        getStream().print(l);
+    }
+
+    @Override
+    public void print(Object obj) {
+        getStream().print(obj);
+    }
+
+    @Override
+    public void print(char[] s) {
+        getStream().print(s);
+    }
+
+    @Override
+    public void print(String s) {
+        getStream().print(s);
+    }
+
+    @Override
+    public PrintStream printf(String format, Object... args) {
+        getStream().printf(format, args);
+        return this;
+    }
+
+    @Override
+    public PrintStream printf(Locale l, String format, Object... args) {
+        getStream().printf(l, format, args);
+        return this;
+    }
+
+    @Override
+    public void println() {
+        getStream().println();
+    }
+
+    @Override
+    public void println(boolean x) {
+        getStream().println(x);
+    }
+
+    @Override
+    public void println(char x) {
+        getStream().println(x);
+    }
+
+    @Override
+    public void println(char[] x) {
+        getStream().println(x);
+    }
+
+    @Override
+    public void println(double x) {
+        getStream().println(x);
+    }
+
+    @Override
+    public void println(float x) {
+        getStream().println(x);
+    }
+
+    @Override
+    public void println(int x) {
+        getStream().println(x);
+    }
+
+    @Override
+    public void println(long x) {
+        getStream().println(x);
+    }
+
+    @Override
+    public void println(Object x) {
+        getStream().println(x);
+    }
+
+    @Override
+    public void println(String x) {
+        getStream().println(x);
+    }
+
+    @Override
+    public void write(int b) {
+        getStream().write(b);
+    }
+
+    @Override
+    public void write(byte[] buf, int off, int len) {
+        getStream().write(buf, off, len);
+    }
+
+    @Override
+    public void write(byte[] b) throws IOException {
+        getStream().write(b);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/LongIdGenerator.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/LongIdGenerator.java
new file mode 100644
index 0000000..155bae2
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/LongIdGenerator.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.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/gradle-core/src/main/groovy/org/gradle/util/MultiParentClassLoader.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/MultiParentClassLoader.java
new file mode 100644
index 0000000..e1767d7
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/MultiParentClassLoader.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.util;
+
+import java.util.*;
+import java.net.URL;
+import java.io.IOException;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * A {@code ClassLoader} which delegates to multiple parent ClassLoaders.
+ */
+public class MultiParentClassLoader extends ClassLoader {
+    private final List<ClassLoader> parents;
+    private final JavaMethod<ClassLoader, Package[]> getPackagesMethod;
+    private final JavaMethod<ClassLoader, Package> getPackageMethod;
+
+    public MultiParentClassLoader(ClassLoader... parents) {
+        super(parents.length == 0 ? null : parents[0]);
+        this.parents = new CopyOnWriteArrayList<ClassLoader>(Arrays.asList(parents));
+        getPackagesMethod = new JavaMethod<ClassLoader, Package[]>(ClassLoader.class, Package[].class, "getPackages");
+        getPackageMethod = new JavaMethod<ClassLoader, Package>(ClassLoader.class, Package.class, "getPackage", String.class);
+    }
+
+    public void addParent(ClassLoader parent) {
+        parents.add(parent);
+    }
+
+    @Override
+    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+        for (ClassLoader parent : parents) {
+            try {
+                return parent.loadClass(name);
+            } catch (ClassNotFoundException e) {
+                // Expected
+            }
+        }
+        throw new ClassNotFoundException(String.format("%s not found.", name));
+    }
+
+    @Override
+    protected Package getPackage(String name) {
+        for (ClassLoader parent : parents) {
+            Package p = getPackageMethod.invoke(parent, name);
+            if (p != null) {
+                return p;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    protected Package[] getPackages() {
+        Set<Package> packages = new LinkedHashSet<Package>();
+        for (ClassLoader parent : parents) {
+            Package[] parentPackages = getPackagesMethod.invoke(parent);
+            packages.addAll(Arrays.asList(parentPackages));
+        }
+        return packages.toArray(new Package[packages.size()]);
+    }
+
+    @Override
+    public URL getResource(String name) {
+        for (ClassLoader parent : parents) {
+            URL resource = parent.getResource(name);
+            if (resource != null) {
+                return resource;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Enumeration<URL> getResources(String name) throws IOException {
+        Set<URL> resources = new LinkedHashSet<URL>();
+        for (ClassLoader parent : parents) {
+            Enumeration<URL> parentResources = parent.getResources(name);
+            while (parentResources.hasMoreElements()) {
+                resources.add(parentResources.nextElement());
+            }
+        }
+        return Collections.enumeration(resources);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/NameMatcher.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/NameMatcher.java
new file mode 100644
index 0000000..a9474ed
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/NameMatcher.java
@@ -0,0 +1,145 @@
+/*
+ * 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.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * Selects a single item from a list of candidates based on a camel-case pattern.
+ */
+public class NameMatcher {
+    private final SortedSet<String> matches = new TreeSet<String>();
+    private final Set<String> candidates = new TreeSet<String>();
+    private String pattern;
+
+    /**
+     * Locates the best match for the given pattern in the given set of candidate items.
+     *
+     * @return The matching item if exactly 1 match found, null if no matches or multiple matches.
+     */
+    public <T> T find(String pattern, Map<String, ? extends T> items) {
+        String name = find(pattern, items.keySet());
+        if (name != null) {
+            return items.get(name);
+        }
+        return null;
+    }
+    
+    /**
+     * Locates the best match for the given pattern in the given set of candidate items.
+     *
+     * @return The match if exactly 1 match found, null if no matches or multiple matches.
+     */
+    public String find(String pattern, Collection<String> items) {
+        this.pattern = pattern;
+        matches.clear();
+        candidates.clear();
+
+        if (items.contains(pattern)) {
+            matches.add(pattern);
+            return pattern;
+        }
+
+        if (pattern.length() == 0) {
+            return null;
+        }
+
+        Pattern camelCasePattern = getPatternForName(pattern);
+        Pattern normalisedCamelCasePattern = Pattern.compile(camelCasePattern.pattern(), Pattern.CASE_INSENSITIVE);
+        String normalisedPattern = pattern.toUpperCase();
+
+        Set<String> matches1 = new TreeSet<String>();
+        Set<String> matches2 = new TreeSet<String>();
+
+        for (String candidate : items) {
+            if (camelCasePattern.matcher(candidate).matches()) {
+                matches1.add(candidate);
+                continue;
+            }
+            if (normalisedCamelCasePattern.matcher(candidate).lookingAt()) {
+                matches2.add(candidate);
+                continue;
+            }
+            if (StringUtils.getLevenshteinDistance(normalisedPattern, candidate.toUpperCase()) <= Math.min(3,
+                    pattern.length() / 2)) {
+                candidates.add(candidate);
+            }
+        }
+
+        if (!matches1.isEmpty()) {
+            matches.addAll(matches1);
+        }
+        else {
+            matches.addAll(matches2);
+        }
+
+        if (matches.size() == 1) {
+            return matches.first();
+        }
+
+        return null;
+    }
+
+    private static Pattern getPatternForName(String name) {
+        Pattern boundaryPattern = Pattern.compile("((^|\\p{Punct})\\p{javaLowerCase}+)|(\\p{javaUpperCase}\\p{javaLowerCase}*)");
+        Matcher matcher = boundaryPattern.matcher(name);
+        int pos = 0;
+        StringBuilder builder = new StringBuilder();
+        while (matcher.find()) {
+            String prefix = name.substring(pos, matcher.start());
+            if (prefix.length() > 0) {
+                builder.append(Pattern.quote(prefix));
+            }
+            builder.append(Pattern.quote(matcher.group()));
+            builder.append("[\\p{javaLowerCase}\\p{Digit}]*");
+            pos = matcher.end();
+        }
+        builder.append(Pattern.quote(name.substring(pos, name.length())));
+        return Pattern.compile(builder.toString());
+    }
+
+    /**
+     * Returns all matches, when there were more than 1.
+     *
+     * @return The matches. Returns an empty set when there are no matches.
+     */
+    public Set<String> getMatches() {
+        return matches;
+    }
+
+    /**
+     * Returns the potential matches, if any.
+     *
+     * @return The matches. Returns an empty set when there are no potential matches.
+     */
+    public Set<String> getCandidates() {
+        return candidates;
+    }
+
+    public String formatErrorMessage(String singularItemDescription, Object container) {
+        String capItem = StringUtils.capitalize(singularItemDescription);
+        if (!matches.isEmpty()) {
+            return String.format("%s '%s' is ambiguous in %s. Candidates are: %s.", capItem, pattern, container, GUtil.toString(matches));
+        }
+        if (!candidates.isEmpty()) {
+            return String.format("%s '%s' not found in %s. Some candidates are: %s.", capItem, pattern, container, GUtil.toString(candidates));
+        }
+        return String.format("%s '%s' not found in %s.", capItem, pattern, container);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/NoOpChangeListener.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/NoOpChangeListener.java
new file mode 100644
index 0000000..5093b74
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/NoOpChangeListener.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+public class NoOpChangeListener<T> implements ChangeListener<T> {
+    public void added(T element) {
+    }
+
+    public void removed(T element) {
+    }
+
+    public void changed(T element) {
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/ObservableUrlClassLoader.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/ObservableUrlClassLoader.java
new file mode 100644
index 0000000..58f75a3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/ObservableUrlClassLoader.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.util;
+
+import org.gradle.api.Action;
+import org.gradle.listener.ListenerBroadcast;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Collection;
+
+public class ObservableUrlClassLoader extends URLClassLoader {
+    private final ListenerBroadcast<Action> broadcast = new ListenerBroadcast<Action>(Action.class);
+
+    public ObservableUrlClassLoader(ClassLoader parent, URL... urls) {
+        super(urls, parent);
+    }
+
+    public ObservableUrlClassLoader(ClassLoader parent, Collection<URL> urls) {
+        super(urls.toArray(new URL[urls.size()]), parent);
+    }
+
+    public void whenUrlAdded(Action<? super ObservableUrlClassLoader> action) {
+        broadcast.add(action);
+    }
+
+    @Override
+    public void addURL(URL url) {
+        super.addURL(url);
+        broadcast.getSource().execute(this);
+    }
+
+    public void addURLs(Iterable<URL> urls) {
+        for (URL url : urls) {
+            addURL(url);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/OperatingSystem.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/OperatingSystem.java
new file mode 100644
index 0000000..26ccec6
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/OperatingSystem.java
@@ -0,0 +1,80 @@
+/*
+ * 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;
+
+public class OperatingSystem {
+    private static final OperatingSystem WINDOWS = new OperatingSystem() {
+        @Override
+        public boolean isCaseSensitiveFileSystem() {
+            return false;
+        }
+
+        @Override
+        public boolean isWindows() {
+            return true;
+        }
+
+        @Override
+        public String getScriptName(String scriptPath) {
+            if (scriptPath.toLowerCase().endsWith(".bat")) {
+                return scriptPath;
+            }
+            return scriptPath + ".bat";
+        }
+    };
+
+    private static final OperatingSystem OS_X = new OperatingSystem() {
+        @Override
+        public boolean isCaseSensitiveFileSystem() {
+            return false;
+        }
+    };
+
+    private static final OperatingSystem OTHER = new OperatingSystem();
+    private static final OperatingSystem CURRENT;
+
+    static {
+        String osName = System.getProperty("os.name").toLowerCase();
+        if (osName.contains("windows")) {
+            CURRENT = WINDOWS;
+        } else if (osName.contains("mac os x")) {
+            CURRENT = OS_X;
+        } else {
+            CURRENT = OTHER;
+        }
+    }
+
+    public static OperatingSystem current() {
+        return CURRENT;
+    }
+
+    public boolean isWindows() {
+        return false;
+    }
+
+    public boolean isUnix() {
+        // Not quite true
+        return !isWindows();
+    }
+
+    public boolean isCaseSensitiveFileSystem() {
+        return true;
+    }
+
+    public String getScriptName(String scriptPath) {
+        return scriptPath;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/PathHelper.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/PathHelper.java
new file mode 100644
index 0000000..8fa6cd3
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/PathHelper.java
@@ -0,0 +1,32 @@
+/*
+ * 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.util;
+
+import org.gradle.api.Project;
+import org.gradle.api.InvalidUserDataException;
+
+/**
+ * @author Hans Dockter
+ */
+public class PathHelper {
+    public static boolean isAbsolutePath(String path) {
+        if (!GUtil.isTrue(path)) {
+            throw new InvalidUserDataException("A path must be specified!");
+        }
+        return path.startsWith(Project.PATH_SEPARATOR);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/PosixUtil.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/PosixUtil.java
new file mode 100644
index 0000000..96c90a8
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/PosixUtil.java
@@ -0,0 +1,75 @@
+/*
+ * 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.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 = POSIXFactory.getPOSIX(new POSIXHandlerImpl(), true);
+
+    public static POSIX current() {
+        return POSIX;
+    }
+    
+    private static class POSIXHandlerImpl implements POSIXHandler {
+        public void error(POSIX.ERRORS errors, String message) {
+            throw new UnsupportedOperationException();
+        }
+
+        public void unimplementedError(String message) {
+            throw new UnsupportedOperationException();
+        }
+
+        public void warn(WARNING_ID warningId, String message, Object... objects) {
+        }
+
+        public boolean isVerbose() {
+            return false;
+        }
+
+        public File getCurrentWorkingDirectory() {
+            throw new UnsupportedOperationException();
+        }
+
+        public String[] getEnv() {
+            throw new UnsupportedOperationException();
+        }
+
+        public InputStream getInputStream() {
+            return System.in;
+        }
+
+        public PrintStream getOutputStream() {
+            return System.out;
+        }
+
+        public int getPID() {
+            throw new UnsupportedOperationException();
+        }
+
+        public PrintStream getErrorStream() {
+            return System.err;
+        }
+    }
+
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/RandomLongIdGenerator.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/RandomLongIdGenerator.java
new file mode 100644
index 0000000..d79692a
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/RandomLongIdGenerator.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.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/gradle-core/src/main/groovy/org/gradle/util/ReflectionUtil.groovy b/subprojects/gradle-core/src/main/groovy/org/gradle/util/ReflectionUtil.groovy
new file mode 100644
index 0000000..cdbe9ac
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/ReflectionUtil.groovy
@@ -0,0 +1,41 @@
+/*
+ * 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
+
+/**
+ * @author Hans Dockter
+ */
+class ReflectionUtil {
+    public static Object invoke(Object object, String method, Object ... params) {
+        return object.invokeMethod(method, params)
+    }
+
+    static <T> T newInstance(Class cl, Object ... args) {
+        return cl.newInstance(args)
+    }
+
+    static Object getProperty(def object, String property) {
+        object.getProperty(property)
+    }
+
+    static void setProperty(def object, String property, Object value) {
+        object."$property" = value
+    }
+
+    static boolean hasProperty(def object, String property) {
+        return object.metaClass.hasProperty(object, property)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/TimeProvider.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/TimeProvider.java
new file mode 100644
index 0000000..cde7956
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/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.util;
+
+public interface TimeProvider {
+
+    long getCurrentTime();
+
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/TrueTimeProvider.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/TrueTimeProvider.java
new file mode 100644
index 0000000..a4868f9
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/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.util;
+
+public class TrueTimeProvider implements TimeProvider {
+
+    public long getCurrentTime() {
+        return System.currentTimeMillis();
+    }
+
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/UncheckedException.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/UncheckedException.java
new file mode 100644
index 0000000..f6b48ad
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/UncheckedException.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+/**
+ * Wraps a checked exception. Carries no other context.
+ */
+public final class UncheckedException extends RuntimeException {
+    public UncheckedException(Throwable cause) {
+        super(cause);
+    }
+
+    public static RuntimeException asUncheckedException(Throwable t) {
+        if (t instanceof RuntimeException) {
+            return (RuntimeException) t;
+        }
+        return new UncheckedException(t);
+    }
+}
diff --git a/subprojects/gradle-core/src/main/groovy/org/gradle/util/WrapUtil.java b/subprojects/gradle-core/src/main/groovy/org/gradle/util/WrapUtil.java
new file mode 100644
index 0000000..fd8c467
--- /dev/null
+++ b/subprojects/gradle-core/src/main/groovy/org/gradle/util/WrapUtil.java
@@ -0,0 +1,120 @@
+/*
+ * 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.util;
+
+import java.util.*;
+
+/**
+ * Common methods to wrap objects in generic collections.
+ *
+ */
+public class WrapUtil {
+    /**
+     * Wraps the given items in a mutable unordered set.
+     */
+    public static <T> Set<T> toSet(T... items) {
+        Set<T> coll = new HashSet<T>();
+        Collections.addAll(coll, items);
+        return coll;
+    }
+
+    /**
+     * Wraps the given items in a mutable ordered set.
+     */
+    public static <T> Set<T> toLinkedSet(T... items) {
+        Set<T> coll = new LinkedHashSet<T>();
+        Collections.addAll(coll, items);
+        return coll;
+    }
+
+    /**
+     * Wraps the given items in a mutable sorted set.
+     */
+    public static <T> SortedSet<T> toSortedSet(T... items) {
+        SortedSet<T> coll = new TreeSet<T>();
+        Collections.addAll(coll, items);
+        return coll;
+    }
+
+    /**
+     * Wraps the given items in a mutable sorted set using the given comparator.
+     */
+    public static <T> SortedSet<T> toSortedSet(Comparator<T> comp, T... items) {
+        SortedSet<T> coll = new TreeSet<T>(comp);
+        Collections.addAll(coll, items);
+        return coll;
+    }
+
+    /**
+     * Wraps the given items in a mutable list.
+     */
+    public static <T> List<T> toList(T... items) {
+        ArrayList<T> coll = new ArrayList<T>();
+        Collections.addAll(coll, items);
+        return coll;
+    }
+
+    /**
+     * Wraps the given items in a mutable list.
+     */
+    public static <T> List<T> toList(Iterable<? extends T> items) {
+        ArrayList<T> coll = new ArrayList<T>();
+        for (T item : items) {
+            coll.add(item);
+        }
+        return coll;
+    }
+
+    /**
+     * Wraps the given key and value in a mutable unordered map.
+     */
+    public static <K, V> Map<K, V> toMap(K key, V value) {
+        Map<K, V> map = new HashMap<K, V>();
+        map.put(key, value);
+        return map;
+    }
+
+    /**
+     * Wraps the given key and value in a mutable sorted map.
+     */
+    public static <K, V> SortedMap<K, V> toSortedMap(K key, V value) {
+        SortedMap<K, V> map = new TreeMap<K,V>();
+        map.put(key, value);
+        return map;
+    }
+
+    /**
+     * Wraps the given key and value in a mutable ordered map.
+     */
+    public static <K, V> Map<K, V> toLinkedMap(K key, V value) {
+        Map<K, V> map = new LinkedHashMap<K, V>();
+        map.put(key, value);
+        return map;
+    }
+
+    /**
+     * Wraps the given key and value in a mutable properties instance.
+     */
+    public static Properties toProperties(String key, String value) {
+        Properties props = new Properties();
+        props.setProperty(key, value);
+        return props;
+    }
+
+    public static <T> T[] toArray(T... items) {
+        return items;
+    }
+}
diff --git a/subprojects/gradle-core/src/main/resources/org/gradle/configuration/default-imports.txt b/subprojects/gradle-core/src/main/resources/org/gradle/configuration/default-imports.txt
new file mode 100644
index 0000000..eb266ee
--- /dev/null
+++ b/subprojects/gradle-core/src/main/resources/org/gradle/configuration/default-imports.txt
@@ -0,0 +1,25 @@
+import org.gradle.*
+import org.gradle.util.*
+import org.gradle.api.*
+import org.gradle.api.artifacts.*
+import org.gradle.api.artifacts.dsl.*
+import org.gradle.api.artifacts.maven.*
+import org.gradle.api.artifacts.specs.*
+import org.gradle.api.execution.*
+import org.gradle.api.file.*
+import org.gradle.api.initialization.*
+import org.gradle.api.invocation.*
+import org.gradle.api.java.archives.*
+import org.gradle.api.logging.*
+import org.gradle.api.plugins.*
+import org.gradle.api.plugins.quality.*
+import org.gradle.api.specs.*
+import org.gradle.api.tasks.*
+import org.gradle.api.tasks.bundling.*
+import org.gradle.api.tasks.diagnostics.*
+import org.gradle.api.tasks.compile.*
+import org.gradle.api.tasks.javadoc.*
+import org.gradle.api.tasks.testing.*
+import org.gradle.api.tasks.util.*
+import org.gradle.api.tasks.wrapper.*
+import org.gradle.process.*
diff --git a/subprojects/gradle-core/src/main/resources/org/gradle/initialization/defaultBuildSourceScript.txt b/subprojects/gradle-core/src/main/resources/org/gradle/initialization/defaultBuildSourceScript.txt
new file mode 100644
index 0000000..a47ee21
--- /dev/null
+++ b/subprojects/gradle-core/src/main/resources/org/gradle/initialization/defaultBuildSourceScript.txt
@@ -0,0 +1,5 @@
+apply plugin: 'groovy'
+dependencies {
+    compile gradleApi()
+    groovy localGroovy()
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/BuildExceptionReporterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/BuildExceptionReporterTest.java
new file mode 100644
index 0000000..e492cd9
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/BuildExceptionReporterTest.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;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.LocationAwareException;
+import org.gradle.util.HelperUtil;
+import org.hamcrest.Matcher;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.slf4j.Logger;
+
+import java.util.Arrays;
+
+import static org.hamcrest.Matchers.*;
+
+ at RunWith(JMock.class)
+public class BuildExceptionReporterTest {
+    private final JUnit4Mockery context = new JUnit4Mockery(){{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+    private Logger logger = context.mock(Logger.class);
+    private StartParameter startParameter = new StartParameter();
+    private BuildExceptionReporter reporter = new BuildExceptionReporter(logger, startParameter);
+
+    @Test
+    public void reportsBuildFailure() {
+        final GradleException exception = new GradleException("<message>");
+        final Matcher<String> errorMessage = allOf(containsString("Build failed with an exception."),
+                containsString("<message>"));
+
+        context.checking(new Expectations() {{
+            one(logger).error(with(errorMessage));
+        }});
+
+        reporter.buildFinished(HelperUtil.createBuildResult(exception));
+    }
+
+    @Test
+    public void reportsBuildFailureWhenCauseHasNoMessage() {
+        final GradleException exception = new GradleException();
+        final Matcher<String> errorMessage = allOf(containsString("Build failed with an exception."),
+                containsString(GradleException.class.getName() + " (no error message)"));
+
+        context.checking(new Expectations() {{
+            one(logger).error(with(errorMessage));
+        }});
+
+        reporter.buildFinished(HelperUtil.createBuildResult(exception));
+    }
+
+    @Test
+    public void reportsLocationAwareException() {
+        final TestException exception = exception("<location>", "<message>", new RuntimeException("<cause>"));
+
+        final Matcher<String> errorMessage = allOf(containsString("Build failed with an exception."),
+                containsString("<location>"),
+                containsString("<message>"),
+                containsString("Cause: <cause>"));
+
+        context.checking(new Expectations() {{
+            one(logger).error(with(errorMessage));
+        }});
+
+        reporter.buildFinished(HelperUtil.createBuildResult(exception));
+    }
+
+    private TestException exception(final String location, final String message, final Throwable... causes) {
+        final TestException testException = context.mock(TestException.class);
+        context.checking(new Expectations() {{
+            allowing(testException).getLocation();
+            will(returnValue(location));
+            allowing(testException).getOriginalMessage();
+            will(returnValue(message));
+            allowing(testException).getReportableCauses();
+            will(returnValue(Arrays.asList(causes)));
+        }});
+        return testException;
+    }
+
+    @Test
+    public void reportsLocationAwareExceptionWithMultipleCauses() {
+        final TestException exception = exception("<location>", "<message>", new RuntimeException("<outer>"),
+                new RuntimeException("<cause>"));
+
+        final Matcher<String> errorMessage = allOf(containsString("Build failed with an exception."),
+                containsString("<location>"),
+                containsString("<message>"),
+                containsString("Cause: <outer>"),
+                containsString("<cause>"));
+
+        context.checking(new Expectations() {{
+            one(logger).error(with(errorMessage));
+        }});
+
+        reporter.buildFinished(HelperUtil.createBuildResult(exception));
+    }
+
+    @Test
+    public void reportsLocationAwareExceptionWhenCauseHasNoMessage() {
+        final TestException exception = exception("<location>", "<message>", new RuntimeException());
+
+        final Matcher<String> errorMessage = allOf(containsString("Build failed with an exception."),
+                containsString("<location>"),
+                containsString("<message>"),
+                containsString("Cause: " + RuntimeException.class.getName() + " (no error message)"));
+
+        context.checking(new Expectations() {{
+            one(logger).error(with(errorMessage));
+        }});
+
+        reporter.buildFinished(HelperUtil.createBuildResult(exception));
+    }
+
+    @Test
+    public void reportsBuildFailureWhenOptionsHaveNotBeenSet() {
+        final GradleException exception = new GradleException("<message>");
+        final Matcher<String> errorMessage = allOf(containsString("Build failed with an exception."),
+                containsString("<message>"));
+
+        context.checking(new Expectations() {{
+            one(logger).error(with(errorMessage));
+        }});
+
+        reporter = new BuildExceptionReporter(logger, startParameter);
+        reporter.buildFinished(HelperUtil.createBuildResult(exception));
+    }
+
+    @Test
+    public void reportsInternalFailure() {
+        final RuntimeException failure = new RuntimeException("<message>");
+
+        context.checking(new Expectations() {{
+            one(logger).error(with(containsString("Build aborted because of an internal error.")), with(sameInstance(failure)));
+        }});
+
+        reporter.buildFinished(HelperUtil.createBuildResult(failure));
+    }
+
+    @Test
+    public void reportsInternalFailureWhenOptionsHaveNotBeenSet() {
+        final RuntimeException failure = new RuntimeException("<message>");
+
+        context.checking(new Expectations() {{
+            one(logger).error(with(containsString("Build aborted because of an internal error.")), with(sameInstance(failure)));
+        }});
+
+        reporter = new BuildExceptionReporter(logger, startParameter);
+        reporter.buildFinished(HelperUtil.createBuildResult(failure));
+    }
+
+    @Test
+    public void doesNothingWheBuildIsSuccessful() {
+        reporter.buildFinished(HelperUtil.createBuildResult(null));
+    }
+
+    public abstract class TestException extends GradleException implements LocationAwareException {
+    }
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/BuildResultLoggerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/BuildResultLoggerTest.java
new file mode 100644
index 0000000..bdac39b
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/BuildResultLoggerTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+import static org.hamcrest.Matchers.*;
+import org.jmock.Expectations;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.gradle.api.logging.Logger;
+import org.gradle.util.Clock;
+
+ at RunWith(org.jmock.integration.junit4.JMock.class)
+public class BuildResultLoggerTest {
+    private final JUnit4Mockery context = new JUnit4Mockery(){{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+    private Logger logger;
+    private BuildListener listener;
+    private Clock buildTimeClock;
+
+    @Before
+    public void setup() {
+        logger = context.mock(Logger.class);
+        buildTimeClock = context.mock(Clock.class);
+        listener = new BuildResultLogger(logger, buildTimeClock);
+    }
+
+    @Test
+    public void logsBuildSuccessAndTotalTime() {
+        context.checking(new Expectations(){{
+            one(buildTimeClock).getTime();
+            will(returnValue("10s"));
+            one(logger).lifecycle(String.format("%nBUILD SUCCESSFUL%n"));
+            one(logger).lifecycle(with(startsWith("Total time: 10s")));
+        }});
+
+        listener.buildFinished(new BuildResult(null, null));
+    }
+
+    @Test
+    public void logsBuildFailedAndTotalTime() {
+        context.checking(new Expectations(){{
+            one(buildTimeClock).getTime();
+            will(returnValue("10s"));
+            one(logger).error(String.format("%nBUILD FAILED%n"));
+            one(logger).lifecycle(with(startsWith("Total time: 10s")));
+        }});
+
+        listener.buildFinished(new BuildResult(null, new RuntimeException()));
+    }
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/BuildResultTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/BuildResultTest.java
new file mode 100644
index 0000000..734c961
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/BuildResultTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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;
+
+import org.gradle.api.GradleException;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+public class BuildResultTest {
+    @Test
+    public void rethrowDoesNothingWhenNoBuildFailure() {
+        BuildResult result = new BuildResult(null, null);
+        result.rethrowFailure();
+    }
+
+    @Test
+    public void rethrowsGradleException() {
+        Throwable failure = new GradleException();
+        BuildResult result = new BuildResult(null, failure);
+
+        try {
+            result.rethrowFailure();
+            fail();
+        } catch (Exception e) {
+            assertThat(e, sameInstance(failure));
+        }
+    }
+
+    @Test
+    public void rethrowWrapsOtherExceptions() {
+        Throwable failure = new RuntimeException();
+        BuildResult result = new BuildResult(null, failure);
+
+        try {
+            result.rethrowFailure();
+            fail();
+        } catch (GradleException e) {
+            assertThat(e.getMessage(), equalTo("Build aborted because of an internal error."));
+            assertThat(e.getCause(), sameInstance(failure));
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/StartParameterTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/StartParameterTest.groovy
new file mode 100644
index 0000000..3a4f72e
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/StartParameterTest.groovy
@@ -0,0 +1,250 @@
+/*
+ * 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
+
+import static org.gradle.util.Matchers.*
+
+import org.gradle.api.artifacts.ProjectDependenciesBuildInstruction
+import org.gradle.api.logging.LogLevel
+import org.gradle.execution.BuildExecuter
+import org.gradle.execution.DefaultBuildExecuter
+import org.gradle.execution.DryRunBuildExecuter
+import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.groovy.scripts.StringScriptSource
+import org.gradle.groovy.scripts.UriScriptSource
+import org.gradle.initialization.BuildFileProjectSpec
+import org.gradle.initialization.DefaultProjectSpec
+import org.gradle.initialization.ProjectDirectoryProjectSpec
+import org.gradle.initialization.ProjectSpec
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+class StartParameterTest {
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    @Test public void testNewInstance() {
+        StartParameter testObj = new StartParameter()
+        testObj.settingsFile = 'settingsfile' as File
+        testObj.buildFile = 'buildfile' as File
+        testObj.taskNames = ['a']
+        testObj.projectDependenciesBuildInstruction = new ProjectDependenciesBuildInstruction([])
+        testObj.currentDir = new File('a')
+        testObj.searchUpwards = false
+        testObj.projectProperties = [a: 'a']
+        testObj.systemPropertiesArgs = [b: 'b']
+        testObj.gradleUserHomeDir = new File('b')
+        testObj.initScripts = [new File('init script'), new File("/path/to/another init script")]
+        testObj.cacheUsage = CacheUsage.ON
+
+        StartParameter startParameter = testObj.newInstance()
+        assertEquals(testObj, startParameter)
+    }
+
+    @Test public void testDefaultValues() {
+        StartParameter parameter = new StartParameter();
+        assertThat(parameter.gradleUserHomeDir, equalTo(StartParameter.DEFAULT_GRADLE_USER_HOME))
+        assertThat(parameter.currentDir, equalTo(new File(System.getProperty("user.dir")).getCanonicalFile()))
+
+        assertThat(parameter.buildFile, nullValue())
+        assertThat(parameter.settingsScriptSource, nullValue())
+
+        assertThat(parameter.logLevel, equalTo(LogLevel.LIFECYCLE))
+        assertThat(parameter.taskNames, isEmpty())
+        assertThat(parameter.excludedTaskNames, isEmpty())
+        assertThat(parameter.projectProperties, isEmptyMap())
+        assertThat(parameter.systemPropertiesArgs, isEmptyMap())
+        assertThat(parameter.buildExecuter, instanceOf(DefaultBuildExecuter))
+        assertThat(parameter.defaultProjectSelector, reflectionEquals(new DefaultProjectSpec(parameter.currentDir)))
+        assertFalse(parameter.dryRun)
+    }
+
+    @Test public void testDefaultWithGradleUserHomeSystemProp() {
+        String gradleUserHome = "/someGradleUserHomePath"
+        System.setProperty(StartParameter.GRADLE_USER_HOME_PROPERTY_KEY, gradleUserHome)
+        try {
+            StartParameter parameter = new StartParameter();
+            assertThat(parameter.gradleUserHomeDir, equalTo(new File(gradleUserHome)))
+        } finally {
+            System.getProperties().remove(StartParameter.GRADLE_USER_HOME_PROPERTY_KEY)    
+        }
+    }
+
+    @Test public void testSetCurrentDir() {
+        StartParameter parameter = new StartParameter()
+        File dir = new File('current')
+        parameter.currentDir = dir
+
+        assertThat(parameter.currentDir, equalTo(dir.canonicalFile))
+        assertThat(parameter.defaultProjectSelector, reflectionEquals(new DefaultProjectSpec(dir.canonicalFile)))
+    }
+
+    @Test public void testSetBuildFile() {
+        StartParameter parameter = new StartParameter()
+        File file = new File('test/build file')
+        parameter.buildFile = file
+
+        assertThat(parameter.buildFile, equalTo(file.canonicalFile))
+        assertThat(parameter.currentDir, equalTo(file.canonicalFile.parentFile))
+        assertThat(parameter.defaultProjectSelector, reflectionEquals(new BuildFileProjectSpec(file.canonicalFile)))
+    }
+
+    @Test public void testSetNullBuildFile() {
+        StartParameter parameter = new StartParameter()
+        parameter.buildFile = new File('test/build file')
+        parameter.buildFile = null
+
+        assertThat(parameter.buildFile, nullValue())
+        assertThat(parameter.currentDir, equalTo(new File(System.getProperty("user.dir")).getCanonicalFile()))
+        assertThat(parameter.defaultProjectSelector, reflectionEquals(new DefaultProjectSpec(parameter.currentDir)))
+        assertThat(parameter.initScripts, equalTo(Collections.emptyList()))
+    }
+
+    @Test public void testSetProjectDir() {
+        StartParameter parameter = new StartParameter()
+        File file = new File('test/project dir')
+        parameter.projectDir = file
+
+        assertThat(parameter.currentDir, equalTo(file.canonicalFile))
+        assertThat(parameter.defaultProjectSelector, reflectionEquals(new ProjectDirectoryProjectSpec(file.canonicalFile)))
+    }
+
+    @Test public void testSetNullProjectDir() {
+        StartParameter parameter = new StartParameter()
+        parameter.projectDir = new File('test/project dir')
+        parameter.projectDir = null
+
+        assertThat(parameter.currentDir, equalTo(new File(System.getProperty("user.dir")).getCanonicalFile()))
+        assertThat(parameter.defaultProjectSelector, reflectionEquals(new DefaultProjectSpec(parameter.currentDir)))
+    }
+
+    @Test public void testSetSettingsFile() {
+        StartParameter parameter = new StartParameter()
+        File file = new File('some dir/settings file')
+        parameter.settingsFile = file
+
+        assertThat(parameter.currentDir, equalTo(file.canonicalFile.parentFile))
+        assertThat(parameter.settingsScriptSource, instanceOf(UriScriptSource.class))
+        assertThat(parameter.settingsScriptSource.resource.file, equalTo(file.canonicalFile))
+    }
+
+    @Test public void testSetNullSettingsFile() {
+        StartParameter parameter = new StartParameter()
+        parameter.settingsFile = null
+
+        assertThat(parameter.settingsScriptSource, nullValue())
+    }
+
+    @Test public void testSetSettingsScriptSource() {
+        StartParameter parameter = new StartParameter()
+        parameter.settingsFile = new File('settings file')
+
+        ScriptSource scriptSource = {} as ScriptSource
+
+        parameter.settingsScriptSource = scriptSource
+
+        assertThat(parameter.settingsScriptSource, sameInstance(scriptSource))
+    }
+
+    @Test public void testSetTaskNames() {
+        StartParameter parameter = new StartParameter()
+        parameter.taskNames = ['a', 'b']
+        assertThat(parameter.buildExecuter, instanceOf(DefaultBuildExecuter))
+        assertThat(parameter.buildExecuter.delegate.names, equalTo(['a', 'b']))
+    }
+
+    @Test public void testSetTaskNamesUsesDefaultExecuter() {
+        StartParameter parameter = new StartParameter()
+
+        parameter.setBuildExecuter({} as BuildExecuter)
+        parameter.taskNames = []
+        assertThat(parameter.buildExecuter, instanceOf(DefaultBuildExecuter))
+    }
+
+    @Test public void testSetExcludedTaskNames() {
+        StartParameter parameter = new StartParameter()
+        parameter.excludedTaskNames = ['a', 'b']
+        assertThat(parameter.buildExecuter, instanceOf(DefaultBuildExecuter))
+        assertThat(parameter.buildExecuter.excludedTaskNames, equalTo(['a', 'b'] as Set))
+    }
+
+    @Test public void testUseEmbeddedBuildFile() {
+        StartParameter parameter = new StartParameter();
+        parameter.useEmbeddedBuildFile("<content>")
+        assertThat(parameter.buildScriptSource, instanceOf(StringScriptSource.class))
+        assertThat(parameter.buildScriptSource.resource.text, equalTo("<content>"))
+        assertThat(parameter.settingsScriptSource, instanceOf(StringScriptSource.class))
+        assertThat(parameter.settingsScriptSource.resource.text, equalTo(""))
+        assertThat(parameter.searchUpwards, equalTo(false))
+    }
+
+    @Test public void testSetNullUserHomeDir() {
+        StartParameter parameter = new StartParameter()
+        parameter.gradleUserHomeDir = null
+        assertThat(parameter.gradleUserHomeDir, equalTo(StartParameter.DEFAULT_GRADLE_USER_HOME))
+    }
+
+    @Test public void testWrapsExecuterWhenDryRunIsTrue() {
+        StartParameter parameter = new StartParameter()
+        def originalExecuter = [:] as BuildExecuter
+        parameter.buildExecuter = originalExecuter
+        parameter.dryRun = true
+        assertThat(parameter.buildExecuter, instanceOf(DryRunBuildExecuter))
+        assertThat(parameter.buildExecuter.delegate, sameInstance(originalExecuter))
+        parameter.dryRun = false
+        assertThat(parameter.buildExecuter, sameInstance(originalExecuter))
+    }
+
+    @Test public void testNewBuild() {
+        StartParameter parameter = new StartParameter()
+
+        // Copied properties
+        parameter.gradleUserHomeDir = new File("home")
+        parameter.cacheUsage = CacheUsage.REBUILD
+        parameter.logLevel = LogLevel.DEBUG
+
+        // Non-copied
+        parameter.currentDir = new File("other")
+        parameter.buildFile = new File("build file")
+        parameter.settingsFile = new File("settings file")
+        parameter.taskNames = ['task1']
+        parameter.excludedTaskNames = ['excluded1']
+        parameter.defaultProjectSelector = [:] as ProjectSpec
+        parameter.dryRun = true
+
+        StartParameter newParameter = parameter.newBuild();
+
+        assertThat(newParameter, not(equalTo(parameter)));
+
+        assertThat(newParameter.gradleUserHomeDir, equalTo(parameter.gradleUserHomeDir));
+        assertThat(newParameter.cacheUsage, equalTo(parameter.cacheUsage));
+        assertThat(newParameter.logLevel, equalTo(parameter.logLevel));
+
+        assertThat(newParameter.buildFile, nullValue())
+        assertThat(newParameter.taskNames, isEmpty())
+        assertThat(newParameter.excludedTaskNames, isEmpty())
+        assertThat(newParameter.currentDir, equalTo(new File(System.getProperty("user.dir")).getCanonicalFile()))
+        assertThat(newParameter.defaultProjectSelector, reflectionEquals(new DefaultProjectSpec(newParameter.currentDir)))
+        assertFalse(newParameter.dryRun)
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/TaskExecutionLoggerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/TaskExecutionLoggerTest.java
new file mode 100644
index 0000000..c1cf114
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/TaskExecutionLoggerTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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;
+
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.logging.ProgressLogger;
+import org.gradle.api.tasks.TaskState;
+import org.gradle.logging.ProgressLoggerFactory;
+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;
+
+ at RunWith(JMock.class)
+public class TaskExecutionLoggerTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final ProgressLoggerFactory progressLoggerFactory = context.mock(ProgressLoggerFactory.class);
+    private final Task task = context.mock(Task.class);
+    private final TaskState state = context.mock(TaskState.class);
+    private final ProgressLogger progressLogger = context.mock(ProgressLogger.class);
+    private final Gradle gradle = context.mock(Gradle.class);
+    private final TaskExecutionLogger executionLogger = new TaskExecutionLogger(progressLoggerFactory);
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations() {{
+            Project project = context.mock(Project.class);
+            allowing(task).getProject();
+            will(returnValue(project));
+            allowing(project).getGradle();
+            will(returnValue(gradle));
+            allowing(task).getPath();
+            will(returnValue(":path"));
+        }});
+    }
+
+    @Test
+    public void logsExecutionOfTaskInRootBuild() {
+        context.checking(new Expectations() {{
+            allowing(gradle).getParent();
+            will(returnValue(null));
+            one(progressLoggerFactory).start(":path");
+            will(returnValue(progressLogger));
+            one(progressLogger).progress(":path");
+        }});
+
+        executionLogger.beforeExecute(task);
+
+        context.checking(new Expectations() {{
+            allowing(state).getSkipMessage();
+            will(returnValue(null));
+            one(progressLogger).completed();
+        }});
+
+        executionLogger.afterExecute(task, state);
+    }
+
+    @Test
+    public void logsExecutionOfTaskInSubBuild() {
+        context.checking(new Expectations() {{
+            Project rootProject = context.mock(Project.class, "rootProject");
+
+            allowing(gradle).getParent();
+            will(returnValue(context.mock(Gradle.class, "rootBuild")));
+            allowing(gradle).getRootProject();
+            will(returnValue(rootProject));
+            allowing(rootProject).getName();
+            will(returnValue("build"));
+
+            one(progressLoggerFactory).start(":build:path");
+            will(returnValue(progressLogger));
+            one(progressLogger).progress(":build:path");
+        }});
+
+        executionLogger.beforeExecute(task);
+
+        context.checking(new Expectations() {{
+            allowing(state).getSkipMessage();
+            will(returnValue(null));
+            one(progressLogger).completed();
+        }});
+
+        executionLogger.afterExecute(task, state);
+    }
+
+    @Test
+    public void logsSkippedTaskExecution() {
+        context.checking(new Expectations() {{
+            allowing(gradle).getParent();
+            will(returnValue(null));
+            one(progressLoggerFactory).start(":path");
+            will(returnValue(progressLogger));
+            one(progressLogger).progress(":path");
+        }});
+
+        executionLogger.beforeExecute(task);
+
+        context.checking(new Expectations() {{
+            allowing(state).getSkipMessage();
+            will(returnValue("skipped"));
+            one(progressLogger).completed("skipped");
+        }});
+
+        executionLogger.afterExecute(task, state);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/AllGradleExceptionsTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/AllGradleExceptionsTest.groovy
new file mode 100644
index 0000000..4523490
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/AllGradleExceptionsTest.groovy
@@ -0,0 +1,52 @@
+/*
+ * 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.api
+
+import org.junit.Test
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+class AllGradleExceptionsTest {
+    static final List EXCEPTION_CLASSES = [UnknownTaskException, UnknownProjectException, InvalidUserDataException, GradleException, CircularReferenceException]
+
+    @Test public void testWithMessage() {
+        String expectedMessage = 'somemessage'
+        checkException([expectedMessage]) { GradleException exception ->
+            assertEquals(expectedMessage, exception.message)
+        }
+    }
+
+    @Test public void testWithMessageAndCause() {
+        String expectedMessage = 'somemessage'
+        Throwable expectedCause = new Throwable()
+        checkException([expectedMessage, expectedCause]) { GradleException exception ->
+            assertEquals(expectedMessage, exception.message)
+            assertEquals(expectedCause, exception.cause)
+        }
+    }
+
+    void checkException(List constructorArgs, Closure testClosure) {
+        EXCEPTION_CLASSES.each { Class clazz ->
+            GradleException exception = clazz.newInstance(constructorArgs as Object[])
+            testClosure(exception)
+        }
+
+    }
+    
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/JavaVersionTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/JavaVersionTest.java
new file mode 100644
index 0000000..0a27d97
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/JavaVersionTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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 static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+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"));
+    }
+
+    @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));
+    }
+
+    @Test
+    public void failsToConvertStringToVersionForUnknownVersion() {
+        conversionFails("1");
+        conversionFails("2");
+
+        conversionFails("7");
+
+        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));
+    }
+    
+    @Test
+    public void failsToConvertNumberToVersionForUnknownVersion() {
+        conversionFails(1);
+        conversionFails(2);
+        conversionFails(7);
+        conversionFails(1.21);
+        conversionFails(2.0);
+        conversionFails(4.2);
+    }
+
+    @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/gradle-core/src/test/groovy/org/gradle/api/artifacts/ProjectDependenciesBuildInstructionTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/artifacts/ProjectDependenciesBuildInstructionTest.java
new file mode 100644
index 0000000..a35cecb
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/artifacts/ProjectDependenciesBuildInstructionTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.artifacts;
+
+import org.junit.Test;
+import static org.junit.Assert.assertThat;
+import static org.hamcrest.Matchers.*;
+import org.gradle.util.WrapUtil;
+
+import java.util.Collections;
+
+/**
+ * @author Hans Dockter
+ */
+public class ProjectDependenciesBuildInstructionTest {
+    @Test
+    public void initWithNull() {
+        ProjectDependenciesBuildInstruction buildInstruction = new ProjectDependenciesBuildInstruction(null);
+        assertThat(buildInstruction.isRebuild(), equalTo(false));
+        assertThat(buildInstruction.getTaskNames(), equalTo(Collections.<String>emptyList()));
+    }
+
+    @Test
+    public void initWithEmptyList() {
+        ProjectDependenciesBuildInstruction buildInstruction = new ProjectDependenciesBuildInstruction(Collections.<String>emptyList());
+        assertThat(buildInstruction.isRebuild(), equalTo(true));
+        assertThat(buildInstruction.getTaskNames(), equalTo(Collections.<String>emptyList()));
+    }
+
+    @Test
+    public void initWithNonEmptyList() {
+        String taskName = "someTaskName";
+        ProjectDependenciesBuildInstruction buildInstruction = new ProjectDependenciesBuildInstruction(
+                WrapUtil.toList(taskName));
+        assertThat(buildInstruction.isRebuild(), equalTo(true));
+        assertThat(buildInstruction.getTaskNames(), equalTo(WrapUtil.toList(taskName)));
+    }
+
+    @Test
+    public void equality() {
+        String taskName = "someTaskName";
+        assertThat(new ProjectDependenciesBuildInstruction(null),
+                equalTo(new ProjectDependenciesBuildInstruction(null)));
+        assertThat(new ProjectDependenciesBuildInstruction(Collections.<String>emptyList()),
+                equalTo(new ProjectDependenciesBuildInstruction(Collections.<String>emptyList())));
+        assertThat(new ProjectDependenciesBuildInstruction(WrapUtil.toList(taskName)),
+                equalTo(new ProjectDependenciesBuildInstruction(WrapUtil.toList(taskName))));
+
+        assertThat(new ProjectDependenciesBuildInstruction(null),
+                not(equalTo(new ProjectDependenciesBuildInstruction(Collections.<String>emptyList()))));
+        assertThat(new ProjectDependenciesBuildInstruction(WrapUtil.toList(taskName)),
+                not(equalTo(new ProjectDependenciesBuildInstruction(WrapUtil.toList(taskName + 'x')))));
+    }
+
+    @Test
+    public void hashCodeEquality() {
+        String taskName = "someTaskName";
+        assertThat(new ProjectDependenciesBuildInstruction(null).hashCode(),
+                equalTo(new ProjectDependenciesBuildInstruction(null).hashCode()));
+        assertThat(new ProjectDependenciesBuildInstruction(Collections.<String>emptyList()).hashCode(),
+                equalTo(new ProjectDependenciesBuildInstruction(Collections.<String>emptyList()).hashCode()));
+        assertThat(new ProjectDependenciesBuildInstruction(WrapUtil.toList(taskName)).hashCode(),
+                equalTo(new ProjectDependenciesBuildInstruction(WrapUtil.toList(taskName)).hashCode()));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/artifacts/PublishInstructionTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/artifacts/PublishInstructionTest.java
new file mode 100644
index 0000000..549a850
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/artifacts/PublishInstructionTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.artifacts;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+import org.gradle.api.InvalidUserDataException;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class PublishInstructionTest {
+    @Test
+    public void initWithNoArgs() {
+        PublishInstruction publishInstruction = new PublishInstruction();
+        assertThat(publishInstruction.isUploadDescriptor(), equalTo(false));
+        assertThat(publishInstruction.getDescriptorDestination(), equalTo(null));
+    }
+
+    @Test
+    public void initWithUploadTrueAndFileNotNull() {
+        File someFile = new File("somePath");
+        PublishInstruction publishInstruction = new PublishInstruction(true, someFile);
+        assertThat(publishInstruction.isUploadDescriptor(), equalTo(true));
+        assertThat(publishInstruction.getDescriptorDestination(), equalTo(someFile));
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void initWithUploadFalseAndFileNotNullShouldThrowInvalidUserDataEx() {
+        new PublishInstruction(false, new File("somePath"));
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void initWithUploadTrueAndFileNullShouldThrowInvalidUserDataEx() {
+        new PublishInstruction(true, null);
+    }
+
+    @Test
+    public void initWithUploadFalseAndFileNull() {
+        PublishInstruction publishInstruction = new PublishInstruction(false, null);
+        assertThat(publishInstruction.isUploadDescriptor(), equalTo(false));
+        assertThat(publishInstruction.getDescriptorDestination(), nullValue());
+    }
+
+    @Test
+    public void equalityAndHash() {
+        assertThat(new PublishInstruction(false, null), equalTo(new PublishInstruction(false, null)));
+        assertThat(new PublishInstruction(true, new File("somePath")), equalTo(new PublishInstruction(true, new File("somePath"))));
+        assertThat(new PublishInstruction(true, new File("somePath")), not(equalTo(new PublishInstruction(true, new File("someOtherPath")))));
+
+        assertThat(new PublishInstruction(true, new File("somePath")).hashCode(),
+                equalTo(new PublishInstruction(true, new File("somePath")).hashCode()));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingTest.java
new file mode 100644
index 0000000..812e8b3
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/artifacts/maven/Conf2ScopeMappingTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.artifacts.maven;
+
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.util.HelperUtil;
+
+/**
+ * @author Hans Dockter
+ */
+public class Conf2ScopeMappingTest {
+    private Conf2ScopeMapping conf2ScopeMapping;
+    private static final String TEST_SCOPE = "somescope";
+    private static final Integer TEST_PRIORITY = 10;
+    private static final Configuration TEST_CONF = HelperUtil.createConfiguration("someconf");
+
+    @Before
+    public void setUp() {
+        conf2ScopeMapping = new Conf2ScopeMapping(TEST_PRIORITY, TEST_CONF, TEST_SCOPE);
+    }
+
+    @Test
+    public void init() {
+        assertEquals(TEST_PRIORITY, conf2ScopeMapping.getPriority());
+        assertEquals(TEST_CONF, conf2ScopeMapping.getConfiguration());
+        assertEquals(TEST_SCOPE, conf2ScopeMapping.getScope());
+    }
+
+    @Test
+    public void equality() {
+        assertTrue(conf2ScopeMapping.equals(new Conf2ScopeMapping(TEST_PRIORITY, TEST_CONF, TEST_SCOPE)));
+        assertFalse(conf2ScopeMapping.equals(new Conf2ScopeMapping(TEST_PRIORITY + 10, TEST_CONF, TEST_SCOPE)));
+    }
+
+    @Test
+    public void hashcode() {
+        assertEquals(conf2ScopeMapping.hashCode(), new Conf2ScopeMapping(TEST_PRIORITY, TEST_CONF, TEST_SCOPE).hashCode());
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/artifacts/specs/DependencyTypeSpecTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/artifacts/specs/DependencyTypeSpecTest.java
new file mode 100644
index 0000000..6be53d8
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/artifacts/specs/DependencyTypeSpecTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.artifacts.specs;
+
+import org.gradle.api.artifacts.ExternalModuleDependency;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+/**
+ * @author Hans Dockter
+ */
+public class DependencyTypeSpecTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    @Test
+    public void init() {
+        assertSame(Type.PROJECT, new DependencyTypeSpec(Type.PROJECT).getType());
+    }
+
+    @Test
+    public void testIsSatisfiedBy() {
+        assertTrue(new DependencyTypeSpec(Type.PROJECT).isSatisfiedBy(context.mock(ProjectDependency.class)));
+        assertFalse(new DependencyTypeSpec(Type.PROJECT).isSatisfiedBy(context.mock(ExternalModuleDependency.class)));
+    }
+
+    @Test
+    public void equality() {
+        assertTrue(new DependencyTypeSpec(Type.PROJECT).equals(new DependencyTypeSpec(Type.PROJECT)));
+        assertFalse(new DependencyTypeSpec(Type.PROJECT).equals(new DependencyTypeSpec(Type.EXTERNAL)));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/file/FileVisitorUtil.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/file/FileVisitorUtil.groovy
new file mode 100644
index 0000000..1b5d55f
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/file/FileVisitorUtil.groovy
@@ -0,0 +1,79 @@
+/*
+ * 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.file
+
+import static org.junit.Assert.*
+import static org.hamcrest.Matchers.*
+
+class FileVisitorUtil {
+    static def assertCanStopVisiting(FileTree tree) {
+        boolean found = false
+        FileVisitor visitor = [
+                visitFile: {FileVisitDetails details ->
+                    assertFalse(found)
+                    found = true
+                    details.stopVisiting()
+                },
+                visitDir: {FileVisitDetails details ->
+                    assertFalse(found)
+                }
+        ] as FileVisitor
+
+        tree.visit(visitor)
+        assertTrue(found)
+    }
+    
+    static def assertVisits(FileTree tree, Iterable<String> expectedFiles, Iterable<String> expectedDirs) {
+        Set files = [] as Set
+        Set dirs = [] as Set
+        FileVisitor visitor = [
+                visitFile: {FileVisitDetails details ->
+                    if (details.relativePath.parent.parent) {
+                        assertThat(dirs, hasItem(details.relativePath.parent.pathString))
+                    }
+                    assertTrue(files.add(details.relativePath.pathString))
+                    assertTrue(details.relativePath.isFile())
+                    assertEquals(details.file.lastModified(), details.lastModified)
+                    assertTrue(details.file.file)
+                    ByteArrayOutputStream outstr = new ByteArrayOutputStream()
+                    details.copyTo(outstr)
+                    assertEquals(details.file.text, outstr.toString())
+                },
+                visitDir: {FileVisitDetails details ->
+                    if (details.relativePath.parent.parent) {
+                        assertThat(dirs, hasItem(details.relativePath.parent.pathString))
+                    }
+                    assertTrue(dirs.add(details.relativePath.pathString))
+                    assertFalse(details.relativePath.isFile())
+                    assertEquals(details.file.lastModified(), details.lastModified)
+                    assertTrue(details.file.directory)
+                }
+        ] as FileVisitor
+
+        tree.visit(visitor)
+
+        assertThat(files, equalTo(expectedFiles as Set))
+        assertThat(dirs, equalTo(expectedDirs as Set))
+
+        files = [] as Set
+        tree.visit {FileVisitDetails details ->
+            assertTrue(files.add(details.relativePath.pathString))
+        }
+
+        assertThat(files, equalTo(expectedFiles + expectedDirs as Set))
+    }
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/AbstractClassGeneratorTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/AbstractClassGeneratorTest.java
new file mode 100644
index 0000000..ebf2c7d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/AbstractClassGeneratorTest.java
@@ -0,0 +1,552 @@
+/*
+ * 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 groovy.lang.GroovyRuntimeException;
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.plugins.DefaultConvention;
+import org.gradle.api.plugins.Convention;
+import org.gradle.api.tasks.ConventionValue;
+import org.gradle.util.GUtil;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.*;
+import java.util.concurrent.Callable;
+
+import static org.gradle.util.HelperUtil.*;
+import static org.gradle.util.Matchers.*;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+public abstract class AbstractClassGeneratorTest {
+    private ClassGenerator generator;
+
+    @Before
+    public void setUp() {
+        generator = createGenerator();
+    }
+
+    protected abstract ClassGenerator 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();
+        conventionAware.setConventionMapping(mapping);
+        assertThat(conventionAware.getConventionMapping(), sameInstance(mapping));
+    }
+
+    @Test
+    public void mixesInDynamicObjectAwareInterface() throws Exception {
+        Class<? extends Bean> generatedClass = generator.generate(Bean.class);
+        assertTrue(DynamicObjectAware.class.isAssignableFrom(generatedClass));
+        Bean bean = generatedClass.newInstance();
+        ((DynamicObjectAware) bean).getAsDynamicObject().setProperty("prop", "value");
+        assertThat(bean.getProp(), equalTo("value"));
+        assertThat(bean.doStuff("some value"), equalTo("{some value}"));
+    }
+
+    @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 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"));
+    }
+
+    @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 (GroovyRuntimeException 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 ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return "conventionValue";
+            }
+        });
+
+        assertThat(bean.getProp(), equalTo("conventionValue"));
+
+        bean.setProp("value");
+        assertThat(bean.getProp(), equalTo("value"));
+
+        bean.setProp(null);
+        assertThat(bean.getProp(), nullValue());
+    }
+
+    @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 ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return conventionValue;
+            }
+        });
+
+        assertThat(bean.getProp(), sameInstance(conventionValue));
+
+        bean.setProp(toList("other"));
+        assertThat(bean.getProp(), equalTo(toList("other")));
+
+        bean.setProp(Collections.EMPTY_LIST);
+        assertThat(bean.getProp(), equalTo(Collections.EMPTY_LIST));
+
+        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));
+    }
+
+    @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(GUtil.map(
+                "property", new ConventionValue(){
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        throw new UnsupportedOperationException();
+                    }
+                },
+                "interfaceProperty", new ConventionValue(){
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        throw new UnsupportedOperationException();
+                    }
+                },
+                "overriddenProperty", new ConventionValue(){
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return "conventionValue";
+                    }
+                },
+                "otherProperty", new ConventionValue(){
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        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));
+        Convention newConvention = new DefaultConvention();
+        bean.setConvention(newConvention);
+        assertThat(bean.getConvention(), sameInstance(newConvention));
+        assertThat(bean.getAsDynamicObject(), sameInstance((DynamicObject) newConvention));
+    }
+
+    @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 usesSameConventionForDynamicObjectAndConventionMappings() throws Exception {
+        Bean bean = generator.generate(Bean.class).newInstance();
+        IConventionAware conventionAware = (IConventionAware) bean;
+        DynamicObjectAware dynamicObjectAware = (DynamicObjectAware) bean;
+        assertThat(dynamicObjectAware.getConvention(), sameInstance(conventionAware.getConventionMapping().getConvention()));
+
+        Convention newConvention = new DefaultConvention();
+        dynamicObjectAware.setConvention(newConvention);
+        assertThat(dynamicObjectAware.getConvention(), sameInstance(newConvention));
+        assertThat(conventionAware.getConventionMapping().getConvention(), sameInstance(newConvention));
+    }
+
+    @Test
+    public void canAddDynamicPropertiesAndMethodsToJavaObject() throws Exception {
+        Bean bean = generator.generate(Bean.class).newInstance();
+        DynamicObjectAware dynamicObjectAware = (DynamicObjectAware) bean;
+        ConventionObject conventionObject = new ConventionObject();
+        dynamicObjectAware.getConvention().getPlugins().put("plugin", conventionObject);
+
+        call("{ it.conventionProperty = 'value' }", bean);
+        assertThat(conventionObject.getConventionProperty(), equalTo("value"));
+        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();
+        dynamicObjectAware.getConvention().getPlugins().put("plugin", conventionObject);
+
+        call("{ it.conventionProperty = 'value' }", bean);
+        assertThat(conventionObject.getConventionProperty(), equalTo("value"));
+        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.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.getConventionProperty() }", bean), equalTo((Object) "value"));
+        assertThat(call("{ it.conventionProperty }", bean), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void usesExistingDynamicObject() 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"));
+    }
+
+    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 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 static class ConventionAwareBean extends Bean implements IConventionAware, ConventionMapping {
+        Map<String, ConventionValue> mapping = new HashMap<String, ConventionValue>();
+
+        public Convention getConvention() {
+            throw new UnsupportedOperationException();
+        }
+
+        public void setConvention(Convention convention) {
+            throw new UnsupportedOperationException();
+        }
+
+        public ConventionMapping map(Map<String, ? extends ConventionValue> properties) {
+            throw new UnsupportedOperationException();
+        }
+
+        public MappedProperty map(String propertyName, Closure value) {
+            throw new UnsupportedOperationException();
+        }
+
+        public MappedProperty map(String propertyName, ConventionValue 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 DefaultConvention();
+
+        public Convention getConvention() {
+            return conv;
+        }
+
+        public void setConvention(Convention convention) {
+            this.conv = convention;
+        }
+
+        public DynamicObject getAsDynamicObject() {
+            return conv;
+        }
+    }
+
+    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 {
+        public String[] getArrayProperty() {
+            return new String[1];
+        }
+
+        public boolean getBooleanProperty() {
+            return false;
+        }
+
+        public long getLongProperty() {
+            return 12L;
+        }
+
+        public String getReturnValueProperty() {
+            return "value";
+        }
+        
+        public BeanWithVariousPropertyTypes setReturnValueProperty(String val) {
+            return this;
+        }
+    }
+
+    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 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/gradle-core/src/test/groovy/org/gradle/api/internal/AbstractDynamicObjectTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/AbstractDynamicObjectTest.java
new file mode 100644
index 0000000..e6bd79b
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/AbstractDynamicObjectTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 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;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import groovy.lang.*;
+import groovy.lang.MissingMethodException;
+
+public class AbstractDynamicObjectTest {
+    private final AbstractDynamicObject object = new AbstractDynamicObject() {
+        protected String getDisplayName() {
+            return "<display-name>";
+        }
+    };
+
+    @Test
+    public void hasNoProperties() {
+        assertFalse(object.hasProperty("something"));
+        assertTrue(object.getProperties().isEmpty());
+
+        try {
+            object.getProperty("something");
+            fail();
+        } catch (MissingPropertyException e) {
+            assertThat(e.getMessage(), equalTo("Could not find property 'something' on <display-name>."));
+        }
+
+        try {
+            object.setProperty("something", "value");
+            fail();
+        } catch (MissingPropertyException e) {
+            assertThat(e.getMessage(), equalTo("Could not find property 'something' on <display-name>."));
+        }
+    }
+
+    @Test
+    public void hasNoMethods() {
+        assertFalse(object.hasMethod("method", "a"));
+
+        try {
+            object.invokeMethod("method", "b");
+            fail();
+        } catch (MissingMethodException e) {
+            // Expected
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/AsmBackedClassGeneratorTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/AsmBackedClassGeneratorTest.java
new file mode 100644
index 0000000..e34fde5
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/AsmBackedClassGeneratorTest.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+public class AsmBackedClassGeneratorTest extends AbstractClassGeneratorTest {
+    @Override
+    protected ClassGenerator createGenerator() {
+        return new AsmBackedClassGenerator();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/AutoCreateDomainObjectContainerTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/AutoCreateDomainObjectContainerTest.groovy
new file mode 100644
index 0000000..dbdfcd2
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/AutoCreateDomainObjectContainerTest.groovy
@@ -0,0 +1,189 @@
+/*
+ * 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 org.junit.Test
+import static org.junit.Assert.*
+import static org.hamcrest.Matchers.*
+import org.junit.Ignore
+
+class AutoCreateDomainObjectContainerTest {
+    private final AutoCreateDomainObjectContainer container = new AsmBackedClassGenerator().newInstance(TestContainer.class)
+
+    @Test
+    public void canAddObjectWithName() {
+        container.add('obj')
+        assertThat(container.getByName('obj'), equalTo(['obj']))
+    }
+
+    @Test
+    public void canAddAndConfigureAnObjectWithName() {
+        container.add('obj') {
+            add(1)
+            add('value')
+        }
+        assertThat(container.getByName('obj'), equalTo(['obj', 1, 'value']))
+    }
+
+    @Test
+    public void failsToAddObjectWhenObjectWithSameNameAlreadyInContainer() {
+        container.add('obj')
+
+        try {
+            container.add('obj')
+            fail()
+        } catch (org.gradle.api.InvalidUserDataException e) {
+            assertThat(e.message, equalTo('Cannot add TestObject \'obj\' as a TestObject with that name already exists.'))
+        }
+    }
+
+    @Test
+    public void canConfigureExistingObject() {
+        container.add('list1')
+        container.configure {
+            list1 { add(1) }
+        }
+        assertThat(container.list1, equalTo(['list1', 1]))
+    }
+
+    @Test
+    public void propagatesNestedMissingMethodException() {
+        container.add('list1')
+        try {
+            container.configure {
+                list1 { unknown { anotherUnknown(2) } }
+            }
+        } catch (groovy.lang.MissingMethodException e) {
+            assertThat(e.method, equalTo('unknown'))
+            assertThat(e.type, equalTo(TestObject))
+        }
+    }
+
+    @Test
+    public void propagatesMethodInvocationException() {
+        RuntimeException failure = new RuntimeException()
+        try {
+            container.configure {
+                list1 { throw failure }
+            }
+        } catch (RuntimeException e) {
+            assertThat(e, sameInstance(failure))
+        }
+    }
+
+    @Test
+    public void implicitlyAddsAnObjectWhenContainerIsBeingConfigured() {
+        container.configure {
+            list1
+            list2 { add(1) }
+        }
+        assertThat(container.list1, equalTo(['list1']))
+        assertThat(container.list2, equalTo(['list2', 1]))
+    }
+
+    @Test
+    public void canReferToPropertiesAndMethodsOfOwner() {
+        new DynamicOwner().configure(container)
+        assertThat(container.asMap.keySet(), equalTo(['list1', 'list2'] as Set))
+        assertThat(container.list1, equalTo(['list1', 'dynamicProp', 'ownerProp', 'ownerMethod', 'dynamicMethod', 'dynamicMethod', 1, 'prop', 'testObjectDynamicMethod']))
+        assertThat(container.list1.prop, equalTo('prop'))
+        assertThat(container.list2, equalTo(['list2', container.list1]))
+    }
+
+    @Test @Ignore
+    public void canUseAnItemCalledMainInAScript() {
+        Script script = new GroovyShell().parse("""import org.gradle.util.ConfigureUtil
+            c.configure {
+                run
+                main { add(1) }
+            }
+
+""")
+        script.getBinding().setProperty("c", container)
+        script.run()
+
+        assertThat(container.run, equalTo(['run']))
+        assertThat(container.main, equalTo(['main', 1]))
+    }
+}
+
+class DynamicOwner {
+    Map values = [:]
+
+    def ownerMethod(String value) {
+        return value
+    }
+    
+    def getOwnerProp() {
+        return 'ownerProp'
+    }
+
+    def propertyMissing(String name) {
+        if (name == 'dynamicProp') {
+            return values[name]
+        }
+        throw new MissingPropertyException("fail")
+    }
+
+    def propertyMissing(String name, Object value) {
+        if (name == 'dynamicProp') {
+            values[name] = value
+            return
+        }
+        throw new MissingPropertyException("fail")
+    }
+
+    def methodMissing(String name, Object params) {
+        if (name == 'dynamicMethod') {
+            return 'dynamicMethod'
+        }
+        throw new groovy.lang.MissingMethodException(name, getClass(), params)
+    }
+
+    def configure(def container) {
+        container.configure {
+            list1 {
+                // owner properties and methods - owner is a DynamicOwner
+                dynamicProp = 'dynamicProp'
+                add dynamicProp
+                add ownerProp
+                add ownerMethod('ownerMethod')
+                add dynamicMethod('a', 'b', 'c')
+                add dynamicMethod { doesntGetEvaluated }
+                // delegate properties and methods - delegate is a TestObject
+                add all.size()
+                prop = 'prop'
+                add prop
+                testObjectDynamicMethod { doesntGetEvaluated }
+            }
+            list2 {
+                add list1
+            }
+        }
+    }
+}
+
+class TestObject extends ArrayList<String> {
+    def String prop
+
+    def methodMissing(String name, Object params) {
+        if (name == 'testObjectDynamicMethod') {
+            add(name)
+            return name
+        }
+        throw new groovy.lang.MissingMethodException(name, getClass(), params)
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/CachingDirectedGraphWalkerTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/CachingDirectedGraphWalkerTest.groovy
new file mode 100644
index 0000000..26da809
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/CachingDirectedGraphWalkerTest.groovy
@@ -0,0 +1,197 @@
+/*
+ * 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 spock.lang.Specification
+
+class CachingDirectedGraphWalkerTest extends Specification {
+    private final DirectedGraphWithEdgeValues<Integer, String> graph = Mock()
+    private final CachingDirectedGraphWalker walker = new CachingDirectedGraphWalker(graph)
+
+    def collectsValuesForASingleNode() {
+        when:
+        walker.add(1)
+        def values = walker.findValues()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1' }
+        0 * _._
+        values == ['1'] as Set
+    }
+
+    def collectsValuesForEachSuccessorNode() {
+        when:
+        walker.add(1)
+        def values = walker.findValues()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 2; args[2] << 3 }
+        1 * graph.getNodeValues(2, _, _) >> { args -> args[1] << '2'; args[2] << 3 }
+        1 * graph.getNodeValues(3, _, _) >> { args -> args[1] << '3' }
+        3 * graph.getEdgeValues(_, _, _)
+        0 * _._
+        values == ['1', '2', '3'] as Set
+    }
+
+    def collectsValuesForEachEdgeTraversed() {
+        when:
+        walker.add(1)
+        def values = walker.findValues()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[2] << 2; args[2] << 3 }
+        1 * graph.getEdgeValues(1, 2, _) >> { args -> args[2] << '1->2' }
+        1 * graph.getEdgeValues(1, 3, _) >> { args -> args[2] << '1->3' }
+        1 * graph.getNodeValues(2, _, _) >> { args -> args[2] << 3 }
+        1 * graph.getEdgeValues(2, 3, _) >> { args -> args[2] << '2->3' }
+        1 * graph.getNodeValues(3, _, _)
+        0 * _._
+        values == ['1->2', '1->3', '2->3'] as Set
+    }
+
+    def collectsValuesForAllStartNodes() {
+        when:
+        walker.add(1, 2)
+        def values = walker.findValues()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 3 }
+        1 * graph.getNodeValues(2, _, _) >> { args -> args[1] << '2'; args[2] << 3 }
+        1 * graph.getNodeValues(3, _, _) >> { args -> args[1] << '3' }
+        2 * graph.getEdgeValues(_, _, _)
+        0 * _._
+        values == ['1', '2', '3'] as Set
+    }
+
+    def collectsValuesWhenCycleIsPresentInGraph() {
+        when:
+        walker.add(1)
+        def values = walker.findValues()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 2 }
+        1 * graph.getEdgeValues(1, 2, _) >> { args -> args[2] << '1->2' }
+        1 * graph.getNodeValues(2, _, _) >> { args -> args[1] << '2'; args[2] << 3 }
+        1 * graph.getEdgeValues(2, 3, _) >> { args -> args[2] << '2->3' }
+        1 * graph.getNodeValues(3, _, _) >> { args -> args[1] << '3'; args[2] << 1 }
+        1 * graph.getEdgeValues(3, 1, _) >> { args -> args[2] << '3->1' }
+        0 * _._
+        values == ['1', '1->2', '2', '2->3', '3', '3->1'] as Set
+    }
+
+    def collectsValuesWhenNodeConnectedToItself() {
+        when:
+        walker.add(1)
+        def values = walker.findValues()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 1 }
+        1 * graph.getEdgeValues(1, 1, _) >> { args -> args[2] << '1->1' }
+        0 * _._
+        values == ['1', '1->1'] as Set
+    }
+
+    def collectsValuesWhenMultipleCyclesInGraph() {
+        when:
+        walker.add(1)
+        def values = walker.findValues()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 1; args[2] << 2 }
+        1 * graph.getNodeValues(2, _, _) >> { args -> args[1] << '2'; args[2] << 3; args[2] << 4 }
+        1 * graph.getNodeValues(3, _, _) >> { args -> args[1] << '3'; args[2] << 2 }
+        1 * graph.getNodeValues(4, _, _) >> { args -> args[1] << '4' }
+        5 * graph.getEdgeValues(_, _, _) >> { args -> args[2] << "${args[0]}->${args[1]}".toString() }
+        0 * _._
+        values == ['1', '1->1', '1->2', '2', '2->3', '2->4', '3', '3->2', '4'] as Set
+    }
+
+    def canReuseWalkerForMultipleSearches() {
+        when:
+        walker.add(1)
+        def values = walker.findValues()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 2; args[2] << 3 }
+        1 * graph.getNodeValues(2, _, _) >> { args -> args[1] << '2'; args[2] << 3 }
+        1 * graph.getNodeValues(3, _, _) >> { args -> args[1] << '3' }
+        3 * graph.getEdgeValues(_, _, _)
+        0 * _._
+        values == ['1', '2', '3'] as Set
+
+        // Cached node (1) is reachable via 2 separate new paths (4->5->1 and 4->6->1)
+        when:
+        walker.add(4)
+        values = walker.findValues()
+
+        then:
+        1 * graph.getNodeValues(4, _, _) >> { args -> args[1] << '4'; args[2] << 5; args[2] << 6 }
+        1 * graph.getNodeValues(5, _, _) >> { args -> args[1] << '5'; args[2] << 1 }
+        1 * graph.getNodeValues(6, _, _) >> { args -> args[1] << '6'; args[2] << 1 }
+        4 * graph.getEdgeValues(_, _, _) >> { args -> args[2] << "${args[0]}->${args[1]}".toString() }
+        0 * _._
+        values == ['4', '4->5', '4->6', '5', '5->1', '6', '6->1', '1', '2', '3'] as Set
+
+        when:
+        walker.add(2)
+        values = walker.findValues()
+
+        then:
+        values == ['2', '3'] as Set
+    }
+
+    def canReuseWalkerWhenGraphContainsACycle() {
+        when:
+        walker.add(1)
+        walker.findValues()
+
+        then:
+        1 * graph.getNodeValues(1, _, _) >> { args -> args[1] << '1'; args[2] << 2 }
+        1 * graph.getNodeValues(2, _, _) >> { args -> args[1] << '2'; args[2] << 3 }
+        1 * graph.getNodeValues(3, _, _) >> { args -> args[1] << '3'; args[2] << 1; args[2] << 4 }
+        1 * graph.getNodeValues(4, _, _) >> { args -> args[1] << '4'; args[2] << 2}
+        5 * graph.getEdgeValues(_, _, _)
+        0 * _._
+
+        when:
+        walker.add(1)
+        def values = walker.findValues()
+
+        then:
+        values == ['1', '2', '3', '4'] as Set
+
+        when:
+        walker.add(2)
+        values = walker.findValues()
+
+        then:
+        values == ['1', '2', '3', '4'] as Set
+
+        when:
+        walker.add(3)
+        values = walker.findValues()
+
+        then:
+        values == ['1', '2', '3', '4'] as Set
+
+        when:
+        walker.add(4)
+        values = walker.findValues()
+
+        then:
+        values == ['1', '2', '3', '4'] as Set
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/ChainingTransformerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/ChainingTransformerTest.java
new file mode 100644
index 0000000..fbdbedf
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/ChainingTransformerTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 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;
+
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.Expectations;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.gradle.util.HelperUtil;
+import org.gradle.api.Transformer;
+import groovy.lang.Closure;
+
+ at RunWith(JMock.class)
+public class ChainingTransformerTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final ChainingTransformer<String> transformer = new ChainingTransformer<String>(String.class);
+
+    @Test
+    public void doesNothingWhenNoTransformersAdded() {
+        assertThat(transformer.transform("value"), equalTo("value"));
+    }
+
+    @Test
+    public void passesObjectToEachTransformerInTurn() {
+        final Transformer<String> transformerA = context.mock(Transformer.class, "transformerA");
+        final Transformer<String> transformerB = context.mock(Transformer.class, "transformerB");
+
+        context.checking(new Expectations(){{
+            one(transformerA).transform("original");
+            will(returnValue("a"));
+
+            one(transformerB).transform("a");
+            will(returnValue("b"));
+        }});
+
+        transformer.add(transformerA);
+        transformer.add(transformerB);
+
+        assertThat(transformer.transform("original"), equalTo("b"));
+    }
+
+    @Test
+    public void canUseAClosureAsATransformer() {
+        Closure closure = HelperUtil.toClosure("{ it + ' transformed' }");
+
+        transformer.add(closure);
+
+        assertThat(transformer.transform("original"), equalTo("original transformed"));
+    }
+
+    @Test
+    public void usesOriginalObjectWhenClosureReturnsNull() {
+        Closure closure = HelperUtil.toClosure("{ null }");
+
+        transformer.add(closure);
+
+        assertThat(transformer.transform("original"), equalTo("original"));
+    }
+
+    @Test
+    public void usesOriginalObjectWhenClosureReturnsObjectOfUnexpectedType() {
+        Closure closure = HelperUtil.toClosure("{ 9 }");
+
+        transformer.add(closure);
+
+        assertThat(transformer.transform("original"), equalTo("original"));
+    }
+
+    @Test
+    public void originalObjectIsSetAsDelegateForClosure() {
+        Closure closure = HelperUtil.toClosure("{ substring(1, 3) }");
+
+        transformer.add(closure);
+
+        assertThat(transformer.transform("original"), equalTo("ri"));
+    }
+    
+    @Test
+    public void closureCanTransformAStringIntoAGString() {
+        Closure closure = HelperUtil.toClosure("{ \"[$it]\" }");
+
+        transformer.add(closure);
+
+        assertThat(transformer.transform("original"), equalTo("[original]"));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/ConventionAwareHelperTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/ConventionAwareHelperTest.java
new file mode 100644
index 0000000..dacea3c
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/ConventionAwareHelperTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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.api.internal;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.internal.plugins.DefaultConvention;
+import org.gradle.api.plugins.Convention;
+import org.gradle.api.tasks.ConventionValue;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.TestTask;
+import org.gradle.util.WrapUtil;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+import static java.util.Collections.*;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * @author Hans Dockter
+ */
+public class ConventionAwareHelperTest {
+    ConventionAwareHelper conventionAware;
+
+    TestTask testTask;
+
+    @Before public void setUp() {
+        testTask = HelperUtil.createTask(TestTask.class);
+        conventionAware = new ConventionAwareHelper(testTask, new DefaultConvention());
+    }
+
+    @Test public void canMapPropertiesUsingConventionValue() {
+        final List expectedList1 = toList("a");
+        final List expectedList2 = toList("b");
+        assertNotNull(conventionAware.map("list1", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                assertSame(conventionAware.getConvention(), convention);
+                assertSame(testTask, conventionAwareObject);
+                return expectedList1;
+            }
+        }));
+        assertNotNull(conventionAware.map("list2", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return expectedList2;
+            }
+        }));
+
+        assertSame(expectedList1, conventionAware.getConventionValue("list1"));
+        assertSame(expectedList2, conventionAware.getConventionValue("list2"));
+    }
+
+    @Test
+    public void canMapPropertiesUsingClosure() {
+        conventionAware.map("list1", HelperUtil.toClosure("{ ['a'] }"));
+        assertThat(conventionAware.getConventionValue("list1"), equalTo((Object) toList("a")));
+
+        conventionAware.map("list1", HelperUtil.toClosure("{ convention -> [convention] }"));
+        assertThat(conventionAware.getConventionValue("list1"), equalTo((Object) toList(conventionAware.getConvention())));
+
+        conventionAware.map("list1", HelperUtil.toClosure("{ convention, object -> [convention, object] }"));
+        assertThat(conventionAware.getConventionValue("list1"), equalTo((Object) toList(conventionAware.getConvention(), testTask)));
+    }
+
+    @Test
+    public void canMapPropertiesUsingCallable() {
+        Callable callable = new Callable() {
+            public Object call() throws Exception {
+                return toList("a");
+            }
+        };
+
+        conventionAware.map("list1", callable);
+        assertThat(conventionAware.getConventionValue("list1"), equalTo((Object) toList("a")));
+    }
+    
+    @Test
+    public void canSetMappingUsingDynamicProperty() {
+        HelperUtil.call("{ it.list1 = { ['a'] } }", conventionAware);
+        assertThat(conventionAware.getConventionValue("list1"), equalTo((Object) toList("a")));
+    }
+    
+    @Test (expected = InvalidUserDataException.class) public void cannotMapUnknownProperty() {
+        conventionAware.map(WrapUtil.<String, ConventionValue>toMap("unknownProp", null));
+    }
+
+    @Test public void canOverwriteProperties() {
+        final List conventionList1 = toList("a");
+        conventionAware.map("list1", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return conventionList1;
+            }
+        });
+        assertSame(conventionList1, conventionAware.getConventionValue("list1"));
+        List expectedList1 = toList("b");
+        testTask.setList1(expectedList1);
+        assertSame(expectedList1, conventionAware.getConventionValue("list1"));
+    }
+
+    @Test public void canEnableCachingOfPropertyValue() {
+        conventionAware.map("list1", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return toList("a");
+            }
+        }).cache();
+        assertSame(conventionAware.getConventionValue("list1"), conventionAware.getConventionValue("list1"));
+    }
+
+    @Test public void notCachesPropertyValuesByDefault() {
+        ConventionMapping.MappedProperty property = conventionAware.map("list1", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return toList("a");
+            }
+        });
+
+        Object value1 = conventionAware.getConventionValue("list1");
+        Object value2 = conventionAware.getConventionValue("list1");
+        assertEquals(value1, value2);
+        assertNotSame(value1, value2);
+    }
+
+    @Test public void doesNotUseMappingWhenExplicitValueProvided() {
+        conventionAware.map("list1", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                throw new UnsupportedOperationException();
+            }
+        });
+
+        List<Object> value = toList();
+        assertThat(conventionAware.getConventionValue(value, "list1", true), sameInstance(value));
+    }
+    
+    @Test public void usesConventionValueForEmptyCollection() {
+        conventionAware.map("list1", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return toList("a");
+            }
+        });
+        assertThat(conventionAware.getConventionValue(toList(), "list1"), equalTo((Object) toList("a")));
+    }
+
+    @Test public void usesConventionValueForEmptyMap() {
+        conventionAware.map("map1", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return toMap("a", "b");
+            }
+        });
+        assertThat(conventionAware.getConventionValue(emptyMap(), "map1"), equalTo((Object) toMap("a", "b")));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/DefaultDomainObjectContainerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/DefaultDomainObjectContainerTest.java
new file mode 100644
index 0000000..94aa023
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/DefaultDomainObjectContainerTest.java
@@ -0,0 +1,326 @@
+/*
+ * 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 org.gradle.api.Action;
+import org.gradle.api.DomainObjectCollection;
+import org.gradle.api.specs.Spec;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.TestClosure;
+import org.hamcrest.Description;
+import org.jmock.Expectations;
+import org.jmock.api.Invocation;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Iterator;
+
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class DefaultDomainObjectContainerTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final DefaultDomainObjectContainer<CharSequence> container = new DefaultDomainObjectContainer<CharSequence>(CharSequence.class);
+
+    @Test
+    public void canGetAllDomainObjectsForEmptyCollection() {
+        assertTrue(container.getAll().isEmpty());
+    }
+
+    @Test
+    public void canGetAllDomainObjectsOrderedByOrderAdded() {
+        container.addObject("b");
+        container.addObject("a");
+        container.addObject("c");
+
+        assertThat(container.getAll(), equalTo(toLinkedSet((CharSequence) "b", "a", "c")));
+    }
+
+    @Test
+    public void canIterateOverEmptyCollection() {
+        Iterator<CharSequence> iterator = container.iterator();
+        assertFalse(iterator.hasNext());
+    }
+
+    @Test
+    public void canIterateOverDomainObjectsOrderedByOrderAdded() {
+        container.addObject("b");
+        container.addObject("a");
+        container.addObject("c");
+
+        Iterator<CharSequence> iterator = container.iterator();
+        assertThat(iterator.next(), equalTo((CharSequence) "b"));
+        assertThat(iterator.next(), equalTo((CharSequence) "a"));
+        assertThat(iterator.next(), equalTo((CharSequence) "c"));
+        assertFalse(iterator.hasNext());
+    }
+
+    @Test
+    public void canGetAllMatchingDomainObjectsOrderedByOrderAdded() {
+        Spec<CharSequence> spec = new Spec<CharSequence>() {
+            public boolean isSatisfiedBy(CharSequence element) {
+                return !element.equals("b");
+            }
+        };
+
+        container.addObject("a");
+        container.addObject("b");
+        container.addObject("c");
+
+        assertThat(container.findAll(spec), equalTo(toLinkedSet((CharSequence) "a", "c")));
+    }
+
+    @Test
+    public void getAllMatchingDomainObjectsReturnsEmptySetWhenNoMatches() {
+        Spec<CharSequence> spec = new Spec<CharSequence>() {
+            public boolean isSatisfiedBy(CharSequence element) {
+                return false;
+            }
+        };
+
+        container.addObject("a");
+
+        assertTrue(container.findAll(spec).isEmpty());
+    }
+
+    @Test
+    public void canGetFilteredCollectionContainingAllObjectsWhichMeetSpec() {
+        Spec<CharSequence> spec = new Spec<CharSequence>() {
+            public boolean isSatisfiedBy(CharSequence element) {
+                return !element.equals("b");
+            }
+        };
+        TestClosure testClosure = new TestClosure() {
+            public Object call(Object param) {
+                return !param.equals("b");    
+            }
+        };
+
+        container.addObject("a");
+        container.addObject("b");
+        container.addObject("c");
+
+        assertThat(container.matching(spec).getAll(), equalTo(toLinkedSet((CharSequence) "a", "c")));
+        assertThat(container.matching(HelperUtil.toClosure(testClosure)).getAll(), equalTo(toLinkedSet((CharSequence) "a", "c")));
+    }
+
+    @Test
+    public void canGetFilteredCollectionContainingAllObjectsWhichHaveType() {
+        container.addObject("c");
+        container.addObject("a");
+        container.addObject(new StringBuffer("b"));
+
+        assertThat(container.withType(CharSequence.class).getAll(), equalTo(container.getAll()));
+        assertThat(container.withType(String.class).getAll(), equalTo(toLinkedSet("c", "a")));
+    }
+
+    @Test
+    public void filteredCollectionIsLive() {
+        Spec<CharSequence> spec = new Spec<CharSequence>() {
+            public boolean isSatisfiedBy(CharSequence element) {
+                return !element.equals("a");
+            }
+        };
+
+        container.addObject("a");
+
+        DomainObjectCollection<CharSequence> filteredCollection = container.matching(spec);
+        assertTrue(filteredCollection.getAll().isEmpty());
+
+        container.addObject("b");
+        container.addObject("c");
+
+        assertThat(filteredCollection.getAll(), equalTo(toLinkedSet((CharSequence) "b", "c")));
+    }
+
+    @Test
+    public void filteredCollectionExecutesActionWhenMatchingObjectAdded() {
+        final Action<CharSequence> action = context.mock(Action.class);
+
+        context.checking(new Expectations() {{
+            one(action).execute("a");
+        }});
+
+        Spec<CharSequence> spec = new Spec<CharSequence>() {
+            public boolean isSatisfiedBy(CharSequence element) {
+                return !element.equals("b");
+            }
+        };
+
+        container.matching(spec).whenObjectAdded(action);
+
+        container.addObject("a");
+        container.addObject("b");
+    }
+
+    @Test
+    public void filteredCollectionExecutesClosureWhenMatchingObjectAdded() {
+        final TestClosure closure = context.mock(TestClosure.class);
+
+        context.checking(new Expectations() {{
+            one(closure).call("a");
+        }});
+
+        Spec<CharSequence> spec = new Spec<CharSequence>() {
+            public boolean isSatisfiedBy(CharSequence element) {
+                return !element.equals("b");
+            }
+        };
+
+        container.matching(spec).whenObjectAdded(HelperUtil.toClosure(closure));
+
+        container.addObject("a");
+        container.addObject("b");
+    }
+
+    @Test
+    public void canChainFilteredCollections() {
+        Spec<CharSequence> spec = new Spec<CharSequence>() {
+            public boolean isSatisfiedBy(CharSequence element) {
+                return !element.equals("b");
+            }
+        };
+        Spec<String> spec2 = new Spec<String>() {
+            public boolean isSatisfiedBy(String element) {
+                return !element.equals("c");
+            }
+        };
+
+        container.addObject("a");
+        container.addObject("b");
+        container.addObject("c");
+        container.addObject(new StringBuffer("d"));
+
+        DomainObjectCollection<String> collection = container.matching(spec).withType(String.class).matching(spec2);
+        assertThat(collection.getAll(), equalTo(toSet("a")));
+    }
+
+    @Test
+    public void callsActionWhenObjectAdded() {
+        final Action<CharSequence> action = context.mock(Action.class);
+
+        context.checking(new Expectations() {{
+            one(action).execute("a");
+        }});
+
+        container.whenObjectAdded(action);
+        container.addObject("a");
+    }
+
+    @Test
+    public void callsClosureWhenObjectAdded() {
+        final TestClosure closure = context.mock(TestClosure.class);
+
+        context.checking(new Expectations() {{
+            one(closure).call("a");
+        }});
+
+        container.whenObjectAdded(HelperUtil.toClosure(closure));
+        container.addObject("a");
+    }
+
+    @Test
+    public void callsActionWhenObjectRemoved() {
+        final Action<CharSequence> action = context.mock(Action.class);
+        final String original = "a";
+
+        context.checking(new Expectations() {{
+            one(action).execute(with(sameInstance(original)));
+        }});
+
+        container.whenObjectRemoved(action);
+        container.addObject(original);
+        container.addObject("a");
+    }
+
+    @Test
+    public void allObjectsCallsActionForEachExistingObject() {
+        final Action<CharSequence> action = context.mock(Action.class);
+
+        context.checking(new Expectations() {{
+            one(action).execute("a");
+        }});
+
+        container.addObject("a");
+        container.allObjects(action);
+    }
+
+    @Test
+    public void allObjectsCallsClosureForEachExistingObject() {
+        final TestClosure closure = context.mock(TestClosure.class);
+
+        context.checking(new Expectations() {{
+            one(closure).call("a");
+        }});
+
+        container.addObject("a");
+        container.allObjects(HelperUtil.toClosure(closure));
+    }
+
+    @Test
+    public void allObjectsCallsActionForEachNewObject() {
+        final Action<CharSequence> action = context.mock(Action.class);
+
+        context.checking(new Expectations() {{
+            one(action).execute("a");
+        }});
+
+        container.allObjects(action);
+        container.addObject("a");
+    }
+
+    @Test
+    public void allObjectsCallsClosureForEachNewObject() {
+        final TestClosure closure = context.mock(TestClosure.class);
+
+        context.checking(new Expectations() {{
+            one(closure).call("a");
+        }});
+
+        container.allObjects(HelperUtil.toClosure(closure));
+        container.addObject("a");
+    }
+
+    @Test
+    public void allObjectsCallsActionForEachNewObjectAddedByTheAction() {
+        final Action<CharSequence> action = context.mock(Action.class);
+
+        context.checking(new Expectations() {{
+            one(action).execute("a");
+            will(new org.jmock.api.Action() {
+                public Object invoke(Invocation invocation) throws Throwable {
+                    container.addObject("c");
+                    return null;
+                }
+
+                public void describeTo(Description description) {
+                    description.appendText("add 'c'");
+                }
+            });
+            one(action).execute("b");
+            one(action).execute("c");
+        }});
+
+        container.addObject("a");
+        container.addObject("b");
+        container.allObjects(action);
+    }
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectContainerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectContainerTest.java
new file mode 100644
index 0000000..48096c4
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/DefaultNamedDomainObjectContainerTest.java
@@ -0,0 +1,628 @@
+/*
+ * 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 groovy.lang.Closure;
+import groovy.lang.MissingPropertyException;
+import org.gradle.api.Action;
+import org.gradle.api.DomainObjectCollection;
+import org.gradle.api.Rule;
+import org.gradle.api.UnknownDomainObjectException;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.util.*;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Iterator;
+
+import static org.gradle.util.HelperUtil.*;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class DefaultNamedDomainObjectContainerTest {
+    private final ClassGenerator classGenerator = new AsmBackedClassGenerator();
+    private final DefaultNamedDomainObjectContainer<Bean> container = classGenerator.newInstance(DefaultNamedDomainObjectContainer.class, Bean.class, classGenerator);
+    private final JUnit4Mockery context = new JUnit4Mockery();
+
+    @Test
+    public void usesTypeNameToGenerateDisplayName() {
+        assertThat(container.getTypeDisplayName(), equalTo("Bean"));
+        assertThat(container.getDisplayName(), equalTo("Bean container"));
+    }
+
+    @Test
+    public void canGetAllDomainObjectsForEmptyContainer() {
+        assertTrue(container.getAll().isEmpty());
+    }
+
+    @Test
+    public void canGetAllDomainObjectsOrderedByName() {
+        Bean bean1 = new Bean();
+        Bean bean2 = new Bean();
+        Bean bean3 = new Bean();
+
+        container.addObject("b", bean2);
+        container.addObject("a", bean1);
+        container.addObject("c", bean3);
+
+        assertThat(container.getAll(), equalTo(toLinkedSet(bean1, bean2, bean3)));
+    }
+
+    @Test
+    public void canIterateOverEmptyContainer() {
+        Iterator<Bean> iterator = container.iterator();
+        assertFalse(iterator.hasNext());
+    }
+
+    @Test
+    public void canIterateOverDomainObjectsOrderedByName() {
+        Bean bean1 = new Bean();
+        Bean bean2 = new Bean();
+        Bean bean3 = new Bean();
+
+        container.addObject("b", bean2);
+        container.addObject("a", bean1);
+        container.addObject("c", bean3);
+
+        Iterator<Bean> iterator = container.iterator();
+        assertThat(iterator.next(), sameInstance(bean1));
+        assertThat(iterator.next(), sameInstance(bean2));
+        assertThat(iterator.next(), sameInstance(bean3));
+        assertFalse(iterator.hasNext());
+    }
+
+    @Test
+    public void canGetAllDomainObjectsAsMapForEmptyContainer() {
+        assertTrue(container.getAsMap().isEmpty());
+    }
+
+    @Test
+    public void canGetAllDomainObjectsAsMap() {
+        Bean bean1 = new Bean();
+        Bean bean2 = new Bean();
+        Bean bean3 = new Bean();
+
+        container.addObject("b", bean2);
+        container.addObject("a", bean1);
+        container.addObject("c", bean3);
+
+        assertThat(container.getAsMap(), equalTo(GUtil.map("a", bean1, "b", bean2, "c", bean3)));
+    }
+
+    @Test
+    public void canGetAllMatchingDomainObjectsOrderedByName() {
+        Bean bean1 = new Bean();
+        final Bean bean2 = new Bean();
+        Bean bean3 = new Bean();
+
+        Spec<Bean> spec = new Spec<Bean>() {
+            public boolean isSatisfiedBy(Bean element) {
+                return element == bean2;
+            }
+        };
+
+        container.addObject("a", bean1);
+        container.addObject("b", bean2);
+        container.addObject("c", bean3);
+
+        assertThat(container.findAll(spec), equalTo(toLinkedSet(bean2)));
+    }
+
+    @Test
+    public void getAllMatchingDomainObjectsReturnsEmptySetWhenNoMatches() {
+        Spec<Bean> spec = new Spec<Bean>() {
+            public boolean isSatisfiedBy(Bean element) {
+                return false;
+            }
+        };
+
+        container.addObject("a", new Bean());
+
+        assertTrue(container.findAll(spec).isEmpty());
+    }
+
+    @Test
+    public void canGetFilteredCollectionContainingAllObjectsWhichMeetSpec() {
+        final Bean bean1 = new Bean();
+        Bean bean2 = new Bean();
+        Bean bean3 = new Bean();
+
+        Spec<Bean> spec = new Spec<Bean>() {
+            public boolean isSatisfiedBy(Bean element) {
+                return element != bean1;
+            }
+        };
+
+        TestClosure testClosure = new TestClosure() {
+            public Object call(Object param) {
+                return param != bean1;
+            }
+        };
+
+        container.addObject("a", bean1);
+        container.addObject("b", bean2);
+        container.addObject("c", bean3);
+
+        assertThat(container.matching(spec).getAll(), equalTo(toLinkedSet(bean2, bean3)));
+        assertThat(container.matching(HelperUtil.toClosure(testClosure)).getAll(), equalTo(toLinkedSet(bean2, bean3)));
+        assertThat(container.matching(spec).findByName("a"), nullValue());
+        assertThat(container.matching(spec).findByName("b"), sameInstance(bean2));
+    }
+
+    @Test
+    public void canGetFilteredCollectionContainingAllObjectsWhichHaveType() {
+        class OtherBean extends Bean {
+        }
+        Bean bean1 = new Bean();
+        OtherBean bean2 = new OtherBean();
+        Bean bean3 = new Bean();
+
+        container.addObject("c", bean3);
+        container.addObject("a", bean1);
+        container.addObject("b", bean2);
+
+        assertThat(container.withType(Bean.class).getAll(), equalTo(toLinkedSet(bean1, bean2, bean3)));
+        assertThat(container.withType(OtherBean.class).getAll(), equalTo(toLinkedSet(bean2)));
+        assertThat(container.withType(OtherBean.class).findByName("a"), nullValue());
+        assertThat(container.withType(OtherBean.class).findByName("b"), sameInstance(bean2));
+    }
+
+    @Test
+    public void filteredCollectionIsLive() {
+        final Bean bean1 = new Bean();
+        Bean bean2 = new Bean();
+        Bean bean3 = new Bean();
+        Bean bean4 = new Bean();
+
+        Spec<Bean> spec = new Spec<Bean>() {
+            public boolean isSatisfiedBy(Bean element) {
+                return element != bean1;
+            }
+        };
+
+        container.addObject("a", bean1);
+
+        DomainObjectCollection<Bean> filteredCollection = container.matching(spec);
+        assertTrue(filteredCollection.getAll().isEmpty());
+
+        container.addObject("b", bean2);
+        container.addObject("c", bean3);
+
+        assertThat(filteredCollection.getAll(), equalTo(toLinkedSet(bean2, bean3)));
+
+        container.addObject("c", bean4);
+
+        assertThat(filteredCollection.getAll(), equalTo(toLinkedSet(bean2, bean4)));
+    }
+
+    @Test
+    public void filteredCollectionExecutesActionWhenMatchingObjectAdded() {
+        final Action<Bean> action = context.mock(Action.class);
+        final Bean bean = new Bean();
+
+        context.checking(new Expectations() {{
+            one(action).execute(bean);
+        }});
+
+        Spec<Bean> spec = new Spec<Bean>() {
+            public boolean isSatisfiedBy(Bean element) {
+                return element == bean;
+            }
+        };
+
+        container.matching(spec).whenObjectAdded(action);
+
+        container.addObject("bean", bean);
+        container.addObject("bean2", new Bean());
+    }
+
+    @Test
+    public void filteredCollectionExecutesClosureWhenMatchingObjectAdded() {
+        final TestClosure closure = context.mock(TestClosure.class);
+        final Bean bean = new Bean();
+
+        context.checking(new Expectations() {{
+            one(closure).call(bean);
+        }});
+
+        Spec<Bean> spec = new Spec<Bean>() {
+            public boolean isSatisfiedBy(Bean element) {
+                return element == bean;
+            }
+        };
+
+        container.matching(spec).whenObjectAdded(HelperUtil.toClosure(closure));
+
+        container.addObject("bean", bean);
+        container.addObject("bean2", new Bean());
+    }
+
+    @Test
+    public void canChainFilteredCollections() {
+        final Bean bean = new Bean();
+        final Bean bean2 = new Bean();
+        final Bean bean3 = new Bean();
+
+        Spec<Bean> spec = new Spec<Bean>() {
+            public boolean isSatisfiedBy(Bean element) {
+                return element != bean;
+            }
+        };
+        Spec<Bean> spec2 = new Spec<Bean>() {
+            public boolean isSatisfiedBy(Bean element) {
+                return element != bean2;
+            }
+        };
+
+        container.addObject("a", bean);
+        container.addObject("b", bean2);
+        container.addObject("c", bean3);
+
+        DomainObjectCollection<Bean> collection = container.matching(spec).matching(spec2);
+        assertThat(collection.getAll(), equalTo(toSet(bean3)));
+    }
+
+    @Test
+    public void canGetDomainObjectByName() {
+        Bean bean = new Bean();
+        container.addObject("a", bean);
+
+        assertThat(container.getByName("a"), sameInstance(bean));
+        assertThat(container.getAt("a"), sameInstance(bean));
+    }
+
+    @Test
+    public void getDomainObjectByNameFailsForUnknownDomainObject() {
+        try {
+            container.getByName("unknown");
+            fail();
+        } catch (UnknownDomainObjectException e) {
+            assertThat(e.getMessage(), equalTo("Bean with name 'unknown' not found."));
+        }
+    }
+
+    @Test
+    public void getDomainObjectInvokesRuleForUnknownDomainObject() {
+        Bean bean = new Bean();
+        addRuleFor(bean);
+
+        assertThat(container.getByName("bean"), sameInstance(bean));
+    }
+
+    @Test
+    public void canConfigureDomainObjectByName() {
+        Bean bean = new Bean();
+        container.addObject("a", bean);
+
+        assertThat(container.getByName("a", toClosure("{ beanProperty = 'hi' }")), sameInstance(bean));
+        assertThat(bean.getBeanProperty(), equalTo("hi"));
+    }
+
+    @Test
+    public void configureDomainObjectInvokesRuleForUnknownDomainObject() {
+        Bean bean = new Bean();
+        addRuleFor(bean);
+
+        assertThat(container.getByName("bean", toClosure("{ beanProperty = 'hi' }")), sameInstance(bean));
+        assertThat(bean.getBeanProperty(), equalTo("hi"));
+    }
+
+    @Test
+    public void canFindDomainObjectByName() {
+        Bean bean = new Bean();
+        container.addObject("a", bean);
+
+        assertThat(container.findByName("a"), sameInstance(bean));
+    }
+
+    @Test
+    public void findDomainObjectByNameReturnsNullForUnknownDomainObject() {
+        assertThat(container.findByName("a"), nullValue());
+    }
+
+    @Test
+    public void findDomainObjectByNameInvokesRulesForUnknownDomainObject() {
+        Bean bean = new Bean();
+        addRuleFor(bean);
+
+        assertThat(container.findByName("bean"), sameInstance(bean));
+    }
+
+    @Test
+    public void findDomainObjectByNameInvokesNestedRulesOnlyOnceForUnknownDomainObject() {
+        final Bean bean1 = new Bean();
+        final Bean bean2 = new Bean();
+        container.addRule(new Rule() {
+            public String getDescription() {
+                return "rule1";
+            }
+
+            public void apply(String domainObjectName) {
+                if (domainObjectName.equals("bean1")) {
+                    container.addObject("bean1", bean1);
+                }
+            }
+        });
+        container.addRule(new Rule() {
+            private boolean applyHasBeenCalled;
+
+            public String getDescription() {
+                return "rule2";
+            }
+
+            public void apply(String domainObjectName) {
+                if (domainObjectName.equals("bean2")) {
+                    assertThat(applyHasBeenCalled, equalTo(false));
+                    container.findByName("bean1");
+                    container.findByName("bean2");
+                    container.addObject("bean2", bean2);
+                    applyHasBeenCalled = true;
+                }
+            }
+        });
+        container.findByName("bean2");
+        assertThat(container.getAll(), equalTo(WrapUtil.toSet(bean1, bean2)));
+    }
+
+    @Test
+    public void callsActionWhenObjectAdded() {
+        final Action<Bean> action = context.mock(Action.class);
+        final Bean bean = new Bean();
+
+        context.checking(new Expectations() {{
+            one(action).execute(bean);
+        }});
+
+        container.whenObjectAdded(action);
+        container.addObject("bean", bean);
+    }
+
+    @Test
+    public void callsClosureWhenObjectAdded() {
+        final TestClosure closure = context.mock(TestClosure.class);
+        final Bean bean = new Bean();
+
+        context.checking(new Expectations() {{
+            one(closure).call(bean);
+        }});
+
+        container.whenObjectAdded(HelperUtil.toClosure(closure));
+        container.addObject("bean", bean);
+    }
+
+    @Test
+    public void callsActionWhenObjectRemoved() {
+        final Action<Bean> action = context.mock(Action.class);
+        final Bean bean = new Bean();
+
+        context.checking(new Expectations() {{
+            one(action).execute(bean);
+        }});
+
+        container.whenObjectRemoved(action);
+        container.addObject("bean", bean);
+        container.addObject("bean", new Bean());
+    }
+
+    @Test
+    public void allObjectsCallsActionForEachExistingObject() {
+        final Action<Bean> action = context.mock(Action.class);
+        final Bean bean = new Bean();
+
+        context.checking(new Expectations() {{
+            one(action).execute(bean);
+        }});
+
+        container.addObject("bean", bean);
+        container.allObjects(action);
+    }
+
+    @Test
+    public void allObjectsCallsClosureForEachExistingObject() {
+        final TestClosure closure = context.mock(TestClosure.class);
+        final Bean bean = new Bean();
+
+        context.checking(new Expectations() {{
+            one(closure).call(bean);
+        }});
+
+        container.addObject("bean", bean);
+        container.allObjects(HelperUtil.toClosure(closure));
+    }
+
+    @Test
+    public void allObjectsCallsActionForEachNewObject() {
+        final Action<Bean> action = context.mock(Action.class);
+        final Bean bean = new Bean();
+
+        context.checking(new Expectations() {{
+            one(action).execute(bean);
+        }});
+
+        container.allObjects(action);
+        container.addObject("bean", bean);
+    }
+
+    @Test
+    public void allObjectsCallsClosureForEachNewObject() {
+        final TestClosure closure = context.mock(TestClosure.class);
+        final Bean bean = new Bean();
+
+        context.checking(new Expectations() {{
+            one(closure).call(bean);
+        }});
+
+        container.allObjects(HelperUtil.toClosure(closure));
+        container.addObject("bean", bean);
+    }
+
+    @Test
+    public void eachObjectIsAvailableAsADynamicProperty() {
+        Bean bean = new Bean();
+        container.addObject("child", bean);
+        assertTrue(container.getAsDynamicObject().hasProperty("child"));
+        assertThat(container.getAsDynamicObject().getProperty("child"), sameInstance((Object) bean));
+        assertThat(container.getAsDynamicObject().getProperties().get("child"), sameInstance((Object) bean));
+        assertThat(call("{ it.child }", container), sameInstance((Object) bean));
+        assertThat(call("{ it.child }", container.withType(Bean.class)), sameInstance((Object) bean));
+        assertThat(call("{ it.child }", container.matching(Specs.satisfyAll())), sameInstance((Object) bean));
+    }
+
+    @Test
+    public void eachObjectIsAvailableUsingAnIndex() {
+        Bean bean = new Bean();
+        container.addObject("child", bean);
+        assertThat(call("{ it['child'] }", container), sameInstance((Object) bean));
+    }
+
+    @Test
+    public void cannotGetUnknownProperty() {
+        assertFalse(container.getAsDynamicObject().hasProperty("unknown"));
+
+        try {
+            container.getAsDynamicObject().getProperty("unknown");
+            fail();
+        } catch (MissingPropertyException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void dynamicPropertyAccessInvokesRulesForUnknownDomainObject() {
+        Bean bean = new Bean();
+        addRuleFor(bean);
+
+        assertTrue(container.getAsDynamicObject().hasProperty("bean"));
+        assertThat(container.getAsDynamicObject().getProperty("bean"), sameInstance((Object) bean));
+    }
+
+    @Test
+    public void eachObjectIsAvailableAsConfigureMethod() {
+        Bean bean = new Bean();
+        container.addObject("child", bean);
+
+        Closure closure = toClosure("{ beanProperty = 'value' }");
+        assertTrue(container.getAsDynamicObject().hasMethod("child", closure));
+        container.getAsDynamicObject().invokeMethod("child", closure);
+        assertThat(bean.getBeanProperty(), equalTo("value"));
+
+        call("{ it.child { beanProperty = 'value 2' } }", container);
+        assertThat(bean.getBeanProperty(), equalTo("value 2"));
+
+        call("{ it.invokeMethod('child') { beanProperty = 'value 3' } }", container);
+        assertThat(bean.getBeanProperty(), equalTo("value 3"));
+    }
+
+    @Test
+    public void canUseDynamicPropertiesAndMethodsInsideConfigureClosures() {
+        Bean bean = new Bean();
+        container.addObject("child", bean);
+        container.addObject("aProp", bean);
+        container.addObject("a", bean);
+        container.addObject("withType", bean);
+        container.addObject("allObjects", bean);
+
+        ConfigureUtil.configure(toClosure("{ child.beanProperty = 'value 1' }"), container);
+        assertThat(bean.getBeanProperty(), equalTo("value 1"));
+
+        ConfigureUtil.configure(toClosure("{ child { beanProperty = 'value 2' } }"), container);
+        assertThat(bean.getBeanProperty(), equalTo("value 2"));
+
+        ConfigureUtil.configure(toClosure("{ aProp.beanProperty = 'value 3' }"), container);
+        assertThat(bean.getBeanProperty(), equalTo("value 3"));
+
+        ConfigureUtil.configure(toClosure("{ a.beanProperty = 'value 4' }"), container);
+        assertThat(bean.getBeanProperty(), equalTo("value 4"));
+
+        // Try with an element with the same name as a method
+        ConfigureUtil.configure(toClosure("{ withType.beanProperty = 'value 6' }"), container);
+        assertThat(bean.getBeanProperty(), equalTo("value 6"));
+
+        ConfigureUtil.configure(toClosure("{ withType { beanProperty = 'value 6' } }"), container);
+        assertThat(bean.getBeanProperty(), equalTo("value 6"));
+    }
+
+    @Test
+    public void cannotInvokeUnknownMethod() {
+        container.addObject("child", new Bean());
+
+        assertMethodUnknown("unknown");
+        assertMethodUnknown("unknown", toClosure("{ }"));
+        assertMethodUnknown("child");
+        assertMethodUnknown("child", "not a closure");
+        assertMethodUnknown("child", toClosure("{ }"), "something else");
+    }
+
+    private void assertMethodUnknown(String name, Object... arguments) {
+        assertFalse(container.getAsDynamicObject().hasMethod(name, arguments));
+        try {
+            container.getAsDynamicObject().invokeMethod(name, arguments);
+            fail();
+        } catch (groovy.lang.MissingMethodException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void configureMethodInvokesRuleForUnknownDomainObject() {
+        Bean bean = new Bean();
+        addRuleFor(bean);
+
+        assertTrue(container.getAsDynamicObject().hasMethod("bean", toClosure("{ }")));
+    }
+
+    @Test
+    public void addRuleByClosure() {
+        String testPropertyKey = "org.gradle.test.addRuleByClosure";
+        String expectedTaskName = "someTaskName";
+        Closure ruleClosure = HelperUtil.toClosure(String.format("{ taskName -> System.setProperty('%s', '%s') }",
+                testPropertyKey, expectedTaskName));
+        container.addRule("description", ruleClosure);
+        container.getRules().get(0).apply(expectedTaskName);
+        assertThat(System.getProperty(testPropertyKey), equalTo(expectedTaskName));
+        System.getProperties().remove(testPropertyKey);
+    }
+
+    private void addRuleFor(final Bean bean) {
+        container.addRule(new Rule() {
+            public String getDescription() {
+                throw new UnsupportedOperationException();
+            }
+
+            public void apply(String taskName) {
+                container.addObject(taskName, bean);
+            }
+        });
+    }
+
+    private class Bean {
+        private String beanProperty;
+
+        public String getBeanProperty() {
+            return beanProperty;
+        }
+
+        public void setBeanProperty(String beanProperty) {
+            this.beanProperty = beanProperty;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/DefaultTaskTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/DefaultTaskTest.groovy
new file mode 100644
index 0000000..ddf0139
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/DefaultTaskTest.groovy
@@ -0,0 +1,143 @@
+/*
+ * 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 org.gradle.api.DefaultTask
+import org.gradle.api.tasks.AbstractTaskTest
+import org.gradle.util.WrapUtil
+import org.junit.Before
+import org.junit.Test
+import static org.gradle.util.Matchers.*
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import java.util.concurrent.Callable
+import org.gradle.listener.ListenerManager
+
+/**
+ * @author Hans Dockter
+ */
+class DefaultTaskTest extends AbstractTaskTest {
+    DefaultTask defaultTask
+
+    Object testCustomPropValue;
+
+    @Before public void setUp() {
+        super.setUp()
+        testCustomPropValue = new Object()
+        defaultTask = createTask(DefaultTask.class)
+    }
+
+    AbstractTask getTask() {
+        defaultTask
+    }
+
+    @Test public void testDefaultTask() {
+        assertThat(defaultTask.dependsOn, isEmpty())
+        assertEquals([], defaultTask.actions)
+    }
+
+    @Test public void testHasUsefulToString() {
+        assertEquals('task \':taskname\'', task.toString())
+    }
+
+    @Test public void testCanInjectValuesIntoTaskWhenUsingNoArgsConstructor() {
+        DefaultTask task = AbstractTask.injectIntoNewInstance(project, TEST_TASK_NAME, { new DefaultTask() } as Callable)
+        assertThat(task.project, sameInstance(project))
+        assertThat(task.name, equalTo(TEST_TASK_NAME))
+    }
+
+    @Test public void testDoFirstWithClosureDelegatesToTask() {
+        Closure testAction = {}
+        defaultTask.doFirst(testAction)
+        assertSame(defaultTask, testAction.delegate)
+        assertEquals(Closure.DELEGATE_FIRST, testAction.getResolveStrategy())
+    }
+
+    @Test public void testDoFirstWithClosure() {
+        List<Integer> executed = new ArrayList<Integer>();
+        Closure testAction1 = { executed.add(1) }
+        Closure testAction2 = {-> executed.add(2) }
+        Closure testAction3 = {task -> executed.add(3) }
+        defaultTask.doFirst(testAction1)
+        defaultTask.doFirst(testAction2)
+        defaultTask.doFirst(testAction3)
+        defaultTask.execute()
+        assertEquals(executed, WrapUtil.toList(3, 2, 1))
+    }
+
+    @Test
+    void getAdditonalProperties() {
+        defaultTask.additionalProperties.customProp = testCustomPropValue
+        assertSame(testCustomPropValue, defaultTask."customProp")
+    }
+
+    @Test
+    void setAdditonalProperties() {
+        defaultTask."customProp" = testCustomPropValue
+        assertSame(testCustomPropValue, defaultTask.additionalProperties.customProp)
+    }
+
+    @Test
+    void getAndSetConventionProperties() {
+        TestConvention convention = new TestConvention()
+        defaultTask.convention.plugins.test = convention
+        assertTrue(defaultTask.hasProperty('conventionProperty'))
+        defaultTask.conventionProperty = 'value'
+        assertEquals(defaultTask.conventionProperty, 'value')
+        assertEquals(convention.conventionProperty, 'value')
+    }
+
+    @Test
+    void canCallConventionMethods() {
+        defaultTask.convention.plugins.test = new TestConvention()
+        assertEquals(defaultTask.conventionMethod('a', 'b').toString(), "a.b")
+    }
+
+    @Test
+    void getProperty() {
+        defaultTask.additionalProperties.customProp = testCustomPropValue
+        assertSame(testCustomPropValue, defaultTask.property("customProp"))
+        assertSame(AbstractTaskTest.TEST_TASK_NAME, defaultTask.property("name"))
+    }
+
+    @Test(expected = MissingPropertyException)
+    void accessNonExistingProperty() {
+        defaultTask."unknownProp"
+    }
+
+    @Test
+    void canGetTemporaryDirectory() {
+        File tmpDir = new File(project.buildDir, "tmp/taskname")
+        assertFalse(tmpDir.exists())
+
+        assertThat(defaultTask.temporaryDir, equalTo(tmpDir))
+        assertTrue(tmpDir.isDirectory())
+    }
+
+    @Test
+    void canAccessServices() {
+        assertNotNull(defaultTask.services.get(ListenerManager))
+    }
+}
+
+class TestConvention {
+    def conventionProperty
+
+    def conventionMethod(a, b) {
+        "$a.$b"
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/DynamicObjectHelperTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/DynamicObjectHelperTest.java
new file mode 100644
index 0000000..94e94fd
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/DynamicObjectHelperTest.java
@@ -0,0 +1,837 @@
+/*
+ * Copyright 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;
+
+import groovy.lang.*;
+import groovy.lang.MissingMethodException;
+import org.gradle.api.plugins.Convention;
+import org.gradle.api.internal.plugins.DefaultConvention;
+import org.gradle.util.HelperUtil;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+import java.util.Map;
+
+public class DynamicObjectHelperTest {
+    @Test
+    public void hasPropertiesDefinedByClass() {
+        Bean bean = new Bean();
+        assertTrue(bean.hasProperty("readWriteProperty"));
+        assertTrue(bean.hasProperty("readOnlyProperty"));
+        assertTrue(bean.hasProperty("writeOnlyProperty"));
+    }
+
+    @Test
+    public void canGetAndSetClassProperty() {
+        Bean bean = new Bean();
+        bean.setReadWriteProperty("value");
+
+        assertThat(bean.getProperty("readWriteProperty"), equalTo((Object) "value"));
+
+        bean.setProperty("readWriteProperty", "new value");
+
+        assertThat(bean.getProperty("readWriteProperty"), equalTo((Object) "new value"));
+        assertThat(bean.getReadWriteProperty(), equalTo((Object) "new value"));
+    }
+
+    @Test
+    public void canGetReadOnlyClassProperty() {
+        Bean bean = new Bean();
+        bean.doSetReadOnlyProperty("value");
+
+        assertThat(bean.getProperty("readOnlyProperty"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void cannotSetReadOnlyClassProperty() {
+        Bean bean = new Bean();
+
+        try {
+            bean.setProperty("readOnlyProperty", "value");
+            fail();
+        } catch (ReadOnlyPropertyException e) {
+            assertThat(e.getMessage(), equalTo("Cannot set the value of read-only property 'readOnlyProperty' on <bean>."));
+        }
+    }
+    
+    @Test
+    public void canSetWriteOnlyClassProperty() {
+        Bean bean = new Bean();
+        bean.setProperty("writeOnlyProperty", "value");
+        assertThat(bean.doGetWriteOnlyProperty(), equalTo("value"));
+    }
+
+    @Test
+    public void cannotGetWriteOnlyClassProperty() {
+        Bean bean = new Bean();
+
+        try {
+            bean.getProperty("writeOnlyProperty");
+            fail();
+        } catch (GroovyRuntimeException e) {
+            assertThat(e.getMessage(), equalTo("Cannot get the value of write-only property 'writeOnlyProperty' on <bean>."));
+        }
+    }
+
+    @Test
+    public void canSetPropertyWhenGetterAndSetterHaveDifferentTypes() {
+        Bean bean = new Bean();
+
+        bean.setProperty("differentTypesProperty", "91");
+        assertThat(bean.getProperty("differentTypesProperty"), equalTo((Object) 91));
+    }
+
+    @Test
+    public void groovyObjectHasPropertiesDefinedByClassMetaInfo() {
+        GroovyBean bean = new GroovyBean();
+        assertTrue(bean.hasProperty("groovyProperty"));
+        assertTrue(bean.hasProperty("dynamicGroovyProperty"));
+    }
+
+    @Test
+    public void groovyObjectHasPropertiesInheritedFromSuperClass() {
+        GroovyBean bean = new GroovyBean();
+        assertTrue(bean.hasProperty("readWriteProperty"));
+        assertTrue(bean.hasProperty("readOnlyProperty"));
+        assertTrue(bean.hasProperty("writeOnlyProperty"));
+    }
+
+    @Test
+    public void canGetAndSetGroovyObjectClassProperty() {
+        GroovyBean bean = new GroovyBean();
+        bean.setGroovyProperty("value");
+
+        assertThat(bean.getProperty("groovyProperty"), equalTo((Object) "value"));
+
+        bean.setProperty("groovyProperty", "new value");
+
+        assertThat(bean.getProperty("groovyProperty"), equalTo((Object) "new value"));
+        assertThat(bean.getGroovyProperty(), equalTo((Object) "new value"));
+    }
+
+    @Test
+    public void canGetAndSetGroovyDynamicProperty() {
+        GroovyBean bean = new GroovyBean();
+
+        assertThat(bean.getProperty("dynamicGroovyProperty"), equalTo(null));
+
+        bean.setProperty("dynamicGroovyProperty", "new value");
+
+        assertThat(bean.getProperty("dynamicGroovyProperty"), equalTo((Object) "new value"));
+    }
+
+    @Test
+    public void canGetButNotSetPropertiesOnJavaObjectFromGroovy() {
+        DynamicObjectHelperTestHelper.assertCanGetProperties(new Bean());
+    }
+    
+    @Test
+    public void canGetAndSetPropertiesOnGroovyObjectFromGroovy() {
+        DynamicObjectHelperTestHelper.assertCanGetAndSetProperties(new GroovyBean());
+    }
+
+    @Test
+    public void canGetAndSetPropertiesOnGroovyObjectFromJava() {
+        assertCanGetAndSetProperties(new GroovyBean());
+    }
+
+    @Test
+    public void canGetAndSetPropertiesOnJavaSubClassOfGroovyObjectFromJava() {
+        assertCanGetAndSetProperties(new DynamicBean());
+    }
+
+    private void assertCanGetAndSetProperties(DynamicObject bean) {
+        bean.setProperty("readWriteProperty", "value");
+        assertThat(bean.getProperty("readWriteProperty"), equalTo((Object) "value"));
+        bean.setProperty("groovyProperty", "value");
+        assertThat(bean.getProperty("groovyProperty"), equalTo((Object) "value"));
+        bean.setProperty("additional", "value");
+        assertThat(bean.getProperty("additional"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void canGetAndSetPropertiesOnJavaSubClassOfGroovyObjectFromGroovy() {
+        DynamicObjectHelperTestHelper.assertCanGetAndSetProperties(new DynamicBean());
+    }
+
+    @Test
+    public void hasPropertyDefinedByConventionObject() {
+        Bean bean = new Bean();
+        Convention convention = new DefaultConvention();
+
+        assertFalse(bean.hasProperty("conventionProperty"));
+
+        bean.setConvention(convention);
+        assertFalse(bean.hasProperty("conventionProperty"));
+
+        convention.getPlugins().put("test", new ConventionBean());
+        assertTrue(bean.hasProperty("conventionProperty"));
+    }
+
+    @Test
+    public void canGetAndSetPropertyDefinedByConventionObject() {
+        Bean bean = new Bean();
+        Convention convention = new DefaultConvention();
+        bean.setConvention(convention);
+        ConventionBean conventionBean = new ConventionBean();
+        convention.getPlugins().put("test", conventionBean);
+
+        conventionBean.setConventionProperty("value");
+
+        assertThat(bean.getProperty("conventionProperty"), equalTo((Object) "value"));
+
+        bean.setProperty("conventionProperty", "new value");
+
+        assertThat(bean.getProperty("conventionProperty"), equalTo((Object) "new value"));
+        assertThat(conventionBean.getConventionProperty(), equalTo((Object) "new value"));
+    }
+
+    @Test
+    public void hasPropertyDefinedByParent() {
+        Bean parent = new Bean();
+        parent.setProperty("parentProperty", "value");
+
+        Bean bean = new Bean();
+        assertFalse(bean.hasProperty("parentProperty"));
+
+        bean.setParent(parent);
+        assertTrue(bean.hasProperty("parentProperty"));
+    }
+
+    @Test
+    public void canGetPropertyDefinedByParent() {
+        Bean parent = new Bean();
+        parent.setProperty("parentProperty", "value");
+
+        Bean bean = new Bean();
+        bean.setParent(parent);
+
+        assertThat(bean.getProperty("parentProperty"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void cannotSetPropertyDefinedByParent() {
+        Bean parent = new Bean();
+
+        Bean bean = new Bean();
+        bean.setParent(parent);
+        bean.setProperty("parentProperty", "value");
+
+        assertFalse(parent.hasProperty("parentProperty"));
+    }
+
+    @Test
+    public void hasAdditionalProperty() {
+        Bean bean = new Bean();
+
+        assertFalse(bean.hasProperty("additional"));
+
+        bean.setProperty("additional", "value");
+        assertTrue(bean.hasProperty("additional"));
+
+        bean.setProperty("additional", null);
+        assertTrue(bean.hasProperty("additional"));
+    }
+
+    @Test
+    public void canGetAndSetAdditionalProperty() {
+        Bean bean = new Bean();
+
+        bean.setProperty("additional", "value");
+        assertThat(bean.getProperty("additional"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void canGetAndSetPropertyDefinedByAdditionalObject() {
+        Bean otherObject = new Bean();
+        otherObject.setProperty("otherObject", "value");
+
+        Bean bean = new Bean();
+        bean.helper.addObject(otherObject, DynamicObjectHelper.Location.BeforeConvention);
+
+        assertTrue(bean.hasProperty("otherObject"));
+        assertThat(bean.getProperty("otherObject"), equalTo((Object) "value"));
+        bean.setProperty("otherObject", "new value");
+
+        assertThat(otherObject.getProperty("otherObject"), equalTo((Object) "new value"));
+    }
+    
+    @Test
+    public void classPropertyTakesPrecedenceOverAdditionalProperty() {
+        Bean bean = new Bean();
+        bean.setReadWriteProperty("value");
+        bean.helper.getAdditionalProperties().put("readWriteProperty", "additional");
+
+        assertThat(bean.getProperty("readWriteProperty"), equalTo((Object) "value"));
+
+        bean.setProperty("readWriteProperty", "new value");
+
+        assertThat(bean.getProperty("readWriteProperty"), equalTo((Object) "new value"));
+        assertThat(bean.getReadWriteProperty(), equalTo((Object) "new value"));
+        assertThat(bean.helper.getAdditionalProperties().get("readWriteProperty"), equalTo((Object) "additional"));
+    }
+
+    @Test
+    public void additionalPropertyTakesPrecedenceOverConventionProperty() {
+        Bean bean = new Bean();
+        bean.setProperty("conventionProperty", "value");
+
+        Convention convention = new DefaultConvention();
+        bean.setConvention(convention);
+        ConventionBean conventionBean = new ConventionBean();
+        convention.getPlugins().put("test", conventionBean);
+
+        assertThat(bean.getProperty("conventionProperty"), equalTo((Object) "value"));
+
+        bean.setProperty("conventionProperty", "new value");
+
+        assertThat(bean.getProperty("conventionProperty"), equalTo((Object) "new value"));
+        assertThat(bean.helper.getAdditionalProperties().get("conventionProperty"), equalTo((Object) "new value"));
+        assertThat(conventionBean.getConventionProperty(), nullValue());
+    }
+
+    @Test
+    public void conventionPropertyTakesPrecedenceOverParentProperty() {
+        Bean parent = new Bean();
+        parent.setProperty("conventionProperty", "parent");
+
+        Bean bean = new Bean();
+        bean.setParent(parent);
+
+        Convention convention = new DefaultConvention();
+        bean.setConvention(convention);
+        ConventionBean conventionBean = new ConventionBean();
+        conventionBean.setConventionProperty("value");
+        convention.getPlugins().put("test", conventionBean);
+
+        assertThat(bean.getProperty("conventionProperty"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void canGetAllProperties() {
+        Bean parent = new Bean();
+        parent.setProperty("parentProperty", "parentProperty");
+        parent.setReadWriteProperty("ignore me");
+        parent.doSetReadOnlyProperty("ignore me");
+        Convention parentConvention = new DefaultConvention();
+        parentConvention.getPlugins().put("parent", new ConventionBean());
+        parent.setConvention(parentConvention);
+
+        GroovyBean bean = new GroovyBean();
+        bean.setProperty("additional", "additional");
+        bean.setReadWriteProperty("readWriteProperty");
+        bean.doSetReadOnlyProperty("readOnlyProperty");
+        bean.setGroovyProperty("groovyProperty");
+        Convention convention = new DefaultConvention();
+        ConventionBean conventionBean = new ConventionBean();
+        conventionBean.setConventionProperty("conventionProperty");
+        convention.getPlugins().put("bean", conventionBean);
+        bean.setConvention(convention);
+        bean.setParent(parent);
+
+        Map<String, Object> properties = bean.getProperties();
+        assertThat(properties.get("properties"), sameInstance((Object) properties));
+        assertThat(properties.get("readWriteProperty"), equalTo((Object) "readWriteProperty"));
+        assertThat(properties.get("readOnlyProperty"), equalTo((Object) "readOnlyProperty"));
+        assertThat(properties.get("parentProperty"), equalTo((Object) "parentProperty"));
+        assertThat(properties.get("additional"), equalTo((Object) "additional"));
+        assertThat(properties.get("groovyProperty"), equalTo((Object) "groovyProperty"));
+        assertThat(properties.get("groovyDynamicProperty"), equalTo(null));
+        assertThat(properties.get("conventionProperty"), equalTo((Object) "conventionProperty"));
+    }
+
+    @Test
+    public void canGetAllPropertiesFromGroovy() {
+        DynamicObjectHelperTestHelper.assertCanGetAllProperties(new Bean());
+        DynamicObjectHelperTestHelper.assertCanGetAllProperties(new GroovyBean());
+        DynamicObjectHelperTestHelper.assertCanGetAllProperties(new DynamicBean());
+    }
+
+    @Test
+    public void getPropertyFailsForUnknownProperty() {
+        Bean bean = new Bean();
+
+        try {
+            bean.getProperty("unknown");
+            fail();
+        } catch (MissingPropertyException e) {
+            assertThat(e.getMessage(), equalTo("Could not find property 'unknown' on <bean>."));
+        }
+
+        bean.setParent(new Bean(){
+            @Override
+            public String toString() {
+                return "<parent>";
+            }
+        });
+
+        try {
+            bean.getProperty("unknown");
+            fail();
+        } catch (MissingPropertyException e) {
+            assertThat(e.getMessage(), equalTo("Could not find property 'unknown' on <bean>."));
+        }
+    }
+
+    @Test
+    public void additionalPropertyWithNullValueIsNotTreatedAsUnknown() {
+        Bean bean = new Bean();
+        bean.setProperty("additional", null);
+        assertThat(bean.getProperty("additional"), nullValue());
+    }
+
+    @Test
+    public void canInvokeMethodDefinedByClass() {
+        Bean bean = new Bean();
+        assertTrue(bean.hasMethod("javaMethod", "a", "b"));
+        assertThat(bean.invokeMethod("javaMethod", "a", "b"), equalTo((Object) "java:a.b"));
+    }
+
+    @Test
+    public void canInvokeMethodDefinedByMetaClass() {
+        Bean bean = new GroovyBean();
+
+        assertTrue(bean.hasMethod("groovyMethod", "a", "b"));
+        assertThat(bean.invokeMethod("groovyMethod", "a", "b"), equalTo((Object) "groovy:a.b"));
+
+        assertTrue(bean.hasMethod("dynamicGroovyMethod", "a", "b"));
+        assertThat(bean.invokeMethod("dynamicGroovyMethod", "a", "b"), equalTo((Object) "dynamicGroovy:a.b"));
+    }
+
+    @Test
+    public void canInvokeMethodDefinedByScriptObject() {
+        Bean bean = new Bean();
+        Script script = HelperUtil.createScript("def scriptMethod(a, b) { \"script:$a.$b\" } ");
+        bean.helper.addObject(new BeanDynamicObject(script), DynamicObjectHelper.Location.BeforeConvention);
+
+        assertTrue(bean.hasMethod("scriptMethod", "a", "b"));
+        assertThat(bean.invokeMethod("scriptMethod", "a", "b").toString(), equalTo((Object) "script:a.b"));
+    }
+
+    @Test
+    public void canInvokeMethodDefinedByConvention() {
+        Bean bean = new Bean();
+        Convention convention = new DefaultConvention();
+        convention.getPlugins().put("bean", new ConventionBean());
+
+        assertFalse(bean.hasMethod("conventionMethod", "a", "b"));
+
+        bean.setConvention(convention);
+
+        assertTrue(bean.hasMethod("conventionMethod", "a", "b"));
+        assertThat(bean.invokeMethod("conventionMethod", "a", "b"), equalTo((Object) "convention:a.b"));
+    }
+
+    @Test
+    public void canInvokeMethodDefinedByParent() {
+        Bean parent = new Bean() {
+            public String parentMethod(String a, String b) {
+                return String.format("parent:%s.%s", a, b);
+            }
+        };
+        Bean bean = new Bean();
+
+        assertFalse(bean.hasMethod("parentMethod", "a", "b"));
+        
+        bean.setParent(parent);
+
+        assertTrue(bean.hasMethod("parentMethod", "a", "b"));
+        assertThat(bean.invokeMethod("parentMethod", "a", "b"), equalTo((Object) "parent:a.b"));
+    }
+
+    @Test
+    public void canInvokeMethodsOnJavaObjectFromGroovy() {
+        Bean bean = new Bean();
+        Convention convention = new DefaultConvention();
+        bean.setConvention(convention);
+        convention.getPlugins().put("bean", new ConventionBean());
+        DynamicObjectHelperTestHelper.assertCanCallMethods(bean);
+    }
+
+    @Test
+    public void canInvokeMethodsOnGroovyObjectFromGroovy() {
+        GroovyBean bean = new GroovyBean();
+        Convention convention = new DefaultConvention();
+        bean.setConvention(convention);
+        convention.getPlugins().put("bean", new ConventionBean());
+        DynamicObjectHelperTestHelper.assertCanCallMethods(bean);
+    }
+
+    @Test
+    public void canInvokeMethodsOnJavaSubClassOfGroovyObjectFromGroovy() {
+        DynamicBean bean = new DynamicBean();
+        Convention convention = new DefaultConvention();
+        bean.setConvention(convention);
+        convention.getPlugins().put("bean", new ConventionBean());
+        DynamicObjectHelperTestHelper.assertCanCallMethods(bean);
+    }
+
+    @Test
+    public void canInvokeClosurePropertyAsAMethod() {
+        Bean bean = new Bean();
+        bean.setProperty("someMethod", HelperUtil.toClosure("{ param -> param.toLowerCase() }"));
+        assertThat(bean.invokeMethod("someMethod", "Param"), equalTo((Object) "param"));
+    }
+    
+    @Test
+    public void invokeMethodFailsForUnknownMethod() {
+        Bean bean = new Bean();
+        try {
+            bean.invokeMethod("unknown", "a", 12);
+            fail();
+        } catch (MissingMethodException e) {
+            assertThat(e.getMessage(), equalTo("Could not find method unknown() for arguments [a, 12] on <bean>."));
+        }
+    }
+
+    @Test
+    public void propagatesGetPropertyException() {
+        final RuntimeException failure = new RuntimeException();
+        Bean bean = new Bean() {
+            String getFailure() {
+                throw failure;
+            }
+        };
+
+        try {
+            bean.getProperty("failure");
+            fail();
+        } catch (Exception e) {
+            assertThat(e, sameInstance((Exception) failure));
+        }
+    }
+
+    @Test
+    public void propagatesSetPropertyException() {
+        final RuntimeException failure = new RuntimeException();
+        Bean bean = new Bean() {
+            void setFailure(String value) {
+                throw failure;
+            }
+        };
+
+        try {
+            bean.setProperty("failure", "a");
+            fail();
+        } catch (Exception e) {
+            assertThat(e, sameInstance((Exception) failure));
+        }
+    }
+
+    @Test
+    public void propagatesInvokeMethodException() {
+        final RuntimeException failure = new RuntimeException();
+        Bean bean = new Bean() {
+            void failure() {
+                throw failure;
+            }
+        };
+
+        try {
+            bean.invokeMethod("failure");
+            fail();
+        } catch (Exception e) {
+            assertThat(e, sameInstance((Exception) failure));
+        }
+    }
+
+    @Test
+    public void additionalPropertiesAreInherited() {
+        Bean bean = new Bean();
+        bean.setProperty("additional", "value");
+
+        DynamicObject inherited = bean.getInheritable();
+        assertTrue(inherited.hasProperty("additional"));
+        assertThat(inherited.getProperty("additional"), equalTo((Object) "value"));
+        assertThat(inherited.getProperties().get("additional"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void inheritedAdditionalPropertiesTrackChanges() {
+        Bean bean = new Bean();
+
+        DynamicObject inherited = bean.getInheritable();
+        assertFalse(inherited.hasProperty("additional"));
+
+        bean.setProperty("additional", "value");
+        assertTrue(inherited.hasProperty("additional"));
+        assertThat(inherited.getProperty("additional"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void additionalObjectPropertiesAreInherited() {
+        Bean other = new Bean();
+        other.setProperty("other", "value");
+        Bean bean = new Bean();
+        bean.helper.addObject(other, DynamicObjectHelper.Location.BeforeConvention);
+
+        DynamicObject inherited = bean.getInheritable();
+        assertTrue(inherited.hasProperty("other"));
+        assertThat(inherited.getProperty("other"), equalTo((Object) "value"));
+        assertThat(inherited.getProperties().get("other"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void inheritedAdditionalObjectPropertiesTrackChanges() {
+        Bean other = new Bean();
+        other.setProperty("other", "value");
+        Bean bean = new Bean();
+
+        DynamicObject inherited = bean.getInheritable();
+        assertFalse(inherited.hasProperty("other"));
+
+        bean.helper.addObject(other, DynamicObjectHelper.Location.BeforeConvention);
+
+        assertTrue(inherited.hasProperty("other"));
+        assertThat(inherited.getProperty("other"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void conventionPropertiesAreInherited() {
+        Bean bean = new Bean();
+        Convention convention = new DefaultConvention();
+        ConventionBean conventionBean = new ConventionBean();
+        conventionBean.setConventionProperty("value");
+        convention.getPlugins().put("convention", conventionBean);
+        bean.setConvention(convention);
+
+        DynamicObject inherited = bean.getInheritable();
+        assertTrue(inherited.hasProperty("conventionProperty"));
+        assertThat(inherited.getProperty("conventionProperty"), equalTo((Object) "value"));
+        assertThat(inherited.getProperties().get("conventionProperty"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void inheritedConventionPropertiesTrackChanges() {
+        Bean bean = new Bean();
+
+        DynamicObject inherited = bean.getInheritable();
+        assertFalse(inherited.hasProperty("conventionProperty"));
+
+        Convention convention = new DefaultConvention();
+        ConventionBean conventionBean = new ConventionBean();
+        conventionBean.setConventionProperty("value");
+        convention.getPlugins().put("convention", conventionBean);
+        bean.setConvention(convention);
+
+        assertTrue(inherited.hasProperty("conventionProperty"));
+        assertThat(inherited.getProperty("conventionProperty"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void parentPropertiesAreInherited() {
+        Bean parent = new Bean();
+        parent.setProperty("parentProperty", "value");
+        Bean bean = new Bean();
+        bean.setParent(parent);
+
+        DynamicObject inherited = bean.getInheritable();
+        assertTrue(inherited.hasProperty("parentProperty"));
+        assertThat(inherited.getProperty("parentProperty"), equalTo((Object) "value"));
+        assertThat(inherited.getProperties().get("parentProperty"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void otherPropertiesAreNotInherited() {
+        Bean bean = new Bean();
+        assertTrue(bean.hasProperty("readWriteProperty"));
+
+        DynamicObject inherited = bean.getInheritable();
+        assertFalse(inherited.hasProperty("readWriteProperty"));
+        assertFalse(inherited.getProperties().containsKey("readWriteProperty"));
+    }
+
+    @Test
+    public void cannotSetInheritedProperties() {
+        Bean bean = new Bean();
+        bean.setProperty("additional", "value");
+
+        DynamicObject inherited = bean.getInheritable();
+        try {
+            inherited.setProperty("additional", "new value");
+            fail();
+        } catch (MissingPropertyException e) {
+            assertThat(e.getMessage(), equalTo("Could not find property 'additional' inherited from <bean>."));
+        }
+    }
+
+    @Test
+    public void conventionMethodsAreInherited() {
+        Bean bean = new Bean();
+        Convention convention = new DefaultConvention();
+        convention.getPlugins().put("convention", new ConventionBean());
+        bean.setConvention(convention);
+
+        DynamicObject inherited = bean.getInheritable();
+        assertTrue(inherited.hasMethod("conventionMethod", "a", "b"));
+        assertThat(inherited.invokeMethod("conventionMethod", "a", "b"), equalTo((Object) "convention:a.b"));
+    }
+
+    @Test
+    public void additionalObjectMethodsAreInherited() {
+        Bean other = new Bean();
+        Convention convention = new DefaultConvention();
+        convention.getPlugins().put("convention", new ConventionBean());
+        other.setConvention(convention);
+
+        Bean bean = new Bean();
+        bean.helper.addObject(other, DynamicObjectHelper.Location.BeforeConvention);
+
+        DynamicObject inherited = bean.getInheritable();
+        assertTrue(inherited.hasMethod("conventionMethod", "a", "b"));
+        assertThat(inherited.invokeMethod("conventionMethod", "a", "b"), equalTo((Object) "convention:a.b"));
+    }
+
+    @Test
+    public void parentMethodsAreInherited() {
+        Bean parent = new Bean();
+        Convention convention = new DefaultConvention();
+        convention.getPlugins().put("convention", new ConventionBean());
+        parent.setConvention(convention);
+        Bean bean = new Bean();
+        bean.setParent(parent);
+
+        DynamicObject inherited = bean.getInheritable();
+        assertTrue(inherited.hasMethod("conventionMethod", "a", "b"));
+        assertThat(inherited.invokeMethod("conventionMethod", "a", "b"), equalTo((Object) "convention:a.b"));
+    }
+
+    @Test
+    public void otherMethodsAreNotInherited() {
+        Bean bean = new Bean();
+        assertTrue(bean.hasMethod("javaMethod", "a", "b"));
+
+        DynamicObject inherited = bean.getInheritable();
+        assertFalse(inherited.hasMethod("javaMethod", "a", "b"));
+    }
+    
+    public static class Bean implements DynamicObject {
+        private String readWriteProperty;
+        private String readOnlyProperty;
+        private String writeOnlyProperty;
+        private Integer differentTypesProperty;
+        final DynamicObjectHelper helper;
+
+        public Bean() {
+            helper = new DynamicObjectHelper(this);
+        }
+
+        @Override
+        public String toString() {
+            return "<bean>";
+        }
+
+        public void setConvention(Convention convention) {
+            helper.setConvention(convention);
+        }
+
+        public void setParent(DynamicObject parent) {
+            helper.setParent(parent);
+        }
+
+        public String getReadOnlyProperty() {
+            return readOnlyProperty;
+        }
+
+        public void doSetReadOnlyProperty(String readOnlyProperty) {
+            this.readOnlyProperty = readOnlyProperty;
+        }
+
+        public String doGetWriteOnlyProperty() {
+            return writeOnlyProperty;
+        }
+
+        public void setWriteOnlyProperty(String writeOnlyProperty) {
+            this.writeOnlyProperty = writeOnlyProperty;
+        }
+
+        public String getReadWriteProperty() {
+            return readWriteProperty;
+        }
+
+        public void setReadWriteProperty(String property) {
+            this.readWriteProperty = property;
+        }
+
+        public Integer getDifferentTypesProperty() {
+            return differentTypesProperty;
+        }
+
+        public void setDifferentTypesProperty(Object differentTypesProperty) {
+            this.differentTypesProperty = Integer.parseInt(differentTypesProperty.toString());
+        }
+
+        public String javaMethod(String a, String b) {
+            return String.format("java:%s.%s", a, b);
+        }
+        
+        public Object getProperty(String name) {
+            return helper.getProperty(name);
+        }
+
+        public boolean hasProperty(String name) {
+            return helper.hasProperty(name);
+        }
+
+        public void setProperty(String name, Object value) {
+            helper.setProperty(name, value);
+        }
+
+        public Map<String, Object> getProperties() {
+            return helper.getProperties();
+        }
+
+        public boolean hasMethod(String name, Object... arguments) {
+            return helper.hasMethod(name, arguments);
+        }
+
+        public Object invokeMethod(String name, Object... arguments) {
+            return helper.invokeMethod(name, arguments);
+        }
+
+        public Object methodMissing(String name, Object params) {
+            return helper.invokeMethod(name, (Object[]) params);
+        }
+
+        public Object propertyMissing(String name) {
+            return getProperty(name);
+        }
+
+        public DynamicObject getInheritable() {
+            return helper.getInheritable();
+        }
+    }
+
+    private static class DynamicBean extends GroovyBean {
+    }
+
+    private static class ConventionBean {
+        private String conventionProperty;
+
+        public String getConventionProperty() {
+            return conventionProperty;
+        }
+
+        public void setConventionProperty(String conventionProperty) {
+            this.conventionProperty = conventionProperty;
+        }
+
+        public String conventionMethod(String a, String b) {
+            return String.format("convention:%s.%s", a, b);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/DynamicObjectHelperTestHelper.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/DynamicObjectHelperTestHelper.groovy
new file mode 100644
index 0000000..2f95345
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/DynamicObjectHelperTestHelper.groovy
@@ -0,0 +1,93 @@
+/*
+ * Copyright 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
+
+import static org.junit.Assert.*
+
+public class DynamicObjectHelperTestHelper {
+    public static void assertCanGetAllProperties (DynamicObjectHelperTest.Bean bean) {
+        bean.readWriteProperty = 'readWrite'
+        bean.setProperty('additional', 'additional')
+        assertEquals(bean.getProperties().readWriteProperty, 'readWrite')
+        assertEquals(bean.getProperties().additional, 'additional')
+    }
+
+    public static void assertCanGetProperties (DynamicObjectHelperTest.Bean bean) {
+        bean.readWriteProperty = 'value'
+        assertEquals(bean.readWriteProperty, 'value')
+
+        bean.doSetReadOnlyProperty('value')
+        assertEquals(bean.readOnlyProperty, 'value')
+
+        bean.helper.additionalProperties.additional = 'value'
+        assertEquals(bean.additional, 'value')
+
+        bean.setProperty 'another', 'value'
+        assertEquals(bean.another, 'value')
+    }
+    
+    public static void assertCanGetAndSetProperties (DynamicObjectHelperTest.Bean bean) {
+        bean.readWriteProperty = 'value'
+        assertEquals(bean.readWriteProperty, 'value')
+
+        bean.doSetReadOnlyProperty('value')
+        assertEquals(bean.readOnlyProperty, 'value')
+
+        bean.additional = 'value'
+        assertEquals(bean.additional, 'value')
+
+        bean.setProperty 'another', 'value'
+        assertEquals(bean.another, 'value')
+    }
+
+    public static void assertCanCallMethods (DynamicObjectHelperTest.Bean bean) {
+        assertEquals(bean.javaMethod('a', 'b'), 'java:a.b')
+        assertTrue(bean.hasMethod('conventionMethod', 'a', 'b'))
+        assertEquals(bean.conventionMethod('a', 'b'), 'convention:a.b')
+    }
+}
+
+public class DynamicBean extends DynamicObjectHelperTest.Bean {
+    def propertyMissing(String name) {
+        super.getProperty(name)
+    }
+
+//    def methodMissing(String name, params) {
+//        super.methodMissing(name, params)
+//    }
+
+    void setProperty(String name, Object value) {
+        super.setProperty(name, value)
+    }
+}
+
+public class GroovyBean extends DynamicBean {
+    String groovyProperty
+
+    def GroovyBean() {
+        Map values = [:]
+        ExpandoMetaClass metaClass = new ExpandoMetaClass(GroovyBean.class, false)
+        metaClass.getDynamicGroovyProperty << {-> values.dynamicGroovyProperty }
+        metaClass.setDynamicGroovyProperty << {value -> values.dynamicGroovyProperty = value}
+        metaClass.dynamicGroovyMethod << {a, b -> "dynamicGroovy:$a.$b".toString() }
+        metaClass.initialize()
+        setMetaClass(metaClass)
+    }
+
+    def groovyMethod(a, b) {
+        "groovy:$a.$b".toString()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/GraphAggregatorTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/GraphAggregatorTest.groovy
new file mode 100644
index 0000000..b956b28
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/GraphAggregatorTest.groovy
@@ -0,0 +1,65 @@
+/*
+ * 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 spock.lang.Specification
+
+class GraphAggregatorTest extends Specification {
+    private final DirectedGraph<String, String> graph = Mock()
+    private final GraphAggregator aggregator = new GraphAggregator(graph)
+
+    def groupsNodeWithTheEntryNodeItIsReachableFrom() {
+        when:
+        def result = aggregator.group(['a'], ['a', 'b'])
+
+        then:
+        1 * graph.getNodeValues('a', !null, !null) >> { args -> args[2].add('b') }
+        result.getNodes('a') == ['a', 'b'] as Set
+    }
+
+    def groupsNodeWithTheClosesEntryNodeItIsReachableFrom() {
+        when:
+        def result = aggregator.group(['a', 'b'], ['a', 'b', 'c'])
+
+        then:
+        1 * graph.getNodeValues('a', !null, !null) >> { args -> args[2].add('b') }
+        1 * graph.getNodeValues('b', !null, !null) >> { args -> args[2].add('c') }
+        result.getNodes('a') == ['a'] as Set
+        result.getNodes('b') == ['b', 'c'] as Set
+    }
+
+    def groupsNodeWithMultipleEntryNodesWhenTheNodeHasMultipleClosesNodes() {
+        when:
+        def result = aggregator.group(['a', 'b'], ['a', 'b', 'c'])
+
+        then:
+        1 * graph.getNodeValues('a', !null, !null) >> { args -> args[2].add('c') }
+        1 * graph.getNodeValues('b', !null, !null) >> { args -> args[2].add('c') }
+        result.getNodes('a') == ['a', 'c'] as Set
+        result.getNodes('b') == ['b', 'c'] as Set
+    }
+
+    def groupsNodesWhichAreNotReachableFromStartNodes() {
+        when:
+        def result = aggregator.group(['a', 'b'], ['a', 'b', 'c', 'd'])
+
+        then:
+        1 * graph.getNodeValues('a', !null, !null) >> { args -> args[2].add('b') }
+        1 * graph.getNodeValues('c', !null, !null) >> { args -> args[2].add('d') }
+        result.topLevelNodes == ['a', 'b', 'c'] as Set
+        result.getNodes('c') == ['c', 'd'] as Set
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/GroovySourceGenerationBackedClassGeneratorTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/GroovySourceGenerationBackedClassGeneratorTest.java
new file mode 100644
index 0000000..778bbeb
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/GroovySourceGenerationBackedClassGeneratorTest.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+public class GroovySourceGenerationBackedClassGeneratorTest extends AbstractClassGeneratorTest {
+    @Override
+    protected ClassGenerator createGenerator() {
+        return new GroovySourceGenerationBackedClassGenerator();
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/MapBackedDynamicObjectTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/MapBackedDynamicObjectTest.java
new file mode 100644
index 0000000..02ecc01
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/MapBackedDynamicObjectTest.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.api.internal;
+
+import org.junit.Test;
+
+import java.util.Map;
+
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+public class MapBackedDynamicObjectTest {
+    private final MapBackedDynamicObject object = new MapBackedDynamicObject(null);
+
+    @Test
+    public void hasNoPropertiesByDefault() {
+        assertTrue(object.getProperties().isEmpty());
+        assertFalse(object.hasProperty("someProp"));
+    }
+
+    @Test
+    public void canAddAProperty() {
+        object.setProperty("someProp", "value");
+        assertThat(object.getProperties(), equalTo((Map) toMap("someProp", (Object) "value")));
+        assertTrue(object.hasProperty("someProp"));
+        assertThat(object.getProperty("someProp"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void canChangeAPropertyValue() {
+        object.setProperty("someProp", "value");
+        assertThat(object.getProperty("someProp"), equalTo((Object) "value"));
+
+        object.setProperty("someProp", "new value");
+        assertThat(object.getProperty("someProp"), equalTo((Object) "new value"));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/TestContainer.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/TestContainer.java
new file mode 100644
index 0000000..02796e7
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/TestContainer.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+public class TestContainer extends AutoCreateDomainObjectContainer<TestObject> {
+
+    public TestContainer(ClassGenerator classGenerator) {
+        super(TestObject.class, classGenerator);
+    }
+
+    protected TestObject create(String name) {
+        TestObject testObject = new TestObject();
+        testObject.add(name);
+        return testObject;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/TestDecoratedGroovyBean.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/TestDecoratedGroovyBean.groovy
new file mode 100644
index 0000000..6febdcb
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/TestDecoratedGroovyBean.groovy
@@ -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.api.internal
+
+class TestDecoratedGroovyBean {
+    def String prop
+
+    def doStuff(String value) {
+        "{$value}"
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/CachingDependencyResolveContextTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/CachingDependencyResolveContextTest.groovy
new file mode 100644
index 0000000..a0db932
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/CachingDependencyResolveContextTest.groovy
@@ -0,0 +1,84 @@
+/*
+ * 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
+
+import spock.lang.Specification
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.file.UnionFileCollection
+
+class CachingDependencyResolveContextTest extends Specification {
+    private final CachingDependencyResolveContext context = new CachingDependencyResolveContext(true)
+
+    def resolvesAFileCollection() {
+        DependencyInternal dependency = Mock()
+        FileCollection fileCollection = Mock()
+
+        when:
+        context.add(dependency)
+        def files = context.resolve()
+
+        then:
+        1 * dependency.resolve(context) >> { context.add(fileCollection) }
+        files instanceof UnionFileCollection
+        files.sourceCollections == [fileCollection]
+    }
+
+    def resolvesADependencyInternal() {
+        DependencyInternal dependency = Mock()
+        DependencyInternal otherDependency = Mock()
+        FileCollection fileCollection = Mock()
+
+        when:
+        context.add(dependency)
+        def files = context.resolve()
+
+        then:
+        1 * dependency.resolve(context) >> { context.add(otherDependency) }
+        1 * otherDependency.resolve(context) >> { context.add(fileCollection) }
+        files instanceof UnionFileCollection
+        files.sourceCollections == [fileCollection]
+    }
+
+    def failsToResolveAnyOtherType() {
+        DependencyInternal dependency = Mock()
+
+        when:
+        context.add(dependency)
+        context.resolve()
+
+        then:
+        1 * dependency.resolve(context) >> { context.add('thing') }
+        def e = thrown(IllegalArgumentException)
+        e.message == 'Cannot resolve object of unknown type String.'
+    }
+
+    def handlesCyclesBetweenDependencies() {
+        DependencyInternal dependency = Mock()
+        DependencyInternal otherDependency = Mock()
+        FileCollection fileCollection = Mock()
+
+        when:
+        context.add(dependency)
+        def files = context.resolve()
+
+        then:
+        1 * dependency.resolve(context) >> { context.add(otherDependency) }
+        1 * otherDependency.resolve(context) >> { context.add(fileCollection); context.add(dependency) }
+        files instanceof UnionFileCollection
+        files.sourceCollections == [fileCollection]
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultConfigurationContainerFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultConfigurationContainerFactoryTest.java
new file mode 100644
index 0000000..5bcc435
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultConfigurationContainerFactoryTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+import org.gradle.api.internal.AsmBackedClassGenerator;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.DomainObjectContext;
+import org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
+import org.gradle.api.internal.artifacts.ivyservice.*;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.sameInstance;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultConfigurationContainerFactoryTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    @Test
+    public void testCreate() {
+        ResolverProvider resolverProviderDummy = context.mock(ResolverProvider.class);
+        final DependencyMetaDataProvider dependencyMetaDataProviderStub = context.mock(DependencyMetaDataProvider.class);
+
+        HashMap clientModuleRegistry = new HashMap();
+        SettingsConverter settingsConverter = context.mock(SettingsConverter.class);
+        ModuleDescriptorConverter resolveModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "resolve");
+        ModuleDescriptorConverter publishModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "publish");
+        ModuleDescriptorConverter fileModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "file");
+        IvyFactory ivyFactory = context.mock(IvyFactory.class);
+        IvyDependencyResolver ivyDependencyResolver = context.mock(IvyDependencyResolver.class);
+        IvyDependencyPublisher ivyDependencyPublisher = context.mock(IvyDependencyPublisher.class);
+        ClassGenerator classGenerator = new AsmBackedClassGenerator();
+        DefaultConfigurationContainer configurationContainer = (DefaultConfigurationContainer)
+                new DefaultConfigurationContainerFactory(clientModuleRegistry, settingsConverter,
+                        resolveModuleDescriptorConverter, publishModuleDescriptorConverter,
+                        fileModuleDescriptorConverter, ivyFactory,
+                        ivyDependencyResolver, ivyDependencyPublisher, classGenerator).createConfigurationContainer(resolverProviderDummy,
+                        dependencyMetaDataProviderStub, context.mock(DomainObjectContext.class));
+
+        assertThat(configurationContainer.getIvyService(), instanceOf(ErrorHandlingIvyService.class));
+        ErrorHandlingIvyService errorHandlingService = (ErrorHandlingIvyService) configurationContainer.getIvyService();
+
+        assertThat(errorHandlingService.getIvyService(), instanceOf(ShortcircuitEmptyConfigsIvyService.class));
+        ShortcircuitEmptyConfigsIvyService service = (ShortcircuitEmptyConfigsIvyService) errorHandlingService.getIvyService();
+
+        assertThat(service.getIvyService(), instanceOf(DefaultIvyService.class));
+        DefaultIvyService defaultIvyService = (DefaultIvyService) service.getIvyService();
+        assertThat(defaultIvyService.getMetaDataProvider(), sameInstance(dependencyMetaDataProviderStub));
+        assertThat(defaultIvyService.getResolverProvider(), sameInstance(resolverProviderDummy));
+        assertThat((HashMap) defaultIvyService.getClientModuleRegistry(), sameInstance(clientModuleRegistry));
+        assertThat(defaultIvyService.getSettingsConverter(), sameInstance(settingsConverter));
+        assertThat(defaultIvyService.getResolveModuleDescriptorConverter(), sameInstance(resolveModuleDescriptorConverter));
+        assertThat(defaultIvyService.getPublishModuleDescriptorConverter(), sameInstance(publishModuleDescriptorConverter));
+        assertThat(defaultIvyService.getFileModuleDescriptorConverter(), sameInstance(fileModuleDescriptorConverter));
+        assertThat(defaultIvyService.getIvyFactory(), sameInstance(ivyFactory));
+        assertThat(defaultIvyService.getDependencyResolver(), sameInstance(ivyDependencyResolver));
+        assertThat(defaultIvyService.getDependencyPublisher(), sameInstance(ivyDependencyPublisher));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifactTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifactTest.java
new file mode 100644
index 0000000..be4addf
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedArtifactTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.report.ArtifactDownloadReport;
+import org.apache.ivy.core.resolve.DownloadOptions;
+import org.apache.ivy.core.resolve.ResolveEngine;
+import org.gradle.api.artifacts.ResolvedDependency;
+import static org.hamcrest.Matchers.*;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultResolvedArtifactTest {
+    private JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+
+    @Test
+    public void init() {
+        String someName = "someName";
+        String someType = "someType";
+        String someExtension = "someExtension";
+        File someFile = new File("someFile");
+        DefaultResolvedArtifact resolvedArtifact = createResolvedArtifact(context, 
+                someName, someType, someExtension, someFile);
+        assertThat(resolvedArtifact.getResolvedDependency(), nullValue());
+        assertThat(resolvedArtifact.getName(), equalTo(someName));
+        assertThat(resolvedArtifact.getType(), equalTo(someType));
+        assertThat(resolvedArtifact.getExtension(), equalTo(someExtension));
+        assertThatFileIsReturnedAndCached(someFile, resolvedArtifact);
+        assertThat(resolvedArtifact.getVersion(), nullValue());
+        assertThat(resolvedArtifact.getDependencyName(), nullValue());
+    }
+
+    private void assertThatFileIsReturnedAndCached(File someFile, DefaultResolvedArtifact resolvedArtifact) {
+        assertThat(resolvedArtifact.getFile(), equalTo(someFile));
+
+        // The resolve engine mock would complain if the file is not cached.
+        assertThat(resolvedArtifact.getFile(), equalTo(someFile));
+    }
+
+    @Test
+    public void setResolvedDependency() {
+        final String someVersion = "someVersion";
+        final String someDependencyName = "someDependencyName";
+        final ResolvedDependency resolvedDependencyStub = context.mock(ResolvedDependency.class);
+        context.checking(new Expectations() {{
+            allowing(resolvedDependencyStub).getModuleVersion();
+            will(returnValue(someVersion));
+            allowing(resolvedDependencyStub).getModuleName();
+            will(returnValue(someDependencyName));
+        }});
+        DefaultResolvedArtifact resolvedArtifact = createResolvedArtifact(context, "someName", "someType", "someExtension", new File("someFile"));
+        resolvedArtifact.setResolvedDependency(resolvedDependencyStub);
+        assertThat(resolvedArtifact.getResolvedDependency(), sameInstance(resolvedDependencyStub));
+        assertThat(resolvedArtifact.getVersion(), equalTo(someVersion));
+        assertThat(resolvedArtifact.getDependencyName(), equalTo(someDependencyName));
+    }
+
+    public static DefaultResolvedArtifact createResolvedArtifact(Mockery context, final String name, final String type, final String extension, File file) {
+        final Artifact artifactStub = context.mock(Artifact.class, "artifact" + name);
+        context.checking(new Expectations() {{
+            allowing(artifactStub).getName();
+            will(returnValue(name));
+            allowing(artifactStub).getType();
+            will(returnValue(type));
+            allowing(artifactStub).getExt();
+            will(returnValue(extension));
+        }});
+        final ResolveEngine resolveEngineMock = context.mock(ResolveEngine.class, "engine" + name);
+        final ArtifactDownloadReport artifactDownloadReport = new ArtifactDownloadReport(artifactStub);
+        artifactDownloadReport.setLocalFile(file);
+        context.checking(new Expectations() {{
+            one(resolveEngineMock).download(with(equal(artifactStub)), with(any(DownloadOptions.class)));
+            will(returnValue(artifactDownloadReport));
+        }});
+        return new DefaultResolvedArtifact(artifactStub, resolveEngineMock);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencyTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencyTest.java
new file mode 100644
index 0000000..4cff2f5
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolvedDependencyTest.java
@@ -0,0 +1,203 @@
+/*
+ * 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;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.artifacts.ResolvedDependency;
+import org.gradle.util.GUtil;
+import org.gradle.util.WrapUtil;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.Set;
+
+import static org.gradle.util.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultResolvedDependencyTest {
+    private JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+
+    @Test
+    public void init() {
+        String someGroup = "someGroup";
+        String someName = "someName";
+        String someVersion = "someVersion";
+        String someConfiguration = "someConfiguration";
+        Set<ResolvedArtifact> someArtifacts = WrapUtil.toSet(createArtifact("someName"));
+        DefaultResolvedDependency resolvedDependency = new DefaultResolvedDependency(someGroup, someName, someVersion, someConfiguration,
+                someArtifacts);
+        assertThat(resolvedDependency.getName(), equalTo(someGroup + ":" + someName + ":" + someVersion));
+        assertThat(resolvedDependency.getModuleGroup(), equalTo(someGroup));
+        assertThat(resolvedDependency.getModuleName(), equalTo(someName));
+        assertThat(resolvedDependency.getModuleVersion(), equalTo(someVersion));
+        assertThat(resolvedDependency.getConfiguration(), equalTo(someConfiguration));
+        assertThat(resolvedDependency.getModuleArtifacts(), equalTo(someArtifacts));
+        assertThat(resolvedDependency.getChildren(), equalTo(Collections.<ResolvedDependency>emptySet()));
+        assertThat(resolvedDependency.getParents(), equalTo(Collections.<ResolvedDependency>emptySet()));
+    }
+
+    @Test
+    public void getAllModuleArtifacts() {
+        Set<ResolvedArtifact> someModuleArtifacts = WrapUtil.<ResolvedArtifact>toSet(createArtifact("moduleArtifact"));
+        Set<ResolvedArtifact> someChildModuleArtifacts = WrapUtil.<ResolvedArtifact>toSet(createArtifact("childModuleArtifact"));
+        DefaultResolvedDependency resolvedDependency = new DefaultResolvedDependency("someGroup", "someName", "someVersion", "someConfiguration",
+                someModuleArtifacts);
+        resolvedDependency.getChildren().add(new DefaultResolvedDependency("someGroup", "someChild", "someVersion", "someChildConfiguration",
+                someChildModuleArtifacts));
+        assertThat(resolvedDependency.getAllModuleArtifacts(), equalTo(GUtil.addSets(someChildModuleArtifacts, someModuleArtifacts)));
+    }
+
+    @Test
+    public void getParentArtifacts() {
+        Set<ResolvedArtifact> someModuleArtifacts = WrapUtil.toSet(createArtifact("someResolvedArtifact"));
+        DefaultResolvedDependency resolvedDependency = createResolvedDependency(someModuleArtifacts);
+
+        Set<ResolvedArtifact> parent1SpecificArtifacts = WrapUtil.toSet(createArtifact("parent1Specific"));
+        DefaultResolvedDependency parentResolvedDependency1 = createAndAddParent("parent1", resolvedDependency, parent1SpecificArtifacts);
+
+        Set<ResolvedArtifact> parent2SpecificArtifacts = WrapUtil.toSet(createArtifact("parent2Specific"));
+        DefaultResolvedDependency parentResolvedDependency2 = createAndAddParent("parent2", resolvedDependency, parent2SpecificArtifacts);
+
+        assertThat(resolvedDependency.getParentArtifacts(parentResolvedDependency1), equalTo(parent1SpecificArtifacts));
+        assertThat(resolvedDependency.getParentArtifacts(parentResolvedDependency2), equalTo(parent2SpecificArtifacts));
+    }
+
+    private ResolvedArtifact createArtifact(String name) {
+        return DefaultResolvedArtifactTest.createResolvedArtifact(context, name, "someType", "someExt", new File("pathTo" + name));
+    }
+
+    private DefaultResolvedDependency createResolvedDependency(Set<ResolvedArtifact> moduleArtifacts) {
+        return new DefaultResolvedDependency("someGroup", "someName", "someVersion", "someConfiguration",
+                moduleArtifacts);
+    }
+
+    @Test
+    public void getArtifacts() {
+        Set<ResolvedArtifact> someModuleArtifacts = WrapUtil.toSet(createArtifact("someModuleResolvedArtifact"));
+        DefaultResolvedDependency resolvedDependency = createResolvedDependency(someModuleArtifacts);
+
+        Set<ResolvedArtifact> parent1SpecificArtifacts = WrapUtil.toSet(createArtifact("parent1Specific"));
+        DefaultResolvedDependency parentResolvedDependency1 = createAndAddParent("parent1", resolvedDependency, parent1SpecificArtifacts);
+
+        assertThat(resolvedDependency.getArtifacts(parentResolvedDependency1), equalTo(GUtil.addSets(someModuleArtifacts, parent1SpecificArtifacts)));
+    }
+
+    @Test
+    public void getArtifactsWithParentWithoutParentArtifacts() {
+        Set<ResolvedArtifact> moduleArtifacts = WrapUtil.toSet(createArtifact("someModuleResolvedArtifact"));
+        DefaultResolvedDependency resolvedDependency = createResolvedDependency(moduleArtifacts);
+
+        DefaultResolvedDependency parent = new DefaultResolvedDependency("someGroup", "parent", "someVersion", "someConfiguration",
+                Collections.<ResolvedArtifact>emptySet());
+        resolvedDependency.getParents().add(parent);
+        assertThat(resolvedDependency.getArtifacts(parent), equalTo(moduleArtifacts));
+    }
+
+    @Test
+    public void getParentArtifactsWithParentWithoutParentArtifacts() {
+        Set<ResolvedArtifact> moduleArtifacts = WrapUtil.toSet(createArtifact("someModuleResolvedArtifact"));
+        DefaultResolvedDependency resolvedDependency = createResolvedDependency(moduleArtifacts);
+
+        DefaultResolvedDependency parent = new DefaultResolvedDependency("someGroup", "parent", "someVersion", "someConfiguration",
+                Collections.<ResolvedArtifact>emptySet());
+        resolvedDependency.getParents().add(parent);
+        assertThat(resolvedDependency.getParentArtifacts(parent), equalTo(Collections.<ResolvedArtifact>emptySet()));
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void getParentArtifactsWithUnknownParent() {
+        DefaultResolvedDependency resolvedDependency = createResolvedDependency(Collections.<ResolvedArtifact>emptySet());
+        DefaultResolvedDependency unknownParent = new DefaultResolvedDependency("someGroup", "parent2", "someVersion", "someConfiguration",
+                Collections.<ResolvedArtifact>emptySet());
+        assertThat(resolvedDependency.getParentArtifacts(unknownParent),
+                equalTo(Collections.<ResolvedArtifact>emptySet()));
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void getArtifactsWithUnknownParent() {
+        Set<ResolvedArtifact> someModuleArtifacts = WrapUtil.toSet(createArtifact("someModuleResolvedArtifact"));
+        DefaultResolvedDependency resolvedDependency = createResolvedDependency(Collections.<ResolvedArtifact>emptySet());
+
+        DefaultResolvedDependency unknownParent = new DefaultResolvedDependency("someGroup", "parent2", "someVersion", "someConfiguration",
+                Collections.<ResolvedArtifact>emptySet());
+        assertThat(resolvedDependency.getParentArtifacts(unknownParent),
+                equalTo(someModuleArtifacts));
+    }
+
+    @Test
+    public void getAllArtifacts() {
+        Set<ResolvedArtifact> someModuleArtifacts = WrapUtil.toSet(createArtifact("someModuleResolvedArtifact"));
+        Set<ResolvedArtifact> someChildModuleArtifacts = WrapUtil.toSet(createArtifact("someChildModuleResolvedArtifact"));
+        DefaultResolvedDependency resolvedDependency = createResolvedDependency(someModuleArtifacts);
+
+        Set<ResolvedArtifact> parent1SpecificArtifacts = WrapUtil.toSet(createArtifact("parent1Specific"));
+        DefaultResolvedDependency parentResolvedDependency1 = createAndAddParent("parent1", resolvedDependency, parent1SpecificArtifacts);
+
+        createAndAddParent("parent2", resolvedDependency, WrapUtil.toSet(createArtifact("parent2Specific")));
+
+        DefaultResolvedDependency child = new DefaultResolvedDependency("someGroup", "someChild", "someVersion", "someChildConfiguration",
+                someChildModuleArtifacts);
+        resolvedDependency.getChildren().add(child);
+
+        Set<ResolvedArtifact> childParent1SpecificArtifacts = WrapUtil.toSet(createArtifact("childParent1Specific"));
+        createAndAddParent("childParent1", child, childParent1SpecificArtifacts);
+
+        Set<ResolvedArtifact> childParent2SpecificArtifacts = WrapUtil.toSet(createArtifact("childParent2Specific"));
+        createAndAddParent("childParent2", child, childParent2SpecificArtifacts);
+
+        assertThat(resolvedDependency.getAllArtifacts(parentResolvedDependency1),
+                equalTo(GUtil.addSets(someModuleArtifacts, parent1SpecificArtifacts, someChildModuleArtifacts, childParent1SpecificArtifacts, childParent2SpecificArtifacts)));
+    }
+
+    @Test
+    public void equalsAndHashCode() {
+        DefaultResolvedDependency dependency = new DefaultResolvedDependency("group", "name", "version", "config",
+                Collections.EMPTY_SET);
+        DefaultResolvedDependency same = new DefaultResolvedDependency("group", "name", "version", "config", Collections.EMPTY_SET);
+        DefaultResolvedDependency differentGroup = new DefaultResolvedDependency("other", "name", "version", "config",
+                Collections.EMPTY_SET);
+        DefaultResolvedDependency differentName = new DefaultResolvedDependency("group", "other", "version", "config",
+                Collections.EMPTY_SET);
+        DefaultResolvedDependency differentVersion = new DefaultResolvedDependency("group", "name", "other", "config",
+                Collections.EMPTY_SET);
+        DefaultResolvedDependency differentConfiguration = new DefaultResolvedDependency("group", "name", "version",
+                "other", Collections.EMPTY_SET);
+
+        assertThat(dependency, strictlyEqual(same));
+        assertThat(dependency, not(equalTo(differentGroup)));
+        assertThat(dependency, not(equalTo(differentName)));
+        assertThat(dependency, not(equalTo(differentVersion)));
+        assertThat(dependency, not(equalTo(differentConfiguration)));
+    }
+
+    private DefaultResolvedDependency createAndAddParent(String parentName, DefaultResolvedDependency resolvedDependency, Set<ResolvedArtifact> parentSpecificArtifacts) {
+        DefaultResolvedDependency parent = new DefaultResolvedDependency("someGroup", parentName, "someVersion", "someConfiguration",
+                Collections.<ResolvedArtifact>emptySet());
+        resolvedDependency.getParents().add(parent);
+        resolvedDependency.addParentSpecificArtifacts(parent, parentSpecificArtifacts);
+        return parent;
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolverContainerTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolverContainerTest.groovy
new file mode 100644
index 0000000..c26ffa6
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/DefaultResolverContainerTest.groovy
@@ -0,0 +1,218 @@
+/*
+ * 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.api.internal.artifacts
+
+import org.apache.ivy.core.cache.DefaultRepositoryCacheManager
+import org.apache.ivy.core.cache.RepositoryCacheManager
+import org.apache.ivy.plugins.resolver.DependencyResolver
+import org.apache.ivy.plugins.resolver.FileSystemResolver
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.artifacts.ResolverContainer
+import org.gradle.api.artifacts.UnknownRepositoryException
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer
+import org.gradle.api.artifacts.maven.GroovyMavenDeployer
+import org.gradle.api.artifacts.maven.MavenResolver
+import org.gradle.api.internal.artifacts.ivyservice.ResolverFactory
+import org.gradle.util.JUnit4GroovyMockery
+import org.junit.Before
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.ClassGenerator
+
+/**
+ * @author Hans Dockter
+ */
+class DefaultResolverContainerTest {
+    static final String TEST_REPO_NAME = 'reponame'
+    
+    DefaultResolverContainer resolverContainer
+
+    RepositoryCacheManager dummyCacheManager = new DefaultRepositoryCacheManager()
+    def expectedUserDescription
+    def expectedUserDescription2
+    def expectedUserDescription3
+    String expectedName
+    String expectedName2
+    String expectedName3
+
+    FileSystemResolver expectedResolver
+    FileSystemResolver expectedResolver2
+    FileSystemResolver expectedResolver3
+
+    ResolverFactory resolverFactoryMock;
+
+    JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+
+    ResolverContainer createResolverContainer() {
+        return new DefaultResolverContainer(resolverFactoryMock, context.mock(ClassGenerator.class))
+    }
+
+    @Before public void setUp() {
+        expectedUserDescription = 'somedescription'
+        expectedUserDescription2 = 'somedescription2'
+        expectedUserDescription3 = 'somedescription3'
+        expectedName = 'somename'
+        expectedName2 = 'somename2'
+        expectedName3 = 'somename3'
+        expectedResolver = new FileSystemResolver()
+        expectedResolver2 = new FileSystemResolver()
+        expectedResolver3 = new FileSystemResolver()
+        expectedResolver.name = expectedName
+        expectedResolver2.name = expectedName2
+        expectedResolver3.name = expectedName3
+        resolverFactoryMock = context.mock(ResolverFactory)
+        context.checking {
+            allowing(resolverFactoryMock).createResolver(expectedUserDescription); will(returnValue(expectedResolver))
+            allowing(resolverFactoryMock).createResolver(expectedUserDescription2); will(returnValue(expectedResolver2))
+            allowing(resolverFactoryMock).createResolver(expectedUserDescription3); will(returnValue(expectedResolver3))
+        }
+        resolverContainer = createResolverContainer()
+    }
+
+    @Test public void testAddResolver() {
+        assert resolverContainer.add(expectedUserDescription).is(expectedResolver)
+        assert resolverContainer.findByName(expectedName).is(expectedResolver)
+        resolverContainer.add(expectedUserDescription2)
+        assertEquals([expectedResolver, expectedResolver2], resolverContainer.resolvers)
+    }
+
+    @Test public void testCannotAddResolverWithDuplicateName() {
+        [expectedResolver, expectedResolver2]*.name = 'resolver'
+        resolverContainer.add(expectedUserDescription)
+
+        try {
+            resolverContainer.add(expectedUserDescription2)
+            fail()
+        } catch (InvalidUserDataException e) {
+            assertThat(e.message, equalTo("Cannot add a resolver with name 'resolver' as a resolver with that name already exists."))
+        }
+    }
+
+    @Test public void testAddResolverWithClosure() {
+        def expectedConfigureValue = 'testvalue'
+        Closure configureClosure = {transactional = expectedConfigureValue}
+        assertThat(resolverContainer.add(expectedUserDescription, configureClosure), sameInstance(expectedResolver))
+        assertThat(resolverContainer.findByName(expectedName), sameInstance(expectedResolver))
+        assert expectedResolver.transactional == expectedConfigureValue
+    }
+
+    @Test public void testAddBefore() {
+        resolverContainer.add(expectedUserDescription)
+        assert resolverContainer.addBefore(expectedUserDescription2, expectedName).is(expectedResolver2)
+        assertEquals([expectedResolver2, expectedResolver], resolverContainer.resolvers)
+    }
+
+    @Test public void testAddAfter() {
+        resolverContainer.add(expectedUserDescription)
+        assert resolverContainer.addAfter(expectedUserDescription2, expectedName).is(expectedResolver2)
+        resolverContainer.addAfter(expectedUserDescription3, expectedName)
+        assertEquals([expectedResolver, expectedResolver3, expectedResolver2], resolverContainer.resolvers)
+    }
+
+    @Test (expected = InvalidUserDataException) public void testAddWithNullUserDescription() {
+        resolverContainer.add(null)
+    }
+
+    @Test (expected = InvalidUserDataException) public void testAddFirstWithNullUserDescription() {
+        resolverContainer.addFirst(null)
+    }
+
+    @Test (expected = InvalidUserDataException) public void testAddBeforeWithNullUserDescription() {
+        resolverContainer.addBefore(null, expectedName)
+    }
+
+    @Test (expected = InvalidUserDataException) public void testAddBeforeWithUnknownResolver() {
+        resolverContainer.addBefore(expectedUserDescription2, 'unknownName')
+    }
+
+    @Test (expected = InvalidUserDataException) public void testAddAfterWithNullUserDescription() {
+        resolverContainer.addAfter(null, expectedName)
+    }
+
+    @Test (expected = InvalidUserDataException) public void testAddAfterWithUnknownResolver() {
+        resolverContainer.addBefore(expectedUserDescription2, 'unknownName')
+    }
+
+    @Test public void testAddFirst() {
+        assert resolverContainer.addFirst(expectedUserDescription).is(expectedResolver)
+        resolverContainer.addFirst(expectedUserDescription2)
+        assertEquals([expectedResolver2, expectedResolver], resolverContainer.resolvers)
+    }
+
+    @Test(expected =  InvalidUserDataException)
+    public void testAddWithUnnamedResolver() {
+        expectedResolver.name = null
+        resolverContainer.add(expectedUserDescription).is(expectedResolver)
+    }
+
+    @Test
+    public void testGetThrowsExceptionForUnknownResolver() {
+        try {
+            resolverContainer.getByName('unknown')
+            fail()
+        } catch (UnknownRepositoryException e) {
+            assertThat(e.message, equalTo("Repository with name 'unknown' not found."))
+        }
+    }
+    
+    @Test
+    public void createMavenUploader() {
+        // todo we have to specify the class name, as this class is extended. This is a Groovy bug. As soon as we switch to a new Groovy version, we can refactor this. 
+        assertSame(prepareMavenDeployerTests(), resolverContainer.createMavenDeployer(DefaultResolverContainerTest.TEST_REPO_NAME));
+    }
+
+    @Test
+    public void createMavenInstaller() {
+        assertSame(prepareMavenInstallerTests(), resolverContainer.createMavenInstaller(DefaultResolverContainerTest.TEST_REPO_NAME));
+    }
+
+    protected GroovyMavenDeployer prepareMavenDeployerTests() {
+        prepareMavenResolverTests(GroovyMavenDeployer, "createMavenDeployer")
+    }
+
+    protected DependencyResolver prepareMavenInstallerTests() {
+        prepareMavenResolverTests(MavenResolver, "createMavenInstaller")
+    }
+
+    protected DependencyResolver prepareMavenResolverTests(Class resolverType, String createMethod) {
+        File testPomDir = new File("pomdir");
+        ConfigurationContainer configurationContainer = [:] as ConfigurationContainer
+        Conf2ScopeMappingContainer conf2ScopeMappingContainer = [:] as Conf2ScopeMappingContainer
+        FileResolver fileResolver = [:] as FileResolver
+        resolverContainer.setMavenPomDir(testPomDir)
+        resolverContainer.setConfigurationContainer(configurationContainer)
+        resolverContainer.setMavenScopeMappings(conf2ScopeMappingContainer)
+        resolverContainer.setFileResolver(fileResolver)
+        DependencyResolver expectedResolver = context.mock(resolverType)
+        context.checking {
+            allowing(expectedResolver).getName(); will(returnValue(DefaultResolverContainerTest.TEST_REPO_NAME))
+            allowing(resolverFactoryMock)."$createMethod"(
+                    withParam(any(String)),
+                    withParam(same(resolverContainer)),
+                    withParam(same(configurationContainer)),
+                    withParam(same(conf2ScopeMappingContainer)),
+                    withParam(same(fileResolver)));
+            will(returnValue(expectedResolver))
+            one(resolverFactoryMock).createResolver(expectedResolver);
+            will(returnValue(expectedResolver))
+        }
+        expectedResolver
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifierTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifierTest.groovy
new file mode 100644
index 0000000..9b12887
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ResolvedConfigurationIdentifierTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * 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
+
+import spock.lang.Specification
+import org.gradle.util.Matchers
+
+class ResolvedConfigurationIdentifierTest extends Specification {
+    def equalsAndHashCode() {
+        when:
+        ResolvedConfigurationIdentifier id = new ResolvedConfigurationIdentifier('group', 'name', 'version', 'config')
+        ResolvedConfigurationIdentifier same = new ResolvedConfigurationIdentifier('group', 'name', 'version', 'config')
+        ResolvedConfigurationIdentifier differentGroup = new ResolvedConfigurationIdentifier('other', 'name', 'version', 'config')
+        ResolvedConfigurationIdentifier differentName = new ResolvedConfigurationIdentifier('group', 'other', 'version', 'config')
+        ResolvedConfigurationIdentifier differentVersion = new ResolvedConfigurationIdentifier('group', 'name', 'other', 'config')
+        ResolvedConfigurationIdentifier differentConfig = new ResolvedConfigurationIdentifier('group', 'name', 'version', 'other')
+
+        then:
+        Matchers.strictlyEquals(id, same)
+        id != differentGroup
+        id != differentName
+        id != differentVersion
+        id != differentConfig
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsTest.java
new file mode 100644
index 0000000..bdd434e
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/ConfigurationsTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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.configurations;
+
+import org.hamcrest.Matchers;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+
+/**
+ * @author Hans Dockter
+ */
+public class ConfigurationsTest {
+    private static final String TEST_CONF = "testConf";
+
+    @Test
+    public void testUploadInternalTaskName() {
+        assertThat(Configurations.uploadInternalTaskName(TEST_CONF), Matchers.equalTo("uploadTestConfInternal"));
+    }
+
+    @Test
+    public void testUploadTaskName() {
+        assertThat(Configurations.uploadTaskName(TEST_CONF), Matchers.equalTo("uploadTestConf"));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.java
new file mode 100644
index 0000000..c5495d7
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationContainerTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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.configurations;
+
+import groovy.lang.Closure;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.UnknownConfigurationException;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.DomainObjectContext;
+import org.gradle.api.internal.artifacts.IvyService;
+import org.gradle.api.specs.Spec;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.WrapUtil;
+import static org.hamcrest.Matchers.*;
+
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultConfigurationContainerTest {
+    private static final String TEST_DESCRIPTION = "testDescription";
+    private static final Closure TEST_CLOSURE = HelperUtil.createSetterClosure("Description", TEST_DESCRIPTION);
+    private static final String TEST_NAME = "testName";
+
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    private IvyService ivyServiceDummy = context.mock(IvyService.class);
+    private ClassGenerator classGenerator = context.mock(ClassGenerator.class);
+    private DomainObjectContext domainObjectContext = context.mock(DomainObjectContext.class);
+    private DefaultConfigurationContainer configurationContainer = new DefaultConfigurationContainer(ivyServiceDummy, classGenerator, domainObjectContext);
+
+    @Test
+    public void init() {
+        assertThat(configurationContainer.getIvyService(), sameInstance(ivyServiceDummy));
+    }
+
+    @Test
+    public void testAdd() {
+        expectConfigurationCreated(TEST_NAME);
+        checkAddGetWithName((DefaultConfiguration) configurationContainer.add(TEST_NAME));
+    }
+
+    @Test
+    public void testAddWithNullClosure() {
+        expectConfigurationCreated(TEST_NAME);
+        checkAddGetWithName((DefaultConfiguration) configurationContainer.add(TEST_NAME, null));
+    }
+
+    @Test
+    public void testAddWithClosure() {
+        expectConfigurationCreated(TEST_NAME);
+        Configuration configuration = checkAddGetWithName((DefaultConfiguration) configurationContainer.add(TEST_NAME, TEST_CLOSURE));
+        assertThat(configuration.getDescription(), equalTo(TEST_DESCRIPTION));
+    }
+
+    private Configuration checkAddGetWithName(DefaultConfiguration configuration) {
+        assertThat(configuration, equalTo(configurationContainer.getByName(TEST_NAME)));
+        return configuration;
+    }
+
+    @Test
+    public void testFind() {
+        expectConfigurationCreated(TEST_NAME);
+        Configuration configuration = configurationContainer.add(TEST_NAME);
+        assertThat(configuration, sameInstance(configurationContainer.findByName(TEST_NAME)));
+    }
+
+    @Test
+    public void testFindNonExisitingConfiguration() {
+        assertThat(configurationContainer.findByName(TEST_NAME + "delta"), equalTo(null));
+    }
+
+    @Test(expected = UnknownConfigurationException.class)
+    public void testGetNonExisitingConfiguration() {
+        configurationContainer.getByName(TEST_NAME + "delta");
+    }
+
+    @Test
+    public void testGetWithClosure() {
+        expectConfigurationCreated(TEST_NAME);
+        configurationContainer.add(TEST_NAME);
+        Configuration configuration = configurationContainer.getByName(TEST_NAME, TEST_CLOSURE);
+        assertThat(configuration.getDescription(), equalTo(TEST_DESCRIPTION));
+    }
+
+    @Test
+    public void testGetWithFilter() {
+        expectConfigurationCreated(TEST_NAME);
+        expectConfigurationCreated(TEST_NAME + "delta");
+        Configuration configuration = configurationContainer.add(TEST_NAME);
+        configurationContainer.add(TEST_NAME + "delta");
+        Set<Configuration> result = configurationContainer.findAll(new Spec<Configuration>() {
+            public boolean isSatisfiedBy(Configuration element) {
+                return element.getName().equals(TEST_NAME);
+            }
+        });
+        assertThat(result, equalTo(WrapUtil.toSet(configuration)));
+    }
+
+    @Test
+    public void testGetAll() {
+        expectConfigurationCreated(TEST_NAME);
+        expectConfigurationCreated(TEST_NAME + "delta");
+        Configuration configuration1 = configurationContainer.add(TEST_NAME);
+        Configuration configuration2 = configurationContainer.add(TEST_NAME + "delta");
+        assertThat(configurationContainer.getAll(), equalTo(WrapUtil.toSet(configuration1, configuration2)));
+    }
+
+    @Test
+    public void testCreateDetached() {
+        Dependency dependency1 = HelperUtil.createDependency("group1", "name1", "version1");
+        Dependency dependency2 = HelperUtil.createDependency("group2", "name2", "version2");
+
+        Configuration detachedConf = configurationContainer.detachedConfiguration(dependency1, dependency2);
+
+        assertThat(detachedConf.getAll(), equalTo(WrapUtil.toSet(detachedConf)));
+        assertThat(detachedConf.getHierarchy(), equalTo(WrapUtil.<Configuration>toSet(detachedConf)));
+        assertThat(detachedConf.getDependencies(), equalTo(WrapUtil.toSet(dependency1, dependency2)));
+        assertNotSameInstances(detachedConf.getDependencies(), WrapUtil.toSet(dependency1, dependency2));
+    }
+
+    private void expectConfigurationCreated(final String name) {
+        context.checking(new Expectations(){{
+            one(domainObjectContext).absolutePath(name);
+            will(returnValue(name));
+            one(classGenerator).newInstance(DefaultConfiguration.class, name, name, configurationContainer, ivyServiceDummy);
+            will(returnValue(new DefaultConfiguration(name, name, configurationContainer, ivyServiceDummy)));
+        }});
+    }
+
+    private void assertNotSameInstances(Set<Dependency> dependencies, Set<Dependency> otherDependencies) {
+        for (Dependency dependency : dependencies) {
+            assertHasEqualButNotSameInstance(dependency, otherDependencies);
+        }
+    }
+
+    private void assertHasEqualButNotSameInstance(Dependency dependency, Set<Dependency> otherDependencies) {
+        assertThat(otherDependencies, hasItem(dependency));
+        for (Dependency otherDependency : otherDependencies) {
+            if (otherDependency.equals(dependency)) {
+                assertThat(otherDependency, not(sameInstance(dependency)));
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java
new file mode 100644
index 0000000..fff5822
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationTest.java
@@ -0,0 +1,940 @@
+/*
+ * 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.configurations;
+
+import groovy.lang.Closure;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.*;
+import org.gradle.api.artifacts.*;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.artifacts.DefaultExcludeRule;
+import org.gradle.api.internal.artifacts.IvyService;
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.api.tasks.TaskContainer;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.TestClosure;
+import org.gradle.util.WrapUtil;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.*;
+
+import static org.gradle.util.Matchers.isEmpty;
+import static org.gradle.util.Matchers.strictlyEqual;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class DefaultConfigurationTest {
+    private JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+
+    private IvyService ivyServiceStub = context.mock(IvyService.class);
+    private ConfigurationsProvider configurationContainer;
+
+    private DefaultConfiguration configuration;
+
+    @Before
+    public void setUp() {
+        configurationContainer = context.mock(ConfigurationsProvider.class);
+        configuration = createNamedConfiguration("path", "name");
+    }
+
+    @Test
+    public void defaultValues() {
+        assertThat(configuration.getName(), equalTo("name"));
+        assertThat(configuration.isVisible(), equalTo(true));
+        assertThat(configuration.getExtendsFrom().size(), equalTo(0));
+        assertThat(configuration.isTransitive(), equalTo(true));
+        assertThat(configuration.getDescription(), nullValue());
+        assertThat(configuration.getState(), equalTo(Configuration.State.UNRESOLVED));
+        assertThat(configuration.getDisplayName(), equalTo("configuration 'path'"));
+    }
+
+    @Test
+    public void withPrivateVisibility() {
+        configuration.setVisible(false);
+        assertFalse(configuration.isVisible());
+    }
+
+    @Test
+    public void withIntransitive() {
+        configuration.setTransitive(false);
+        assertFalse(configuration.isTransitive());
+    }
+
+    @Test
+    public void exclude() {
+        Map<String, String> excludeArgs1 = toMap("org", "value");
+        Map<String, String> excludeArgs2 = toMap("org2", "value2");
+        assertThat(configuration.exclude(excludeArgs1), sameInstance(configuration));
+        configuration.exclude(excludeArgs2);
+        assertThat(configuration.getExcludeRules(), equalTo(WrapUtil.<ExcludeRule>toSet(
+                new DefaultExcludeRule(excludeArgs1), new DefaultExcludeRule(excludeArgs2))));
+    }
+
+    @Test
+    public void setExclude() {
+        Set<ExcludeRule> excludeRules = WrapUtil.<ExcludeRule>toSet(new DefaultExcludeRule(toMap("org", "value")));
+        configuration.setExcludeRules(excludeRules);
+        assertThat(configuration.getExcludeRules(), equalTo(excludeRules));
+    }
+
+    @Test
+    public void withDescription() {
+        configuration.setDescription("description");
+        assertThat(configuration.getDescription(), equalTo("description"));
+    }
+
+    @Test
+    public void extendsOtherConfigurations() {
+        Configuration configuration1 = createNamedConfiguration("otherConf1");
+        configuration.extendsFrom(configuration1);
+        assertThat(configuration.getExtendsFrom(), equalTo(toSet(configuration1)));
+
+        Configuration configuration2 = createNamedConfiguration("otherConf2");
+        configuration.extendsFrom(configuration2);
+        assertThat(configuration.getExtendsFrom(), equalTo(toSet(configuration1, configuration2)));
+    }
+
+    @Test
+    public void setExtendsFrom() {
+        Configuration configuration1 = createNamedConfiguration("otherConf1");
+
+        configuration.setExtendsFrom(toSet(configuration1));
+        assertThat(configuration.getExtendsFrom(), equalTo(toSet(configuration1)));
+
+        Configuration configuration2 = createNamedConfiguration("otherConf2");
+        configuration.setExtendsFrom(toSet(configuration2));
+        assertThat(configuration.getExtendsFrom(), equalTo(toSet(configuration2)));
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void extendsFromWithDirectCycleShouldThrowInvalidUserDataEx() {
+        Configuration otherConfiguration = createNamedConfiguration("otherConf");
+        otherConfiguration.extendsFrom(configuration);
+        configuration.extendsFrom(otherConfiguration);
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void extendsFromWithIndirectCycleShouldThrowInvalidUserDataEx() {
+        Configuration otherConfiguration1 = createNamedConfiguration("otherConf1");
+        Configuration otherConfiguration2 = createNamedConfiguration("otherConf2");
+        configuration.extendsFrom(otherConfiguration1);
+        otherConfiguration1.extendsFrom(otherConfiguration2);
+        otherConfiguration2.extendsFrom(configuration);
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void setExtendsFromWithCycleShouldThrowInvalidUserDataEx() {
+        Configuration otherConfiguration = createNamedConfiguration("otherConf");
+        otherConfiguration.extendsFrom(configuration);
+        configuration.setExtendsFrom(toSet(otherConfiguration));
+    }
+
+    @Test
+    public void getHierarchy() {
+        Configuration root1 = createNamedConfiguration("root1");
+        Configuration middle1 = createNamedConfiguration("middle1").extendsFrom(root1);
+        Configuration root2 = createNamedConfiguration("root2");
+        Configuration middle2 = createNamedConfiguration("middle2").extendsFrom(root1, root2);
+        createNamedConfiguration("root3");
+        Configuration leaf = createNamedConfiguration("leaf1").extendsFrom(middle1, middle2);
+        Set<Configuration> hierarchy = leaf.getHierarchy();
+        assertThat(hierarchy.size(), equalTo(5));
+        assertThat(hierarchy.iterator().next(), equalTo(leaf));
+        assertBothExistsAndOneIsBeforeOther(hierarchy, middle1, root1);
+        assertBothExistsAndOneIsBeforeOther(hierarchy, middle2, root2);
+    }
+
+    private void assertBothExistsAndOneIsBeforeOther(Set<Configuration> hierarchy, Configuration beforeConf, Configuration afterConf) {
+        assertThat(hierarchy, hasItem(beforeConf));
+        assertThat(hierarchy, hasItem(afterConf));
+
+        boolean foundBeforeConf = false;
+        for (Configuration configuration : hierarchy) {
+            if (configuration.equals(beforeConf)) {
+                foundBeforeConf = true;
+            }
+            if (configuration.equals(afterConf)) {
+                assertThat(foundBeforeConf, equalTo(true));
+            }
+        }
+    }
+
+    @Test
+    public void getAll() {
+        final Configuration conf1 = createNamedConfiguration("testConf1");
+        final Configuration conf2 = createNamedConfiguration("testConf2");
+        context.checking(new Expectations(){{
+            one(configurationContainer).getAll();
+            will(returnValue(toSet(conf1, conf2)));
+        }});
+        assertThat(configuration.getAll(), equalTo(toSet(conf1, conf2)));
+    }
+
+    @Test(expected = GradleException.class)
+    public void getAsPathShouldRethrownFailure() {
+        prepareForResolveWithErrors();
+        configuration.resolve();
+    }
+
+    @Test
+    public void resolve() {
+        final Set<File> fileSet = toSet(new File("somePath"));
+        makeResolveReturnFileSet(fileSet);
+        assertThat(configuration.resolve(), equalTo(fileSet));
+        assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
+    }
+
+    @Test
+    public void filesWithDependencies() {
+        final Set<File> fileSet = toSet(new File("somePath"));
+        prepareForFilesBySpec(fileSet);
+        assertThat(configuration.files(context.mock(Dependency.class)), equalTo(fileSet));
+        assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
+    }
+
+    @Test
+    public void fileCollectionWithDependencies() {
+        Dependency dependency1 = HelperUtil.createDependency("group1", "name", "version");
+        Dependency dependency2 = HelperUtil.createDependency("group2", "name", "version");
+        DefaultConfiguration.ConfigurationFileCollection fileCollection = (DefaultConfiguration.ConfigurationFileCollection)
+                configuration.fileCollection(dependency1);
+        assertThat(fileCollection.getDependencySpec().isSatisfiedBy(dependency1),
+                equalTo(true));
+        assertThat(fileCollection.getDependencySpec().isSatisfiedBy(dependency2),
+                equalTo(false));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void filesWithSpec() {
+        final Set<File> fileSet = toSet(new File("somePath"));
+        prepareForFilesBySpec(fileSet);
+        assertThat(configuration.files(context.mock(Spec.class)), equalTo(fileSet));
+        assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
+    }
+
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void fileCollectionWithSpec() {
+        Spec<Dependency> spec = context.mock(Spec.class);
+        DefaultConfiguration.ConfigurationFileCollection fileCollection = (DefaultConfiguration.ConfigurationFileCollection)
+                configuration.fileCollection(spec);
+        assertThat(fileCollection.getDependencySpec(), sameInstance(spec));
+    }
+
+    @Test
+    public void filesWithClosureSpec() {
+        Closure closure = HelperUtil.toClosure("{ dep -> dep.group == 'group1' }");
+        final Set<File> fileSet = toSet(new File("somePath"));
+        prepareForFilesBySpec(fileSet);
+        assertThat(configuration.files(closure), equalTo(fileSet));
+        assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
+    }
+
+    @Test
+    public void fileCollectionWithClosureSpec() {
+        Closure closure = HelperUtil.toClosure("{ dep -> dep.group == 'group1' }");
+        DefaultConfiguration.ConfigurationFileCollection fileCollection = (DefaultConfiguration.ConfigurationFileCollection)
+                configuration.fileCollection(closure);
+        assertThat(fileCollection.getDependencySpec().isSatisfiedBy(HelperUtil.createDependency("group1", "name", "version")),
+                equalTo(true));
+        assertThat(fileCollection.getDependencySpec().isSatisfiedBy(HelperUtil.createDependency("group2", "name", "version")),
+                equalTo(false));
+    }
+
+    @SuppressWarnings("unchecked")
+    private void prepareForFilesBySpec(final Set<File> fileSet) {
+        final ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
+        prepareResolve(resolvedConfiguration, false);
+        context.checking(new Expectations() {{
+            one(resolvedConfiguration).getFiles(with(any(Spec.class)));
+            will(returnValue(fileSet));
+        }});
+    }
+
+    @Test(expected = GradleException.class)
+    public void resolveShouldRethrowFailure() {
+        prepareForResolveWithErrors();
+        configuration.resolve();
+    }
+
+    private void prepareForResolveWithErrors() {
+        final ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
+        prepareResolve(resolvedConfiguration, true);
+        context.checking(new Expectations(){{
+            one(resolvedConfiguration).rethrowFailure();
+            will(throwException(new GradleException()));
+        }});
+    }
+
+    @SuppressWarnings("unchecked")
+    private void makeResolveReturnFileSet(final Set<File> fileSet) {
+        final ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
+        context.checking(new Expectations() {{
+            prepareResolve(resolvedConfiguration, false);
+            allowing(resolvedConfiguration).getFiles(Specs.SATISFIES_ALL);
+            will(returnValue(fileSet));
+        }});
+    }
+
+    @Test
+    public void resolveSuccessfullyAsResolvedConfiguration() {
+        ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
+        prepareResolve(resolvedConfiguration, false);
+        assertThat(configuration.getResolvedConfiguration(), equalTo(resolvedConfiguration));
+        assertThat(configuration.getState(), equalTo(Configuration.State.RESOLVED));
+    }
+
+    private void prepareResolve(final ResolvedConfiguration resolvedConfiguration, final boolean withErrors) {
+        context.checking(new Expectations() {{
+            allowing(ivyServiceStub).resolve(configuration);
+            will(returnValue(resolvedConfiguration));
+            allowing(resolvedConfiguration).hasError();
+            will(returnValue(withErrors));
+        }});
+    }
+
+    @Test
+    public void multipleResolvesShouldUseCachedResult() {
+        prepareResolve(context.mock(ResolvedConfiguration.class), true);
+        assertThat(configuration.getResolvedConfiguration(), sameInstance(configuration.getResolvedConfiguration()));
+    }
+
+    @Test
+    public void publish() {
+        final Configuration otherConfiguration = createNamedConfiguration("testConf").extendsFrom(configuration);
+        final File someDescriptorDestination = new File("somePath");
+        final List<DependencyResolver> dependencyResolvers = toList(context.mock(DependencyResolver.class, "publish"));
+        context.checking(new Expectations() {{
+            allowing(ivyServiceStub).publish(new LinkedHashSet<Configuration>(otherConfiguration.getHierarchy()), someDescriptorDestination, dependencyResolvers);
+        }});
+        otherConfiguration.publish(dependencyResolvers, someDescriptorDestination);
+    }
+
+    @Test
+    public void uploadTaskName() {
+        assertThat(configuration.getUploadTaskName(), equalTo("uploadName"));
+    }
+
+    @Test
+    public void equality() {
+        Configuration sameConf = createNamedConfiguration("path", "name");
+        Configuration differentPath = createNamedConfiguration("other", "name");
+
+        assertThat(configuration, strictlyEqual(sameConf));
+        assertThat(configuration, not(equalTo(differentPath)));
+    }
+
+    private DefaultConfiguration createNamedConfiguration(String confName) {
+        return new DefaultConfiguration(confName, confName, configurationContainer, ivyServiceStub);
+    }
+    
+    private DefaultConfiguration createNamedConfiguration(String path, String confName) {
+        return new DefaultConfiguration(path, confName, configurationContainer, ivyServiceStub);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void buildArtifacts() {
+        final Task otherConfTaskMock = context.mock(Task.class, "otherConfTask");
+        final Task artifactTaskMock = context.mock(Task.class, "artifactTask");
+        final Configuration otherConfiguration = context.mock(Configuration.class);
+        final TaskDependency otherConfTaskDependencyMock = context.mock(TaskDependency.class, "otherConfTaskDep");
+        final TaskDependency artifactTaskDependencyMock = context.mock(TaskDependency.class, "artifactTaskDep");
+        DefaultPublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
+        artifact.setTaskDependency(artifactTaskDependencyMock);
+        configuration.addArtifact(artifact);
+
+        context.checking(new Expectations() {{
+            allowing(otherConfiguration).getBuildArtifacts();
+            will(returnValue(otherConfTaskDependencyMock));
+
+            allowing(otherConfiguration).getHierarchy();
+            will(returnValue(toSet()));
+
+            allowing(otherConfTaskDependencyMock).getDependencies(with(any(Task.class)));
+            will(returnValue(toSet(otherConfTaskMock)));
+
+            allowing(artifactTaskDependencyMock).getDependencies(with(any(Task.class)));
+            will(returnValue(toSet(artifactTaskMock)));
+        }});
+        configuration.setExtendsFrom(toSet(otherConfiguration));
+        assertThat((Set<Task>) configuration.getBuildArtifacts().getDependencies(context.mock(Task.class, "caller")),
+                equalTo(toSet(artifactTaskMock, otherConfTaskMock)));
+    }
+
+    @Test
+    public void getAllArtifactFiles() {
+        final Task otherConfTaskMock = context.mock(Task.class, "otherConfTask");
+        final Task artifactTaskMock = context.mock(Task.class, "artifactTask");
+        final Configuration otherConfiguration = context.mock(Configuration.class);
+        final TaskDependency otherConfTaskDependencyMock = context.mock(TaskDependency.class, "otherConfTaskDep");
+        final TaskDependency artifactTaskDependencyMock = context.mock(TaskDependency.class, "artifactTaskDep");
+        final File artifactFile1 = new File("artifact1");
+        final File artifactFile2 = new File("artifact2");
+        final PublishArtifact artifact = context.mock(PublishArtifact.class, "artifact");
+        final PublishArtifact otherArtifact = context.mock(PublishArtifact.class, "otherArtifact");
+
+        context.checking(new Expectations() {{
+            allowing(otherConfiguration).getHierarchy();
+            will(returnValue(toSet()));
+
+            allowing(otherConfiguration).getExtendsFrom();
+            will(returnValue(toSet()));
+
+            allowing(otherConfiguration).getArtifacts();
+            will(returnValue(toSet(otherArtifact)));
+
+            allowing(otherConfTaskDependencyMock).getDependencies(with(any(Task.class)));
+            will(returnValue(toSet(otherConfTaskMock)));
+
+            allowing(artifactTaskDependencyMock).getDependencies(with(any(Task.class)));
+            will(returnValue(toSet(artifactTaskMock)));
+
+            allowing(artifact).getFile();
+            will(returnValue(artifactFile1));
+
+            allowing(otherArtifact).getFile();
+            will(returnValue(artifactFile2));
+
+            allowing(artifact).getBuildDependencies();
+            will(returnValue(artifactTaskDependencyMock));
+
+            allowing(otherConfiguration).getBuildArtifacts();
+            will(returnValue(otherConfTaskDependencyMock));
+        }});
+
+        configuration.addArtifact(artifact);
+        configuration.setExtendsFrom(toSet(otherConfiguration));
+
+        FileCollection files = configuration.getAllArtifactFiles();
+        assertThat(files.getFiles(), equalTo(toSet(artifactFile1, artifactFile2)));
+        assertThat(files.getBuildDependencies().getDependencies(null), equalTo((Set) toSet(otherConfTaskMock, artifactTaskMock)));
+    }
+
+    @Test
+    public void buildDependenciesDelegatesToAllSelfResolvingDependencies() {
+        final Task target = context.mock(Task.class, "target");
+        final Task projectDepTaskDummy = context.mock(Task.class, "projectDepTask");
+        final Task fileDepTaskDummy = context.mock(Task.class, "fileDepTask");
+        final ProjectDependency projectDependencyStub = context.mock(ProjectDependency.class);
+        final FileCollectionDependency fileCollectionDependencyStub = context.mock(FileCollectionDependency.class);
+
+        context.checking(new Expectations() {{
+            TaskDependency projectTaskDependencyDummy = context.mock(TaskDependency.class, "projectDep");
+            TaskDependency fileTaskDependencyStub = context.mock(TaskDependency.class, "fileDep");
+
+            allowing(projectDependencyStub).getBuildDependencies();
+            will(returnValue(projectTaskDependencyDummy));
+
+            allowing(projectTaskDependencyDummy).getDependencies(target);
+            will(returnValue(toSet(projectDepTaskDummy)));
+
+            allowing(fileCollectionDependencyStub).getBuildDependencies();
+            will(returnValue(fileTaskDependencyStub));
+
+            allowing(fileTaskDependencyStub).getDependencies(target);
+            will(returnValue(toSet(fileDepTaskDummy)));
+        }});
+
+        configuration.addDependency(projectDependencyStub);
+        configuration.addDependency(fileCollectionDependencyStub);
+
+        assertThat(configuration.getBuildDependencies().getDependencies(target), equalTo((Set) toSet(fileDepTaskDummy,
+                projectDepTaskDummy)));
+    }
+
+    @Test
+    public void buildDependenciesDelegatesToInheritedConfigurations() {
+        final Task target = context.mock(Task.class, "target");
+        final Task otherConfTaskMock = context.mock(Task.class, "otherConfTask");
+        final TaskDependency otherConfTaskDependencyMock = context.mock(TaskDependency.class, "otherConfTaskDep");
+        final Configuration otherConfiguration = context.mock(Configuration.class, "otherConf");
+
+        context.checking(new Expectations() {{
+            allowing(otherConfiguration).getBuildDependencies();
+            will(returnValue(otherConfTaskDependencyMock));
+
+            allowing(otherConfiguration).getHierarchy();
+            will(returnValue(toSet()));
+
+            allowing(otherConfTaskDependencyMock).getDependencies(target);
+            will(returnValue(toSet(otherConfTaskMock)));
+        }});
+
+        configuration.extendsFrom(otherConfiguration);
+
+        assertThat(configuration.getBuildDependencies().getDependencies(target), equalTo((Set) toSet(otherConfTaskMock)));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test public void taskDependencyFromProjectDependencyUsingNeeded() {
+        Configuration superConfig = createNamedConfiguration("superConf");
+        configuration.extendsFrom(superConfig);
+
+        final ProjectDependency projectDependencyStub = context.mock(ProjectDependency.class);
+        superConfig.addDependency(projectDependencyStub);
+
+        final Project projectStub = context.mock(Project.class);
+        final TaskContainer taskContainerStub = context.mock(TaskContainer.class);
+        final Task taskStub = context.mock(Task.class);
+        final String taskName = "testit";
+
+        context.checking(new Expectations() {{
+            allowing(projectDependencyStub).getDependencyProject(); will(returnValue(projectStub));
+            allowing(projectStub).getTasks(); will(returnValue(taskContainerStub));
+            allowing(taskContainerStub).findByName(taskName); will(returnValue(taskStub));
+        }});
+
+        TaskDependency td = configuration.getTaskDependencyFromProjectDependency(true, taskName);
+        Task unusedTask = context.mock(Task.class, "unused");
+
+        assertThat((Set<Task>) td.getDependencies(unusedTask), equalTo(toSet(taskStub)));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test public void taskDependencyFromProjectDependencyUsingDependents() {
+        final String configName = configuration.getName();
+        final String taskName = "testit";
+        final Task tdTask = context.mock(Task.class, "tdTask");
+        final Project taskProject = context.mock(Project.class, "taskProject");
+        final Project rootProject = context.mock(Project.class, "rootProject");
+        final Project dependentProject = context.mock(Project.class, "dependentProject");
+        final Task desiredTask = context.mock(Task.class, "desiredTask");
+        final Set<Task> taskSet = toSet(desiredTask);
+        final ConfigurationContainer configurationContainer = context.mock(ConfigurationContainer.class);
+        final Configuration dependentConfig = context.mock(Configuration.class);
+        final ProjectDependency projectDependency = context.mock(ProjectDependency.class);
+        final Set<ProjectDependency> projectDependencies = toSet(projectDependency);
+
+
+        context.checking(new Expectations() {{
+            allowing(tdTask).getProject(); will(returnValue(taskProject));
+            allowing(taskProject).getRootProject(); will(returnValue(rootProject));
+            allowing(rootProject).getTasksByName(taskName, true); will(returnValue(taskSet));
+            allowing(desiredTask).getProject(); will(returnValue(dependentProject));
+            allowing(dependentProject).getConfigurations(); will(returnValue(configurationContainer));
+            allowing(configurationContainer).findByName(configName); will(returnValue(dependentConfig));
+
+            allowing(dependentConfig).getAllDependencies(ProjectDependency.class); will(returnValue(projectDependencies));
+            allowing(projectDependency).getDependencyProject(); will(returnValue(taskProject));
+        }});
+
+        TaskDependency td = configuration.getTaskDependencyFromProjectDependency(false, taskName);
+        assertThat((Set<Task>) td.getDependencies(tdTask), equalTo(toSet(desiredTask)));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test public void taskDependencyFromProjectDependencyWithoutCommonConfiguration() {
+        // This test exists because a NullPointerException was thrown by
+        // getTaskDependencyFromProjectDependency() if the rootProject
+        // defined a task as the same name as a subproject's task, but did
+        // not define the same configuration.
+        final String configName = configuration.getName();
+        final String taskName = "testit";
+        final Task tdTask = context.mock(Task.class, "tdTask");
+        final Project taskProject = context.mock(Project.class, "taskProject");
+        final Project rootProject = context.mock(Project.class, "rootProject");
+        final Project dependentProject = context.mock(Project.class, "dependentProject");
+        final Task desiredTask = context.mock(Task.class, "desiredTask");
+        final Set<Task> taskSet = toSet(desiredTask);
+        final ConfigurationContainer configurationContainer = context.mock(ConfigurationContainer.class);
+
+        context.checking(new Expectations() {{
+            allowing(tdTask).getProject(); will(returnValue(taskProject));
+            allowing(taskProject).getRootProject(); will(returnValue(rootProject));
+            allowing(rootProject).getTasksByName(taskName, true); will(returnValue(taskSet));
+            allowing(desiredTask).getProject(); will(returnValue(dependentProject));
+            allowing(dependentProject).getConfigurations(); will(returnValue(configurationContainer));
+
+            // return null to mock not finding the given configuration
+            allowing(configurationContainer).findByName(configName); will(returnValue(null));
+        }});
+
+        TaskDependency td = configuration.getTaskDependencyFromProjectDependency(false, taskName);
+        assertThat(td.getDependencies(tdTask), equalTo(Collections.EMPTY_SET));
+    }
+
+
+    @Test
+    public void getDependencies() {
+        Dependency dependency = context.mock(Dependency.class);
+        configuration.addDependency(dependency);
+        assertThat(configuration.getDependencies(), equalTo(toSet(dependency)));
+    }
+
+    @Test
+    public void getTypedDependencies() {
+        ProjectDependency projectDependency = context.mock(ProjectDependency.class);
+        configuration.addDependency(context.mock(Dependency.class));
+        configuration.addDependency(projectDependency);
+        assertThat(configuration.getDependencies(ProjectDependency.class), equalTo(toSet(projectDependency)));
+    }
+
+    @Test
+    public void getTypedDependenciesReturnsEmptySetWhenNoMatches() {
+        configuration.addDependency(context.mock(Dependency.class));
+        assertThat(configuration.getDependencies(ProjectDependency.class), isEmpty());
+    }
+
+    @Test
+    public void getAllDependencies() {
+        Dependency dependencyConf = HelperUtil.createDependency("group1", "name1", "version1");
+        Dependency dependencyOtherConf1 = HelperUtil.createDependency("group1", "name1", "version1");
+        Dependency dependencyOtherConf2 = context.mock(Dependency.class, "dep2");
+        Configuration otherConf = createNamedConfiguration("otherConf");
+        configuration.addDependency(dependencyConf);
+        configuration.extendsFrom(otherConf);
+        otherConf.addDependency(dependencyOtherConf1);
+        otherConf.addDependency(dependencyOtherConf2);
+
+        assertThat(configuration.getAllDependencies(), equalTo(toSet(dependencyConf, dependencyOtherConf2)));
+        assertCorrectInstanceInAllDependencies(configuration.getAllDependencies(), dependencyConf);
+    }
+
+    @Test
+    public void getAllTypedDependencies() {
+        ProjectDependency projectDependencyCurrentConf = context.mock(ProjectDependency.class, "projectDepCurrentConf");
+        configuration.addDependency(context.mock(Dependency.class, "depCurrentConf"));
+        configuration.addDependency(projectDependencyCurrentConf);
+        Configuration otherConf = createNamedConfiguration("otherConf");
+        configuration.extendsFrom(otherConf);
+        ProjectDependency projectDependencyExtendedConf = context.mock(ProjectDependency.class, "projectDepExtendedConf");
+        otherConf.addDependency(context.mock(Dependency.class, "depExtendedConf"));
+        otherConf.addDependency(projectDependencyExtendedConf);
+
+        assertThat(configuration.getAllDependencies(ProjectDependency.class), equalTo(toSet(projectDependencyCurrentConf, projectDependencyExtendedConf)));
+    }
+
+    @Test
+    public void getAllTypedDependenciesReturnsEmptySetWhenNoMatches() {
+        configuration.addDependency(context.mock(Dependency.class, "depCurrentConf"));
+        Configuration otherConf = createNamedConfiguration("otherConf");
+        configuration.extendsFrom(otherConf);
+        otherConf.addDependency(context.mock(Dependency.class, "depExtendedConf"));
+
+        assertThat(configuration.getAllDependencies(ProjectDependency.class), isEmpty());
+    }
+
+    @Test
+    public void getAllArtifacts() {
+        PublishArtifact artifactConf = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
+        PublishArtifact artifactOtherConf2 = HelperUtil.createPublishArtifact("name2", "ext2", "type2", "classifier2");
+        Configuration otherConf = createNamedConfiguration("otherConf");
+        configuration.addArtifact(artifactConf);
+        configuration.extendsFrom(otherConf);
+        otherConf.addArtifact(artifactOtherConf2);
+        assertThat(configuration.getAllArtifacts(), equalTo(toSet(artifactConf, artifactOtherConf2)));
+    }
+
+    @Test
+    public void removeArtifact() {
+        PublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
+        configuration.addArtifact(artifact);
+        configuration.removeArtifact(artifact);
+        assertThat(configuration.getAllArtifacts(), equalTo(Collections.<PublishArtifact>emptySet()));
+    }
+
+    @Test
+    public void removeArtifactWithUnknownArtifact() {
+        PublishArtifact artifact = HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1");
+        configuration.addArtifact(artifact);
+        configuration.removeArtifact(HelperUtil.createPublishArtifact("name2", "ext1", "type1", "classifier1"));
+        assertThat(configuration.getAllArtifacts(), equalTo(WrapUtil.toSet(artifact)));
+    }
+
+    private void assertCorrectInstanceInAllDependencies(Set<Dependency> allDependencies, Dependency correctInstance) {
+        for (Dependency dependency : allDependencies) {
+            if (dependency == correctInstance) {
+                return;
+            }
+        }
+        fail("Correct instance is missing!");
+    }
+
+    @Test
+    public void getConfiguration() {
+        Dependency configurationDependency = HelperUtil.createDependency("group1", "name1", "version1");
+        Dependency otherConfSimilarDependency = HelperUtil.createDependency("group1", "name1", "version1");
+        Dependency otherConfDependency = HelperUtil.createDependency("group2", "name2", "version2");
+        Configuration otherConf = createNamedConfiguration("otherConf");
+        configuration.extendsFrom(otherConf);
+        otherConf.addDependency(otherConfDependency);
+        otherConf.addDependency(otherConfSimilarDependency);
+        configuration.addDependency(configurationDependency);
+
+        assertThat((DefaultConfiguration) configuration.getConfiguration(configurationDependency), equalTo(configuration));
+        assertThat((DefaultConfiguration) configuration.getConfiguration(otherConfSimilarDependency), equalTo(configuration));
+        assertThat(configuration.getConfiguration(otherConfDependency), equalTo(otherConf));
+    }
+
+    @Test
+    public void getConfigurationWithUnknownDependency() {
+        assertThat(configuration.getConfiguration(HelperUtil.createDependency("group1", "name1", "version1")), equalTo(null));
+    }
+
+    @Test
+    public void copy() {
+        prepareConfigurationForCopyTest();
+
+        Configuration copiedConfiguration = configuration.copy();
+
+        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, configuration.getDependencies());
+    }
+
+    @Test
+    public void copyWithSpec() {
+        prepareConfigurationForCopyTest();
+        Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getDependencies());
+        configuration.addDependency(HelperUtil.createDependency("group3", "name3", "version3"));
+
+        Configuration copiedConfiguration = configuration.copy(new Spec<Dependency>() {
+            public boolean isSatisfiedBy(Dependency element) {
+                return !element.getGroup().equals("group3");
+            }
+        });
+
+        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, expectedDependenciesToCopy);
+    }
+
+    @Test
+    public void copyWithClosure() {
+        prepareConfigurationForCopyTest();
+        Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getDependencies());
+        configuration.addDependency(HelperUtil.createDependency("group3", "name3", "version3"));
+
+        Closure specClosure = HelperUtil.toClosure("{ element ->  !element.group.equals(\"group3\")}");
+        Configuration copiedConfiguration = configuration.copy(specClosure);
+
+        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, expectedDependenciesToCopy);
+    }
+
+    private void prepareConfigurationForCopyTest() {
+        configuration.setVisible(false);
+        configuration.setTransitive(false);
+        configuration.setDescription("descript");
+        configuration.exclude(toMap("org", "value"));
+        configuration.exclude(toMap("org2", "value2"));
+        configuration.addArtifact(HelperUtil.createPublishArtifact("name1", "ext1", "type1", "classifier1"));
+        configuration.addArtifact(HelperUtil.createPublishArtifact("name2", "ext2", "type2", "classifier2"));
+        configuration.addDependency(HelperUtil.createDependency("group1", "name1", "version1"));
+        configuration.addDependency(HelperUtil.createDependency("group2", "name2", "version2"));
+    }
+
+    private void assertThatCopiedConfigurationHasElementsAndName(Configuration copiedConfiguration, Set<Dependency> expectedDependencies) {
+        assertThat(copiedConfiguration.getName(), equalTo(configuration.getName() + "Copy"));
+        assertThat(copiedConfiguration.isVisible(), equalTo(configuration.isVisible()));
+        assertThat(copiedConfiguration.isTransitive(), equalTo(configuration.isTransitive()));
+        assertThat(copiedConfiguration.getDescription(), equalTo(configuration.getDescription()));
+        assertThat(copiedConfiguration.getAllArtifacts(), equalTo(configuration.getAllArtifacts()));
+        assertThat(copiedConfiguration.getExcludeRules(), equalTo(configuration.getExcludeRules()));
+        assertThat(copiedConfiguration.getExcludeRules().iterator().next(), not(sameInstance(configuration.getExcludeRules().iterator().next())));
+        assertThat(copiedConfiguration.getDependencies(), equalTo(expectedDependencies));
+        assertNotSameInstances(copiedConfiguration.getDependencies(), expectedDependencies);
+    }
+
+    @Test
+    public void copyRecursive() {
+        prepareConfigurationForCopyRecursiveTest();
+
+        Configuration copiedConfiguration = configuration.copyRecursive();
+
+        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, configuration.getAllDependencies());
+    }
+
+    @Test
+    public void copyRecursiveWithSpec() {
+        prepareConfigurationForCopyRecursiveTest();
+        Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getAllDependencies());
+        configuration.addDependency(HelperUtil.createDependency("group3", "name3", "version3"));
+
+        Closure specClosure = HelperUtil.toClosure("{ element ->  !element.group.equals(\"group3\")}");
+        Configuration copiedConfiguration = configuration.copyRecursive(specClosure);
+
+        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, expectedDependenciesToCopy);
+    }
+
+    @Test
+    public void copyRecursiveWithClosure() {
+        prepareConfigurationForCopyRecursiveTest();
+        Set<Dependency> expectedDependenciesToCopy = new HashSet<Dependency>(configuration.getAllDependencies());
+        configuration.addDependency(HelperUtil.createDependency("group3", "name3", "version3"));
+
+        Configuration copiedConfiguration = configuration.copyRecursive(new Spec<Dependency>() {
+            public boolean isSatisfiedBy(Dependency element) {
+                return !element.getGroup().equals("group3");
+            }
+        });
+
+        assertThatCopiedConfigurationHasElementsAndName(copiedConfiguration, expectedDependenciesToCopy);
+    }
+
+    private void prepareConfigurationForCopyRecursiveTest() {
+        prepareConfigurationForCopyTest();
+        Dependency similarDependency2InOtherConf = HelperUtil.createDependency("group2", "name2", "version2");
+        Dependency otherConfDependency = HelperUtil.createDependency("group4", "name4", "version4");
+        Configuration otherConf = createNamedConfiguration("otherConf");
+        otherConf.addDependency(similarDependency2InOtherConf);
+        otherConf.addDependency(otherConfDependency);
+        configuration.extendsFrom(otherConf);
+    }
+
+    private void assertNotSameInstances(Set<Dependency> dependencies, Set<Dependency> otherDependencies) {
+        for (Dependency dependency : dependencies) {
+            assertHasEqualButNotSameInstance(dependency, otherDependencies);
+        }
+    }
+
+    private void assertHasEqualButNotSameInstance(Dependency dependency, Set<Dependency> otherDependencies) {
+        assertThat(otherDependencies, hasItem(dependency));
+        for (Dependency otherDependency : otherDependencies) {
+            if (otherDependency.equals(dependency)) {
+                assertThat(otherDependency, not(sameInstance(dependency)));
+            }
+        }
+    }
+
+    @Test
+    public void allDependencies() {
+        DefaultExternalModuleDependency dependency1 = (DefaultExternalModuleDependency) HelperUtil.createDependency("group1", "name", "version");
+        configuration.addDependency(dependency1);
+        configuration.allDependencies(new Action<Dependency>() {
+            public void execute(Dependency dependency) {
+             ((DefaultExternalModuleDependency) dependency).setForce(true);
+            }
+        });
+        configuration.allDependencies(HelperUtil.toClosure(new TestClosure() {
+            public Object call(Object param) {
+                return ((DefaultExternalModuleDependency) param).setChanging(true);
+            }
+        }));
+        DefaultExternalModuleDependency dependency2 = (DefaultExternalModuleDependency) HelperUtil.createDependency("group2", "name2", "version2");
+        configuration.addDependency(dependency2);
+        
+        assertThat(dependency1.isForce(), equalTo(true));
+        assertThat(dependency1.isForce(), equalTo(true));
+        assertThat(dependency2.isChanging(), equalTo(true));
+        assertThat(dependency2.isChanging(), equalTo(true));
+    }
+
+    @Test
+    public void whenDependencyAdded() {
+        DefaultExternalModuleDependency dependency1 = (DefaultExternalModuleDependency) HelperUtil.createDependency("group1", "name", "version");
+        configuration.addDependency(dependency1);
+        configuration.whenDependencyAdded(new Action<Dependency>() {
+            public void execute(Dependency dependency) {
+             ((DefaultExternalModuleDependency) dependency).setForce(true);
+            }
+        });
+        configuration.whenDependencyAdded(HelperUtil.toClosure(new TestClosure() {
+            public Object call(Object param) {
+                return ((DefaultExternalModuleDependency) param).setChanging(true);
+            }
+        }));
+        DefaultExternalModuleDependency dependency2 = (DefaultExternalModuleDependency) HelperUtil.createDependency("group2", "name2", "version2");
+        configuration.addDependency(dependency2);
+
+        assertThat(dependency1.isForce(), equalTo(false));
+        assertThat(dependency1.isForce(), equalTo(false));
+        assertThat(dependency2.isChanging(), equalTo(true));
+        assertThat(dependency2.isChanging(), equalTo(true));
+    }
+
+    @Test
+    public void propertyChangeWithNonUnresolvedStateShouldThrowEx() {
+        makeResolveReturnFileSet(new HashSet<File>());
+        configuration.resolve();
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.setTransitive(true);
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.setDescription("someDesc");
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.setExcludeRules(new HashSet<ExcludeRule>());
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.setExtendsFrom(new HashSet<Configuration>());
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.setVisible(true);
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.addArtifact(context.mock(PublishArtifact.class));
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.addDependency(context.mock(Dependency.class));
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.exclude(new HashMap<String, String>());
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.extendsFrom(context.mock(Configuration.class));
+            }
+        });
+        assertInvalidUserDataException(new Executer() {
+            public void execute() {
+                configuration.removeArtifact(context.mock(PublishArtifact.class, "removeeArtifact"));
+            }
+        });
+    }
+
+    private void assertInvalidUserDataException(Executer executer) {
+        try {
+            executer.execute();
+            fail();
+        } catch (InvalidUserDataException e) {
+            // ignore
+        }
+    }
+
+    private static interface Executer {
+        void execute();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencyTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencyTest.java
new file mode 100644
index 0000000..a4a5cf7
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/AbstractModuleDependencyTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.DependencyArtifact;
+import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.internal.artifacts.DefaultExcludeRule;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.JUnit4GroovyMockery;
+import static org.gradle.util.Matchers.*;
+import org.gradle.util.WrapUtil;
+import static org.hamcrest.Matchers.*;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+abstract public class AbstractModuleDependencyTest {
+
+    protected abstract AbstractModuleDependency getDependency();
+
+    protected abstract AbstractModuleDependency createDependency(String group, String name, String version);
+
+    protected abstract AbstractModuleDependency createDependency(String group, String name, String version, String configuration);
+
+    protected JUnit4Mockery context = new JUnit4GroovyMockery();
+
+    @Test
+    public void defaultValues() {
+        assertTrue(getDependency().isTransitive());
+        assertThat(getDependency().getArtifacts(), isEmpty());
+        assertThat(getDependency().getExcludeRules(), isEmpty());
+        assertThat(getDependency().getConfiguration(), equalTo(Dependency.DEFAULT_CONFIGURATION));
+    }
+
+    @Test
+    public void exclude() {
+        Map<String,String> excludeArgs1 = WrapUtil.toMap("key", "value");
+        Map<String,String> excludeArgs2 = WrapUtil.toMap("key2", "value2");
+
+        getDependency().exclude(excludeArgs1);
+        getDependency().exclude(excludeArgs2);
+
+        assertThat(getDependency().getExcludeRules().size(), equalTo(2));
+        assertThat(getDependency().getExcludeRules(), hasItem((ExcludeRule) new DefaultExcludeRule(excludeArgs1)));
+        assertThat(getDependency().getExcludeRules(), hasItem((ExcludeRule) new DefaultExcludeRule(excludeArgs2)));
+    }
+
+    @Test
+    public void addArtifact() {
+        DependencyArtifact artifact1 = createAnonymousArtifact();
+        DependencyArtifact artifact2 = createAnonymousArtifact();
+
+        getDependency().addArtifact(artifact1);
+        getDependency().addArtifact(artifact2);
+
+        assertThat(getDependency().getArtifacts().size(), equalTo(2));
+        assertThat(getDependency().getArtifacts(), hasItem(artifact1));
+        assertThat(getDependency().getArtifacts(), hasItem(artifact2));
+    }
+
+    private DependencyArtifact createAnonymousArtifact() {
+        return new DefaultDependencyArtifact(HelperUtil.createUniqueId(), "type", "org", "classifier", "url");
+    }
+
+    @Test
+    public void equality() {
+        assertThat(createDependency("group1", "name1", "version1"), equalTo(createDependency("group1", "name1", "version1")));
+        assertThat(createDependency("group1", "name1", "version1").hashCode(), equalTo(createDependency("group1", "name1", "version1").hashCode()));
+        assertThat(createDependency("group1", "name1", "version1"), not(equalTo(createDependency("group1", "name1", "version2"))));
+        assertThat(createDependency("group1", "name1", "version1"), not(equalTo(createDependency("group1", "name2", "version1"))));
+        assertThat(createDependency("group1", "name1", "version1"), not(equalTo(createDependency("group2", "name1", "version1"))));
+        assertThat(createDependency("group1", "name1", "version1"), not(equalTo(createDependency("group2", "name1", "version1"))));
+        assertThat(createDependency("group1", "name1", "version1", "depConf1"), not(equalTo(createDependency("group1", "name1", "version1", "depConf2"))));
+    }
+
+    protected void assertDeepCopy(ModuleDependency dependency, ModuleDependency copiedDependency) {
+        assertThat(copiedDependency.getGroup(), equalTo(dependency.getGroup()));
+        assertThat(copiedDependency.getName(), equalTo(dependency.getName()));
+        assertThat(copiedDependency.getVersion(), equalTo(dependency.getVersion()));
+        assertThat(copiedDependency.getConfiguration(), equalTo(dependency.getConfiguration()));
+        assertThat(copiedDependency.isTransitive(), equalTo(dependency.isTransitive()));
+        assertThat(copiedDependency.getArtifacts(), equalTo(dependency.getArtifacts()));
+        assertThat(copiedDependency.getArtifacts(), not(sameInstance(dependency.getArtifacts())));
+        assertThat(copiedDependency.getExcludeRules(), equalTo(dependency.getExcludeRules()));
+        assertThat(copiedDependency.getExcludeRules(), not(sameInstance(dependency.getExcludeRules())));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModuleTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModuleTest.java
new file mode 100644
index 0000000..8e9cd24
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultClientModuleTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.util.HelperUtil;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultClientModuleTest extends AbstractModuleDependencyTest {
+    private static final String TEST_GROUP = "org.gradle";
+    private static final String TEST_NAME = "gradle-core";
+    private static final String TEST_VERSION = "4.4-beta2";
+
+    DefaultClientModule clientModule;
+
+    DefaultDependencyDescriptor expectedDependencyDescriptor;
+
+    protected AbstractModuleDependency getDependency() {
+        return clientModule;
+    }
+
+    protected AbstractModuleDependency createDependency(String group, String name, String version) {
+        return new DefaultClientModule(group, name, version);
+    }
+
+    protected AbstractModuleDependency createDependency(String group, String name, String version, String configuration) {
+        return new DefaultClientModule(group, name, version, configuration);
+    }
+
+    @Before
+    public void setUp() {
+        clientModule = new DefaultClientModule(TEST_GROUP, TEST_NAME, TEST_VERSION);
+        expectedDependencyDescriptor = HelperUtil.getTestDescriptor();
+    }
+
+    @Test
+    public void init() {
+        assertThat(clientModule.getGroup(), equalTo(TEST_GROUP));
+        assertThat(clientModule.getName(), equalTo(TEST_NAME));
+        assertThat(clientModule.getVersion(), equalTo(TEST_VERSION));
+        assertThat(clientModule.isForce(), equalTo(false));
+        assertThat(clientModule.isTransitive(), equalTo(true));
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void initWithNullNameShouldThrowInvalidUserDataEx() {
+        new DefaultClientModule(TEST_GROUP, null, TEST_VERSION);
+    }
+
+    @Test
+    public void contentEqualsWithEqualDependencies() {
+        DefaultClientModule clientModule1 = createModule();
+        DefaultClientModule clientModule2 = createModule();
+        assertThat(clientModule1.contentEquals(clientModule2), equalTo(true));
+    }
+
+    @Test
+    public void contentEqualsWithNonEqualDependencies() {
+        DefaultClientModule clientModule1 = createModule();
+        DefaultClientModule clientModule2 = createModule();
+        clientModule2.setGroup(clientModule1.getGroup() + "delta");
+        assertThat(clientModule1.contentEquals(clientModule2), equalTo(false));
+    }
+
+    @Test
+    public void copy() {
+        DefaultClientModule clientModule = createModule();
+        DefaultClientModule copiedClientModule = (DefaultClientModule) clientModule.copy();
+        assertThat(clientModule.contentEquals(copiedClientModule), equalTo(true));
+        assertDeepCopy(clientModule, copiedClientModule);
+        assertThat(copiedClientModule.getDependencies().iterator().next(), not(sameInstance(clientModule.getDependencies().iterator().next())));
+    }
+
+    private DefaultClientModule createModule() {
+        DefaultClientModule clientModule =  new DefaultClientModule("group", "name", "version", "conf");
+        clientModule.addArtifact(new DefaultDependencyArtifact("name", "type", "ext", "classifier", "url"));
+        clientModule.addDependency(new DefaultExternalModuleDependency("org", "name", "version"));
+        return clientModule;
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultDependencyArtifactTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultDependencyArtifactTest.java
new file mode 100644
index 0000000..e27779a
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultDependencyArtifactTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.artifacts.DependencyArtifact;
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultDependencyArtifactTest {
+    @Test
+    public void testInit() {
+        String testName = "name";
+        String testType = "type";
+        String testExtension = "ext";
+        String testClassifier = "classifier";
+        String testUrl = "url";
+        DependencyArtifact artifact = new DefaultDependencyArtifact(testName, testType, testExtension, testClassifier, testUrl);
+        assertEquals(testName, artifact.getName());
+        assertEquals(testType, artifact.getType());
+        assertEquals(testExtension, artifact.getExtension());
+        assertEquals(testClassifier, artifact.getClassifier());
+        assertEquals(testUrl, artifact.getUrl());
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExcludeRuleContainerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExcludeRuleContainerTest.java
new file mode 100644
index 0000000..dcd5fa7
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExcludeRuleContainerTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.api.internal.artifacts.DefaultExcludeRule;
+import org.gradle.api.internal.artifacts.DefaultExcludeRuleContainer;
+import org.gradle.util.WrapUtil;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultExcludeRuleContainerTest {
+    @Test
+    public void testInit() {
+        assertThat(new DefaultExcludeRuleContainer().getRules().size(), equalTo(0));
+    }
+
+    @Test
+    public void testInitWithRules() {
+        Set<ExcludeRule> sourceExcludeRules = new HashSet<ExcludeRule>();
+        sourceExcludeRules.add(new DefaultExcludeRule(WrapUtil.toMap("key", "value")));
+        DefaultExcludeRuleContainer defaultExcludeRuleContainer = new DefaultExcludeRuleContainer(sourceExcludeRules);
+        assertThat(defaultExcludeRuleContainer.getRules(), equalTo(sourceExcludeRules));
+        assertThat(defaultExcludeRuleContainer.getRules(), not(sameInstance(sourceExcludeRules)));
+    }
+
+    @Test
+    public void testAdd() {
+        DefaultExcludeRuleContainer excludeRuleContainer = new DefaultExcludeRuleContainer();
+        Map excludeRuleArgs1 = WrapUtil.toMap("key1", "value1");
+        Map excludeRuleArgs2 = WrapUtil.toMap("key2", "value2");
+        excludeRuleContainer.add(excludeRuleArgs1);
+        excludeRuleContainer.add(excludeRuleArgs2);
+        assertThat(excludeRuleContainer.getRules().size(), equalTo(2));
+        assertExcludeRuleContainerHasCorrectExcludeRules(excludeRuleContainer.getRules(), excludeRuleArgs1, excludeRuleArgs2);
+    }
+
+    private void assertExcludeRuleContainerHasCorrectExcludeRules(Set<ExcludeRule> excludeRules, Map... excludeRuleArgs) {
+        Set<Map> foundRules = new HashSet<Map>();
+        for (ExcludeRule excludeRule : excludeRules) {
+            for (Map excludeRuleArg : excludeRuleArgs) {
+                if (excludeRule.getExcludeArgs().equals(excludeRuleArg)) {
+                    foundRules.add(excludeRuleArg);
+                    continue;
+                }
+            }
+        }
+    }
+
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependencyTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependencyTest.java
new file mode 100644
index 0000000..2103efc
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultExternalModuleDependencyTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependencyDescriptorFactory;
+import org.gradle.util.HelperUtil;
+import static org.hamcrest.Matchers.equalTo;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.lib.legacy.ClassImposteriser;
+import static org.junit.Assert.assertThat;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultExternalModuleDependencyTest extends AbstractModuleDependencyTest {
+    private static final String TEST_GROUP = "org.gradle";
+    private static final String TEST_NAME = "gradle-core";
+    private static final String TEST_VERSION = "4.4-beta2";
+
+    protected DependencyDescriptorFactory dependencyDescriptorFactoryMock;
+
+    protected DefaultDependencyDescriptor expectedDependencyDescriptor;
+
+    private DefaultExternalModuleDependency moduleDependency;
+
+    public AbstractModuleDependency getDependency() {
+        return moduleDependency;
+    }
+
+    protected AbstractModuleDependency createDependency(String group, String name, String version) {
+        return new DefaultExternalModuleDependency(group, name, version);
+    }
+
+    protected AbstractModuleDependency createDependency(String group, String name, String version, String configuration) {
+        return new DefaultExternalModuleDependency(group, name, version, configuration);
+    }
+
+    @Before public void setUp() {
+        moduleDependency = new DefaultExternalModuleDependency(TEST_GROUP, TEST_NAME, TEST_VERSION);
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        dependencyDescriptorFactoryMock = context.mock(DependencyDescriptorFactory.class);
+        expectedDependencyDescriptor = HelperUtil.getTestDescriptor();
+    }
+
+    @Test
+    public void init() {
+        assertThat(moduleDependency.getGroup(), equalTo(TEST_GROUP));
+        assertThat(moduleDependency.getName(), equalTo(TEST_NAME));
+        assertThat(moduleDependency.getVersion(), equalTo(TEST_VERSION));
+        assertThat(moduleDependency.isChanging(), equalTo(false));
+        assertThat(moduleDependency.isForce(), equalTo(false));
+        assertThat(moduleDependency.isTransitive(), equalTo(true));
+        assertThat(moduleDependency.getVersion(), equalTo(TEST_VERSION));
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void initWithNullNameShouldThrowInvalidUserDataEx() {
+        new DefaultExternalModuleDependency(TEST_GROUP, null, TEST_VERSION);
+    }
+    
+    @Test
+    public void contentEqualsWithEqualDependencies() {
+        DefaultExternalModuleDependency dependency1 = createModuleDependency();
+        DefaultExternalModuleDependency dependency2 = createModuleDependency();
+        assertThat(dependency1.contentEquals(dependency2), equalTo(true));
+    }
+
+    @Test
+    public void contentEqualsWithNonEqualDependencies() {
+        DefaultExternalModuleDependency dependency1 = createModuleDependency();
+        DefaultExternalModuleDependency dependency2 = createModuleDependency();
+        dependency2.setTransitive(!dependency1.isTransitive());
+        assertThat(dependency1.contentEquals(dependency2), equalTo(false));
+    }
+
+    @Test
+    public void copy() {
+        DefaultExternalModuleDependency dependency = createModuleDependency();
+        DefaultExternalModuleDependency copiedDependency = dependency.copy();
+        assertDeepCopy(dependency, copiedDependency);
+    }
+
+    private DefaultExternalModuleDependency createModuleDependency() {
+        DefaultExternalModuleDependency moduleDependency = new DefaultExternalModuleDependency("group", "name", "version", "conf");
+        moduleDependency.addArtifact(new DefaultDependencyArtifact("name", "type", "ext", "classifier", "url"));
+        return moduleDependency;
+    }
+}
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependencyTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependencyTest.java
new file mode 100644
index 0000000..a6588b9
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultProjectDependencyTest.java
@@ -0,0 +1,296 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.*;
+import org.gradle.api.internal.artifacts.DependencyResolveContext;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.tasks.TaskContainer;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.util.WrapUtil;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+import static org.gradle.util.Matchers.*;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultProjectDependencyTest extends AbstractModuleDependencyTest {
+    private final ProjectDependenciesBuildInstruction instruction = new ProjectDependenciesBuildInstruction(WrapUtil.<String>toList());
+    private final Project dependencyProjectStub = context.mock(Project.class);
+    private final ConfigurationContainer projectConfigurationsStub = context.mock(ConfigurationContainer.class);
+    private final Configuration projectConfigurationStub = context.mock(Configuration.class);
+    private final TaskContainer dependencyProjectTaskContainerStub = context.mock(TaskContainer.class);
+    private final DefaultProjectDependency projectDependency = new DefaultProjectDependency(dependencyProjectStub, instruction);
+
+    protected AbstractModuleDependency getDependency() {
+        return projectDependency;
+    }
+
+    protected AbstractModuleDependency createDependency(String group, String name, String version) {
+        return createDependency(group, name, version, null);    
+    }
+
+    protected AbstractModuleDependency createDependency(String group, String name, String version, String configuration) {
+        ProjectInternal dependencyProject = context.mock(ProjectInternal.class);
+        DefaultProjectDependency projectDependency;
+        if (configuration != null) {
+            projectDependency = new DefaultProjectDependency(dependencyProject, configuration, instruction);
+        } else {
+            projectDependency = new DefaultProjectDependency(dependencyProject, instruction);
+        }
+        return projectDependency;
+    }
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations() {{
+            allowing(dependencyProjectStub).getConfigurations();
+            will(returnValue(projectConfigurationsStub));
+            allowing(projectConfigurationsStub).getByName("default");
+            will(returnValue(projectConfigurationStub));
+            allowing(dependencyProjectStub).getTasks();
+            will(returnValue(dependencyProjectTaskContainerStub));
+            allowing(dependencyProjectStub).getName();
+            will(returnValue("target-name"));
+            allowing(dependencyProjectStub).getGroup();
+            will(returnValue("target-group"));
+            allowing(dependencyProjectStub).getVersion();
+            will(returnValue("target-version"));
+        }});
+    }
+
+    @Test
+    public void init() {
+        assertTrue(projectDependency.isTransitive());
+        assertEquals("target-name", projectDependency.getName());
+        assertEquals("target-group", projectDependency.getGroup());
+        assertEquals("target-version", projectDependency.getVersion());
+    }
+
+    @Test
+    public void getConfiguration() {
+        context.checking(new Expectations() {{
+            allowing(projectConfigurationsStub).getByName("conf1");
+            will(returnValue(projectConfigurationStub));
+        }});
+
+        DefaultProjectDependency projectDependency = new DefaultProjectDependency(dependencyProjectStub, "conf1", instruction);
+        assertThat(projectDependency.getProjectConfiguration(), sameInstance(projectConfigurationStub));
+    }
+
+    @Test
+    public void resolveDelegatesToAllSelfResolvingDependenciesInTargetConfiguration() {
+        final DependencyResolveContext resolveContext = context.mock(DependencyResolveContext.class);
+        final Dependency projectSelfResolvingDependency = context.mock(Dependency.class);
+        final ProjectDependency transitiveProjectDependencyStub = context.mock(ProjectDependency.class);
+        context.checking(new Expectations() {{
+            allowing(projectConfigurationsStub).getByName("conf1");
+            will(returnValue(projectConfigurationStub));
+
+            allowing(projectConfigurationStub).getAllDependencies();
+            will(returnValue(toSet(projectSelfResolvingDependency, transitiveProjectDependencyStub)));
+
+            allowing(resolveContext).isTransitive();
+            will(returnValue(true));
+            
+            one(resolveContext).add(projectSelfResolvingDependency);
+            
+            one(resolveContext).add(transitiveProjectDependencyStub);
+        }});
+
+        DefaultProjectDependency projectDependency = new DefaultProjectDependency(dependencyProjectStub, "conf1",
+                instruction);
+        projectDependency.resolve(resolveContext);
+    }
+
+    @Test
+    public void resolveNotDelegatesToProjectDependenciesInTargetConfigurationIfConfigurationIsNonTransitive() {
+        final DependencyResolveContext resolveContext = context.mock(DependencyResolveContext.class);
+        final Dependency projectSelfResolvingDependency = context.mock(Dependency.class);
+        final ProjectDependency transitiveProjectDependencyStub = context.mock(ProjectDependency.class);
+        context.checking(new Expectations() {{
+            allowing(projectConfigurationsStub).getByName("conf1");
+            will(returnValue(projectConfigurationStub));
+
+            allowing(projectConfigurationStub).getAllDependencies();
+            will(returnValue(toSet(projectSelfResolvingDependency, transitiveProjectDependencyStub)));
+
+            allowing(resolveContext).isTransitive();
+            will(returnValue(false));
+
+            one(resolveContext).add(projectSelfResolvingDependency);
+        }});
+        DefaultProjectDependency projectDependency = new DefaultProjectDependency(dependencyProjectStub, "conf1",
+                instruction);
+        projectDependency.resolve(resolveContext);
+    }
+    
+    @Test
+    public void resolveNotDelegatesToTransitiveProjectDependenciesIfProjectDependencyIsNonTransitive() {
+        final DependencyResolveContext resolveContext = context.mock(DependencyResolveContext.class);
+        final SelfResolvingDependency projectSelfResolvingDependency = context.mock(SelfResolvingDependency.class);
+        final ProjectDependency transitiveProjectDependencyStub = context.mock(ProjectDependency.class);
+        context.checking(new Expectations() {{
+            allowing(projectConfigurationsStub).getByName("conf1");
+            will(returnValue(projectConfigurationStub));
+
+            allowing(projectConfigurationStub).getAllDependencies();
+            will(returnValue(toSet(projectSelfResolvingDependency, transitiveProjectDependencyStub)));
+
+            one(resolveContext).add(projectSelfResolvingDependency);
+        }});
+        DefaultProjectDependency projectDependency = new DefaultProjectDependency(dependencyProjectStub, "conf1", instruction);
+        projectDependency.setTransitive(false);
+        projectDependency.resolve(resolveContext);
+    }
+
+    private Task taskInTargetProject(final String name) {
+        final Task task = context.mock(Task.class, name);
+        context.checking(new Expectations(){{
+            allowing(dependencyProjectTaskContainerStub).getByName(name);
+            will(returnValue(task));
+        }});
+        return task;
+    }
+
+    @Test
+    public void dependsOnTargetConfigurationAndArtifactsOfTargetConfiguration() {
+        Task a = taskInTargetProject("a");
+        Task b = taskInTargetProject("b");
+        Task c = taskInTargetProject("c");
+        expectTargetConfigurationHasDependencies(a, b);
+        expectTargetConfigurationHasArtifacts(c);
+
+        assertThat(projectDependency.getBuildDependencies().getDependencies(null), equalTo((Set) toSet(a, b, c)));
+    }
+
+    private void expectTargetConfigurationHasDependencies(final Task... tasks) {
+        context.checking(new Expectations(){{
+            TaskDependency dependencyStub = context.mock(TaskDependency.class, "dependencies");
+
+            allowing(projectConfigurationStub).getBuildDependencies();
+            will(returnValue(dependencyStub));
+
+            allowing(dependencyStub).getDependencies(null);
+            will(returnValue(toSet(tasks)));
+        }});
+    }
+
+    private void expectTargetConfigurationHasArtifacts(final Task... tasks) {
+        context.checking(new Expectations(){{
+            TaskDependency dependencyStub = context.mock(TaskDependency.class, "artifacts");
+
+            allowing(projectConfigurationStub).getBuildArtifacts();
+            will(returnValue(dependencyStub));
+
+            allowing(dependencyStub).getDependencies(null);
+            will(returnValue(toSet(tasks)));
+        }});
+    }
+
+    private void expectTargetConfigurationHasNoDependencies() {
+        context.checking(new Expectations(){{
+            TaskDependency dependencyStub = context.mock(TaskDependency.class);
+
+            allowing(projectConfigurationStub).getBuildDependencies();
+            will(returnValue(dependencyStub));
+
+            allowing(projectConfigurationStub).getBuildArtifacts();
+            will(returnValue(dependencyStub));
+
+            allowing(dependencyStub).getDependencies(null);
+            will(returnValue(toSet()));
+        }});
+    }
+
+    @Test
+    public void doesNotDependOnAnythingWhenProjectRebuildIsDisabled() {
+        DefaultProjectDependency dependency = new DefaultProjectDependency(dependencyProjectStub,
+                new ProjectDependenciesBuildInstruction(null));
+        assertThat(dependency.getBuildDependencies().getDependencies(null), isEmpty());
+    }
+
+    @Test
+    public void dependsOnAdditionalTasksFromTargetProject() {
+        expectTargetConfigurationHasNoDependencies();
+
+        Task a = taskInTargetProject("a");
+        Task b = taskInTargetProject("b");
+
+        DefaultProjectDependency dependency = new DefaultProjectDependency(dependencyProjectStub,
+                new ProjectDependenciesBuildInstruction(toList("a", "b")));
+        assertThat(dependency.getBuildDependencies().getDependencies(null), equalTo((Set) toSet(a, b)));
+    }
+
+    @Test
+    public void contentEqualsWithEqualDependencies() {
+        ProjectDependency dependency1 = createProjectDependency();
+        ProjectDependency dependency2 = createProjectDependency();
+        assertThat(dependency1.contentEquals(dependency2), equalTo(true));
+    }
+
+    @Test
+    public void contentEqualsWithNonEqualDependencies() {
+        ProjectDependency dependency1 = createProjectDependency();
+        ProjectDependency dependency2 = createProjectDependency();
+        dependency2.setTransitive(!dependency1.isTransitive());
+        assertThat(dependency1.contentEquals(dependency2), equalTo(false));
+    }
+
+    @Test
+    public void copy() {
+        ProjectDependency dependency = createProjectDependency();
+        ProjectDependency copiedDependency = dependency.copy();
+        assertDeepCopy(dependency, copiedDependency);
+        assertThat(copiedDependency.getDependencyProject(), sameInstance(dependency.getDependencyProject()));
+    }
+
+    private ProjectDependency createProjectDependency() {
+        ProjectDependency projectDependency = new DefaultProjectDependency(dependencyProjectStub, "conf", instruction);
+        projectDependency.addArtifact(new DefaultDependencyArtifact("name", "type", "ext", "classifier", "url"));
+        return projectDependency;
+    }
+
+    @Test
+    @Override
+    public void equality() {
+        assertThat(new DefaultProjectDependency(dependencyProjectStub, instruction), strictlyEqual(new DefaultProjectDependency(
+                dependencyProjectStub, instruction)));
+        assertThat(new DefaultProjectDependency(dependencyProjectStub, "conf1", instruction), strictlyEqual(new DefaultProjectDependency(
+                dependencyProjectStub, "conf1", instruction)));
+        assertThat(new DefaultProjectDependency(dependencyProjectStub, "conf1", instruction), not(equalTo(new DefaultProjectDependency(
+                dependencyProjectStub, "conf2", instruction))));
+        Project otherProject = context.mock(Project.class, "otherProject");
+        assertThat(new DefaultProjectDependency(dependencyProjectStub, instruction), not(equalTo(new DefaultProjectDependency(
+                otherProject, instruction))));
+        assertThat(new DefaultProjectDependency(dependencyProjectStub, instruction), not(equalTo(new DefaultProjectDependency(
+                dependencyProjectStub, new ProjectDependenciesBuildInstruction(null)))));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultSelfResolvingDependencyTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultSelfResolvingDependencyTest.java
new file mode 100644
index 0000000..80f9b66
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dependencies/DefaultSelfResolvingDependencyTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.SelfResolvingDependency;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.artifacts.DependencyResolveContext;
+import org.gradle.api.tasks.TaskDependency;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+ at RunWith(JMock.class)
+public class DefaultSelfResolvingDependencyTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final FileCollection source = context.mock(FileCollection.class);
+    private final DefaultSelfResolvingDependency dependency = new DefaultSelfResolvingDependency(source);
+
+    @Test
+    public void defaultValues() {
+        assertThat(dependency.getGroup(), nullValue());
+        assertThat(dependency.getName(), equalTo("unspecified"));
+        assertThat(dependency.getVersion(), nullValue());
+    }
+
+    @Test
+    public void resolvesToTheSourceFileCollection() {
+        final DependencyResolveContext resolveContext = context.mock(DependencyResolveContext.class);
+
+        context.checking(new Expectations() {{
+            one(resolveContext).add(source);
+        }});
+
+        dependency.resolve(resolveContext);
+    }
+    
+    @Test
+    public void usesSourceFileCollectionToResolveFiles() {
+        final File file = new File("file");
+
+        context.checking(new Expectations(){{
+            allowing(source).getFiles();
+            will(returnValue(toSet(file)));
+        }});
+
+        assertThat(dependency.resolve(), equalTo(toLinkedSet(file)));
+        assertThat(dependency.resolve(true), equalTo(toLinkedSet(file)));
+        assertThat(dependency.resolve(false), equalTo(toLinkedSet(file)));
+    }
+
+    @Test
+    public void createsCopy() {
+        Dependency copy = dependency.copy();
+        assertThat(copy, instanceOf(SelfResolvingDependency.class));
+        assertTrue(copy.contentEquals(dependency));
+        assertTrue(dependency.contentEquals(copy));
+    }
+
+    @Test
+    public void contentsAreEqualWhenFileSetsAreEqual() {
+        SelfResolvingDependency equalDependency = new DefaultSelfResolvingDependency(source);
+        SelfResolvingDependency differentSource = new DefaultSelfResolvingDependency(context.mock(FileCollection.class, "other"));
+        Dependency differentType = context.mock(Dependency.class);
+
+        assertTrue(dependency.contentEquals(dependency));
+        assertTrue(dependency.contentEquals(equalDependency));
+        assertFalse(dependency.contentEquals(differentSource));
+        assertFalse(dependency.contentEquals(differentType));
+    }
+
+    @Test
+    public void usesSourceFileCollectionToDetermineBuildDependencies() {
+        final TaskDependency taskDependency = context.mock(TaskDependency.class);
+
+        context.checking(new Expectations() {{
+            allowing(source).getBuildDependencies();
+            will(returnValue(taskDependency));
+        }});
+
+        assertThat(dependency.getBuildDependencies(), sameInstance(taskDependency));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandlerTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandlerTest.groovy
new file mode 100644
index 0000000..c081631
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultArtifactHandlerTest.groovy
@@ -0,0 +1,97 @@
+/*
+ * 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.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.util.JUnit4GroovyMockery
+import spock.lang.Specification
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
+
+/**
+ * @author Hans Dockter
+ */
+class DefaultArtifactHandlerTest extends Specification {
+
+    private static final String TEST_CONF_NAME = "someConf"
+
+    private JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+
+    private ConfigurationContainer configurationContainerStub = Mock()
+    private PublishArtifactFactory artifactFactoryStub = Mock()
+    private Configuration configurationMock = Mock()
+
+    private DefaultArtifactHandler artifactHandler = new DefaultArtifactHandler(configurationContainerStub, artifactFactoryStub)
+
+    void setup() {
+        configurationContainerStub.findByName(TEST_CONF_NAME) >> configurationMock
+    }
+
+    void pushOneDependency() {
+        String someNotation = "someNotation"
+        PublishArtifact artifactDummy = Mock()
+
+        when:
+        artifactFactoryStub.createArtifact(someNotation) >> artifactDummy
+        artifactHandler."$TEST_CONF_NAME"(someNotation)
+
+        then:
+        1 * configurationMock.addArtifact(artifactDummy)
+    }
+
+    void pushOneDependencyWithClosure() {
+        String someNotation = "someNotation"
+        DefaultPublishArtifact artifact = new DefaultPublishArtifact("name", "ext", "jar", "classifier", null, new File(""))
+
+        when:
+        artifactFactoryStub.createArtifact(someNotation) >> artifact
+        artifactHandler."$TEST_CONF_NAME"(someNotation) { type = 'source' }
+
+        then:
+        artifact.type == 'source'
+        1 * configurationMock.addArtifact(artifact)
+    }
+
+    void pushMultipleDependencies() {
+        String someNotation1 = "someNotation"
+        String someNotation2 = "someNotation2"
+        PublishArtifact artifactDummy1 = Mock()
+        PublishArtifact artifactDummy2 = Mock()
+
+        when:
+        artifactFactoryStub.createArtifact(someNotation1) >> artifactDummy1
+        artifactFactoryStub.createArtifact(someNotation2) >> artifactDummy2
+        artifactHandler."$TEST_CONF_NAME"(someNotation1, someNotation2)
+
+        then:
+        1 * configurationMock.addArtifact(artifactDummy1)
+        1 * configurationMock.addArtifact(artifactDummy2)
+
+    }
+
+    void pushToUnknownConfiguration() {
+        String unknownConf = TEST_CONF_NAME + "delta"
+
+        when:
+        artifactHandler."$unknownConf"("someNotation")
+        configurationContainerStub.findByName(unknownConf) >> null
+
+        then:
+        thrown(MissingMethodException)
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultConfigurationHandlerTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultConfigurationHandlerTest.groovy
new file mode 100644
index 0000000..378b772
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultConfigurationHandlerTest.groovy
@@ -0,0 +1,103 @@
+/*
+ * 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.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.UnknownConfigurationException
+import org.gradle.api.internal.artifacts.IvyService
+import org.gradle.util.JUnit4GroovyMockery
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer
+import org.gradle.api.internal.ClassGenerator
+import org.gradle.api.internal.AsmBackedClassGenerator
+import org.gradle.api.internal.DomainObjectContext
+
+/**
+ * @author Hans Dockter
+ */
+
+class DefaultConfigurationHandlerTest {
+    private JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+
+    private IvyService ivyService = context.mock(IvyService)
+    private DomainObjectContext domainObjectContext = context.mock(DomainObjectContext.class)
+
+    private ClassGenerator classGenerator = new AsmBackedClassGenerator()
+    private DefaultConfigurationContainer configurationHandler = classGenerator.newInstance(DefaultConfigurationContainer.class, ivyService, classGenerator, { name -> name } as DomainObjectContext)
+
+    @Test
+    void addsNewConfigurationWhenConfiguringSelf() {
+        configurationHandler.configure {
+            newConf
+        }
+        assertThat(configurationHandler.findByName('newConf'), notNullValue())
+        assertThat(configurationHandler.newConf, notNullValue())
+    }
+
+    @Test(expected = UnknownConfigurationException)
+    void doesNotAddNewConfigurationWhenNotConfiguringSelf() {
+        configurationHandler.getByName('unknown')
+    }
+
+    @Test
+    void makesExistingConfigurationAvailableAsProperty() {
+        Configuration configuration = configurationHandler.add('newConf')
+        assertThat(configuration, is(not(null)))
+        assertThat(configurationHandler.getByName("newConf"), sameInstance(configuration))
+        assertThat(configurationHandler.newConf, sameInstance(configuration))
+    }
+
+    @Test
+    void addsNewConfigurationWithClosureWhenConfiguringSelf() {
+        String someDesc = 'desc1'
+        configurationHandler.configure {
+            newConf {
+                description = someDesc
+            }
+        }
+        assertThat(configurationHandler.newConf.getDescription(), equalTo(someDesc))
+    }
+
+    @Test
+    void makesExistingConfigurationAvailableAsConfigureMethod() {
+        String someDesc = 'desc1'
+        configurationHandler.add('newConf')
+        Configuration configuration = configurationHandler.newConf {
+            description = someDesc
+        }
+        assertThat(configuration.getDescription(), equalTo(someDesc))
+    }
+
+    @Test
+    void makesExistingConfigurationAvailableAsConfigureMethodWhenConfiguringSelf() {
+        String someDesc = 'desc1'
+        Configuration configuration = configurationHandler.add('newConf')
+        configurationHandler.configure {
+            newConf {
+                description = someDesc
+            }
+        }
+        assertThat(configuration.getDescription(), equalTo(someDesc))
+    }
+
+    @Test(expected = MissingMethodException)
+    void newConfigurationWithNonClosureParametersShouldThrowMissingMethodEx() {
+        configurationHandler.newConf('a', 'b')
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactoryTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactoryTest.groovy
new file mode 100644
index 0000000..0e7c459
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultPublishArtifactFactoryTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * 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 java.awt.Point
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact
+import org.gradle.api.tasks.bundling.AbstractArchiveTask
+import org.hamcrest.Matchers
+import spock.lang.Specification
+import static org.junit.Assert.assertThat
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultPublishArtifactFactoryTest extends Specification {
+    private DefaultPublishArtifactFactory publishArtifactFactory = new DefaultPublishArtifactFactory();
+
+    def createArtifact() {
+        AbstractArchiveTask archiveTaskMock = Mock()
+        archiveTaskMock.getArchivePath() >> new File("")
+
+        when:
+        ArchivePublishArtifact publishArtifact = (ArchivePublishArtifact) publishArtifactFactory.createArtifact(archiveTaskMock);
+
+        then:
+        assertThat(publishArtifact.getArchiveTask(), Matchers.sameInstance(archiveTaskMock));
+    }
+
+    public void createArtifactWithNullNotationShouldThrowInvalidUserDataEx() {
+        when:
+        publishArtifactFactory.createArtifact(null);
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    public void createArtifactWithUnknownNotationShouldThrowInvalidUserDataEx() {
+        when:
+        publishArtifactFactory.createArtifact(new Point(1,2));
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerFactoryTest.java
new file mode 100644
index 0000000..bf6b41b
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerFactoryTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.gradle.api.internal.artifacts.ivyservice.ResolverFactory;
+import org.gradle.api.internal.plugins.DefaultConvention;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.IConventionAware;
+import org.gradle.api.internal.ConventionMapping;
+import org.gradle.api.plugins.Convention;
+import static org.hamcrest.Matchers.*;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.Expectations;
+import org.jmock.lib.legacy.ClassImposteriser;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultRepositoryHandlerFactoryTest {
+    private JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+    private ResolverFactory repositoryFactoryMock = context.mock(ResolverFactory.class);
+    private ClassGenerator classGeneratorMock = context.mock(ClassGenerator.class);
+    private Convention convention = new DefaultConvention();
+
+    @Test
+    public void createsARepositoryHandlerAndSetsConvention() {
+        final ConventionAwareRepositoryHandler repositoryHandlerMock = context.mock(ConventionAwareRepositoryHandler.class);
+        final ConventionMapping conventionMappingMock = context.mock(ConventionMapping.class);
+
+        context.checking(new Expectations() {{
+            one(classGeneratorMock).newInstance(DefaultRepositoryHandler.class, repositoryFactoryMock, classGeneratorMock);
+            will(returnValue(repositoryHandlerMock));
+            allowing(repositoryHandlerMock).getConventionMapping();
+            will(returnValue(conventionMappingMock));
+            one(conventionMappingMock).setConvention(convention);
+        }});
+
+        DefaultRepositoryHandlerFactory repositoryHandlerFactory = new DefaultRepositoryHandlerFactory(
+                repositoryFactoryMock, classGeneratorMock);
+        DefaultRepositoryHandler repositoryHandler = repositoryHandlerFactory.createRepositoryHandler(convention);
+        assertThat(repositoryHandler, sameInstance((Object) repositoryHandlerMock));
+    }
+
+    public static abstract class ConventionAwareRepositoryHandler extends DefaultRepositoryHandler
+            implements IConventionAware {
+        protected ConventionAwareRepositoryHandler(ResolverFactory resolverFactory, ClassGenerator classGenerator) {
+            super(resolverFactory, classGenerator);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerTest.groovy
new file mode 100644
index 0000000..b0a8b0d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandlerTest.groovy
@@ -0,0 +1,264 @@
+/*
+ * 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.apache.ivy.plugins.resolver.ResolverSettings
+import org.apache.maven.artifact.ant.RemoteRepository
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.artifacts.ResolverContainer
+import org.gradle.api.artifacts.dsl.RepositoryHandler
+import org.gradle.api.artifacts.maven.GroovyMavenDeployer
+import org.gradle.api.artifacts.maven.MavenResolver
+import org.gradle.api.internal.artifacts.DefaultResolverContainerTest
+import org.gradle.util.HashUtil
+import org.junit.Test
+import static org.junit.Assert.*
+import org.gradle.api.internal.AsmBackedClassGenerator
+
+/**
+ * @author Hans Dockter
+ */
+class DefaultRepositoryHandlerTest extends DefaultResolverContainerTest {
+    static final String TEST_REPO_URL = 'http://www.gradle.org'
+
+    private DefaultRepositoryHandler repositoryHandler
+
+    public ResolverContainer createResolverContainer() {
+        AsmBackedClassGenerator classGenerator = new AsmBackedClassGenerator()
+        repositoryHandler = classGenerator.newInstance(DefaultRepositoryHandler.class, resolverFactoryMock, classGenerator);
+        return repositoryHandler;
+    }
+
+    @Test public void testFlatDirWithNameAndDirs() {
+        String resolverName = 'libs'
+        prepareFlatDirResolverCreation(resolverName, createFlatDirTestDirs())
+        prepareResolverFactoryToTakeAndReturnExpectedResolver()
+        assert repositoryHandler.flatDir([name: resolverName] + [dirs: createFlatDirTestDirsArgs()]).is(expectedResolver)
+        assertEquals([expectedResolver], repositoryHandler.getResolvers())
+    }
+
+    @Test public void testFlatDirWithNameAndSingleDir() {
+        String resolverName = 'libs'
+        prepareFlatDirResolverCreation(resolverName, ['a' as File] as File[])
+        prepareResolverFactoryToTakeAndReturnExpectedResolver()
+        assert repositoryHandler.flatDir([name: resolverName] + [dirs: 'a']).is(expectedResolver)
+        assertEquals([expectedResolver], repositoryHandler.getResolvers())
+    }
+
+    @Test public void testFlatDirWithoutNameAndWithDirs() {
+        Object[] expectedDirs = createFlatDirTestDirs()
+        String expectedName = HashUtil.createHash(expectedDirs.join(''))
+        prepareFlatDirResolverCreation(expectedName, expectedDirs)
+        prepareResolverFactoryToTakeAndReturnExpectedResolver()
+        assert repositoryHandler.flatDir([dirs: createFlatDirTestDirsArgs()]).is(expectedResolver)
+        assertEquals([expectedResolver], repositoryHandler.getResolvers())
+    }
+
+    @Test (expected = InvalidUserDataException)
+    public void testFlatDirWithMissingDirs() {
+        repositoryHandler.flatDir([name: 'someName'])
+    }
+
+    @Test
+    public void testMavenCentralWithNoArgs() {
+        prepareCreateMavenRepo(ResolverContainer.DEFAULT_MAVEN_CENTRAL_REPO_NAME, ResolverContainer.MAVEN_CENTRAL_URL)
+        prepareResolverFactoryToTakeAndReturnExpectedResolver()
+        assert repositoryHandler.mavenCentral().is(expectedResolver)
+        assertEquals([expectedResolver], repositoryHandler.resolvers)
+    }
+
+    @Test
+    public void testMavenCentralWithSingleUrl() {
+        String testUrl2 = 'http://www.gradle2.org'
+        prepareCreateMavenRepo(ResolverContainer.DEFAULT_MAVEN_CENTRAL_REPO_NAME, ResolverContainer.MAVEN_CENTRAL_URL, testUrl2)
+        prepareResolverFactoryToTakeAndReturnExpectedResolver()
+        assert repositoryHandler.mavenCentral(urls: testUrl2).is(expectedResolver)
+        assertEquals([expectedResolver], repositoryHandler.resolvers)
+    }
+
+    @Test
+    public void testMavenCentralWithNameAndUrls() {
+        String testUrl1 = 'http://www.gradle1.org'
+        String testUrl2 = 'http://www.gradle2.org'
+        String name = 'customName'
+        prepareCreateMavenRepo(name, ResolverContainer.MAVEN_CENTRAL_URL, testUrl1, testUrl2)
+        prepareResolverFactoryToTakeAndReturnExpectedResolver()
+        assert repositoryHandler.mavenCentral(name: name, urls: [testUrl1, testUrl2]).is(expectedResolver)
+        assertEquals([expectedResolver], repositoryHandler.resolvers)
+    }
+
+    @Test(expected = InvalidUserDataException)
+    public void testMavenRepoWithMissingUrls() {
+        repositoryHandler.mavenRepo([name: 'someName'])
+    }
+
+    @Test
+    public void testMavenRepoWithNameAndUrls() {
+        String testUrl2 = 'http://www.gradle2.org'
+        String repoRoot = 'http://www.reporoot.org'
+        String repoName = 'mavenRepoName'
+        prepareCreateMavenRepo(repoName, repoRoot, testUrl2)
+        prepareResolverFactoryToTakeAndReturnExpectedResolver()
+        assert repositoryHandler.mavenRepo([name: repoName, urls: [repoRoot, testUrl2]]).is(expectedResolver)
+        assertEquals([expectedResolver], repositoryHandler.resolvers)
+    }
+
+    @Test
+    public void testMavenRepoWithNameAndRootUrlOnly() {
+        String repoRoot = 'http://www.reporoot.org'
+        String repoName = 'mavenRepoName'
+        prepareCreateMavenRepo(repoName, repoRoot)
+        prepareResolverFactoryToTakeAndReturnExpectedResolver()
+        assert repositoryHandler.mavenRepo([name: repoName, urls: repoRoot]).is(expectedResolver)
+        assertEquals([expectedResolver], repositoryHandler.resolvers)
+    }
+
+    @Test
+    public void testMavenRepoWithoutName() {
+        String testUrl2 = 'http://www.gradle2.org'
+        String repoRoot = 'http://www.reporoot.org'
+        prepareCreateMavenRepo(repoRoot, repoRoot, testUrl2)
+        prepareResolverFactoryToTakeAndReturnExpectedResolver()
+        assert repositoryHandler.mavenRepo([urls: [repoRoot, testUrl2]]).is(expectedResolver)
+        assertEquals([expectedResolver], repositoryHandler.resolvers)
+    }
+
+    private prepareCreateMavenRepo(String name, String mavenUrl, String[] jarUrls) {
+        context.checking {
+            one(resolverFactoryMock).createMavenRepoResolver(name, mavenUrl, jarUrls);
+            will(returnValue(expectedResolver))
+        }
+    }
+
+    private def prepareFlatDirResolverCreation(String expectedName, File[] expectedDirs) {
+        context.checking {
+          one(resolverFactoryMock).createFlatDirResolver(expectedName, expectedDirs); will(returnValue(expectedResolver))
+        }
+    }
+
+    private List createFlatDirTestDirsArgs() {
+        return ['a', 'b' as File]
+    }
+
+    private File[] createFlatDirTestDirs() {
+        return ['a' as File, 'b' as File] as File[]
+    }
+
+    private def prepareResolverFactoryToTakeAndReturnExpectedResolver() {
+      context.checking {
+        one(resolverFactoryMock).createResolver(expectedResolver); will(returnValue(expectedResolver))
+      }
+    }
+
+    @Test
+    public void mavenDeployerWithoutName() {
+        GroovyMavenDeployer expectedResolver = prepareMavenDeployerTests()
+        String expectedName = RepositoryHandler.DEFAULT_MAVEN_DEPLOYER_NAME + "-" +
+                System.identityHashCode(expectedResolver)
+        prepareName(expectedResolver, expectedName)
+        assertSame(expectedResolver, repositoryHandler.mavenDeployer());
+    }
+
+    @Test
+    public void mavenDeployerWithName() {
+        GroovyMavenDeployer expectedResolver = prepareMavenDeployerTests()
+        String expectedName = "someName"
+        prepareName(expectedResolver, expectedName)
+        assertSame(expectedResolver, repositoryHandler.mavenDeployer(name: expectedName));
+    }
+
+    @Test
+    public void mavenDeployerWithNameAndClosure() {
+        GroovyMavenDeployer expectedResolver = prepareMavenDeployerTests()
+        String expectedName = RepositoryHandler.DEFAULT_MAVEN_DEPLOYER_NAME + "-" +
+                System.identityHashCode(expectedResolver)
+        prepareName(expectedResolver, expectedName) 
+        RemoteRepository repositoryDummy = new RemoteRepository()
+        context.checking {
+            one(expectedResolver).setRepository(repositoryDummy)
+        }
+        assertSame(expectedResolver, repositoryHandler.mavenDeployer() {
+            setRepository(repositoryDummy)
+        });
+    }
+
+    @Test
+    public void mavenDeployerWithoutArgsAndWithClosure() {
+        GroovyMavenDeployer expectedResolver = prepareMavenDeployerTests()
+        String expectedName = "someName"
+        prepareName(expectedResolver, expectedName) 
+        RemoteRepository repositoryDummy = new RemoteRepository()
+        context.checking {
+            one(expectedResolver).setRepository(repositoryDummy)
+        }
+        assertSame(expectedResolver, repositoryHandler.mavenDeployer(name: expectedName) {
+            setRepository(repositoryDummy)
+        });
+    }
+
+    @Test
+    public void mavenInstallerWithoutName() {
+        MavenResolver expectedResolver = prepareMavenInstallerTests()
+        String expectedName = RepositoryHandler.DEFAULT_MAVEN_INSTALLER_NAME + "-" +
+                System.identityHashCode(expectedResolver)
+        prepareName(expectedResolver, expectedName)
+        assertSame(expectedResolver, repositoryHandler.mavenInstaller());
+    }
+
+    @Test
+    public void mavenInstallerWithName() {
+        MavenResolver expectedResolver = prepareMavenInstallerTests()
+        String expectedName = "someName"
+        prepareName(expectedResolver, expectedName)
+        assertSame(expectedResolver, repositoryHandler.mavenInstaller(name: expectedName));
+    }
+
+    @Test
+    public void mavenInstallerWithNameAndClosure() {
+        MavenResolver expectedResolver = prepareMavenInstallerTests()
+        String expectedName = RepositoryHandler.DEFAULT_MAVEN_INSTALLER_NAME + "-" +
+                System.identityHashCode(expectedResolver)
+        prepareName(expectedResolver, expectedName)
+        ResolverSettings resolverSettings = [:] as ResolverSettings
+        context.checking {
+            one(expectedResolver).setSettings(resolverSettings)
+        }
+        assertSame(expectedResolver, repositoryHandler.mavenInstaller() {
+            setSettings(resolverSettings)
+        });
+    }
+
+    @Test
+    public void mavenInstallerWithoutArgsAndWithClosure() {
+        MavenResolver expectedResolver = prepareMavenInstallerTests()
+        String expectedName = "someName"
+        prepareName(expectedResolver, expectedName)
+        ResolverSettings resolverSettings = [:] as ResolverSettings
+        context.checking {
+            one(expectedResolver).setSettings(resolverSettings)
+        }
+        assertSame(expectedResolver, repositoryHandler.mavenInstaller(name: expectedName) {
+            setSettings(resolverSettings)
+        });
+    }
+
+    private void prepareName(mavenResolver, String expectedName) {
+        context.checking {
+            one(mavenResolver).setName(expectedName)
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/AbstractModuleFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/AbstractModuleFactoryTest.java
new file mode 100644
index 0000000..30f7c24
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/AbstractModuleFactoryTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.artifacts.DependencyArtifact;
+import org.gradle.api.artifacts.ExternalDependency;
+import org.gradle.api.IllegalDependencyNotation;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.GUtil;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.hamcrest.Matchers.nullValue;
+import static org.hamcrest.Matchers.*;
+
+import java.awt.*;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractModuleFactoryTest {
+    protected static final String TEST_GROUP = "org.gradle";
+    protected static final String TEST_NAME = "gradle-core";
+    protected static final String TEST_VERSION = "4.4-beta2";
+    protected static final String TEST_CONFIGURATION = "testConf";
+    protected static final String TEST_TYPE = "mytype";
+    protected static final String TEST_CLASSIFIER = "jdk-1.4";
+    protected static final String TEST_MODULE_DESCRIPTOR = String.format("%s:%s:%s", TEST_GROUP, TEST_NAME, TEST_VERSION);
+    protected static final String TEST_MODULE_DESCRIPTOR_WITH_CLASSIFIER = TEST_MODULE_DESCRIPTOR + ":" + TEST_CLASSIFIER;
+
+    protected abstract ExternalDependency createDependency(Object notation);
+
+    @Test(expected = IllegalDependencyNotation.class)
+    public void testStringNotationWithOneElementStringShouldThrowInvalidUserDataEx() {
+        createDependency("singlestring");
+    }
+
+    @Test(expected = IllegalDependencyNotation.class)
+    public void testUnknownTypeShouldThrowInvalidUserDataEx() {
+        createDependency(new Point(3, 4));
+    }
+
+    @Test
+    public void testStringNotationWithGString() {
+        checkCommonModuleProperties(createDependency(HelperUtil.createScript(
+                 "descriptor = '" + TEST_MODULE_DESCRIPTOR + "'; \"$descriptor\"").run()));
+    }
+
+    @Test
+    public void testStringNotationWithModule() {
+        ExternalDependency moduleDependency = createDependency(TEST_MODULE_DESCRIPTOR);
+        checkCommonModuleProperties(moduleDependency);
+        assertTrue(moduleDependency.isTransitive());
+    }
+
+    @Test
+    public void testStringNotationWithNoGroup() {
+        ExternalDependency moduleDependency = createDependency(
+                String.format(":%s:%s", TEST_NAME, TEST_VERSION));
+        checkName(moduleDependency);
+        checkVersion(moduleDependency);
+        assertThat(moduleDependency.getGroup(), nullValue());
+        assertTrue(moduleDependency.isTransitive());
+    }
+
+    @Test
+    public void testStringNotationWithNoVersion() {
+        ExternalDependency moduleDependency = createDependency(
+                String.format("%s:%s", TEST_GROUP, TEST_NAME));
+        checkGroup(moduleDependency);
+        checkName(moduleDependency);
+        assertThat(moduleDependency.getVersion(), nullValue());
+        assertTrue(moduleDependency.isTransitive());
+    }
+
+    @Test
+    public void testStringNotationWithNoVersionAndNoGroup() {
+        ExternalDependency moduleDependency = createDependency(
+                String.format(":%s", TEST_NAME));
+        checkName(moduleDependency);
+        assertThat(moduleDependency.getGroup(), nullValue());
+        assertThat(moduleDependency.getVersion(), nullValue());
+        assertTrue(moduleDependency.isTransitive());
+    }
+
+    @Test
+    public void testStringNotationWithModuleAndClassifier() {
+        ExternalDependency moduleDependency = createDependency(TEST_MODULE_DESCRIPTOR_WITH_CLASSIFIER);
+        assertCorrectnesForModuleWithClassifier(moduleDependency);
+    }
+
+    @Test
+    public void testMapNotationWithModuleAndClassifier() {
+        ExternalDependency moduleDependency = createDependency(
+                GUtil.map("group", TEST_GROUP, "name", TEST_NAME, "version", TEST_VERSION, "classifier", TEST_CLASSIFIER));
+        assertCorrectnesForModuleWithClassifier(moduleDependency);
+    }
+
+    private void assertCorrectnesForModuleWithClassifier(ExternalDependency moduleDependency) {
+        checkCommonModuleProperties(moduleDependency);
+        assertTrue(moduleDependency.isTransitive());
+        assertEquals(1, moduleDependency.getArtifacts().size());
+        DependencyArtifact artifact = moduleDependency.getArtifacts().iterator().next();
+        assertEquals(TEST_NAME, artifact.getName());
+        assertEquals(DependencyArtifact.DEFAULT_TYPE, artifact.getType());
+        assertEquals(DependencyArtifact.DEFAULT_TYPE, artifact.getExtension());
+        assertEquals(TEST_CLASSIFIER, artifact.getClassifier());
+    }
+
+    @Test
+    public void mapNotation() {
+        ExternalDependency moduleDependency = createDependency(GUtil.map("group", TEST_GROUP, "name", TEST_NAME, "version", TEST_VERSION));
+        checkCommonModuleProperties(moduleDependency);
+        assertTrue(moduleDependency.isTransitive());
+    }
+
+    @Test
+    public void mapNotationWithConfiguration() {
+        ExternalDependency moduleDependency = createDependency(GUtil.map("group", TEST_GROUP, "name", TEST_NAME, "version", TEST_VERSION,
+                "configuration", TEST_CONFIGURATION));
+        checkCommonModuleProperties(moduleDependency);
+        assertTrue(moduleDependency.isTransitive());
+        assertThat(moduleDependency.getConfiguration(), equalTo(TEST_CONFIGURATION));
+    }
+
+    @Test
+    public void mapNotationWithProperty() {
+        ExternalDependency moduleDependency = createDependency(
+                GUtil.map("group", TEST_GROUP, "name", TEST_NAME, "version", TEST_VERSION, "transitive", false));
+        checkCommonModuleProperties(moduleDependency);
+        assertThat(moduleDependency.isTransitive(), equalTo(false));
+    }
+
+    protected void checkCommonModuleProperties(ExternalDependency moduleDependency) {
+        checkGroup(moduleDependency);
+        checkName(moduleDependency);
+        checkVersion(moduleDependency);
+        checkOtherProperties(moduleDependency);
+    }
+
+    protected void checkOtherProperties(ExternalDependency moduleDependency) {
+        assertFalse(moduleDependency.isForce());
+    }
+
+    private void checkVersion(ExternalDependency moduleDependency) {
+        assertEquals(TEST_VERSION, moduleDependency.getVersion());
+    }
+
+    private void checkName(ExternalDependency moduleDependency) {
+        assertEquals(TEST_NAME, moduleDependency.getName());
+    }
+
+    private void checkGroup(ExternalDependency moduleDependency) {
+        assertEquals(TEST_GROUP, moduleDependency.getGroup());
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ClassPathDependencyFactoryTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ClassPathDependencyFactoryTest.groovy
new file mode 100644
index 0000000..840ce1d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ClassPathDependencyFactoryTest.groovy
@@ -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.api.internal.artifacts.dsl.dependencies
+
+
+import static org.junit.Assert.*
+import static org.hamcrest.Matchers.*
+
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.runner.RunWith
+import org.junit.Test
+import org.gradle.api.internal.ClassGenerator
+import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency
+import org.gradle.api.internal.ClassPathRegistry
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.file.FileCollection
+import org.gradle.api.artifacts.Dependency
+
+ at RunWith(JMock.class)
+public class ClassPathDependencyFactoryTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final ClassGenerator classGenerator = context.mock(ClassGenerator.class)
+    private final ClassPathRegistry classPathRegistry = context.mock(ClassPathRegistry.class)
+    private final FileResolver fileResolver = context.mock(FileResolver.class)
+    private final ClassPathDependencyFactory factory = new ClassPathDependencyFactory(classGenerator, classPathRegistry, fileResolver)
+
+    @Test
+    void createsDependencyForAClassPathNotation() {
+        Dependency dependency = context.mock(Dependency.class)
+
+        context.checking {
+            Set files = []
+            FileCollection fileCollection = context.mock(FileCollection.class)
+
+            one(classPathRegistry).getClassPathFiles('GRADLE_API')
+            will(returnValue(files))
+
+            one(fileResolver).resolveFiles(withParam(hasItemInArray(files)))
+            will(returnValue(fileCollection))
+
+            one(classGenerator).newInstance(DefaultSelfResolvingDependency.class, fileCollection)
+            will(returnValue(dependency))
+        }
+
+        assertThat(factory.createDependency(Dependency.class, DependencyFactory.ClassPathNotation.GRADLE_API), sameInstance(dependency))
+    }
+}
+
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultClientModuleFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultClientModuleFactoryTest.java
new file mode 100644
index 0000000..be81460
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultClientModuleFactoryTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.artifacts.ExternalDependency;
+import org.gradle.api.internal.AsmBackedClassGenerator;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultClientModuleFactoryTest extends AbstractModuleFactoryTest {
+    private DefaultClientModuleFactory clientModuleFactory = new DefaultClientModuleFactory(new AsmBackedClassGenerator());
+
+    protected ExternalDependency createDependency(Object notation) {
+        return clientModuleFactory.createDependency(ExternalDependency.class, notation);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyFactoryTest.java
new file mode 100644
index 0000000..f2443d6
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyFactoryTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.dependencies;
+
+import groovy.lang.Closure;
+import org.gradle.api.IllegalDependencyNotation;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.ClientModule;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.WrapUtil;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.awt.*;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultDependencyFactoryTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    private IDependencyImplementationFactory testImplPointFactoryStub = context.mock(IDependencyImplementationFactory.class, "Point");
+    private DefaultDependencyFactory dependencyFactory = new DefaultDependencyFactory(
+            WrapUtil.toSet(testImplPointFactoryStub), null, null);
+
+    @Test
+    public void testCreateDependencyWithValidDescription() {
+        final Point point = createAnonymousPoint();
+        final Dependency pointDependencyDummy = context.mock(Dependency.class, "PointDependency");
+        context.checking(new Expectations() {{
+            allowing(testImplPointFactoryStub).createDependency(Dependency.class, point);
+            will(returnValue(pointDependencyDummy));
+        }});
+        assertSame(pointDependencyDummy, dependencyFactory.createDependency(point));
+    }
+
+    @Test
+    public void createDependencyWithDependencyObject() {
+        final Dependency dependencyDummy = context.mock(Dependency.class);
+        assertSame(dependencyDummy, dependencyFactory.createDependency(dependencyDummy));    
+    }
+
+    @Test
+    public void testCreateDependencyWithValidDescriptionAndClosure() {
+        final Point point = createAnonymousPoint();
+        final Dependency pointDependencyMock = context.mock(Dependency.class, "PointDependency");
+        context.checking(new Expectations() {{
+            allowing(testImplPointFactoryStub).createDependency(Dependency.class, point);
+            will(returnValue(pointDependencyMock));
+        }});
+        assertSame(pointDependencyMock, dependencyFactory.createDependency(point));
+    }
+
+    private Point createAnonymousPoint() {
+        return new Point(5,4);
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void testCreateDependencyWithInvalidDescriptionShouldThrowInvalidUserDataEx() {
+        final IDependencyImplementationFactory testImplStringFactoryStub = context.mock(IDependencyImplementationFactory.class, "String");
+        context.checking(new Expectations() {{
+            allowing(testImplPointFactoryStub).createDependency(with(equalTo(Dependency.class)), with(not(instanceOf(Point.class))));
+            will(throwException(new IllegalDependencyNotation()));
+            allowing(testImplStringFactoryStub).createDependency(with(equalTo(Dependency.class)), with(not(instanceOf(String.class))));
+            will(throwException(new IllegalDependencyNotation()));
+        }});
+        dependencyFactory.createDependency(createAnonymousInteger());
+    }
+
+    private Integer createAnonymousInteger() {
+        return new Integer(5);
+    }
+
+    @Test
+    public void createProject() {
+        final ProjectDependencyFactory projectDependencyFactoryStub = context.mock(ProjectDependencyFactory.class);
+        final ProjectDependency projectDependency = context.mock(ProjectDependency.class);
+        final ProjectFinder projectFinderDummy = context.mock(ProjectFinder.class);
+        DefaultDependencyFactory dependencyFactory = new DefaultDependencyFactory(null, null, projectDependencyFactoryStub);
+        final Map map = WrapUtil.toMap("key", "value");
+        context.checking(new Expectations() {{
+            allowing(projectDependencyFactoryStub).createProjectDependencyFromMap(projectFinderDummy, map);
+            will(returnValue(projectDependency));
+        }});
+        Closure configureClosure = HelperUtil.toClosure("{ transitive = false }");
+        assertThat(dependencyFactory.createProjectDependencyFromMap(projectFinderDummy, map), sameInstance(projectDependency));
+    }
+
+    @Test
+    public void createModule() {
+        final IDependencyImplementationFactory testImplStringFactoryStub = context.mock(IDependencyImplementationFactory.class, "String");
+        final IDependencyImplementationFactory clientModuleFactoryStub = context.mock(IDependencyImplementationFactory.class);
+        final ClientModule clientModuleMock = context.mock(ClientModule.class);
+        DefaultDependencyFactory dependencyFactory = new DefaultDependencyFactory(WrapUtil.toSet(testImplStringFactoryStub), clientModuleFactoryStub, null);
+        final String someNotation1 = "someNotation1";
+        final String someNotation2 = "someNotation2";
+        final String someNotation3 = "someNotation3";
+        final String someNotation4 = "someNotation4";
+        final String someModuleNotation = "junit:junit:4.4";
+        final ModuleDependency dependencyDummy1 = context.mock(ModuleDependency.class, "dep1");
+        final ModuleDependency dependencyDummy2 = context.mock(ModuleDependency.class, "dep2");
+        final ModuleDependency dependencyDummy3 = context.mock(ModuleDependency.class, "dep3");
+        final ModuleDependency dependencyMock = context.mock(ModuleDependency.class, "dep4");
+        context.checking(new Expectations() {{
+            allowing(clientModuleFactoryStub).createDependency(ClientModule.class, someModuleNotation);
+            will(returnValue(clientModuleMock));
+            allowing(testImplStringFactoryStub).createDependency(Dependency.class, someNotation1);
+            will(returnValue(dependencyDummy1));
+            allowing(testImplStringFactoryStub).createDependency(Dependency.class, someNotation2);
+            will(returnValue(dependencyDummy2));
+            allowing(testImplStringFactoryStub).createDependency(Dependency.class, someNotation3);
+            will(returnValue(dependencyDummy3));
+            allowing(testImplStringFactoryStub).createDependency(Dependency.class, someNotation4);
+            will(returnValue(dependencyMock));
+            one(dependencyMock).setTransitive(true);
+            one(clientModuleMock).addDependency(dependencyDummy1);
+            one(clientModuleMock).addDependency(dependencyDummy2);
+            one(clientModuleMock).addDependency(dependencyDummy3);
+            one(clientModuleMock).addDependency(dependencyMock);
+        }});
+        Closure configureClosure = HelperUtil.toClosure(String.format(
+                "{dependency('%s'); dependencies('%s', '%s'); dependency('%s') { transitive = true }}",
+                someNotation1, someNotation2, someNotation3, someNotation4));
+        assertThat(dependencyFactory.createModule(someModuleNotation, configureClosure), equalTo(clientModuleMock));
+    }
+
+    @Test
+    public void createModuleWithNullClosure() {
+        final IDependencyImplementationFactory testImplStringFactoryStub = context.mock(IDependencyImplementationFactory.class, "String");
+        final IDependencyImplementationFactory clientModuleFactoryStub = context.mock(IDependencyImplementationFactory.class);
+        final ClientModule clientModuleMock = context.mock(ClientModule.class);
+        DefaultDependencyFactory dependencyFactory = new DefaultDependencyFactory(WrapUtil.toSet(testImplStringFactoryStub), clientModuleFactoryStub, null);
+
+        final String someModuleNotation = "junit:junit:4.4";
+        context.checking(new Expectations() {{
+            allowing(clientModuleFactoryStub).createDependency(ClientModule.class, someModuleNotation);
+            will(returnValue(clientModuleMock));
+        }});
+        assertThat(dependencyFactory.createModule(someModuleNotation, null), equalTo(clientModuleMock));
+    }
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandlerTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandlerTest.groovy
new file mode 100644
index 0000000..d86e097
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandlerTest.groovy
@@ -0,0 +1,260 @@
+/*
+ * 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.dependencies
+
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
+import org.gradle.util.ConfigureUtil
+import org.gradle.util.HelperUtil
+import org.gradle.util.JUnit4GroovyMockery
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.gradle.api.artifacts.*
+import static org.hamcrest.Matchers.equalTo
+import static org.hamcrest.Matchers.sameInstance
+import static org.junit.Assert.assertThat
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith (org.jmock.integration.junit4.JMock)
+class DefaultDependencyHandlerTest {
+    private static final String TEST_CONF_NAME = "someConf"
+
+    private JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+
+    private ConfigurationContainer configurationContainerStub = context.mock(ConfigurationContainer)
+    private DependencyFactory dependencyFactoryStub = context.mock(DependencyFactory)
+    private Configuration configurationMock = context.mock(Configuration)
+    private ProjectFinder projectFinderDummy = context.mock(ProjectFinder)
+
+    private DefaultDependencyHandler dependencyHandler = new DefaultDependencyHandler(
+            configurationContainerStub, dependencyFactoryStub, projectFinderDummy)
+
+    @Before
+    void setUp() {
+        context.checking {
+            allowing(configurationContainerStub).findByName(TEST_CONF_NAME); will(returnValue(configurationMock))
+        }
+    }
+
+    @Test
+    void add() {
+        String someNotation = "someNotation"
+        Dependency dependencyDummy = context.mock(Dependency)
+        context.checking {
+            allowing(configurationContainerStub).getAt(TEST_CONF_NAME); will(returnValue(configurationMock))
+            allowing(dependencyFactoryStub).createDependency(someNotation); will(returnValue(dependencyDummy))
+            one(configurationMock).addDependency(dependencyDummy);
+        }
+
+        assertThat(dependencyHandler.add(TEST_CONF_NAME, someNotation), equalTo(dependencyDummy))
+    }
+
+    @Test
+    void addWithClosure() {
+        String someNotation = "someNotation"
+        def closure = { }
+        DefaultExternalModuleDependency returnedDependency = HelperUtil.createDependency("group", "name", "1.0")
+        context.checking {
+            allowing(configurationContainerStub).getAt(TEST_CONF_NAME); will(returnValue(configurationMock))
+            allowing(dependencyFactoryStub).createDependency(someNotation); will(returnValue(returnedDependency))
+            one(configurationMock).addDependency(returnedDependency);
+        }
+        def dependency = dependencyHandler.add(TEST_CONF_NAME, someNotation) {
+            force = true    
+        }
+        assertThat(dependency, equalTo(returnedDependency))
+        assertThat(dependency.force, equalTo(true))
+    }
+
+    @Test
+    void pushOneDependency() {
+        String someNotation = "someNotation"
+        Dependency dependencyDummy = context.mock(Dependency)
+        context.checking {
+            allowing(dependencyFactoryStub).createDependency(someNotation); will(returnValue(dependencyDummy))
+            one(configurationMock).addDependency(dependencyDummy);
+        }
+
+        assertThat(dependencyHandler."$TEST_CONF_NAME"(someNotation), equalTo(dependencyDummy))
+    }
+
+    @Test
+    void pushOneDependencyWithClosure() {
+        String someNotation = "someNotation"
+        DefaultExternalModuleDependency returnedDependency = HelperUtil.createDependency("group", "name", "1.0")
+        context.checking {
+            allowing(dependencyFactoryStub).createDependency(someNotation); will(returnValue(returnedDependency))
+            one(configurationMock).addDependency(returnedDependency);
+        }
+
+        def dependency = dependencyHandler."$TEST_CONF_NAME"(someNotation) {
+            force = true
+        }
+        assertThat(dependency, equalTo(returnedDependency))
+        assertThat(dependency.force, equalTo(true))
+    }
+
+    @Test
+    void pushMultipleDependencies() {
+        String someNotation1 = "someNotation"
+        Map someNotation2 = [a: 'b', c: 'd']
+        Dependency dependencyDummy1 = context.mock(Dependency, "dep1")
+        Dependency dependencyDummy2 = context.mock(Dependency, "dep2")
+        context.checking {
+            allowing(dependencyFactoryStub).createDependency(someNotation1); will(returnValue(dependencyDummy1))
+            allowing(dependencyFactoryStub).createDependency(someNotation2); will(returnValue(dependencyDummy2))
+            one(configurationMock).addDependency(dependencyDummy1);
+            one(configurationMock).addDependency(dependencyDummy2);
+        }
+
+        dependencyHandler."$TEST_CONF_NAME"(someNotation1, someNotation2)
+    }
+
+    @Test
+    void pushMultipleDependenciesViaNestedList() {
+        String someNotation1 = "someNotation"
+        Map someNotation2 = [a: 'b', c: 'd']
+        Dependency dependencyDummy1 = context.mock(Dependency, "dep1")
+        Dependency dependencyDummy2 = context.mock(Dependency, "dep2")
+        context.checking {
+            allowing(dependencyFactoryStub).createDependency(someNotation1); will(returnValue(dependencyDummy1))
+            allowing(dependencyFactoryStub).createDependency(someNotation2); will(returnValue(dependencyDummy2))
+            one(configurationMock).addDependency(dependencyDummy1);
+            one(configurationMock).addDependency(dependencyDummy2);
+        }
+
+        dependencyHandler."$TEST_CONF_NAME"([[someNotation1, someNotation2]])
+    }
+
+    @Test
+    void pushProjectByMap() {
+        ProjectDependency projectDependency = context.mock(ProjectDependency)
+        Map someMapNotation = [:]
+        Closure projectDependencyClosure = {
+            assertThat("$TEST_CONF_NAME"(project(someMapNotation)), equalTo(projectDependency))
+        }
+        context.checking {
+            allowing(dependencyFactoryStub).createProjectDependencyFromMap(projectFinderDummy, someMapNotation); will(returnValue(projectDependency))
+            allowing(dependencyFactoryStub).createDependency(projectDependency); will(returnValue(projectDependency))
+            one(configurationMock).addDependency(projectDependency);
+        }
+
+        ConfigureUtil.configure(projectDependencyClosure, dependencyHandler)
+    }
+
+    @Test
+    void pushProjectByMapWithConfigureClosure() {
+        ProjectDependency projectDependency = context.mock(ProjectDependency)
+        Map someMapNotation = [:]
+        Closure projectDependencyClosure = {
+            def dependency = "$TEST_CONF_NAME"(project(someMapNotation)) {
+                copy()    
+            }
+            assertThat(dependency, equalTo(projectDependency))
+        }
+        context.checking {
+            allowing(dependencyFactoryStub).createProjectDependencyFromMap(projectFinderDummy, someMapNotation); will(returnValue(projectDependency))
+            allowing(dependencyFactoryStub).createDependency(projectDependency); will(returnValue(projectDependency))
+            one(configurationMock).addDependency(projectDependency);
+            one(projectDependency).copy();
+        }
+
+        ConfigureUtil.configure(projectDependencyClosure, dependencyHandler)
+    }
+
+    @Test
+    void pushModule() {
+        ClientModule clientModule = context.mock(ClientModule)
+        String someNotation = "someNotation"
+        Closure moduleClosure = {
+            assertThat("$TEST_CONF_NAME"(module(someNotation)), equalTo(clientModule))
+        }
+        context.checking {
+            allowing(dependencyFactoryStub).createModule(someNotation, null); will(returnValue(clientModule))
+            allowing(dependencyFactoryStub).createDependency(clientModule); will(returnValue(clientModule))
+            one(configurationMock).addDependency(clientModule);
+        }
+
+        ConfigureUtil.configure(moduleClosure, dependencyHandler)
+    }
+
+    @Test
+    void pushModuleWithConfigureClosure() {
+        ClientModule clientModule = context.mock(ClientModule)
+        String someNotation = "someNotation"
+        Closure configureClosure = {}
+        Closure moduleClosure = {
+            assertThat("$TEST_CONF_NAME"(module(someNotation, configureClosure)), equalTo(clientModule))
+        }
+        context.checking {
+            allowing(dependencyFactoryStub).createModule(someNotation, configureClosure); will(returnValue(clientModule))
+            allowing(dependencyFactoryStub).createDependency(clientModule); will(returnValue(clientModule))
+            one(configurationMock).addDependency(clientModule);
+        }
+
+        ConfigureUtil.configure(moduleClosure, dependencyHandler)
+    }
+
+    @Test
+    void pushGradleApi() {
+        Dependency dependency = context.mock(Dependency)
+        context.checking {
+            one(dependencyFactoryStub).createDependency(DependencyFactory.ClassPathNotation.GRADLE_API)
+            will(returnValue(dependency))
+
+            one(dependencyFactoryStub).createDependency(dependency)
+            will(returnValue(dependency))
+
+            one(configurationMock).addDependency(dependency)
+        }
+
+        Closure moduleClosure = {
+            assertThat("$TEST_CONF_NAME"(gradleApi()), sameInstance(dependency))
+        }
+        ConfigureUtil.configure(moduleClosure, dependencyHandler)
+    }
+
+    @Test
+    void pushLocalGroovy() {
+        Dependency dependency = context.mock(Dependency)
+        context.checking {
+            one(dependencyFactoryStub).createDependency(DependencyFactory.ClassPathNotation.LOCAL_GROOVY)
+            will(returnValue(dependency))
+
+            one(dependencyFactoryStub).createDependency(dependency)
+            will(returnValue(dependency))
+
+            one(configurationMock).addDependency(dependency)
+        }
+
+        Closure moduleClosure = {
+            assertThat("$TEST_CONF_NAME"(localGroovy()), sameInstance(dependency))
+        }
+        ConfigureUtil.configure(moduleClosure, dependencyHandler)
+    }
+    
+    @Test (expected = MissingMethodException)
+    void pushToUnknownConfiguration() {
+        String unknownConf = TEST_CONF_NAME + "delta"
+        context.checking {
+            allowing(configurationContainerStub).findByName(unknownConf); will(returnValue(null))
+        }
+        dependencyHandler."$unknownConf"("someNotation")
+    }
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultProjectDependencyFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultProjectDependencyFactoryTest.java
new file mode 100644
index 0000000..54e21f3
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultProjectDependencyFactoryTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.IllegalDependencyNotation;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ProjectDependenciesBuildInstruction;
+import org.gradle.api.internal.AsmBackedClassGenerator;
+import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.util.GUtil;
+import org.gradle.util.HelperUtil;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+
+import java.awt.*;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultProjectDependencyFactoryTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    private final ProjectDependenciesBuildInstruction projectDependenciesBuildInstruction = new ProjectDependenciesBuildInstruction(null);
+    private ProjectDependencyFactory projectDependencyFactory = new DefaultProjectDependencyFactory(projectDependenciesBuildInstruction, new AsmBackedClassGenerator());
+    private ProjectFinder projectFinder = context.mock(ProjectFinder.class);
+
+    @Test
+    public void testCreateProjectDependencyWithProject() {
+        Project dependencyProject = HelperUtil.createRootProject();
+        DefaultProjectDependency projectDependency = (DefaultProjectDependency)
+                projectDependencyFactory.createDependency(Dependency.class, dependencyProject);
+        assertThat((ProjectInternal) projectDependency.getDependencyProject(), equalTo(dependencyProject));
+    }
+
+    @Test
+    public void testCreateProjectDependencyWithMapNotation() {
+        boolean expectedTransitive = false;
+        final Map<String, Object> mapNotation = GUtil.map("path", ":path", "configuration", "conf", "transitive", expectedTransitive);
+        final ProjectInternal projectDummy = context.mock(ProjectInternal.class);
+        context.checking(new Expectations() {{
+            allowing(projectFinder).getProject((String) mapNotation.get("path"));
+            will(returnValue(projectDummy));
+        }});
+        DefaultProjectDependency projectDependency = (DefaultProjectDependency)
+                projectDependencyFactory.createProjectDependencyFromMap(projectFinder, mapNotation);
+        assertThat((ProjectInternal) projectDependency.getDependencyProject(), equalTo(projectDummy));
+        assertThat(projectDependency.getConfiguration(), equalTo(mapNotation.get("configuration")));
+        assertThat(projectDependency.isTransitive(), equalTo(expectedTransitive));
+    }
+
+    @Test (expected = IllegalDependencyNotation.class)
+    public void testWithUnknownTypeShouldThrowUnknownDependencyNotationEx() {
+        projectDependencyFactory.createDependency(Dependency.class, new Point(3, 4));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDependencyFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDependencyFactoryTest.java
new file mode 100644
index 0000000..b31edf3
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleDependencyFactoryTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.dsl.dependencies;
+
+import org.gradle.api.internal.AsmBackedClassGenerator;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import org.gradle.api.artifacts.DependencyArtifact;
+import org.gradle.api.artifacts.ExternalDependency;
+import org.gradle.api.artifacts.ExternalModuleDependency;
+import org.gradle.util.GUtil;
+
+/**
+ * @author Hans Dockter
+ */
+public class ModuleDependencyFactoryTest extends AbstractModuleFactoryTest {
+    private static final String TEST_ARTIFACT_DESCRIPTOR = TEST_MODULE_DESCRIPTOR + "@" + TEST_TYPE;
+    private static final String TEST_ARTIFACT_DESCRIPTOR_WITH_CLASSIFIER = TEST_MODULE_DESCRIPTOR + String.format(":%s@%s", TEST_CLASSIFIER, TEST_TYPE);
+    
+    private ModuleDependencyFactory moduleDependencyFactory = new ModuleDependencyFactory(new AsmBackedClassGenerator());
+
+    protected ExternalDependency createDependency(Object notation) {
+        return moduleDependencyFactory.createDependency(ExternalDependency.class, notation);
+    }
+
+    protected void checkOtherProperties(ExternalDependency moduleDependency) {
+        super.checkOtherProperties(moduleDependency);
+        assertFalse(((ExternalModuleDependency)moduleDependency).isChanging());
+    }
+
+    @Test
+    public void testStringNotationWithArtifact() {
+        ExternalDependency moduleDependency = createDependency(TEST_ARTIFACT_DESCRIPTOR);
+        assertIsArtifactOnly(moduleDependency);
+    }
+
+    @Test
+    public void testStringNotationWithArtifactAndClassifier() {
+        ExternalDependency moduleDependency = createDependency(TEST_ARTIFACT_DESCRIPTOR_WITH_CLASSIFIER);
+        assertIsArtifactOnlyWithClassifier(moduleDependency);
+    }
+
+    @Test
+    public void testMapNotationWithArtifact() {
+        ExternalDependency moduleDependency = createDependency(GUtil.map("group", TEST_GROUP, "name", TEST_NAME, "version", TEST_VERSION, "ext", TEST_TYPE));
+        assertIsArtifactOnly(moduleDependency);
+    }
+
+    @Test
+    public void testMapNotationWithArtifactAndClassifier() {
+        ExternalDependency moduleDependency = createDependency(GUtil.map("group", TEST_GROUP, "name", TEST_NAME, "version",
+                TEST_VERSION, "ext", TEST_TYPE, "classifier", TEST_CLASSIFIER));
+        assertIsArtifactOnlyWithClassifier(moduleDependency);
+    }
+
+    private void assertIsArtifactOnlyWithClassifier(ExternalDependency moduleDependency) {
+        checkCommonModuleProperties(moduleDependency);
+        assertFalse(moduleDependency.isTransitive());
+        assertEquals(1, moduleDependency.getArtifacts().size());
+        DependencyArtifact artifact = moduleDependency.getArtifacts().iterator().next();
+        assertEquals(TEST_NAME, artifact.getName());
+        assertEquals(TEST_TYPE, artifact.getType());
+        assertEquals(TEST_TYPE, artifact.getExtension());
+        assertEquals(TEST_CLASSIFIER, artifact.getClassifier());
+    }
+
+    private void assertIsArtifactOnly(ExternalDependency moduleDependency) {
+        checkCommonModuleProperties(moduleDependency);
+        assertFalse(moduleDependency.isTransitive());
+        assertEquals(1, moduleDependency.getArtifacts().size());
+        DependencyArtifact artifact = moduleDependency.getArtifacts().iterator().next();
+        assertEquals(TEST_NAME, artifact.getName());
+        assertEquals(TEST_TYPE, artifact.getType());
+        assertEquals(null, artifact.getClassifier());
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleFactoryDelegateTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleFactoryDelegateTest.java
new file mode 100644
index 0000000..27fe2f6
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/ModuleFactoryDelegateTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.dependencies;
+
+import groovy.lang.Closure;
+import org.gradle.api.artifacts.ClientModule;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.internal.artifacts.dependencies.DefaultClientModule;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.WrapUtil;
+import org.hamcrest.Matchers;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Hans Dockter
+ */
+public class ModuleFactoryDelegateTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    private DependencyFactory dependencyFactoryStub = context.mock(DependencyFactory.class);
+    private ClientModule clientModule = new DefaultClientModule("junit", "junit", "4.4");
+    
+    private ModuleFactoryDelegate moduleFactoryDelegate = new ModuleFactoryDelegate(clientModule, dependencyFactoryStub);
+
+    @Test
+    public void dependency() {
+        final String dependencyNotation = "someNotation";
+        final ModuleDependency dependencyDummy = context.mock(ModuleDependency.class);
+        letFactoryStubReturnDependency(dependencyNotation, dependencyDummy);
+        moduleFactoryDelegate.dependency(dependencyNotation);
+        assertThat(clientModule.getDependencies(), Matchers.equalTo(WrapUtil.toSet(dependencyDummy)));
+    }
+
+    @Test
+    public void dependencyWithClosure() {
+        final String dependencyNotation = "someNotation";
+        final Closure configureClosure = HelperUtil.toClosure("{}");
+        final ModuleDependency dependencyDummy = context.mock(ModuleDependency.class);
+        letFactoryStubReturnDependency(dependencyNotation, dependencyDummy);
+        moduleFactoryDelegate.dependency(dependencyNotation, configureClosure);
+        assertThat(clientModule.getDependencies(), Matchers.equalTo(WrapUtil.toSet(dependencyDummy)));
+    }
+
+    @Test
+    public void dependencies() {
+        final String dependencyNotation1 = "someNotation1";
+        final String dependencyNotation2 = "someNotation2";
+        final ModuleDependency dependencyDummy1 = context.mock(ModuleDependency.class, "dep1");
+        final ModuleDependency dependencyDummy2 = context.mock(ModuleDependency.class, "dep2");
+        letFactoryStubReturnDependency(dependencyNotation1, dependencyDummy1);
+        letFactoryStubReturnDependency(dependencyNotation2, dependencyDummy2);
+        moduleFactoryDelegate.dependencies((Object[])WrapUtil.toArray(dependencyNotation1, dependencyNotation2));
+        assertThat(clientModule.getDependencies(), Matchers.equalTo(WrapUtil.toSet(dependencyDummy1, dependencyDummy2)));
+    }
+
+    private void letFactoryStubReturnDependency(final String dependencyNotation, final Dependency dependencyDummy) {
+        context.checking(new Expectations() {{
+            allowing(dependencyFactoryStub).createDependency(dependencyNotation);
+            will(returnValue(dependencyDummy));
+        }});
+    }
+
+    @Test
+    public void module() {
+        final String clientModuleNotation = "someNotation";
+        final Closure configureClosure = HelperUtil.toClosure("{}");
+        final ClientModule clientModuleDummy = context.mock(ClientModule.class);
+        context.checking(new Expectations() {{
+            allowing(dependencyFactoryStub).createModule(clientModuleNotation, configureClosure);
+            will(returnValue(clientModuleDummy));
+        }});
+        moduleFactoryDelegate.module(clientModuleNotation, configureClosure);
+        assertThat(this.clientModule.getDependencies(), Matchers.equalTo(WrapUtil.<ModuleDependency>toSet(clientModuleDummy)));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/SelfResolvingDependencyFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/SelfResolvingDependencyFactoryTest.java
new file mode 100644
index 0000000..329b741
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/SelfResolvingDependencyFactoryTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.dependencies;
+
+import org.gradle.api.IllegalDependencyNotation;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.internal.AsmBackedClassGenerator;
+import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency;
+import static org.hamcrest.Matchers.*;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+ at RunWith(JMock.class)
+public class SelfResolvingDependencyFactoryTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final SelfResolvingDependencyFactory factory = new SelfResolvingDependencyFactory(new AsmBackedClassGenerator());
+
+    @Test
+    public void createsADependencyFromAFileCollectionNotation() {
+        FileCollection collection = context.mock(FileCollection.class);
+
+        Dependency dependency = factory.createDependency(Dependency.class, collection);
+        assertThat(dependency, instanceOf(DefaultSelfResolvingDependency.class));
+        DefaultSelfResolvingDependency selfResolvingDependency = (DefaultSelfResolvingDependency) dependency;
+        assertThat(selfResolvingDependency.getSource(), sameInstance(collection));
+    }
+
+    @Test(expected = IllegalDependencyNotation.class)
+    public void throwsExceptionForOtherNotation() {
+        factory.createDependency(Dependency.class, "something else");
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ClientModuleResolverTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ClientModuleResolverTest.groovy
new file mode 100644
index 0000000..2e6d05c
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ClientModuleResolverTest.groovy
@@ -0,0 +1,37 @@
+/*
+ * 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.junit.Test
+
+/**
+ * @author Hans Dockter
+ */
+class ClientModuleResolverTest {
+    @Test public void testGetDependency() {
+      // todo implement
+//        ClientModuleResolver clientModuleResolver = new ClientModuleResolver()
+//        clientModuleResolver.moduleRegistry = [:]
+//        clientModuleResolver.mainResolver = [:] as DependencyResolver
+//
+//        DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(null,
+//                createModuleRevisionId([(CLIENT_MODULE_KEY): id]), false, true, true)
+//        ResolveData resolveData = new ResolveData(new ResolveEngine(), new ResolveOptions())
+//        ModuleDescriptor = clientModuleResolver.getDependency(dd, resolveData)
+
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisherTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisherTest.java
new file mode 100644
index 0000000..8a527e7
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyPublisherTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyResolverTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyResolverTest.java
new file mode 100644
index 0000000..16dea6b
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyDependencyResolverTest.java
@@ -0,0 +1,290 @@
+/*
+ * 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.ivyservice;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.ivy.core.report.ResolveReport;
+import org.apache.ivy.core.resolve.ResolveOptions;
+import org.gradle.api.GradleException;
+import org.gradle.api.artifacts.*;
+import org.gradle.api.internal.artifacts.DefaultResolvedArtifactTest;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.specs.Specs;
+import org.gradle.util.GUtil;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultIvyDependencyResolverTest {
+    private JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+
+    private Configuration configurationStub = context.mock(Configuration.class, "<configuration>");
+    private Ivy ivyStub = context.mock(Ivy.class);
+    private DefaultIvyReportConverter ivyReportConverterStub = context.mock(DefaultIvyReportConverter.class);
+    private ResolveReport resolveReportMock = context.mock(ResolveReport.class);
+
+    private DefaultIvyDependencyResolver ivyDependencyResolver = new DefaultIvyDependencyResolver(ivyReportConverterStub);
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations() {{
+            allowing(configurationStub).getName();
+            will(returnValue("someConfName"));
+        }});
+    }
+
+    @Test
+    public void testResolveAndGetFilesWithDependencySubset() throws IOException, ParseException {
+        prepareResolveReport();
+        final ModuleDependency moduleDependencyDummy1 = context.mock(ModuleDependency.class, "dep1");
+        final ModuleDependency moduleDependencyDummy2 = context.mock(ModuleDependency.class, "dep2");
+        final SelfResolvingDependency selfResolvingDependencyDummy = context.mock(SelfResolvingDependency.class);
+        final ResolvedDependency root = context.mock(ResolvedDependency.class, "root");
+        final ResolvedDependency resolvedDependency1 = context.mock(ResolvedDependency.class, "resolved1");
+        final ResolvedDependency resolvedDependency2 = context.mock(ResolvedDependency.class, "resolved2");
+        ResolvedDependency resolvedDependency3 = context.mock(ResolvedDependency.class, "resolved3");
+        final IvyConversionResult conversionResultStub = context.mock(IvyConversionResult.class);
+        final Map<Dependency, Set<ResolvedDependency>> firstLevelResolvedDependencies = GUtil.map(
+                moduleDependencyDummy1,
+                toSet(resolvedDependency1, resolvedDependency2),
+                moduleDependencyDummy2,
+                toSet(resolvedDependency3));
+
+        context.checking(new Expectations() {{
+            allowing(resolvedDependency1).getParentArtifacts(root);
+            will(returnValue(toSet(DefaultResolvedArtifactTest.createResolvedArtifact(context, "dep1parent", "someType", "someExtension", new File("dep1parent")))));
+            allowing(resolvedDependency1).getModuleArtifacts();
+            will(returnValue(toSet(DefaultResolvedArtifactTest.createResolvedArtifact(context, "dep1", "someType", "someExtension", new File("dep1")))));
+            allowing(resolvedDependency1).getChildren();
+            will(returnValue(toSet()));
+            allowing(resolvedDependency2).getParentArtifacts(root);
+            will(returnValue(toSet()));
+            allowing(resolvedDependency2).getModuleArtifacts();
+            will(returnValue(toSet(DefaultResolvedArtifactTest.createResolvedArtifact(context, "dep2", "someType", "someExtension", new File("dep2")))));
+            allowing(resolvedDependency2).getChildren();
+            will(returnValue(toSet()));
+            allowing(configurationStub).getAllDependencies();
+            will(returnValue(toSet(moduleDependencyDummy1, moduleDependencyDummy2, selfResolvingDependencyDummy)));
+            allowing(configurationStub).getAllDependencies(ModuleDependency.class);
+            will(returnValue(toSet(moduleDependencyDummy1, moduleDependencyDummy2)));
+            allowing(ivyReportConverterStub).convertReport(resolveReportMock, configurationStub);
+            will(returnValue(conversionResultStub));
+            allowing(conversionResultStub).getFirstLevelResolvedDependencies();
+            will(returnValue(firstLevelResolvedDependencies));
+            allowing(conversionResultStub).getRoot();
+            will(returnValue(root));
+        }});
+        ModuleDescriptor moduleDescriptor = createAnonymousModuleDescriptor();
+        prepareTestsThatRetrieveDependencies(moduleDescriptor);
+
+        Set<File> actualFiles = ivyDependencyResolver.resolve(configurationStub, ivyStub, moduleDescriptor).getFiles(
+                new Spec<Dependency>() {
+                    public boolean isSatisfiedBy(Dependency element) {
+                        return element == moduleDependencyDummy1 || element == selfResolvingDependencyDummy;
+                    }
+                });
+        assertThat(actualFiles, equalTo(toSet(new File("dep1"), new File("dep2"), new File("dep1parent"))));
+    }
+
+    @Test
+    public void testGetModuleDependencies() throws IOException, ParseException {
+        prepareResolveReport();
+        final ModuleDependency moduleDependencyDummy1 = context.mock(ModuleDependency.class, "dep1");
+        final ResolvedDependency root = context.mock(ResolvedDependency.class, "root");
+        final ResolvedDependency resolvedDependency1 = context.mock(ResolvedDependency.class, "resolved1");
+        final ResolvedDependency resolvedDependency2 = context.mock(ResolvedDependency.class, "resolved2");
+        final IvyConversionResult conversionResultStub = context.mock(IvyConversionResult.class);
+        final Set<ResolvedDependency> resolvedDependenciesSet = toSet(resolvedDependency1, resolvedDependency2);
+
+        context.checking(new Expectations() {{
+            allowing(ivyReportConverterStub).convertReport(resolveReportMock, configurationStub);
+            will(returnValue(conversionResultStub));
+            allowing(conversionResultStub).getRoot();
+            will(returnValue(root));
+            allowing(root).getChildren();
+            will(returnValue(resolvedDependenciesSet));
+        }});
+        ModuleDescriptor moduleDescriptor = createAnonymousModuleDescriptor();
+        prepareTestsThatRetrieveDependencies(moduleDescriptor);
+
+        Set<ResolvedDependency> actualFirstLevelModuleDependencies = ivyDependencyResolver.resolve(configurationStub, ivyStub, moduleDescriptor).getFirstLevelModuleDependencies();
+        assertThat(actualFirstLevelModuleDependencies, equalTo(resolvedDependenciesSet));
+    }
+
+    @Test
+    public void testGetResolvedArtifacts() throws IOException, ParseException {
+        prepareResolveReport();
+        final IvyConversionResult conversionResultStub = context.mock(IvyConversionResult.class);
+        final ResolvedArtifact resolvedArtifactDummy = context.mock(ResolvedArtifact.class);
+        final Set<ResolvedArtifact> resolvedArtifacts = toSet(resolvedArtifactDummy);
+        context.checking(new Expectations() {{
+            allowing(ivyReportConverterStub).convertReport(resolveReportMock, configurationStub);
+            will(returnValue(conversionResultStub));
+            allowing(conversionResultStub).getResolvedArtifacts();
+            will(returnValue(resolvedArtifacts));
+        }});
+        ModuleDescriptor moduleDescriptor = createAnonymousModuleDescriptor();
+        prepareTestsThatRetrieveDependencies(moduleDescriptor);
+        assertThat(ivyDependencyResolver.resolve(configurationStub, ivyStub, moduleDescriptor)
+                .getResolvedArtifacts(), equalTo(resolvedArtifacts));
+    }
+
+    @Test
+    public void testResolveAndGetFilesWithMissingDependenciesShouldThrowGradleEx() throws IOException, ParseException {
+        ModuleDescriptor moduleDescriptor = createAnonymousModuleDescriptor();
+        prepareTestsThatRetrieveDependencies(moduleDescriptor);
+        prepareResolveReportWithError();
+        ResolvedConfiguration configuration = ivyDependencyResolver.resolve(configurationStub, ivyStub, moduleDescriptor);
+        context.checking(new Expectations() {{
+            allowing(configurationStub).getAllDependencies();
+            allowing(configurationStub).getAllDependencies(ModuleDependency.class);
+        }});
+        try {
+            configuration.getFiles(Specs.SATISFIES_ALL);
+            fail();
+        } catch (GradleException e) {
+            assertThat(e.getMessage(), startsWith("Could not resolve all dependencies for <configuration>"));
+        }
+    }
+
+    @Test
+    public void testResolveAndRethrowFailureWithMissingDependenciesShouldThrowGradleEx() throws IOException, ParseException {
+        ModuleDescriptor moduleDescriptor = createAnonymousModuleDescriptor();
+        prepareTestsThatRetrieveDependencies(moduleDescriptor);
+        prepareResolveReportWithError();
+        ResolvedConfiguration configuration = ivyDependencyResolver.resolve(configurationStub, ivyStub,
+                moduleDescriptor);
+
+        assertTrue(configuration.hasError());
+        try {
+            configuration.rethrowFailure();
+            fail();
+        } catch (GradleException e) {
+            assertThat(e.getMessage(), startsWith("Could not resolve all dependencies for <configuration>"));
+        }
+    }
+
+    @Test
+    public void testResolveAndRethrowFailureWithNoMissingDependenciesShouldDoNothing() throws IOException, ParseException {
+        ModuleDescriptor moduleDescriptor = createAnonymousModuleDescriptor();
+        prepareTestsThatRetrieveDependencies(moduleDescriptor);
+        prepareResolveReport();
+        context.checking(new Expectations() {{
+            allowing(ivyReportConverterStub).convertReport(resolveReportMock, configurationStub);
+        }});
+        ResolvedConfiguration configuration = ivyDependencyResolver.resolve(configurationStub, ivyStub,
+                moduleDescriptor);
+
+        assertFalse(configuration.hasError());
+        configuration.rethrowFailure();
+    }
+
+    @Test
+    public void testResolveAndGetReport() throws IOException, ParseException {
+        prepareResolveReport();
+        context.checking(new Expectations() {{
+            allowing(ivyReportConverterStub).convertReport(resolveReportMock, configurationStub);
+        }});
+        ModuleDescriptor moduleDescriptor = createAnonymousModuleDescriptor();
+        prepareTestsThatRetrieveDependencies(moduleDescriptor);
+        assertEquals(false, ivyDependencyResolver.resolve(configurationStub, ivyStub, moduleDescriptor).hasError());
+    }
+
+    @Test
+    public void testResolveAndGetReportWithMissingDependenciesAndFailFalse() throws IOException, ParseException {
+        prepareResolveReportWithError();
+        ModuleDescriptor moduleDescriptor = createAnonymousModuleDescriptor();
+        prepareTestsThatRetrieveDependencies(moduleDescriptor);
+        assertEquals(true, ivyDependencyResolver.resolve(configurationStub, ivyStub, moduleDescriptor).hasError());
+    }
+
+    private ModuleDescriptor createAnonymousModuleDescriptor() {
+        return DefaultModuleDescriptor.newDefaultInstance(
+                ModuleRevisionId.newInstance("org", "name", "1.0", new HashMap()));
+    }
+
+    private void prepareResolveReport() throws IOException, ParseException {
+        context.checking(new Expectations() {
+            {
+                allowing(resolveReportMock).hasError();
+                will(returnValue(false));
+            }
+        });
+    }
+
+    private void prepareResolveReportWithError() throws IOException, ParseException {
+        context.checking(new Expectations() {
+            {
+                allowing(resolveReportMock).hasError();
+                will(returnValue(true));
+                allowing(resolveReportMock).getAllProblemMessages();
+                will(returnValue(toList("a problem")));
+            }
+        });
+    }
+
+    private void prepareTestsThatRetrieveDependencies(final ModuleDescriptor moduleDescriptor) throws IOException, ParseException {
+        final String confName = configurationStub.getName();
+        context.checking(new Expectations() {
+            {
+                allowing(ivyStub).resolve(with(equal(moduleDescriptor)), with(equaltResolveOptions(confName)));
+                will(returnValue(resolveReportMock));
+            }
+        });
+    }
+
+    Matcher<ResolveOptions> equaltResolveOptions(final String... confs) {
+        return new BaseMatcher<ResolveOptions>() {
+            public boolean matches(Object o) {
+                ResolveOptions otherOptions = (ResolveOptions) o;
+                return Arrays.equals(confs, otherOptions.getConfs());
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("Checking Resolveoptions");
+            }
+        };
+    }
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactoryTest.java
new file mode 100644
index 0000000..0f9f963
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyFactoryTest.java
@@ -0,0 +1,36 @@
+/*
+ * 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.Ivy;
+import org.apache.ivy.core.settings.IvySettings;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultIvyFactoryTest {
+    @Test
+    public void testCreateIvy() {
+        IvySettings ivySettings = new IvySettings();
+        Ivy ivy = new DefaultIvyFactory().createIvy(ivySettings);
+        assertThat(ivy, notNullValue());
+        assertThat(ivy.getSettings(), equalTo(ivySettings));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyServicePublishTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyServicePublishTest.java
new file mode 100644
index 0000000..bd59fba
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyServicePublishTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.ivyservice;
+
+import org.apache.ivy.Ivy;
+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;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.artifacts.repositories.InternalRepository;
+import org.gradle.api.internal.artifacts.configurations.Configurations;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
+import org.gradle.util.WrapUtil;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultIvyServicePublishTest {
+    private JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+
+    private ModuleDescriptor publishModuleDescriptorDummy = context.mock(ModuleDescriptor.class, "publishModuleDescriptor");
+    private ModuleDescriptor fileModuleDescriptorMock = context.mock(ModuleDescriptor.class, "fileModuleDescriptor");
+    private PublishEngine publishEngineDummy = context.mock(PublishEngine.class);
+    private InternalRepository internalRepositoryDummy = context.mock(InternalRepository.class);
+    private DependencyMetaDataProvider dependencyMetaDataProviderMock = context.mock(DependencyMetaDataProvider.class);
+
+    @Test
+    public void testPublish() throws IOException, ParseException {
+        final IvySettings ivySettingsDummy = new IvySettings();
+        final Set<Configuration> configurations = createConfigurations();
+        final File someDescriptorDestination = new File("somePath");
+        final List<DependencyResolver> publishResolversDummy = createPublishResolversDummy();
+        final Module moduleDummy = context.mock(Module.class, "moduleForResolve");
+        File cacheParentDirDummy = createCacheParentDirDummy();
+        final DefaultIvyService ivyService = createIvyService();
+
+        setUpForPublish(configurations, publishResolversDummy, moduleDummy, cacheParentDirDummy,
+                ivyService, ivySettingsDummy);
+
+        final Set<String> expectedConfigurations = Configurations.getNames(configurations, true);
+        context.checking(new Expectations() {{
+            one(fileModuleDescriptorMock).toIvyFile(someDescriptorDestination);
+            one(ivyService.getDependencyPublisher()).publish(expectedConfigurations,
+                    publishResolversDummy, publishModuleDescriptorDummy, someDescriptorDestination, publishEngineDummy);
+        }});
+
+        ivyService.publish(configurations, someDescriptorDestination, publishResolversDummy);
+    }
+
+    private DefaultIvyService createIvyService() {
+        SettingsConverter settingsConverterStub = context.mock(SettingsConverter.class);
+        ModuleDescriptorConverter resolveModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "resolve");
+        ModuleDescriptorConverter publishModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "publishConverter");
+        ModuleDescriptorConverter fileModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "fileConverter");
+        IvyDependencyPublisher ivyDependencyPublisherMock = context.mock(IvyDependencyPublisher.class);
+        ResolverProvider resolverProvider = context.mock(ResolverProvider.class);
+
+        DefaultIvyService ivyService = new DefaultIvyService(dependencyMetaDataProviderMock, resolverProvider,
+                settingsConverterStub, resolveModuleDescriptorConverter, publishModuleDescriptorConverter,
+                fileModuleDescriptorConverter,
+                new DefaultIvyFactory(), context.mock(IvyDependencyResolver.class),
+                ivyDependencyPublisherMock, new HashMap());
+
+        return ivyService;
+    }
+
+    private File createCacheParentDirDummy() {
+        return new File("cacheParentDirDummy");
+    }
+
+    private List<DependencyResolver> createPublishResolversDummy() {
+        return WrapUtil.toList(context.mock(DependencyResolver.class, "publish"));
+    }
+
+    private Set<Configuration> createConfigurations() {
+        final Configuration configurationStub1 = context.mock(Configuration.class, "confStub1");
+        final Configuration configurationStub2 = context.mock(Configuration.class, "confStub2");
+        context.checking(new Expectations() {{
+            allowing(configurationStub1).getName();
+            will(returnValue("conf1"));
+
+            allowing(configurationStub1).getHierarchy();
+            will(returnValue(WrapUtil.toLinkedSet(configurationStub1)));
+
+            allowing(configurationStub1).getAll();
+            will(returnValue(WrapUtil.toLinkedSet(configurationStub1, configurationStub2)));
+
+            allowing(configurationStub2).getName();
+            will(returnValue("conf2"));
+
+            allowing(configurationStub2).getHierarchy();
+            will(returnValue(WrapUtil.toLinkedSet(configurationStub2)));
+
+            allowing(configurationStub2).getAll();
+            will(returnValue(WrapUtil.toLinkedSet(configurationStub1, configurationStub2)));
+        }});
+        return WrapUtil.toSet(configurationStub1, configurationStub2);
+    }
+
+    private void setUpForPublish(final Set<Configuration> configurations,
+                                 final List<DependencyResolver> publishResolversDummy, final Module moduleDummy,
+                                 final File cacheParentDirDummy, final DefaultIvyService ivyService, final IvySettings ivySettingsDummy) {
+        context.checking(new Expectations() {{
+            allowing(dependencyMetaDataProviderMock).getInternalRepository();
+            will(returnValue(internalRepositoryDummy));
+
+            allowing(dependencyMetaDataProviderMock).getGradleUserHomeDir();
+            will(returnValue(cacheParentDirDummy));
+
+            allowing(dependencyMetaDataProviderMock).getModule();
+            will(returnValue(moduleDummy));
+
+            allowing(ivyService.getSettingsConverter()).convertForPublish(publishResolversDummy, cacheParentDirDummy,
+                    internalRepositoryDummy);
+            will(returnValue(ivySettingsDummy));
+
+            allowing(setUpIvyFactory(ivySettingsDummy, ivyService)).getPublishEngine();
+            will(returnValue(publishEngineDummy));
+
+            allowing(ivyService.getPublishModuleDescriptorConverter()).convert(configurations,
+                    moduleDummy, ivySettingsDummy);
+            will(returnValue(publishModuleDescriptorDummy));
+
+            allowing(ivyService.getFileModuleDescriptorConverter()).convert(configurations,
+                    moduleDummy, ivySettingsDummy);
+            will(returnValue(fileModuleDescriptorMock));
+        }});
+    }
+
+    private Ivy setUpIvyFactory(final IvySettings ivySettingsDummy, DefaultIvyService ivyService) {
+        final IvyFactory ivyFactoryStub = context.mock(IvyFactory.class);
+        final Ivy ivyStub = context.mock(Ivy.class);
+        context.checking(new Expectations() {{
+            allowing(ivyFactoryStub).createIvy(ivySettingsDummy);
+            will(returnValue(ivyStub));
+
+            allowing(ivyStub).getSettings();
+            will(returnValue(ivySettingsDummy));
+        }});
+        ivyService.setIvyFactory(ivyFactoryStub);
+        return ivyStub;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyServiceResolveTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyServiceResolveTest.java
new file mode 100644
index 0000000..f009500
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyServiceResolveTest.java
@@ -0,0 +1,129 @@
+/*
+ * 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.ivyservice;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.artifacts.ResolvedConfiguration;
+import org.gradle.api.artifacts.repositories.InternalRepository;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.WrapUtil;
+import static org.hamcrest.Matchers.sameInstance;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import static org.junit.Assert.assertThat;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultIvyServiceResolveTest {
+    private JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+
+    private Module moduleDummy = context.mock(Module.class);
+    private File cacheParentDirDummy = new File("cacheParentDirDummy");
+    private Map<String, ModuleDescriptor> clientModuleRegistryDummy = WrapUtil.toMap("a", context.mock(ModuleDescriptor.class));
+    private List<DependencyResolver> dependencyResolversDummy = WrapUtil.toList(context.mock(DependencyResolver.class, "dependencies"));
+
+    private InternalRepository internalRepositoryDummy = context.mock(InternalRepository.class);
+    private DependencyMetaDataProvider dependencyMetaDataProviderMock = context.mock(DependencyMetaDataProvider.class);
+    private ResolverProvider resolverProvider = context.mock(ResolverProvider.class);
+
+    // SUT
+    private DefaultIvyService ivyService;
+
+    @Before
+    public void setUp() {
+        SettingsConverter settingsConverterMock = context.mock(SettingsConverter.class);
+        ModuleDescriptorConverter resolveModuleDescriptorConverterStub = context.mock(ModuleDescriptorConverter.class, "resolve");
+        ModuleDescriptorConverter publishModuleDescriptorConverterDummy = context.mock(ModuleDescriptorConverter.class, "publish");
+        IvyDependencyResolver ivyDependencyResolverMock = context.mock(IvyDependencyResolver.class);
+
+        context.checking(new Expectations() {{
+            allowing(dependencyMetaDataProviderMock).getInternalRepository();
+            will(returnValue(internalRepositoryDummy));
+
+            allowing(dependencyMetaDataProviderMock).getGradleUserHomeDir();
+            will(returnValue(cacheParentDirDummy));
+
+            allowing(dependencyMetaDataProviderMock).getModule();
+            will(returnValue(moduleDummy));
+
+            allowing(resolverProvider).getResolvers();
+            will(returnValue(dependencyResolversDummy));
+        }});
+
+        ivyService = new DefaultIvyService(dependencyMetaDataProviderMock, resolverProvider,
+                settingsConverterMock, resolveModuleDescriptorConverterStub, publishModuleDescriptorConverterDummy,
+                publishModuleDescriptorConverterDummy,
+                new DefaultIvyFactory(), ivyDependencyResolverMock, 
+                context.mock(IvyDependencyPublisher.class), clientModuleRegistryDummy);
+    }
+
+    @Test
+    public void testResolve() {
+        final Configuration configurationDummy = context.mock(Configuration.class);
+        final Set<Configuration> configurations = WrapUtil.toSet(configurationDummy);
+        final ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
+        final ModuleDescriptor moduleDescriptorDummy = HelperUtil.createModuleDescriptor(WrapUtil.toSet("someConf"));
+        final IvyFactory ivyFactoryStub = context.mock(IvyFactory.class);
+        final Ivy ivyStub = context.mock(Ivy.class);
+        final IvySettings ivySettingsDummy = new IvySettings();
+
+        context.checking(new Expectations() {{
+            allowing(ivyFactoryStub).createIvy(ivySettingsDummy);
+            will(returnValue(ivyStub));
+
+            allowing(configurationDummy).getAll();
+            will(returnValue(configurations));
+
+            allowing(ivyStub).getSettings();
+            will(returnValue(ivySettingsDummy));
+
+            allowing(ivyService.getDependencyResolver()).resolve(configurationDummy, ivyStub, moduleDescriptorDummy);
+            will(returnValue(resolvedConfiguration));
+
+            allowing(ivyService.getResolveModuleDescriptorConverter()).convert(WrapUtil.toSet(configurationDummy), moduleDummy,
+                    ivySettingsDummy);
+            will(returnValue(moduleDescriptorDummy));
+
+            allowing(ivyService.getSettingsConverter()).convertForResolve(dependencyResolversDummy, cacheParentDirDummy,
+                    internalRepositoryDummy, clientModuleRegistryDummy);
+            will(returnValue(ivySettingsDummy));
+        }});
+
+        ivyService.setIvyFactory(ivyFactoryStub);
+        assertThat(ivyService.resolve(configurationDummy), sameInstance(resolvedConfiguration));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyServiceTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyServiceTest.java
new file mode 100644
index 0000000..e2c35a9
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultIvyServiceTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
+import static org.hamcrest.Matchers.sameInstance;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultIvyServiceTest {
+    private JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+
+    @Test
+    public void init() {
+        DependencyMetaDataProvider dependencyMetaDataProvider = context.mock(DependencyMetaDataProvider.class);
+        ResolverProvider resolverProvider = context.mock(ResolverProvider.class);
+        SettingsConverter settingsConverter = context.mock(SettingsConverter.class);
+        ModuleDescriptorConverter resolveModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "resolve");
+        ModuleDescriptorConverter publishModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "publish");
+        ModuleDescriptorConverter fileModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class, "file");
+        IvyFactory ivyFactory = context.mock(IvyFactory.class);
+        IvyDependencyResolver dependencyResolver = context.mock(IvyDependencyResolver.class);
+        IvyDependencyPublisher dependencyPublisher = context.mock(IvyDependencyPublisher.class);
+        Map clientModuleRegistry = new HashMap();
+
+        DefaultIvyService ivyService = new DefaultIvyService(dependencyMetaDataProvider, resolverProvider,
+                settingsConverter, resolveModuleDescriptorConverter, publishModuleDescriptorConverter,
+                fileModuleDescriptorConverter,
+                ivyFactory, dependencyResolver, dependencyPublisher, clientModuleRegistry);
+        
+        assertThat(ivyService.getMetaDataProvider(), sameInstance(dependencyMetaDataProvider));
+        assertThat(ivyService.getSettingsConverter(), sameInstance(settingsConverter));
+        assertThat(ivyService.getResolveModuleDescriptorConverter(), sameInstance(resolveModuleDescriptorConverter));
+        assertThat(ivyService.getPublishModuleDescriptorConverter(), sameInstance(publishModuleDescriptorConverter));
+        assertThat(ivyService.getFileModuleDescriptorConverter(), sameInstance(fileModuleDescriptorConverter));
+        assertThat(ivyService.getIvyFactory(), sameInstance(ivyFactory));
+        assertThat(ivyService.getDependencyResolver(), sameInstance(dependencyResolver));
+        assertThat(ivyService.getDependencyPublisher(), sameInstance(dependencyPublisher));
+        assertThat(ivyService.getClientModuleRegistry(), sameInstance(clientModuleRegistry));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultPublishOptionsFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultPublishOptionsFactoryTest.java
new file mode 100644
index 0000000..5563d7a
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultPublishOptionsFactoryTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultResolverFactoryTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultResolverFactoryTest.groovy
new file mode 100644
index 0000000..af3b866
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultResolverFactoryTest.groovy
@@ -0,0 +1,111 @@
+/*
+ * 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.api.internal.artifacts.ivyservice
+
+import org.apache.ivy.core.cache.DefaultRepositoryCacheManager
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.artifacts.ResolverContainer
+import org.junit.Before
+import org.junit.Test
+import org.apache.ivy.plugins.resolver.*
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertTrue
+
+/**
+ * @author Hans Dockter
+ */
+class DefaultResolverFactoryTest {
+    static final String RESOLVER_URL = 'http://a.b.c/'
+    static final Map RESOLVER_MAP = [name: 'mapresolver', url: 'http://x.y.z/']
+    static final IBiblioResolver TEST_RESOLVER = new IBiblioResolver()
+    static {
+        TEST_RESOLVER.name = 'ivyResolver'
+    }
+
+    static final String TEST_REPO_NAME = 'reponame'
+    static final String TEST_REPO_URL = 'http://www.gradle.org'
+    static final File TEST_CACHE_DIR = 'somepath' as File
+
+    DefaultResolverFactory factory
+
+    @Before public void setUp() {
+        factory = new DefaultResolverFactory()
+    }
+
+    @Test (expected = InvalidUserDataException) public void testCreateResolver() {
+        checkMavenResolver(factory.createResolver(RESOLVER_URL), RESOLVER_URL, RESOLVER_URL)
+        checkMavenResolver(factory.createResolver(RESOLVER_MAP), RESOLVER_MAP.name, RESOLVER_MAP.url)
+        DependencyResolver resolver = factory.createResolver(TEST_RESOLVER)
+        assert resolver.is(TEST_RESOLVER)
+        def someIllegalDescription = new NullPointerException()
+        factory.createResolver(someIllegalDescription)
+    }
+
+    private void checkMavenResolver(IBiblioResolver resolver, String name, String url) {
+        assertEquals url, resolver.root
+        assertEquals name, resolver.name
+        assertTrue resolver.allownomd
+    }
+
+    @Test
+    public void testCreateMavenRepoWithAdditionalJarUrls() {
+        String testUrl2 = 'http://www.gradle2.org'
+        DualResolver dualResolver = factory.createMavenRepoResolver(TEST_REPO_NAME, TEST_REPO_URL, testUrl2)
+        assertTrue dualResolver.allownomd
+        checkIBiblio(dualResolver.ivyResolver, "_poms")
+        URLResolver urlResolver = dualResolver.artifactResolver
+        assert urlResolver.m2compatible
+        assert urlResolver.artifactPatterns.contains("$TEST_REPO_URL/$ResolverContainer.MAVEN_REPO_PATTERN" as String)
+        assert urlResolver.artifactPatterns.contains("$testUrl2/$ResolverContainer.MAVEN_REPO_PATTERN" as String)
+        assertEquals("${TEST_REPO_NAME}_jars" as String, urlResolver.name)
+    }
+
+    @Test
+    public void testCreateMavenRepoWithNoAdditionalJarUrls() {
+        checkIBiblio(factory.createMavenRepoResolver(TEST_REPO_NAME, TEST_REPO_URL), "")
+    }
+
+    private void checkIBiblio(IBiblioResolver iBiblioResolver, String expectedNameSuffix) {
+        assert iBiblioResolver.usepoms
+        assert iBiblioResolver.m2compatible
+        assertTrue iBiblioResolver.allownomd
+        assertEquals(TEST_REPO_URL + '/', iBiblioResolver.root)
+        assertEquals(ResolverContainer.MAVEN_REPO_PATTERN, iBiblioResolver.pattern)
+        assertEquals("${TEST_REPO_NAME}$expectedNameSuffix" as String, iBiblioResolver.name)
+    }
+
+    @Test public void testCreateFlatDirResolver() {
+        File dir1 = new File('/rootFolder')
+        File dir2 = new File('/rootFolder2')
+        String expectedName = 'libs'
+        FileSystemResolver resolver = factory.createFlatDirResolver(expectedName, [dir1, dir2] as File[])
+        checkNoModuleRepository(resolver, expectedName,
+                [dir1, dir2].collect {"$it.absolutePath/$ResolverContainer.FLAT_DIR_RESOLVER_PATTERN"}, [])
+        assertEquals(new File(System.getProperty('java.io.tmpdir')).getCanonicalPath(),
+                new File(((DefaultRepositoryCacheManager) resolver.getRepositoryCacheManager()).getBasedir().getParent()).getCanonicalPath())
+
+    }
+
+    private void checkNoModuleRepository(RepositoryResolver resolver, String expectedName, List expectedArtifactPatterns,
+                                         List expectedIvyPatterns) {
+        assertEquals(expectedName, resolver.name)
+        assertEquals(expectedIvyPatterns, resolver.ivyPatterns)
+        assert expectedArtifactPatterns == resolver.artifactPatterns
+        assertTrue(resolver.allownomd)
+    }
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverterTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverterTest.groovy
new file mode 100644
index 0000000..fdaf37c
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/DefaultSettingsConverterTest.groovy
@@ -0,0 +1,139 @@
+/*
+ * 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.api.internal.artifacts.ivyservice
+
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor
+import org.apache.ivy.core.settings.IvySettings
+import org.apache.ivy.plugins.resolver.ChainResolver
+import org.apache.ivy.plugins.resolver.IBiblioResolver
+import org.gradle.api.artifacts.ResolverContainer
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.lib.legacy.ClassImposteriser
+import org.junit.Before
+import org.junit.Test
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertTrue
+import static org.junit.Assert.assertSame
+
+/**
+ * @author Hans Dockter
+ */
+class DefaultSettingsConverterTest {
+    static final IBiblioResolver TEST_RESOLVER = new IBiblioResolver()
+    static final IBiblioResolver TEST_RESOLVER_2 = new IBiblioResolver()
+    static {
+        TEST_RESOLVER.name = 'resolver'
+    }
+
+    static final IBiblioResolver TEST_BUILD_RESOLVER = new IBiblioResolver()
+    static {
+        TEST_BUILD_RESOLVER.name = 'buildResolver'
+    }
+
+    DefaultSettingsConverter converter
+
+    Map clientModuleRegistry
+
+    File testGradleUserHome
+
+    JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+
+    @Before public void setUp()  {
+        context.setImposteriser(ClassImposteriser.INSTANCE)
+        converter = new DefaultSettingsConverter()
+        clientModuleRegistry = [a: [:] as ModuleDescriptor]
+        testGradleUserHome = new File('gradleUserHome')
+    }
+
+    @Test public void testConvertForResolve() {
+        IvySettings settings = converter.convertForResolve([TEST_RESOLVER, TEST_RESOLVER_2], testGradleUserHome,
+                TEST_BUILD_RESOLVER, clientModuleRegistry)
+        ChainResolver chainResolver = settings.getResolver(DefaultSettingsConverter.CHAIN_RESOLVER_NAME)
+        assertEquals(3, chainResolver.resolvers.size())
+        assert chainResolver.resolvers[0].name.is(TEST_BUILD_RESOLVER.name)
+        assert chainResolver.resolvers[1].is(TEST_RESOLVER)
+        assert chainResolver.resolvers[2].is(TEST_RESOLVER_2)
+        assertTrue chainResolver.returnFirst
+
+        ClientModuleResolver clientModuleResolver = settings.getResolver(DefaultSettingsConverter.CLIENT_MODULE_NAME)
+        ChainResolver clientModuleChain = settings.getResolver(DefaultSettingsConverter.CLIENT_MODULE_CHAIN_NAME)
+        assertTrue clientModuleChain.returnFirst
+        assert clientModuleChain.resolvers[0].is(clientModuleResolver)
+        assert clientModuleChain.resolvers[1].is(chainResolver)
+        assert settings.defaultResolver.is(clientModuleChain)
+
+        [TEST_BUILD_RESOLVER.name, TEST_RESOLVER.name, TEST_RESOLVER_2.name, DefaultSettingsConverter.CHAIN_RESOLVER_NAME,
+                DefaultSettingsConverter.CLIENT_MODULE_NAME, DefaultSettingsConverter.CLIENT_MODULE_CHAIN_NAME].each {
+            assert settings.getResolver(it)
+            assert settings.getResolver(it).getRepositoryCacheManager().settings == settings
+        }
+
+        assertEquals(new File(testGradleUserHome, ResolverContainer.DEFAULT_CACHE_DIR_NAME),
+                settings.defaultCache)
+        assertEquals(settings.defaultCacheArtifactPattern, ResolverContainer.DEFAULT_CACHE_ARTIFACT_PATTERN)
+    }
+
+    @Test public void testConvertForPublish() {
+        IvySettings settings = converter.convertForPublish([TEST_RESOLVER, TEST_RESOLVER_2], testGradleUserHome,
+                TEST_BUILD_RESOLVER)
+        ChainResolver chainResolver = settings.getResolver(DefaultSettingsConverter.CHAIN_RESOLVER_NAME)
+        assertEquals(1, chainResolver.resolvers.size())
+        assert chainResolver.resolvers[0].name.is(TEST_BUILD_RESOLVER.name)
+        assertTrue chainResolver.returnFirst
+
+        ClientModuleResolver clientModuleResolver = settings.getResolver(DefaultSettingsConverter.CLIENT_MODULE_NAME)
+        ChainResolver clientModuleChain = settings.getResolver(DefaultSettingsConverter.CLIENT_MODULE_CHAIN_NAME)
+        assertTrue clientModuleChain.returnFirst
+        assert clientModuleChain.resolvers[0].is(clientModuleResolver)
+        assert clientModuleChain.resolvers[1].is(chainResolver)
+        assert settings.defaultResolver.is(clientModuleChain)
+
+        [TEST_BUILD_RESOLVER.name, TEST_RESOLVER.name, TEST_RESOLVER_2.name, DefaultSettingsConverter.CHAIN_RESOLVER_NAME,
+                DefaultSettingsConverter.CLIENT_MODULE_NAME, DefaultSettingsConverter.CLIENT_MODULE_CHAIN_NAME].each {
+            assert settings.getResolver(it)
+            assert settings.getResolver(it).getRepositoryCacheManager().settings == settings
+        }
+
+        assert settings.getResolver(TEST_RESOLVER.name).is(TEST_RESOLVER)
+        assert settings.getResolver(TEST_RESOLVER_2.name).is(TEST_RESOLVER_2)
+        assertEquals(new File(testGradleUserHome, ResolverContainer.DEFAULT_CACHE_DIR_NAME),
+                settings.defaultCache)
+        assertEquals(settings.defaultCacheArtifactPattern, ResolverContainer.DEFAULT_CACHE_ARTIFACT_PATTERN)
+    }
+
+    @Test
+    public void repositoryCacheManagerShouldBeSharedBetweenSettings() {
+        IvySettings settings1 = converter.convertForPublish([TEST_RESOLVER, TEST_RESOLVER_2], testGradleUserHome,
+                TEST_BUILD_RESOLVER)
+        IvySettings settings2 = converter.convertForPublish([TEST_RESOLVER, TEST_RESOLVER_2], testGradleUserHome,
+                TEST_BUILD_RESOLVER)
+        IvySettings settings3 = converter.convertForResolve([TEST_RESOLVER, TEST_RESOLVER_2], testGradleUserHome,
+                TEST_BUILD_RESOLVER, clientModuleRegistry)
+        assertSame(settings1.getDefaultRepositoryCacheManager(), settings2.getDefaultRepositoryCacheManager())
+        assertSame(settings1.getDefaultRepositoryCacheManager(), settings3.getDefaultRepositoryCacheManager())
+
+    }
+
+    @Test public void testWithGivenSettings() {
+        IvySettings ivySettings = [:] as IvySettings
+        converter.ivySettings = ivySettings
+        assert ivySettings.is(converter.convertForResolve([TEST_RESOLVER], new File(''),
+                TEST_BUILD_RESOLVER, clientModuleRegistry))
+        assert ivySettings.is(converter.convertForPublish([TEST_RESOLVER], new File(''),
+                TEST_BUILD_RESOLVER))
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingIvyServiceTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingIvyServiceTest.groovy
new file mode 100644
index 0000000..100ab39
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ErrorHandlingIvyServiceTest.groovy
@@ -0,0 +1,158 @@
+/*
+ * 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.ivyservice;
+
+
+import org.gradle.api.GradleException
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.artifacts.ResolveException
+import org.gradle.api.artifacts.ResolvedConfiguration
+import org.gradle.api.internal.artifacts.IvyService
+import org.gradle.api.specs.Spec
+import org.gradle.util.JUnit4GroovyMockery
+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.*
+
+ at RunWith(JMock.class)
+public class ErrorHandlingIvyServiceTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery();
+    private final IvyService ivyServiceMock = context.mock(IvyService.class);
+    private final ResolvedConfiguration resolvedConfigurationMock = context.mock(ResolvedConfiguration.class);
+    private final Configuration configurationMock = context.mock(Configuration.class, "<config display name>");
+    private final Spec<Dependency> specDummy = context.mock(Spec.class);
+    private final RuntimeException failure = new RuntimeException();
+    private final ErrorHandlingIvyService ivyService = new ErrorHandlingIvyService(ivyServiceMock);
+
+    @Test
+    public void resolveDelegatesToBackingService() {
+        context.checking {
+            one(ivyServiceMock).resolve(configurationMock);
+            will(returnValue(resolvedConfigurationMock));
+        }
+
+        ResolvedConfiguration resolvedConfiguration = ivyService.resolve(configurationMock);
+
+        context.checking {
+            one(resolvedConfigurationMock).hasError();
+            one(resolvedConfigurationMock).rethrowFailure();
+            one(resolvedConfigurationMock).getFiles(specDummy);
+        }
+
+        resolvedConfiguration.hasError();
+        resolvedConfiguration.rethrowFailure();
+        resolvedConfiguration.getFiles(specDummy);
+    }
+
+    @Test
+    public void returnsAnExceptionThrowingConfigurationWhenResolveFails() {
+
+        context.checking {
+            one(ivyServiceMock).resolve(configurationMock);
+            will(throwException(failure));
+        }
+
+        ResolvedConfiguration resolvedConfiguration = ivyService.resolve(configurationMock);
+
+        assertThat(resolvedConfiguration.hasError(), equalTo(true));
+
+        assertFailsWithResolveException {
+            resolvedConfiguration.rethrowFailure();
+        }
+        assertFailsWithResolveException {
+            resolvedConfiguration.getFiles(specDummy);
+        }
+        assertFailsWithResolveException {
+            resolvedConfiguration.getFirstLevelModuleDependencies();
+        }
+        assertFailsWithResolveException {
+            resolvedConfiguration.getResolvedArtifacts();
+        }
+    }
+
+    @Test
+    public void wrapsExceptionsThrownByResolvedConfiguration() {
+        context.checking {
+            one(ivyServiceMock).resolve(configurationMock);
+            will(returnValue(resolvedConfigurationMock));
+        }
+
+        ResolvedConfiguration resolvedConfiguration = ivyService.resolve(configurationMock);
+
+        context.checking {
+            allowing(resolvedConfigurationMock).rethrowFailure()
+            will(throwException(failure))
+            allowing(resolvedConfigurationMock).getFiles(specDummy)
+            will(throwException(failure))
+            allowing(resolvedConfigurationMock).getFirstLevelModuleDependencies()
+            will(throwException(failure))
+            allowing(resolvedConfigurationMock).getResolvedArtifacts()
+            will(throwException(failure))
+        }
+
+        assertFailsWithResolveException {
+            resolvedConfiguration.rethrowFailure();
+        }
+        assertFailsWithResolveException {
+            resolvedConfiguration.getFiles(specDummy);
+        }
+        assertFailsWithResolveException {
+            resolvedConfiguration.getFirstLevelModuleDependencies();
+        }
+        assertFailsWithResolveException {
+            resolvedConfiguration.getResolvedArtifacts();
+        }
+    }
+
+    @Test
+    public void publishDelegatesToBackingService() {
+        context.checking {
+            one(ivyServiceMock).publish([configurationMock] as Set, null, [])
+        }
+
+        ivyService.publish([configurationMock] as Set, null, [])
+    }
+
+    @Test
+    public void wrapsPublishException() {
+        context.checking {
+            one(ivyServiceMock).publish([configurationMock] as Set, null, [])
+            will(throwException(failure))
+        }
+
+        try {
+            ivyService.publish([configurationMock] as Set, null, [])
+            fail()
+        }
+        catch(GradleException e) {
+            assertThat e.message, equalTo("Could not publish configurations [<config display name>].")
+            assertThat(e.cause, sameInstance((Throwable) failure));
+        }
+    }
+
+    def assertFailsWithResolveException(Closure cl) {
+        try {
+            cl();
+            fail();
+        } catch (ResolveException e) {
+            assertThat(e.message, equalTo("Could not resolve all dependencies for <config display name>."));
+            assertThat(e.cause, sameInstance((Throwable) failure));
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/GradleIBiblioResolverTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/GradleIBiblioResolverTest.groovy
new file mode 100644
index 0000000..b91c082
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/GradleIBiblioResolverTest.groovy
@@ -0,0 +1,72 @@
+/*
+ * 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.ivyservice
+
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+class GradleIBiblioResolverTest extends Specification{
+    GradleIBiblioResolver gradleIBiblioResolver = new GradleIBiblioResolver()
+
+    def testInit() {
+        expect:
+        gradleIBiblioResolver.getSnapshotTimeout().is(GradleIBiblioResolver.DAILY)
+    }
+    
+    def timeoutStrategyNever_shouldReturnAlwaysFalse() {
+        expect:
+        !GradleIBiblioResolver.NEVER.isCacheTimedOut(0)
+        !GradleIBiblioResolver.NEVER.isCacheTimedOut(System.currentTimeMillis())
+    }
+
+    def timeoutStrategyAlways_shouldReturnAlwaysTrue() {
+        expect:
+        GradleIBiblioResolver.ALWAYS.isCacheTimedOut(0)
+        GradleIBiblioResolver.ALWAYS.isCacheTimedOut(System.currentTimeMillis())
+    }
+
+    def timeoutStrategyDaily() {
+        expect:
+        !GradleIBiblioResolver.DAILY.isCacheTimedOut(System.currentTimeMillis())
+        GradleIBiblioResolver.ALWAYS.isCacheTimedOut(System.currentTimeMillis() - 24 * 60 * 60 * 1000)
+    }
+
+    def timeoutInterval() {
+        def interval = new GradleIBiblioResolver.Interval(1000)
+
+        expect:
+        interval.isCacheTimedOut(System.currentTimeMillis() - 5000)
+        !interval.isCacheTimedOut(System.currentTimeMillis())
+    }
+
+    def setTimeoutByMilliseconds() {
+        when:
+        gradleIBiblioResolver.setSnapshotTimeout(1000)
+
+        then:
+        ((GradleIBiblioResolver.Interval) gradleIBiblioResolver.getSnapshotTimeout()).interval == 1000
+    }
+
+    def setTimeoutByStrategy() {
+        when:
+        gradleIBiblioResolver.setSnapshotTimeout(GradleIBiblioResolver.NEVER)
+
+        then:
+        gradleIBiblioResolver.getSnapshotTimeout().is(GradleIBiblioResolver.NEVER)
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtilTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtilTest.groovy
new file mode 100644
index 0000000..6dbe063
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/IvyUtilTest.groovy
@@ -0,0 +1,34 @@
+/*
+ * 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.api.internal.artifacts.ivyservice
+
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.junit.Test
+import static org.junit.Assert.assertEquals
+
+/**
+ * @author Hans Dockter
+ */
+class IvyUtilTest {
+    @Test public void testModuleRevisionId() {
+        List l = ['myorg', 'myname', 'myrev']
+        ModuleRevisionId moduleRevisionId = ModuleRevisionId.newInstance(*l)
+        assertEquals(l[0], moduleRevisionId.organisation)
+        assertEquals(l[1], moduleRevisionId.name)
+        assertEquals(l[2], moduleRevisionId.revision)
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/Report2ClasspathTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/Report2ClasspathTest.groovy
new file mode 100644
index 0000000..1b53a8e
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/Report2ClasspathTest.groovy
@@ -0,0 +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.api.internal.artifacts.ivyservice
+
+import org.junit.Test
+
+/**
+ * @author Hans Dockter
+ */
+class Report2ClasspathTest {
+    @Test public void testGetClasspath() {
+      // todo implement 
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.java
new file mode 100644
index 0000000..0143013
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/SelfResolvingDependencyResolverTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.ivyservice;
+
+import org.apache.ivy.Ivy;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ResolvedArtifact;
+import org.gradle.api.artifacts.ResolvedConfiguration;
+import org.gradle.api.artifacts.ResolvedDependency;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.artifacts.DependencyInternal;
+import org.gradle.api.internal.artifacts.DependencyResolveContext;
+import org.gradle.api.specs.Specs;
+import org.hamcrest.Description;
+import org.jmock.Expectations;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.ParseException;
+
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class SelfResolvingDependencyResolverTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final IvyDependencyResolver delegate = context.mock(IvyDependencyResolver.class);
+    private final ResolvedConfiguration resolvedConfiguration = context.mock(ResolvedConfiguration.class);
+    private final Configuration configuration = context.mock(Configuration.class);
+    private final Ivy ivy = Ivy.newInstance();
+    private final ModuleDescriptor moduleDescriptor = context.mock(ModuleDescriptor.class);
+
+    private final SelfResolvingDependencyResolver resolver = new SelfResolvingDependencyResolver(delegate);
+
+    @Test
+    public void wrapsResolvedConfigurationProvidedByDelegate() {
+        context.checking(new Expectations() {{
+            one(delegate).resolve(configuration, ivy, moduleDescriptor);
+            will(returnValue(resolvedConfiguration));
+            allowing(configuration).getAllDependencies(DependencyInternal.class);
+            will(returnValue(toSet()));
+            allowing(configuration).isTransitive();
+            will(returnValue(true));
+        }});
+
+        ResolvedConfiguration configuration = resolver.resolve(this.configuration, ivy, moduleDescriptor);
+        assertThat(configuration, not(sameInstance(resolvedConfiguration)));
+
+        final File file = new File("file");
+
+        context.checking(new Expectations() {{
+            one(resolvedConfiguration).getFiles(Specs.SATISFIES_ALL);
+            will(returnValue(toSet(file)));
+        }});
+
+        assertThat(configuration.getFiles(Specs.SATISFIES_ALL), equalTo(toLinkedSet(file)));
+    }
+    
+    @Test
+    public void addsFilesFromSelfResolvingDependenciesBeforeFilesFromResolvedConfiguration() {
+        final DependencyInternal dependency = context.mock(DependencyInternal.class);
+
+        context.checking(new Expectations() {{
+            one(delegate).resolve(configuration, ivy, moduleDescriptor);
+            will(returnValue(resolvedConfiguration));
+            allowing(configuration).getAllDependencies(DependencyInternal.class);
+            will(returnValue(toSet(dependency)));
+            allowing(configuration).isTransitive();
+            will(returnValue(true));
+        }});
+
+        ResolvedConfiguration actualResolvedConfiguration = resolver.resolve(this.configuration, ivy, moduleDescriptor);
+        assertThat(actualResolvedConfiguration, not(sameInstance(resolvedConfiguration)));
+
+        final File configFile = new File("from config");
+        final File depFile = new File("from dep");
+        final FileCollection depFiles = context.mock(FileCollection.class);
+
+        final boolean transitive = true;
+        context.checking(new Expectations() {{
+            allowing(configuration);
+            will(returnValue(transitive));
+            one(resolvedConfiguration).getFiles(Specs.SATISFIES_ALL);
+            will(returnValue(toSet(configFile)));
+            one(dependency).resolve(with(notNullValue(DependencyResolveContext.class)));
+            will(new Action() {
+                public void describeTo(Description description) {
+                    description.appendText("add files to context");
+                }
+
+                public Object invoke(Invocation invocation) throws Throwable {
+                    ((DependencyResolveContext) invocation.getParameter(0)).add(depFiles);
+                    return null;
+                }
+            });
+            allowing(depFiles).getFiles();
+            will(returnValue(toSet(depFile)));
+        }});
+
+        assertThat(actualResolvedConfiguration.getFiles(Specs.SATISFIES_ALL), equalTo(toLinkedSet(depFile, configFile)));
+    }
+
+    @Test
+    public void testGetModuleDependencies() throws IOException, ParseException {
+        context.checking(new Expectations() {{
+            one(delegate).resolve(configuration, ivy, moduleDescriptor);
+            will(returnValue(resolvedConfiguration));
+            allowing(configuration).getAllDependencies(DependencyInternal.class);
+            will(returnValue(toSet()));
+            allowing(configuration).isTransitive();
+            will(returnValue(true));
+        }});
+
+        final ResolvedDependency resolvedDependency = context.mock(ResolvedDependency.class);
+
+        context.checking(new Expectations() {{
+            one(resolvedConfiguration).getFirstLevelModuleDependencies();
+            will(returnValue(toSet(resolvedDependency)));
+        }});
+
+        assertThat(resolver.resolve(this.configuration, ivy, moduleDescriptor).getFirstLevelModuleDependencies(),
+                equalTo(toSet(resolvedDependency)));
+    }
+
+    @Test
+    public void testGetResolvedArtifacts() {
+        context.checking(new Expectations() {{
+            one(delegate).resolve(configuration, ivy, moduleDescriptor);
+            will(returnValue(resolvedConfiguration));
+            allowing(configuration).getAllDependencies(DependencyInternal.class);
+            will(returnValue(toSet()));
+            allowing(configuration).isTransitive();
+            will(returnValue(true));
+        }});
+
+        final ResolvedArtifact resolvedArtifact = context.mock(ResolvedArtifact.class);
+
+        context.checking(new Expectations() {{
+            one(resolvedConfiguration).getResolvedArtifacts();
+            will(returnValue(toSet(resolvedArtifact)));
+        }});
+
+        assertThat(resolver.resolve(this.configuration, ivy, moduleDescriptor).getResolvedArtifacts(),
+                equalTo(toSet(resolvedArtifact)));
+    }
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsIvyServiceTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsIvyServiceTest.java
new file mode 100644
index 0000000..96efa5e
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ShortcircuitEmptyConfigsIvyServiceTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.ivyservice;
+
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ResolvedConfiguration;
+import org.gradle.api.internal.artifacts.IvyService;
+import org.gradle.api.specs.Specs;
+import static org.gradle.util.Matchers.isEmpty;
+import static org.gradle.util.WrapUtil.toList;
+import static org.gradle.util.WrapUtil.toSet;
+import static org.hamcrest.Matchers.sameInstance;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+ at RunWith(JMock.class)
+public class ShortcircuitEmptyConfigsIvyServiceTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final IvyService delegate = context.mock(IvyService.class);
+    private final Configuration configuration = context.mock(Configuration.class);
+    private final ShortcircuitEmptyConfigsIvyService ivyService = new ShortcircuitEmptyConfigsIvyService(delegate);
+
+    @Test
+    public void resolveReturnsEmptyResolvedConfigWhenConfigHasNoDependencies() {
+        context.checking(new Expectations(){{
+            allowing(configuration).getAllDependencies();
+            will(returnValue(Collections.emptySet()));
+        }});
+
+        ResolvedConfiguration resolvedConfig = ivyService.resolve(configuration);
+
+        assertFalse(resolvedConfig.hasError());
+        resolvedConfig.rethrowFailure();
+        assertThat(resolvedConfig.getFiles(Specs.<Dependency>satisfyAll()), isEmpty());
+        assertThat(resolvedConfig.getFirstLevelModuleDependencies(), isEmpty());
+        assertThat(resolvedConfig.getResolvedArtifacts(), isEmpty());
+    }
+
+    @Test
+    public void resolveDelegatesToBackingServiceWhenConfigHasDependencies() {
+        final Dependency dependencyDummy = context.mock(Dependency.class);
+        final ResolvedConfiguration resolvedConfigDummy = context.mock(ResolvedConfiguration.class);
+
+        context.checking(new Expectations() {{
+            allowing(configuration).getAllDependencies();
+            will(returnValue(toSet(dependencyDummy)));
+
+            one(delegate).resolve(configuration);
+            will(returnValue(resolvedConfigDummy));
+        }});
+
+        assertThat(ivyService.resolve(configuration), sameInstance(resolvedConfigDummy));
+    }
+
+    @Test
+    public void publishDelegatesToBackingService() {
+        final Set<Configuration> configurations = toSet(configuration);
+        final File someDescriptorDestination = new File("somePth");
+        final List<DependencyResolver> resolvers = toList(context.mock(DependencyResolver.class));
+
+        context.checking(new Expectations(){{
+            one(delegate).publish(configurations, someDescriptorDestination, resolvers);
+        }});
+
+        ivyService.publish(configurations, someDescriptorDestination, resolvers);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverterTest.java
new file mode 100644
index 0000000..69cea10
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultArtifactsToModuleDescriptorConverterTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.internal.artifacts.ivyservice.DefaultIvyDependencyPublisher;
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact;
+import org.gradle.util.HelperUtil;
+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;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultArtifactsToModuleDescriptorConverterTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    @Test
+    public void testAddArtifacts() {
+        final PublishArtifact publishArtifactConf1 = createNamedPublishArtifact("conf1");
+        Configuration configurationStub1 = createConfigurationStub(publishArtifactConf1);
+        final PublishArtifact publishArtifactConf2 = createNamedPublishArtifact("conf2");
+        Configuration configurationStub2 = createConfigurationStub(publishArtifactConf2);
+        final ArtifactsExtraAttributesStrategy artifactsExtraAttributesStrategyMock = context.mock(ArtifactsExtraAttributesStrategy.class);
+        final Map<String, String> extraAttributesArtifact1 = WrapUtil.toMap("name", publishArtifactConf1.getName());
+        final Map<String, String> extraAttributesArtifact2 = WrapUtil.toMap("name", publishArtifactConf2.getName());
+        context.checking(new Expectations() {{
+            one(artifactsExtraAttributesStrategyMock).createExtraAttributes(publishArtifactConf1);
+            will(returnValue(extraAttributesArtifact1));
+            one(artifactsExtraAttributesStrategyMock).createExtraAttributes(publishArtifactConf2);
+            will(returnValue(extraAttributesArtifact2));
+        }});
+        DefaultModuleDescriptor moduleDescriptor = HelperUtil.createModuleDescriptor(WrapUtil.toSet(configurationStub1.getName(),
+                configurationStub2.getName()));
+
+        DefaultArtifactsToModuleDescriptorConverter artifactsToModuleDescriptorConverter =
+                new DefaultArtifactsToModuleDescriptorConverter(artifactsExtraAttributesStrategyMock);
+
+        artifactsToModuleDescriptorConverter.addArtifacts(moduleDescriptor, WrapUtil.toSet(configurationStub1, configurationStub2));
+
+        assertArtifactIsAdded(configurationStub1, moduleDescriptor, extraAttributesArtifact1);
+        assertArtifactIsAdded(configurationStub2, moduleDescriptor, extraAttributesArtifact2);
+        assertThat(moduleDescriptor.getAllArtifacts().length, equalTo(2));
+    }
+
+    @Test
+    public void testIvyFileStrategy() {
+        assertThat(
+                DefaultArtifactsToModuleDescriptorConverter.IVY_FILE_STRATEGY.createExtraAttributes(context.mock(PublishArtifact.class)),
+                equalTo((Map) new HashMap<String, String>()));
+    }
+
+    @Test
+    public void testResolveStrategy() {
+        PublishArtifact publishArtifact = createNamedPublishArtifact("someName");
+        Map<String, String> expectedExtraAttributes = WrapUtil.toMap(DefaultIvyDependencyPublisher.FILE_PATH_EXTRA_ATTRIBUTE, publishArtifact.getFile().getAbsolutePath());
+        assertThat(
+                DefaultArtifactsToModuleDescriptorConverter.RESOLVE_STRATEGY.createExtraAttributes(publishArtifact),
+                equalTo(expectedExtraAttributes));
+    }
+
+    private void assertArtifactIsAdded(Configuration configuration, DefaultModuleDescriptor moduleDescriptor, Map<String, String> extraAttributes) {
+        assertThat(moduleDescriptor.getArtifacts(configuration.getName()),
+                equalTo(WrapUtil.toArray(expectedIvyArtifact(configuration, moduleDescriptor, extraAttributes))));
+    }
+
+    private Artifact expectedIvyArtifact(Configuration configuration, ModuleDescriptor moduleDescriptor, Map<String, String> additionalExtraAttributes) {
+        PublishArtifact publishArtifact = configuration.getArtifacts().iterator().next();
+        Map<String,String> extraAttributes = WrapUtil.toMap(Dependency.CLASSIFIER, publishArtifact.getClassifier());
+        extraAttributes.putAll(additionalExtraAttributes);
+        return new DefaultArtifact(moduleDescriptor.getModuleRevisionId(),
+                publishArtifact.getDate(),
+                publishArtifact.getName(),
+                publishArtifact.getType(),
+                publishArtifact.getExtension(),
+                extraAttributes);
+    }
+
+    private Configuration createConfigurationStub(final PublishArtifact publishArtifact) {
+        final Configuration configurationStub = IvyConverterTestUtil.createNamedConfigurationStub(publishArtifact.getName(), context);
+        context.checking(new Expectations() {{
+            allowing(configurationStub).getArtifacts();
+            will(returnValue(WrapUtil.toSet(publishArtifact)));
+        }});
+        return configurationStub;
+    }
+
+    private PublishArtifact createNamedPublishArtifact(String name) {
+        return new DefaultPublishArtifact(name, "ext", "type", "classifier", new Date(), new File("somePath"));
+    }
+
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverterTest.java
new file mode 100644
index 0000000..3b27122
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultConfigurationsToModuleDescriptorConverterTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.internal.artifacts.configurations.Configurations;
+import org.gradle.util.HelperUtil;
+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;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultConfigurationsToModuleDescriptorConverterTest {
+    private DefaultConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverter = new DefaultConfigurationsToModuleDescriptorConverter();
+
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    @Test
+    public void testAddConfigurations() {
+        Configuration configurationStub1 = createNamesAndExtendedConfigurationStub("conf1");
+        Configuration configurationStub2 = createNamesAndExtendedConfigurationStub("conf2", configurationStub1);
+        final DefaultModuleDescriptor moduleDescriptor = HelperUtil.createModuleDescriptor(Collections.EMPTY_SET);
+
+        configurationsToModuleDescriptorConverter.addConfigurations(moduleDescriptor, WrapUtil.toSet(configurationStub1, configurationStub2));
+
+        assertIvyConfigurationIsCorrect(moduleDescriptor.getConfiguration(configurationStub1.getName()),
+                expectedIvyConfiguration(configurationStub1));
+        assertIvyConfigurationIsCorrect(moduleDescriptor.getConfiguration(configurationStub2.getName()),
+                expectedIvyConfiguration(configurationStub2));
+        assertThat(moduleDescriptor.getConfigurations().length, equalTo(2));
+    }
+
+    private void assertIvyConfigurationIsCorrect(org.apache.ivy.core.module.descriptor.Configuration actualConfiguration,
+                                                 org.apache.ivy.core.module.descriptor.Configuration expectedConfiguration) {
+        assertThat(actualConfiguration.getDescription(), equalTo(expectedConfiguration.getDescription()));
+        assertThat(actualConfiguration.isTransitive(), equalTo(expectedConfiguration.isTransitive()));
+        assertThat(actualConfiguration.getVisibility(), equalTo(expectedConfiguration.getVisibility()));
+        assertThat(actualConfiguration.getName(), equalTo(expectedConfiguration.getName()));
+        assertThat(actualConfiguration.getExtends(), equalTo(expectedConfiguration.getExtends()));
+    }
+
+    private org.apache.ivy.core.module.descriptor.Configuration expectedIvyConfiguration(Configuration configuration) {
+        return new org.apache.ivy.core.module.descriptor.Configuration(
+                configuration.getName(),
+                configuration.isVisible() ? org.apache.ivy.core.module.descriptor.Configuration.Visibility.PUBLIC : org.apache.ivy.core.module.descriptor.Configuration.Visibility.PRIVATE,
+                configuration.getDescription(),
+                Configurations.getNames(configuration.getExtendsFrom(), false).toArray(new String[configuration.getExtendsFrom().size()]),
+                configuration.isTransitive(),
+                null);
+    }
+
+    private Configuration createNamesAndExtendedConfigurationStub(final String name, final Configuration... extendsFromConfigurations) {
+        final Configuration configurationStub = IvyConverterTestUtil.createNamedConfigurationStub(name, context);
+        context.checking(new Expectations() {{
+            allowing(configurationStub).isTransitive();
+            will(returnValue(true));
+
+            allowing(configurationStub).getDescription();
+            will(returnValue(HelperUtil.createUniqueId()));
+
+            allowing(configurationStub).isVisible();
+            will(returnValue(true));
+
+            allowing(configurationStub).getExtendsFrom();
+            will(returnValue(WrapUtil.toSet(extendsFromConfigurations)));
+        }});
+        return configurationStub;
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverterTest.java
new file mode 100644
index 0000000..e3dd41c
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultExcludeRuleConverterTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.ivyservice.moduleconverter;
+
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
+import org.apache.ivy.plugins.matcher.PatternMatcher;
+import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.api.internal.artifacts.DefaultExcludeRule;
+import org.gradle.util.GUtil;
+import org.gradle.util.WrapUtil;
+import org.hamcrest.Matchers;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultExcludeRuleConverterTest {
+
+    @Test
+    public void testCreateExcludeRule() {
+        String configurationName = "someConf";
+        Map excludeRuleArgs = GUtil.map(ExcludeRule.GROUP_KEY, "someOrg", ExcludeRule.MODULE_KEY, "someModule");
+        org.apache.ivy.core.module.descriptor.ExcludeRule ivyExcludeRule =
+                new DefaultExcludeRuleConverter().createExcludeRule(configurationName, new DefaultExcludeRule(excludeRuleArgs));
+        assertThat(ivyExcludeRule.getId().getModuleId().getOrganisation(),
+                Matchers.equalTo(excludeRuleArgs.get(ExcludeRule.GROUP_KEY)));
+        assertThat(ivyExcludeRule.getId().getName(),
+                Matchers.equalTo(PatternMatcher.ANY_EXPRESSION));
+        assertThat(ivyExcludeRule.getId().getExt(),
+                Matchers.equalTo(PatternMatcher.ANY_EXPRESSION));
+        assertThat(ivyExcludeRule.getId().getType(), 
+                Matchers.equalTo(PatternMatcher.ANY_EXPRESSION));
+        assertThat((ExactPatternMatcher) ivyExcludeRule.getMatcher(),
+                Matchers.equalTo(ExactPatternMatcher.INSTANCE));
+        assertThat(ivyExcludeRule.getConfigurations(),
+                Matchers.equalTo(WrapUtil.toArray(configurationName)));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactoryTest.java
new file mode 100644
index 0000000..7ce1eb3
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/DefaultModuleDescriptorFactoryTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.internal.artifacts.DefaultModule;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultModuleDescriptorFactoryTest {
+    @Test
+    public void testCreateModuleDescriptor() {
+        Module module = new DefaultModule("org", "name", "version", "status");
+        DefaultModuleDescriptor moduleDescriptor = new DefaultModuleDescriptorFactory().createModuleDescriptor(module);
+        assertThat(moduleDescriptor.getModuleRevisionId().getOrganisation(), equalTo(module.getGroup()));
+        assertThat(moduleDescriptor.getModuleRevisionId().getName(), equalTo(module.getName()));
+        assertThat(moduleDescriptor.getModuleRevisionId().getRevision(), equalTo(module.getVersion()));
+        assertThat(moduleDescriptor.getStatus(), equalTo(module.getStatus()));
+        assertThat(moduleDescriptor.getPublicationDate(), equalTo(null));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/IvyConverterTestUtil.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/IvyConverterTestUtil.java
new file mode 100644
index 0000000..9cea636
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/IvyConverterTestUtil.java
@@ -0,0 +1,34 @@
+/*
+ * 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.ivyservice.moduleconverter;
+
+import org.gradle.api.artifacts.Configuration;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+
+/**
+ * @author Hans Dockter
+ */
+public class IvyConverterTestUtil {
+    public static Configuration createNamedConfigurationStub(final String name, Mockery context) {
+        final Configuration configurationStub = context.mock(Configuration.class, name);
+        context.checking(new Expectations() {{
+            allowing(configurationStub).getName();
+            will(returnValue(name));
+        }});
+        return configurationStub;
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverterTest.java
new file mode 100644
index 0000000..7c379c1
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/PublishModuleDescriptorConverterTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.api.internal.artifacts.ivyservice.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.settings.IvySettings;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.internal.artifacts.ivyservice.ModuleDescriptorConverter;
+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;
+import org.jmock.lib.legacy.ClassImposteriser;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class PublishModuleDescriptorConverterTest {
+    private JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+
+    @Test
+    public void convert() {
+        final Set<Configuration> configurationsDummy = WrapUtil.toSet(context.mock(Configuration.class, "conf1"),
+                context.mock(Configuration.class, "conf2"));
+        final Module moduleDummy = context.mock(Module.class);
+        final IvySettings ivySettingsDummy = context.mock(IvySettings.class);
+        final DefaultModuleDescriptor moduleDescriptorDummy = context.mock(DefaultModuleDescriptor.class);
+        final ArtifactsToModuleDescriptorConverter artifactsToModuleDescriptorConverter = context.mock(ArtifactsToModuleDescriptorConverter.class);
+        final ModuleDescriptorConverter resolveModuleDescriptorConverter = context.mock(ModuleDescriptorConverter.class);
+        PublishModuleDescriptorConverter publishModuleDescriptorConverter = new PublishModuleDescriptorConverter(
+                resolveModuleDescriptorConverter,
+                artifactsToModuleDescriptorConverter);
+
+        context.checking(new Expectations() {{
+            allowing(resolveModuleDescriptorConverter).convert(configurationsDummy, moduleDummy, ivySettingsDummy);
+            will(returnValue(moduleDescriptorDummy));
+            one(moduleDescriptorDummy).addExtraAttributeNamespace(PublishModuleDescriptorConverter.IVY_MAVEN_NAMESPACE_PREFIX,
+                    PublishModuleDescriptorConverter.IVY_MAVEN_NAMESPACE);
+            one(artifactsToModuleDescriptorConverter).addArtifacts(moduleDescriptorDummy, configurationsDummy);
+        }});
+
+        DefaultModuleDescriptor actualModuleDescriptor = (DefaultModuleDescriptor)
+                publishModuleDescriptorConverter.convert(configurationsDummy, moduleDummy, ivySettingsDummy);
+
+        assertThat(actualModuleDescriptor, equalTo(moduleDescriptorDummy));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverterTest.java
new file mode 100644
index 0000000..cbda0cc
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/ResolveModuleDescriptorConverterTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.api.internal.artifacts.ivyservice.moduleconverter;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.settings.IvySettings;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DependenciesToModuleDescriptorConverter;
+import org.gradle.util.WrapUtil;
+import static org.hamcrest.Matchers.equalTo;
+import org.jmock.Expectations;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class ResolveModuleDescriptorConverterTest {
+    private JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+
+    @Test
+    public void convert() {
+        final Set<Configuration> configurationsDummy = WrapUtil.toSet(context.mock(Configuration.class, "conf1"),
+                context.mock(Configuration.class, "conf2"));
+        final Module moduleDummy = context.mock(Module.class);
+        final IvySettings ivySettingsDummy = new IvySettings();
+        final DefaultModuleDescriptor moduleDescriptorDummy = context.mock(DefaultModuleDescriptor.class);
+        final ModuleDescriptorFactory moduleDescriptorFactoryStub = context.mock(ModuleDescriptorFactory.class);
+        final ConfigurationsToModuleDescriptorConverter configurationsToModuleDescriptorConverterMock = context.mock(ConfigurationsToModuleDescriptorConverter.class);
+        final DependenciesToModuleDescriptorConverter dependenciesToModuleDescriptorConverterMock = context.mock(DependenciesToModuleDescriptorConverter.class);
+
+        ResolveModuleDescriptorConverter resolveModuleDescriptorConverter = new ResolveModuleDescriptorConverter(
+                moduleDescriptorFactoryStub,
+                configurationsToModuleDescriptorConverterMock,
+                dependenciesToModuleDescriptorConverterMock);
+
+        context.checking(new Expectations() {{
+            allowing(moduleDescriptorFactoryStub).createModuleDescriptor(moduleDummy);
+            will(returnValue(moduleDescriptorDummy));
+            one(configurationsToModuleDescriptorConverterMock).addConfigurations(moduleDescriptorDummy, configurationsDummy);
+            one(dependenciesToModuleDescriptorConverterMock).addDependencyDescriptors(moduleDescriptorDummy, configurationsDummy, ivySettingsDummy);
+        }});
+
+        DefaultModuleDescriptor actualModuleDescriptor = (DefaultModuleDescriptor)
+                resolveModuleDescriptorConverter.convert(configurationsDummy, moduleDummy, ivySettingsDummy);
+
+        assertThat(actualModuleDescriptor, equalTo(moduleDescriptorDummy));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java
new file mode 100644
index 0000000..d39231a
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/AbstractDependencyDescriptorFactoryInternalTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyArtifactDescriptor;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.DependencyArtifact;
+import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.internal.artifacts.dependencies.DefaultDependencyArtifact;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.WrapUtil;
+import org.hamcrest.Matchers;
+import static org.hamcrest.Matchers.equalTo;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public abstract class AbstractDependencyDescriptorFactoryInternalTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    protected static final String TEST_CONF = "conf";
+    protected static final String TEST_DEP_CONF = "depconf1";
+    protected static final String TEST_OTHER_DEP_CONF = "depconf2";
+
+    private static final ExcludeRule TEST_EXCLUDE_RULE = new org.gradle.api.internal.artifacts.DefaultExcludeRule(WrapUtil.toMap("org", "testOrg"));
+    private static final org.apache.ivy.core.module.descriptor.ExcludeRule TEST_IVY_EXCLUDE_RULE = HelperUtil.getTestExcludeRule();
+    protected ExcludeRuleConverter excludeRuleConverterStub = context.mock(ExcludeRuleConverter.class);
+    protected final DefaultModuleDescriptor moduleDescriptor = HelperUtil.createModuleDescriptor(WrapUtil.toSet(TEST_CONF));
+    private DefaultDependencyArtifact artifact = new DefaultDependencyArtifact("name", "type", null, null, null);
+    private DefaultDependencyArtifact artifactWithClassifiers = new DefaultDependencyArtifact("name2", "type2", "ext2", "classifier2", "http://www.url2.com");
+
+    private DependencyDescriptorFactoryInternal dependencyDescriptorFactoryInternal;
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations() {{
+            allowing(excludeRuleConverterStub).createExcludeRule(TEST_CONF, TEST_EXCLUDE_RULE);
+            will(returnValue(TEST_IVY_EXCLUDE_RULE));
+        }});
+    }
+
+    protected void assertThataddDependenciesWithSameModuleRevisionIdAndDifferentConfsShouldBePartOfSameDependencyDescriptor(
+            ModuleDependency dependency1, ModuleDependency dependency2, DependencyDescriptorFactoryInternal factoryInternal
+    ) {
+        factoryInternal.addDependencyDescriptor(TEST_CONF, moduleDescriptor, dependency1);
+        factoryInternal.addDependencyDescriptor(TEST_CONF, moduleDescriptor, dependency2);
+        assertThat(moduleDescriptor.getDependencies().length, equalTo(1));
+
+        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor.getDependencies()[0];
+        assertThat(dependencyDescriptor.getDependencyConfigurations(TEST_CONF), Matchers.hasItemInArray(TEST_DEP_CONF));
+        assertThat(dependencyDescriptor.getDependencyConfigurations(TEST_CONF), Matchers.hasItemInArray(TEST_OTHER_DEP_CONF));
+        assertThat(dependencyDescriptor.getDependencyConfigurations(TEST_CONF).length, equalTo(2));
+    }
+
+    protected Dependency setUpDependency(ModuleDependency dependency) {
+        return dependency.addArtifact(artifact).
+                addArtifact(artifactWithClassifiers).
+                exclude(TEST_EXCLUDE_RULE.getExcludeArgs()).
+                setTransitive(true);
+    }
+
+    protected void assertDependencyDescriptorHasCommonFixtureValues(DefaultDependencyDescriptor dependencyDescriptor) {
+        assertThat(dependencyDescriptor.getParentRevisionId(), equalTo(moduleDescriptor.getModuleRevisionId()));
+        assertEquals(TEST_IVY_EXCLUDE_RULE, dependencyDescriptor.getExcludeRules(TEST_CONF)[0]);
+        assertThat(dependencyDescriptor.getDependencyConfigurations(TEST_CONF), equalTo(WrapUtil.toArray(TEST_DEP_CONF)));
+        assertThat(dependencyDescriptor.isTransitive(), equalTo(true));
+        assertDependencyDescriptorHasArtifacts(dependencyDescriptor);
+    }
+
+    private void assertDependencyDescriptorHasArtifacts(DefaultDependencyDescriptor dependencyDescriptor) {
+        List<DependencyArtifactDescriptor> artifactDescriptors = WrapUtil.toList(dependencyDescriptor.getDependencyArtifacts(TEST_CONF));
+        assertThat(artifactDescriptors.size(), equalTo(2));
+
+        
+        DependencyArtifactDescriptor artifactDescriptorWithoutClassifier = findDescriptor(artifactDescriptors, artifact);
+        assertEquals(new HashMap(), artifactDescriptorWithoutClassifier.getExtraAttributes());
+        assertEquals(null, artifactDescriptorWithoutClassifier.getUrl());
+        compareArtifacts(artifact, artifactDescriptorWithoutClassifier);
+        assertEquals(artifact.getType(), artifactDescriptorWithoutClassifier.getExt());
+
+        DependencyArtifactDescriptor artifactDescriptorWithClassifierAndConfs = findDescriptor(artifactDescriptors, artifactWithClassifiers);
+        assertEquals(WrapUtil.toMap(Dependency.CLASSIFIER, artifactWithClassifiers.getClassifier()), artifactDescriptorWithClassifierAndConfs.getQualifiedExtraAttributes());
+        compareArtifacts(artifactWithClassifiers, artifactDescriptorWithClassifierAndConfs);
+        assertEquals(artifactWithClassifiers.getExtension(), artifactDescriptorWithClassifierAndConfs.getExt());
+        try {
+            assertEquals(new URL(artifactWithClassifiers.getUrl()), artifactDescriptorWithClassifierAndConfs.getUrl());
+        } catch (MalformedURLException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private DependencyArtifactDescriptor findDescriptor(List<DependencyArtifactDescriptor> artifactDescriptors, DefaultDependencyArtifact dependencyArtifact) {
+        for (DependencyArtifactDescriptor artifactDescriptor : artifactDescriptors) {
+            if (artifactDescriptor.getName().equals(dependencyArtifact.getName())) {
+                return artifactDescriptor;
+            }
+        }
+        throw new RuntimeException("Descriptor could not be found");
+    }
+
+    private void compareArtifacts(DependencyArtifact artifact, DependencyArtifactDescriptor artifactDescriptor) {
+        assertEquals(artifact.getName(), artifactDescriptor.getName());
+        assertEquals(artifact.getType(), artifactDescriptor.getType());
+    }
+}
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactoryTest.java
new file mode 100644
index 0000000..27808f5
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ClientModuleDependencyDescriptorFactoryTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ClientModule;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.artifacts.dependencies.DefaultClientModule;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.util.WrapUtil;
+import org.hamcrest.Matchers;
+import static org.hamcrest.Matchers.equalTo;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+import java.util.HashMap;
+
+/**
+ * @author Hans Dockter
+ */
+public class ClientModuleDependencyDescriptorFactoryTest extends AbstractDependencyDescriptorFactoryInternalTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    private ModuleDescriptorFactoryForClientModule moduleDescriptorFactoryForClientModule = context.mock(ModuleDescriptorFactoryForClientModule.class);
+    private HashMap clientModuleRegistry = new HashMap();
+    private ClientModuleDependencyDescriptorFactory clientModuleDependencyDescriptorFactory = new ClientModuleDependencyDescriptorFactory(
+            excludeRuleConverterStub,
+            moduleDescriptorFactoryForClientModule,
+            clientModuleRegistry);
+
+    @Test
+    public void canConvert() {
+        assertThat(clientModuleDependencyDescriptorFactory.canConvert(context.mock(ProjectDependency.class)), Matchers.equalTo(false));
+        assertThat(clientModuleDependencyDescriptorFactory.canConvert(context.mock(ClientModule.class)), Matchers.equalTo(true));
+    }
+
+    @Test
+    public void testAddDependencyDescriptorForClientModule() {
+        final ModuleDependency dependencyDependency = context.mock(ModuleDependency.class, "dependencyDependency");
+        final DefaultClientModule clientModule = new DefaultClientModule("org.gradle", "gradle-core", "1.0", TEST_DEP_CONF);
+        final ModuleRevisionId testModuleRevisionId = IvyUtil.createModuleRevisionId(
+                clientModule, WrapUtil.toMap(ClientModule.CLIENT_MODULE_KEY, clientModule.getId()));
+
+        setUpDependency(clientModule);
+        clientModule.addDependency(dependencyDependency);
+        final ModuleDescriptor moduleDescriptorForClientModule = context.mock(ModuleDescriptor.class);
+        context.checking(new Expectations() {{
+            allowing(moduleDescriptorFactoryForClientModule).createModuleDescriptor(
+                    testModuleRevisionId,
+                    WrapUtil.toSet(dependencyDependency)
+            );
+            will(returnValue(moduleDescriptorForClientModule));
+        }});
+
+        clientModuleDependencyDescriptorFactory.addDependencyDescriptor(TEST_CONF, moduleDescriptor, clientModule);
+        assertThat((ModuleDescriptor) clientModuleRegistry.get(clientModule.getId()), equalTo(moduleDescriptorForClientModule));
+        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor.getDependencies()[0];
+        assertDependencyDescriptorHasCommonFixtureValues(dependencyDescriptor);
+        assertEquals(testModuleRevisionId,
+                dependencyDescriptor.getDependencyRevisionId());
+        assertFalse(dependencyDescriptor.isChanging());
+    }
+
+    @Test
+    public void testAddWithNullGroupAndNullVersionShouldHaveEmptyStringModuleRevisionValues() {
+        ClientModule clientModule = new DefaultClientModule(null, "gradle-core", null, TEST_DEP_CONF);
+        final ModuleRevisionId testModuleRevisionId = IvyUtil.createModuleRevisionId(
+                clientModule, WrapUtil.toMap(ClientModule.CLIENT_MODULE_KEY, clientModule.getId()));
+        context.checking(new Expectations() {{
+            allowing(moduleDescriptorFactoryForClientModule).createModuleDescriptor(
+                    testModuleRevisionId,
+                    WrapUtil.<ModuleDependency>toSet()
+            );
+        }});
+
+        clientModuleDependencyDescriptorFactory.addDependencyDescriptor(TEST_CONF, moduleDescriptor, clientModule);
+        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor.getDependencies()[0];
+        assertThat(dependencyDescriptor.getDependencyRevisionId(), equalTo(testModuleRevisionId));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.java
new file mode 100644
index 0000000..2536759
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultDependenciesToModuleDescriptorConverterTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.settings.IvySettings;
+import org.apache.ivy.plugins.conflict.ConflictManager;
+import org.apache.ivy.plugins.conflict.LatestConflictManager;
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.internal.artifacts.DefaultExcludeRule;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.ExcludeRuleConverter;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.IvyConverterTestUtil;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.WrapUtil;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultDependenciesToModuleDescriptorConverterTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    private static final ExcludeRule GRADLE_EXCLUDE_RULE_DUMMY_1 = new DefaultExcludeRule(toMap("org", "testOrg"));
+    private static final ExcludeRule GRADLE_EXCLUDE_RULE_DUMMY_2 = new DefaultExcludeRule(toMap("org2", "testOrg2"));
+
+    private ModuleDependency dependencyDummy1 = context.mock(ModuleDependency.class, "dep1");
+    private ModuleDependency dependencyDummy2 = context.mock(ModuleDependency.class, "dep2");
+    private ModuleDependency similarDependency1 = HelperUtil.createDependency("group", "name", "version");
+    private ModuleDependency similarDependency2 = HelperUtil.createDependency("group", "name", "version");
+    private ModuleDependency similarDependency3 = HelperUtil.createDependency("group", "name", "version");
+    private org.apache.ivy.core.module.descriptor.ExcludeRule ivyExcludeRuleStub1 = context.mock(org.apache.ivy.core.module.descriptor.ExcludeRule.class, "rule1");
+    private org.apache.ivy.core.module.descriptor.ExcludeRule ivyExcludeRuleStub2 = context.mock(org.apache.ivy.core.module.descriptor.ExcludeRule.class, "rule2");
+
+    private DependencyDescriptorFactory dependencyDescriptorFactoryStub = context.mock(DependencyDescriptorFactory.class);
+    private ExcludeRuleConverter excludeRuleConverterStub = context.mock(ExcludeRuleConverter.class);
+    private IvySettings ivySettingsDummy = new IvySettings();
+
+    @Test
+    public void testAddDependencyDescriptors() {
+        DefaultDependenciesToModuleDescriptorConverter converter = new DefaultDependenciesToModuleDescriptorConverter(
+                dependencyDescriptorFactoryStub, excludeRuleConverterStub);
+        Configuration configurationStub1 = createNamedConfigurationStubWithDependenciesAndExcludeRules("conf1", GRADLE_EXCLUDE_RULE_DUMMY_1, dependencyDummy1, similarDependency1);
+        Configuration configurationStub2 = createNamedConfigurationStubWithDependenciesAndExcludeRules("conf2", GRADLE_EXCLUDE_RULE_DUMMY_2, dependencyDummy2, similarDependency2);
+        Configuration configurationStub3 = createNamedConfigurationStubWithDependenciesAndExcludeRules("conf3", null, similarDependency3);
+        DefaultModuleDescriptor moduleDescriptor = HelperUtil.createModuleDescriptor(toSet(configurationStub1.getName(),
+                configurationStub2.getName()));
+        associateDependencyWithDescriptor(dependencyDummy1, moduleDescriptor, configurationStub1);
+        associateDependencyWithDescriptor(dependencyDummy2, moduleDescriptor, configurationStub2);
+        associateDependencyWithDescriptor(similarDependency1, moduleDescriptor, configurationStub1);
+        associateDependencyWithDescriptor(similarDependency2, moduleDescriptor, configurationStub2);
+        associateDependencyWithDescriptor(similarDependency3, moduleDescriptor, configurationStub3);
+        associateGradleExcludeRuleWithIvyExcludeRule(GRADLE_EXCLUDE_RULE_DUMMY_1, ivyExcludeRuleStub1, configurationStub1);
+        associateGradleExcludeRuleWithIvyExcludeRule(GRADLE_EXCLUDE_RULE_DUMMY_2, ivyExcludeRuleStub2, configurationStub2);
+
+        converter.addDependencyDescriptors(moduleDescriptor, toSet(configurationStub1, configurationStub2, configurationStub3),
+                ivySettingsDummy);
+                
+        assertThat(moduleDescriptor.getExcludeRules(toArray(configurationStub1.getName())), equalTo(toArray(
+                ivyExcludeRuleStub1)));
+        assertThat(moduleDescriptor.getExcludeRules(toArray(configurationStub2.getName())), equalTo(toArray(
+                ivyExcludeRuleStub2)));
+        assertIsCorrectConflictResolver(moduleDescriptor);
+        
+    }
+
+    private void assertIsCorrectConflictResolver(DefaultModuleDescriptor moduleDescriptor) {
+        ConflictManager conflictManager = moduleDescriptor.getConflictManager(new ModuleId(ExactPatternMatcher.ANY_EXPRESSION, ExactPatternMatcher.ANY_EXPRESSION));
+        assertThat(conflictManager, instanceOf(LatestConflictManager.class));
+        assertThat(((LatestConflictManager) conflictManager).getSettings(), equalTo(ivySettingsDummy));
+
+    }
+
+    private void associateGradleExcludeRuleWithIvyExcludeRule(final ExcludeRule gradleExcludeRule,
+                                                              final org.apache.ivy.core.module.descriptor.ExcludeRule ivyExcludeRule,
+                                                              final Configuration configuration) {
+        final String expectedConfigurationName = configuration.getName();
+        context.checking(new Expectations() {{
+            allowing(excludeRuleConverterStub).createExcludeRule(expectedConfigurationName, gradleExcludeRule);
+            will(returnValue(ivyExcludeRule));
+
+            allowing(ivyExcludeRule).getConfigurations();
+            will(returnValue(WrapUtil.toArray(configuration.getName())));
+        }});
+    }
+
+    private void associateDependencyWithDescriptor(final ModuleDependency dependency, final DefaultModuleDescriptor parent,
+                                                   final Configuration configuration) {
+        final String configurationName = configuration.getName();
+        context.checking(new Expectations() {{
+            allowing(dependencyDescriptorFactoryStub).addDependencyDescriptor(with(equal(configurationName)),
+                    with(equal(parent)), with(sameInstance(dependency)));
+        }});
+    }
+    
+    private Configuration createNamedConfigurationStubWithDependenciesAndExcludeRules(final String name, final ExcludeRule excludeRule,
+                                                                                      final ModuleDependency... dependencies) {
+        final Configuration configurationStub = IvyConverterTestUtil.createNamedConfigurationStub(name, context);
+        context.checking(new Expectations() {{
+            allowing(configurationStub).getDependencies(ModuleDependency.class);
+            will(returnValue(toSet(dependencies)));    
+
+            allowing(configurationStub).getExcludeRules();
+            will(returnValue(excludeRule == null ? Collections.emptySet() : toSet(excludeRule)));
+        }});
+        return configurationStub;
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModuleTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModuleTest.java
new file mode 100644
index 0000000..5faf335
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DefaultModuleDescriptorFactoryForClientModuleTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
+import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.util.WrapUtil;
+import static org.hamcrest.Matchers.equalTo;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultModuleDescriptorFactoryForClientModuleTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    @Test
+    public void testCreateModuleDescriptor() {
+        DependencyDescriptor dependencyDescriptorDummy = context.mock(DependencyDescriptor.class);
+        DependencyDescriptorFactorySpy dependencyDescriptorFactorySpy = new DependencyDescriptorFactorySpy(dependencyDescriptorDummy);
+        DefaultModuleDescriptorFactoryForClientModule clientModuleDescriptorFactory =
+                new DefaultModuleDescriptorFactoryForClientModule();
+        clientModuleDescriptorFactory.setDependencyDescriptorFactory(dependencyDescriptorFactorySpy);
+        ModuleDependency dependencyMock = context.mock(ModuleDependency.class);
+        final ModuleRevisionId moduleRevisionId = ModuleRevisionId.newInstance("org", "name", "version");
+
+        ModuleDescriptor moduleDescriptor = clientModuleDescriptorFactory.createModuleDescriptor(
+                moduleRevisionId,
+                WrapUtil.toSet(dependencyMock));
+
+        assertThat(moduleDescriptor.getModuleRevisionId(), equalTo(moduleRevisionId));
+        assertThatDescriptorHasOnlyDefaultConfiguration(moduleDescriptor);
+        assertCorrectCallToDependencyDescriptorFactory(dependencyDescriptorFactorySpy, Dependency.DEFAULT_CONFIGURATION, moduleDescriptor, dependencyMock);
+    }
+
+    private void assertThatDescriptorHasOnlyDefaultConfiguration(ModuleDescriptor moduleDescriptor) {
+        assertThat(moduleDescriptor.getConfigurations().length, equalTo(1));
+        assertThat(moduleDescriptor.getConfigurationsNames()[0], equalTo(Dependency.DEFAULT_CONFIGURATION));
+    }
+
+    private void assertCorrectCallToDependencyDescriptorFactory(DependencyDescriptorFactorySpy dependencyDescriptorFactorySpy,
+                                                                String configuration,
+                                                                ModuleDescriptor parent,
+                                                                Dependency dependency) {
+        assertThat(dependencyDescriptorFactorySpy.configuration, equalTo(configuration));
+        assertThat(dependencyDescriptorFactorySpy.parent, equalTo(parent));
+        assertThat(dependencyDescriptorFactorySpy.dependency, equalTo(dependency));
+    }
+
+    private static class DependencyDescriptorFactorySpy implements DependencyDescriptorFactory {
+        DependencyDescriptor dependencyDescriptor;
+
+        String configuration;
+        ModuleDescriptor parent;
+        Dependency dependency;
+
+        private DependencyDescriptorFactorySpy(DependencyDescriptor dependencyDescriptor) {
+            this.dependencyDescriptor = dependencyDescriptor;
+        }
+
+        public void addDependencyDescriptor(String configuration, DefaultModuleDescriptor moduleDescriptor,
+                                            ModuleDependency dependency) {
+            this.configuration = configuration;
+            this.parent = moduleDescriptor;
+            this.dependency = dependency;
+        }
+
+        public ModuleRevisionId createModuleRevisionId(ModuleDependency dependency) {
+            // do nothing
+            return null;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegateTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegateTest.java
new file mode 100644
index 0000000..64bf566
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/DependencyDescriptorFactoryDelegateTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Test;
+
+/**
+ * @author Hans Dockter
+ */
+public class DependencyDescriptorFactoryDelegateTest {
+    private JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+
+    private String configurationName = "conf";
+    private DefaultModuleDescriptor moduleDescriptorDummy = context.mock(DefaultModuleDescriptor.class);
+    private ProjectDependency projectDependency = context.mock(ProjectDependency.class);
+
+    @Test
+    public void convertShouldDelegateToTypeSpecificFactory() {
+        final DependencyDescriptorFactoryInternal dependencyDescriptorFactoryInternal1 = context.mock(DependencyDescriptorFactoryInternal.class, "factory1");
+        final DependencyDescriptorFactoryInternal dependencyDescriptorFactoryInternal2 = context.mock(DependencyDescriptorFactoryInternal.class, "factory2");
+        context.checking(new Expectations() {{
+            allowing(dependencyDescriptorFactoryInternal1).canConvert(projectDependency);
+            will(returnValue(false));
+            allowing(dependencyDescriptorFactoryInternal2).canConvert(projectDependency);
+            will(returnValue(true));
+            one(dependencyDescriptorFactoryInternal2).addDependencyDescriptor(configurationName, moduleDescriptorDummy, projectDependency);
+        }});
+        DependencyDescriptorFactoryDelegate dependencyDescriptorFactoryDelegate = new DependencyDescriptorFactoryDelegate(
+            dependencyDescriptorFactoryInternal1, dependencyDescriptorFactoryInternal2            
+        );
+        dependencyDescriptorFactoryDelegate.addDependencyDescriptor(configurationName, moduleDescriptorDummy, projectDependency);
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void convertShouldThrowExForUnknownDependencyType() {
+        final DependencyDescriptorFactoryInternal dependencyDescriptorFactoryInternal1 = context.mock(DependencyDescriptorFactoryInternal.class, "factory1");
+        context.checking(new Expectations() {{
+            allowing(dependencyDescriptorFactoryInternal1).canConvert(projectDependency);
+            will(returnValue(false));
+        }});
+        DependencyDescriptorFactoryDelegate dependencyDescriptorFactoryDelegate = new DependencyDescriptorFactoryDelegate(
+            dependencyDescriptorFactoryInternal1            
+        );
+        dependencyDescriptorFactoryDelegate.addDependencyDescriptor(configurationName, moduleDescriptorDummy, projectDependency);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java
new file mode 100644
index 0000000..f927ab0
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ExternalModuleDependencyDescriptorFactoryTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.gradle.api.artifacts.ExternalDependency;
+import org.gradle.api.artifacts.ExternalModuleDependency;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
+import org.gradle.api.internal.artifacts.dsl.dependencies.ModuleFactoryHelper;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.hamcrest.Matchers;
+import static org.hamcrest.Matchers.equalTo;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+
+/**
+ * @author Hans Dockter
+ */
+public class ExternalModuleDependencyDescriptorFactoryTest extends AbstractDependencyDescriptorFactoryInternalTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    ExternalModuleDependencyDescriptorFactory externalModuleDependencyDescriptorFactory =
+            new ExternalModuleDependencyDescriptorFactory(excludeRuleConverterStub);
+    
+    @Test
+    public void canConvert() {
+        assertThat(externalModuleDependencyDescriptorFactory.canConvert(context.mock(ProjectDependency.class)), Matchers.equalTo(false));
+        assertThat(externalModuleDependencyDescriptorFactory.canConvert(context.mock(ExternalModuleDependency.class)), Matchers.equalTo(true));
+    }
+
+    @Test
+    public void testAddWithNullGroupAndNullVersionShouldHaveEmptyStringModuleRevisionValues() {
+        ModuleDependency dependency = new DefaultExternalModuleDependency(null, "gradle-core", null, TEST_DEP_CONF);
+        externalModuleDependencyDescriptorFactory.addDependencyDescriptor(TEST_CONF, moduleDescriptor, dependency);
+        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor.getDependencies()[0];
+        assertThat(dependencyDescriptor.getDependencyRevisionId(), equalTo(IvyUtil.createModuleRevisionId(dependency)));
+    }
+
+    @Test
+    public void testCreateFromModuleDependency() {
+        DefaultExternalModuleDependency moduleDependency = new DefaultExternalModuleDependency("org.gradle",
+                "gradle-core", "1.0", TEST_DEP_CONF);
+        setUpDependency(moduleDependency);
+
+        externalModuleDependencyDescriptorFactory.addDependencyDescriptor(TEST_CONF, moduleDescriptor,
+                moduleDependency);
+        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor
+                .getDependencies()[0];
+
+        assertEquals(moduleDependency.isChanging(), dependencyDescriptor.isChanging());
+        assertEquals(dependencyDescriptor.isForce(), moduleDependency.isForce());
+        assertEquals(IvyUtil.createModuleRevisionId(moduleDependency), dependencyDescriptor.getDependencyRevisionId());
+        assertDependencyDescriptorHasCommonFixtureValues(dependencyDescriptor);
+    }
+
+    @Test
+    public void addExternalModuleDependenciesWithSameModuleRevisionIdAndDifferentConfsShouldBePartOfSameDependencyDescriptor() {
+        ModuleDependency dependency1 = new DefaultExternalModuleDependency("org.gradle", "gradle-core", "1.0", TEST_DEP_CONF);
+        ModuleDependency dependency2 = new DefaultExternalModuleDependency("org.gradle", "gradle-core", "1.0", TEST_OTHER_DEP_CONF);
+
+        assertThataddDependenciesWithSameModuleRevisionIdAndDifferentConfsShouldBePartOfSameDependencyDescriptor(
+                dependency1, dependency2, externalModuleDependencyDescriptorFactory
+        );
+    }
+
+    @Test
+    public void addExternalModuleDependenciesWithSameModuleRevisionIdAndSameClassifiersShouldBePartOfSameDependencyDescriptor() {
+        ExternalDependency dependency1 = new DefaultExternalModuleDependency("org.gradle", "gradle-core", "1.0", TEST_DEP_CONF);
+        ModuleFactoryHelper.addExplicitArtifactsIfDefined(dependency1, null, "jdk14");
+
+        ExternalDependency dependency2 = new DefaultExternalModuleDependency("org.gradle", "gradle-core", "1.0", TEST_OTHER_DEP_CONF);
+        ModuleFactoryHelper.addExplicitArtifactsIfDefined(dependency2, null, "jdk14");
+
+        assertThataddDependenciesWithSameModuleRevisionIdAndDifferentConfsShouldBePartOfSameDependencyDescriptor(
+                dependency1, dependency2, externalModuleDependencyDescriptorFactory
+        );
+    }
+
+
+    @Test
+    public void addExternalModuleDependenciesWithSameModuleRevisionIdAndDifferentClassifiersShouldNotBePartOfSameDependencyDescriptor() {
+        ExternalDependency dependency1 = new DefaultExternalModuleDependency("org.gradle", "gradle-core", "1.0", TEST_DEP_CONF);
+        ModuleFactoryHelper.addExplicitArtifactsIfDefined(dependency1, null, "jdk14");
+
+        ExternalDependency dependency2 = new DefaultExternalModuleDependency("org.gradle", "gradle-core", "1.0", TEST_OTHER_DEP_CONF);
+        ModuleFactoryHelper.addExplicitArtifactsIfDefined(dependency2, null, "jdk15");
+
+        assertThataddDependenciesWithSameModuleRevisionIdAndDifferentClassifiersShouldNotBePartOfSameDependencyDescriptor(
+                dependency1, dependency2, externalModuleDependencyDescriptorFactory
+        );
+    }
+
+    private void assertThataddDependenciesWithSameModuleRevisionIdAndDifferentClassifiersShouldNotBePartOfSameDependencyDescriptor(
+            ModuleDependency dependency1, ModuleDependency dependency2, DependencyDescriptorFactoryInternal factoryInternal
+    ) {
+        factoryInternal.addDependencyDescriptor(TEST_CONF, moduleDescriptor, dependency1);
+        factoryInternal.addDependencyDescriptor(TEST_CONF, moduleDescriptor, dependency2);
+        assertThat(moduleDescriptor.getDependencies().length, equalTo(2));
+
+        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor.getDependencies()[0];
+        assertThat(dependencyDescriptor.getDependencyConfigurations(TEST_CONF), Matchers.hasItemInArray(TEST_DEP_CONF));
+        assertThat(dependencyDescriptor.getDependencyConfigurations(TEST_CONF).length, equalTo(1));
+
+        dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor.getDependencies()[1];
+        assertThat(dependencyDescriptor.getDependencyConfigurations(TEST_CONF), Matchers.hasItemInArray(TEST_OTHER_DEP_CONF));
+        assertThat(dependencyDescriptor.getDependencyConfigurations(TEST_CONF).length, equalTo(1));
+    }
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.java
new file mode 100644
index 0000000..728f882
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/moduleconverter/dependencies/ProjectDependencyDescriptorFactoryTest.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.api.internal.artifacts.ivyservice.moduleconverter.dependencies;
+
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.ExternalModuleDependency;
+import org.gradle.api.artifacts.Module;
+import org.gradle.api.artifacts.ProjectDependenciesBuildInstruction;
+import org.gradle.api.artifacts.ProjectDependency;
+import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependency;
+import org.gradle.api.internal.artifacts.ivyservice.IvyUtil;
+import org.gradle.api.internal.project.AbstractProject;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.WrapUtil;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class ProjectDependencyDescriptorFactoryTest extends AbstractDependencyDescriptorFactoryInternalTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    private ProjectDependencyDescriptorStrategy descriptorStrategyStub = context.mock(ProjectDependencyDescriptorStrategy.class);
+    private ProjectDependencyDescriptorFactory projectDependencyDescriptorFactory =
+            new ProjectDependencyDescriptorFactory(excludeRuleConverterStub, descriptorStrategyStub);
+
+    @Test
+    public void canConvert() {
+        assertThat(projectDependencyDescriptorFactory.canConvert(context.mock(ProjectDependency.class)), equalTo(true));
+        assertThat(projectDependencyDescriptorFactory.canConvert(context.mock(ExternalModuleDependency.class)), equalTo(false));
+    }
+
+    @Test
+    public void testCreateFromProjectDependency() {
+        final ModuleRevisionId someModuleRevisionId = ModuleRevisionId.newInstance("a", "b", "c");
+        final ProjectDependency projectDependency = createProjectDependency(TEST_DEP_CONF);
+        setUpDependency(projectDependency);
+        context.checking(new Expectations() {{
+            allowing(descriptorStrategyStub).createModuleRevisionId(projectDependency);
+            will(returnValue(someModuleRevisionId));
+            allowing(descriptorStrategyStub).isChanging();
+            will(returnValue(true));
+        }});
+        projectDependencyDescriptorFactory.addDependencyDescriptor(TEST_CONF, moduleDescriptor, projectDependency);
+        DefaultDependencyDescriptor dependencyDescriptor = (DefaultDependencyDescriptor) moduleDescriptor.getDependencies()[0];
+
+        assertDependencyDescriptorHasCommonFixtureValues(dependencyDescriptor);
+        assertTrue(dependencyDescriptor.isChanging());
+        assertFalse(dependencyDescriptor.isForce());
+        assertEquals(someModuleRevisionId,
+                dependencyDescriptor.getDependencyRevisionId());
+    }
+
+    private ProjectDependency createProjectDependency(String dependencyConfiguration) {
+        AbstractProject dependencyProject = HelperUtil.createRootProject();
+        dependencyProject.setGroup("someGroup");
+        dependencyProject.setVersion("someVersion");
+        return new DefaultProjectDependency(dependencyProject, dependencyConfiguration, new ProjectDependenciesBuildInstruction(Collections.<String>emptyList()));
+    }
+
+    @Test
+    public void addExternalModuleDependenciesWithSameModuleRevisionIdAndDifferentConfsShouldBePartOfSameDependencyDescriptor() {
+        final ProjectDependency dependency1 = createProjectDependency(TEST_DEP_CONF);
+        final ProjectDependency dependency2 = createProjectDependency(TEST_OTHER_DEP_CONF);
+
+        context.checking(new Expectations() {{
+            allowing(descriptorStrategyStub).isChanging();
+            will(returnValue(true));
+            allowing(descriptorStrategyStub).createModuleRevisionId(dependency1);
+            will(returnValue(IvyUtil.createModuleRevisionId(dependency1)));
+            allowing(descriptorStrategyStub).createModuleRevisionId(dependency2);
+            will(returnValue(IvyUtil.createModuleRevisionId(dependency2)));
+        }});
+        
+        assertThataddDependenciesWithSameModuleRevisionIdAndDifferentConfsShouldBePartOfSameDependencyDescriptor(
+                dependency1, dependency2, projectDependencyDescriptorFactory
+        );
+    }
+
+    @Test
+    public void ivyFileModuleRevisionIdShouldBeDeterminedByModuleForPublicDescriptorWithoutExtraAttributes() {
+        ProjectDependency projectDependency = createProjectDependency(TEST_CONF);
+        Module module = ((ProjectInternal) projectDependency.getDependencyProject()).getModule();
+        ModuleRevisionId moduleRevisionId =
+                ProjectDependencyDescriptorFactory.IVY_FILE_DESCRIPTOR_STRATEGY.createModuleRevisionId(projectDependency);
+        assertThat(moduleRevisionId.getOrganisation(), equalTo(module.getGroup()));
+        assertThat(moduleRevisionId.getName(), equalTo(module.getName()));
+        assertThat(moduleRevisionId.getRevision(), equalTo(module.getVersion()));
+        assertThat(moduleRevisionId.getExtraAttributes(), equalTo((Map) new HashMap()));
+    }
+
+    @Test
+    public void resolveModuleRevisionIdShouldBeDeterminedByModuleForResolvePlusExtraAttributes() {
+        ProjectDependency projectDependency = createProjectDependency(TEST_CONF);
+        Module module = ((ProjectInternal) projectDependency.getDependencyProject()).getModule();
+        ModuleRevisionId moduleRevisionId =
+                ProjectDependencyDescriptorFactory.RESOLVE_DESCRIPTOR_STRATEGY.createModuleRevisionId(projectDependency);
+        assertThat(moduleRevisionId.getOrganisation(), equalTo(module.getGroup()));
+        assertThat(moduleRevisionId.getName(), equalTo(module.getName()));
+        assertThat(moduleRevisionId.getRevision(), equalTo(module.getVersion()));
+        assertThat(moduleRevisionId.getExtraAttributes(),
+                equalTo((Map) WrapUtil.toMap(DependencyDescriptorFactory.PROJECT_PATH_KEY, projectDependency.getDependencyProject().getPath())));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifactTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifactTest.java
new file mode 100644
index 0000000..b29f542
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/AbstractPublishArtifactTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.publish;
+
+import org.apache.ivy.core.module.id.ModuleId;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.util.Date;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractPublishArtifactTest {
+    private static final File TEST_FILE = new File("artifactFile");
+    private static final String TEST_NAME = "myfile-1";
+    private static final String TEST_EXT = "ext";
+    private static final String TEST_TYPE = "type";
+    private static final String TEST_CLASSIFIER = "classifier";
+    private static final Date TEST_DATE = new Date();
+    private static final ModuleRevisionId TEST_MODULE_REVISION_ID = new ModuleRevisionId(new ModuleId("group", "name"), "version");
+
+    protected File getTestFile() {
+        return TEST_FILE;
+    }
+
+    protected String getTestName() {
+        return TEST_NAME;
+    }
+
+    protected String getTestExt() {
+        return TEST_EXT;
+    }
+
+    protected String getTestType() {
+        return TEST_TYPE;
+    }
+
+    protected String getTestClassifier() {
+        return TEST_CLASSIFIER;
+    }
+
+    protected ModuleRevisionId getTestModuleRevisionId() {
+        return TEST_MODULE_REVISION_ID;
+    }
+
+    protected Date getDate() {
+        return TEST_DATE;
+    }
+
+    protected JUnit4Mockery context = new JUnit4GroovyMockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+
+    abstract PublishArtifact createPublishArtifact(String classifier);
+
+    protected void assertCommonPropertiesAreSet(PublishArtifact artifact, boolean shouldHaveClassifier) {
+        assertEquals(getTestName(), artifact.getName());
+        assertEquals(getTestType(), artifact.getType());
+        assertEquals(getTestExt(), artifact.getExtension());
+        assertEquals(getTestFile(), artifact.getFile());
+        if (shouldHaveClassifier) {
+            assertEquals(getTestClassifier(), artifact.getClassifier());
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifactTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifactTest.java
new file mode 100644
index 0000000..fb831b9
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/ArchivePublishArtifactTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.publish;
+
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.tasks.bundling.AbstractArchiveTask;
+import org.gradle.util.WrapUtil;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.sameInstance;
+import org.jmock.Expectations;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class ArchivePublishArtifactTest extends AbstractPublishArtifactTest {
+    private AbstractArchiveTask archiveTask = context.mock(AbstractArchiveTask.class);
+    
+    @Override
+    protected PublishArtifact createPublishArtifact(final String classifier) {
+        prepareMocks(classifier, "");
+        return new ArchivePublishArtifact(archiveTask);
+    }
+
+    private void prepareMocks(final String classifier, final String appendix) {
+        context.checking(new Expectations() {{
+            allowing(archiveTask).getExtension();
+            will(returnValue(getTestExt()));
+
+            allowing(archiveTask).getBaseName();
+            will(returnValue(getTestName()));
+
+            allowing(archiveTask).getAppendix();
+            will(returnValue(appendix));
+
+            allowing(archiveTask).getArchivePath();
+            will(returnValue(getTestFile()));
+
+            allowing(archiveTask).getClassifier();
+            will(returnValue(classifier));
+        }});
+    }
+
+    @Override
+    protected String getTestType() {
+        return getTestExt();
+    }
+
+    @Test
+    public void init() {
+        ArchivePublishArtifact publishArtifact = (ArchivePublishArtifact) createPublishArtifact(getTestClassifier());
+        assertThat((Set<AbstractArchiveTask>) publishArtifact.getBuildDependencies().getDependencies(null), equalTo(WrapUtil.toSet(archiveTask)));
+        assertCommonPropertiesAreSet(publishArtifact, true);
+        assertThat(publishArtifact.getArchiveTask(), sameInstance(archiveTask));
+    }
+    
+    @Test
+    public void nameWithAppendix() {
+        String testAppendix = "appendix";
+        prepareMocks(getTestClassifier(), testAppendix);
+        PublishArtifact publishArtifact = new ArchivePublishArtifact(archiveTask);
+        assertThat(publishArtifact.getName(), equalTo(getTestName() + "-" + testAppendix));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/DefaultArtifactContainerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/DefaultArtifactContainerTest.java
new file mode 100644
index 0000000..9b51701
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/DefaultArtifactContainerTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.publish;
+
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.api.specs.Spec;
+import org.gradle.util.WrapUtil;
+import static org.hamcrest.Matchers.equalTo;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultArtifactContainerTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    private DefaultArtifactContainer artifactContainer = new DefaultArtifactContainer();
+
+    @Test
+    public void addGetArtifactsWithArtifactInstance() {
+        PublishArtifact publishArtifact1 = context.mock(PublishArtifact.class, "artifact1");
+        PublishArtifact publishArtifact2 = context.mock(PublishArtifact.class, "artifact2");
+        artifactContainer.addArtifacts(publishArtifact1);
+        assertThat(artifactContainer.getArtifacts(), equalTo(WrapUtil.toSet(publishArtifact1)));
+        artifactContainer.addArtifacts(publishArtifact2);
+        assertThat(artifactContainer.getArtifacts(), equalTo(WrapUtil.toSet(publishArtifact1, publishArtifact2)));
+    }
+
+    @Test
+    public void getArtifactsWithFilterSpec() {
+        final PublishArtifact publishArtifact1 = context.mock(PublishArtifact.class, "artifact1");
+        final PublishArtifact publishArtifact2 = context.mock(PublishArtifact.class, "artifact2");
+        artifactContainer.addArtifacts(publishArtifact1);
+        artifactContainer.addArtifacts(publishArtifact2);
+        final boolean[] artifact1Offered = new boolean[]{false};
+        final boolean[] artifact2Offered = new boolean[]{false};
+        Set<PublishArtifact> actualArtifacts = artifactContainer.getArtifacts(new Spec<PublishArtifact>() {
+            public boolean isSatisfiedBy(PublishArtifact filterCandidate) {
+                if (filterCandidate == publishArtifact1) {
+                    artifact1Offered[0] = true;
+                }
+                if (filterCandidate == publishArtifact2) {
+                    artifact2Offered[0] = true;
+                }
+                return filterCandidate == publishArtifact1;
+            }
+        });
+        assertThat(actualArtifacts, equalTo(WrapUtil.toSet(publishArtifact1)));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifactTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifactTest.java
new file mode 100644
index 0000000..4a3163e
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/DefaultPublishArtifactTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.api.internal.artifacts.publish;
+
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.PublishArtifact;
+import org.gradle.util.WrapUtil;
+import org.hamcrest.Matchers;
+import org.jmock.integration.junit4.JMock;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+/**
+* @author Hans Dockter
+*/
+ at RunWith(JMock.class)
+public class DefaultPublishArtifactTest extends AbstractPublishArtifactTest {
+    protected PublishArtifact createPublishArtifact(String classifier) {
+        return new DefaultPublishArtifact(getTestName(), getTestExt(), getTestType(), classifier, getDate(), getTestFile());
+    }
+
+    @Test
+    public void init() {
+        Task task1 = context.mock(Task.class, "task1");
+        Task task2 = context.mock(Task.class, "task2");
+        DefaultPublishArtifact publishArtifact = new DefaultPublishArtifact(getTestName(), getTestExt(), getTestType(),
+                getTestClassifier(), getDate(), getTestFile(), task1, task2);
+        assertThat((Set<Task>) publishArtifact.getBuildDependencies().getDependencies(null), Matchers.equalTo(WrapUtil.toSet(task1, task2)));
+        assertCommonPropertiesAreSet(publishArtifact, true);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPomFactoryTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPomFactoryTest.groovy
new file mode 100644
index 0000000..3374a84
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPomFactoryTest.groovy
@@ -0,0 +1,46 @@
+/*
+ * 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.publish.maven;
+
+
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.internal.artifacts.publish.maven.dependencies.DefaultConf2ScopeMappingContainer
+import org.gradle.api.internal.artifacts.publish.maven.dependencies.PomDependenciesConverter
+import org.gradle.api.internal.file.FileResolver
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultMavenPomFactoryTest extends Specification {
+    def createMavenPom() {
+        DefaultConf2ScopeMappingContainer scopeMappings = new DefaultConf2ScopeMappingContainer();
+        PomDependenciesConverter pomDependenciesConverter = Mock(PomDependenciesConverter); 
+        ConfigurationContainer configurationContainer = Mock(ConfigurationContainer); 
+        FileResolver fileResolver = Mock(FileResolver); 
+        DefaultMavenPomFactory mavenPomFactory = new DefaultMavenPomFactory(configurationContainer, scopeMappings,
+                pomDependenciesConverter, fileResolver);
+        DefaultMavenPom mavenPom = (DefaultMavenPom) mavenPomFactory.createMavenPom();
+
+        expect:
+        !scopeMappings.is(mavenPom.scopeMappings)
+        scopeMappings == mavenPom.scopeMappings
+        mavenPom.mavenProject != null
+        mavenPom.pomDependenciesConverter.is(pomDependenciesConverter)
+        mavenPom.configurations.is(configurationContainer)
+        mavenPom.fileResolver = fileResolver
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPomTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPomTest.groovy
new file mode 100644
index 0000000..2bfee59
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DefaultMavenPomTest.groovy
@@ -0,0 +1,190 @@
+/*
+ * 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.publish.maven
+
+import org.apache.commons.lang.builder.EqualsBuilder
+import org.apache.maven.model.Dependency
+import org.gradle.api.Action
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer
+import org.gradle.api.internal.artifacts.publish.maven.dependencies.DefaultConf2ScopeMappingContainer
+import org.gradle.api.internal.artifacts.publish.maven.dependencies.PomDependenciesConverter
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Specification
+import org.apache.maven.model.Model
+
+class DefaultMavenPomTest extends Specification {
+    static final String EXPECTED_PACKAGING = "something";
+    static final String EXPECTED_GROUP_ID = "someGroup";
+    static final String EXPECTED_ARTIFACT_ID = "artifactId";
+    static final String EXPECTED_VERSION = "version";
+
+    @Rule
+    TemporaryFolder tmpDir = new TemporaryFolder()
+
+    Conf2ScopeMappingContainer conf2ScopeMappingContainer = new DefaultConf2ScopeMappingContainer()
+    PomDependenciesConverter pomDependenciesConverterStub = Mock()
+    ConfigurationContainer configurationContainerStub = Mock()
+    FileResolver fileResolver = Mock()
+    DefaultMavenPom mavenPom = new DefaultMavenPom(configurationContainerStub, conf2ScopeMappingContainer, pomDependenciesConverterStub,
+            fileResolver)
+
+    void setup() {
+        mavenPom.packaging = EXPECTED_PACKAGING
+        mavenPom.groupId = EXPECTED_GROUP_ID
+        mavenPom.artifactId = EXPECTED_ARTIFACT_ID
+        mavenPom.version = EXPECTED_VERSION
+    }
+
+    def init() {
+        expect:
+        mavenPom.scopeMappings.is(conf2ScopeMappingContainer)
+        mavenPom.configurations.is(configurationContainerStub)
+        mavenPom.fileResolver.is(fileResolver)
+        mavenPom.mavenProject.modelVersion == "4.0.0"
+    }
+
+    def setModel() {
+        def newModel = new Model()
+
+        when:
+        mavenPom.model = newModel
+
+        then:
+        mavenPom.model.is(newModel)
+    }
+
+    def effectivePomShouldHaveGeneratedDependencies() {
+        Set configurations = [Mock(Configuration)]
+        configurationContainerStub.getAll() >> configurations
+        List generatedDependencies = [new Dependency(groupId: 'someGroup')]
+        List manuallyAddedDependencies = [new Dependency()]
+        pomDependenciesConverterStub.convert(conf2ScopeMappingContainer, configurations) >> generatedDependencies
+
+        when:
+        mavenPom.dependencies = manuallyAddedDependencies.clone()
+
+        then:
+        EqualsBuilder.reflectionEquals(mavenPom.getEffectivePom().getMavenProject().getDependencies(), manuallyAddedDependencies + generatedDependencies)
+
+        when:
+        mavenPom.dependencies = []
+
+        then:
+        mavenPom.getEffectivePom().getMavenProject().getDependencies() == generatedDependencies
+    }
+
+    def configureActionsShouldBeAppliedAgainstEffectivePom() {
+        mavenPom.configurations = null
+        when:
+        mavenPom.whenConfigured(new Action() {
+            void execute(def mavenPom) {
+                mavenPom.mavenProject.inceptionYear = '1999'
+            }
+        })
+
+        then:
+        mavenPom.effectivePom.mavenProject.inceptionYear == '1999'
+        mavenPom.mavenProject.inceptionYear == null
+    }
+
+
+    def writeShouldUseEffectivePom() {
+        Set configurations = [Mock(Configuration)]
+        configurationContainerStub.getAll() >> configurations
+        List generatedDependencies = [new Dependency(groupId: 'someGroup')]
+        pomDependenciesConverterStub.convert(conf2ScopeMappingContainer, configurations) >> generatedDependencies
+
+        when:
+        StringWriter pomWriter = new StringWriter()
+        mavenPom.writeTo pomWriter
+
+        then:
+        pomWriter.toString().contains('someGroup')
+    }
+
+    def effectivePomWithNullConfigurationsShouldWork() {
+        when:
+        mavenPom.configurations = null
+
+        then:
+        mavenPom.getEffectivePom().getMavenProject().getDependencies() == []
+    }
+
+    void projectBuilder() {
+        mavenPom.mavenProject.inceptionYear = '2007'
+        mavenPom.mavenProject.description = 'some description'
+        mavenPom.project {
+            inceptionYear '2008'
+            licenses {
+                license {
+                    name 'The Apache Software License, Version 2.0'
+                    url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+                    distribution 'repo'
+                }
+            }
+        }
+
+        expect:
+        mavenPom.mavenProject.modelVersion == "4.0.0"
+        mavenPom.version == EXPECTED_VERSION
+        mavenPom.mavenProject.description == 'some description'
+        mavenPom.mavenProject.inceptionYear == '2008'
+        mavenPom.mavenProject.licenses.size() == 1
+        mavenPom.mavenProject.licenses[0].name == 'The Apache Software License, Version 2.0'
+        mavenPom.mavenProject.licenses[0].url == 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+        mavenPom.mavenProject.licenses[0].distribution == 'repo'
+    }
+
+    void writeToShouldApplyXmlActions() {
+        mavenPom.configurations = null
+        StringWriter pomWriter = new StringWriter()
+
+        when:
+        mavenPom.withXml {xmlProvider ->
+            xmlProvider.asString().append('someAppendix')
+        }
+        mavenPom.writeTo(pomWriter);
+
+        then:
+        pomWriter.toString().endsWith("someAppendix")
+    }
+
+    void writeToWritesCorrectPom() {
+        mavenPom.configurations = null
+        TestFile pomFile = tmpDir.file('someNonexistingDir').file('someFile')
+        fileResolver.resolve('file') >> pomFile
+
+        when:
+        mavenPom.writeTo('file');
+
+        then:
+        pomFile.text == '''<?xml version="1.0" encoding="UTF-8"?>
+<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>someGroup</groupId>
+  <artifactId>artifactId</artifactId>
+  <version>version</version>
+  <packaging>something</packaging>
+</project>
+'''
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DeployTaskFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DeployTaskFactoryTest.java
new file mode 100644
index 0000000..ff032bb
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/DeployTaskFactoryTest.java
@@ -0,0 +1,31 @@
+/*
+ * 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.publish.maven;
+
+import static junit.framework.Assert.assertTrue;
+import org.gradle.api.internal.artifacts.publish.maven.deploy.CustomDeployTask;
+import org.gradle.api.internal.artifacts.publish.maven.deploy.DefaultDeployTaskFactory;
+import org.junit.Test;
+
+/**
+ * @author Hans Dockter
+ */
+public class DeployTaskFactoryTest {
+    @Test
+    public void create() {
+        assertTrue(new DefaultDeployTaskFactory().createDeployTask() instanceof CustomDeployTask);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultConf2ScopeMappingContainerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultConf2ScopeMappingContainerTest.java
new file mode 100644
index 0000000..2fe834b
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultConf2ScopeMappingContainerTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.publish.maven.dependencies;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.maven.Conf2ScopeMapping;
+import org.gradle.util.HelperUtil;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.util.Arrays.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultConf2ScopeMappingContainerTest {
+    private DefaultConf2ScopeMappingContainer conf2ScopeMappingContainer;
+    private static final Configuration TEST_CONF_1 = HelperUtil.createConfiguration("testCompile");
+    private static final Configuration TEST_CONF_2 = HelperUtil.createConfiguration("testCompile2");
+    private static final Configuration TEST_CONF_3 = HelperUtil.createConfiguration("testCompile3");
+    private static final String TEST_SCOPE_1 = "test";
+    private static final String TEST_SCOPE_2 = "test2";
+    private static final int TEST_PRIORITY_1 = 10;
+    private static final int TEST_PRIORITY_2 = 20;
+
+    @Before
+    public void setUp() {
+        conf2ScopeMappingContainer = new DefaultConf2ScopeMappingContainer();
+        conf2ScopeMappingContainer.addMapping(TEST_PRIORITY_1, TEST_CONF_1, TEST_SCOPE_1);
+    }
+
+    @Test
+    public void init() {
+        conf2ScopeMappingContainer = new DefaultConf2ScopeMappingContainer();
+        assertTrue(conf2ScopeMappingContainer.isSkipUnmappedConfs());
+        assertEquals(0, conf2ScopeMappingContainer.getMappings().size());
+        Map<Configuration, Conf2ScopeMapping> testMappings = createTestMappings();
+        conf2ScopeMappingContainer = new DefaultConf2ScopeMappingContainer(testMappings);
+        assertNotSame(testMappings, conf2ScopeMappingContainer.getMappings());
+        assertEquals(testMappings, conf2ScopeMappingContainer.getMappings());
+    }
+
+    @Test
+    public void equalsAndHashCode() {
+        Map<Configuration, Conf2ScopeMapping> testMappings = createTestMappings();
+        conf2ScopeMappingContainer = new DefaultConf2ScopeMappingContainer(testMappings);
+        assertTrue(conf2ScopeMappingContainer.equals(new DefaultConf2ScopeMappingContainer(testMappings)));
+        assertEquals(conf2ScopeMappingContainer.hashCode(), new DefaultConf2ScopeMappingContainer(testMappings).hashCode());
+        conf2ScopeMappingContainer.addMapping(10, HelperUtil.createConfiguration("conf2"), "scope");
+        assertFalse(conf2ScopeMappingContainer.equals(new DefaultConf2ScopeMappingContainer(testMappings)));
+    }
+
+    private Map<Configuration, Conf2ScopeMapping> createTestMappings() {
+        Map<Configuration, Conf2ScopeMapping> testMappings = new HashMap<Configuration, Conf2ScopeMapping>() {{
+            Configuration configuration = HelperUtil.createConfiguration("conf");
+            put(configuration, new Conf2ScopeMapping(10, configuration, "scope"));
+        }};
+        return testMappings;
+    }
+
+    @Test
+    public void addGetMapping() {
+        assertEquals(new Conf2ScopeMapping(TEST_PRIORITY_1, TEST_CONF_1, TEST_SCOPE_1),
+                conf2ScopeMappingContainer.getMapping(asList(TEST_CONF_1)));
+    }
+
+    @Test
+    public void singleMappedConfiguration() {
+        assertThat(conf2ScopeMappingContainer.getMapping(asList(TEST_CONF_1)), equalTo(
+                new Conf2ScopeMapping(TEST_PRIORITY_1, TEST_CONF_1, TEST_SCOPE_1)));
+    }
+
+    @Test
+    public void unmappedConfiguration() {
+        assertThat(conf2ScopeMappingContainer.getMapping(asList(TEST_CONF_2)), equalTo(
+                new Conf2ScopeMapping(null, TEST_CONF_2, null)));
+    }
+
+    @Test
+    public void mappedConfigurationAndUnmappedConfiguration() {
+        assertThat(conf2ScopeMappingContainer.getMapping(asList(TEST_CONF_1, TEST_CONF_2)), equalTo(
+                new Conf2ScopeMapping(TEST_PRIORITY_1, TEST_CONF_1, TEST_SCOPE_1)));
+    }
+
+    @Test
+    public void mappingWithDifferentPrioritiesDifferentConfsDifferentScopes() {
+        conf2ScopeMappingContainer.addMapping(TEST_PRIORITY_2, TEST_CONF_2, TEST_SCOPE_2);
+        assertThat(conf2ScopeMappingContainer.getMapping(asList(TEST_CONF_1, TEST_CONF_2)), equalTo(
+                new Conf2ScopeMapping(TEST_PRIORITY_2, TEST_CONF_2, TEST_SCOPE_2)));
+    }
+    
+    @Test(expected = InvalidUserDataException.class)
+    public void mappingWithSamePrioritiesDifferentConfsSameScope() {
+        conf2ScopeMappingContainer.addMapping(TEST_PRIORITY_1, TEST_CONF_2, TEST_SCOPE_1);
+        conf2ScopeMappingContainer.getMapping(asList(TEST_CONF_1, TEST_CONF_2));
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void mappingWithSamePrioritiesDifferentConfsDifferentScopes() {
+        conf2ScopeMappingContainer.addMapping(TEST_PRIORITY_1, TEST_CONF_2, TEST_SCOPE_1);
+        conf2ScopeMappingContainer.addMapping(TEST_PRIORITY_1, TEST_CONF_3, TEST_SCOPE_2);
+        conf2ScopeMappingContainer.getMapping(asList(TEST_CONF_1, TEST_CONF_2, TEST_CONF_3));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultExcludeRuleConverterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultExcludeRuleConverterTest.java
new file mode 100644
index 0000000..0bd43b0
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultExcludeRuleConverterTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.publish.maven.dependencies;
+
+import org.apache.maven.model.Exclusion;
+import org.gradle.api.artifacts.ExcludeRule;
+import org.gradle.api.internal.artifacts.DefaultExcludeRule;
+import org.gradle.util.GUtil;
+import org.gradle.util.WrapUtil;
+import org.junit.Before;
+import org.junit.Test;
+
+import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultExcludeRuleConverterTest {
+    private static final String TEST_ORG = "org";
+    private static final String TEST_MODULE = "module";
+
+    private DefaultExcludeRuleConverter excludeRuleConverter;
+
+    @Before
+    public void setUp() {
+        excludeRuleConverter = new DefaultExcludeRuleConverter();   
+    }
+    
+    @Test
+    public void convertableRule() {
+        DefaultExcludeRule excludeRule = new DefaultExcludeRule(GUtil.map(ExcludeRule.GROUP_KEY, TEST_ORG, ExcludeRule.MODULE_KEY, TEST_MODULE));
+        Exclusion mavenExclude = excludeRuleConverter.convert(excludeRule);
+        assertEquals(TEST_ORG, mavenExclude.getGroupId());
+        assertEquals(TEST_MODULE, mavenExclude.getArtifactId());
+    }
+    
+    @Test
+    public void unconvertableRules() {
+        checkForNull(new DefaultExcludeRule(WrapUtil.toMap(ExcludeRule.GROUP_KEY, TEST_ORG)));
+        checkForNull(new DefaultExcludeRule(WrapUtil.toMap(ExcludeRule.MODULE_KEY, TEST_MODULE)));
+    }
+
+    private void checkForNull(DefaultExcludeRule excludeRule) {
+        assertNull(excludeRuleConverter.convert(excludeRule));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultPomDependenciesConverterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultPomDependenciesConverterTest.java
new file mode 100644
index 0000000..495b603
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/dependencies/DefaultPomDependenciesConverterTest.java
@@ -0,0 +1,220 @@
+/*
+ * 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.publish.maven.dependencies;
+
+import org.apache.maven.model.Exclusion;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.ModuleDependency;
+import org.gradle.api.artifacts.maven.Conf2ScopeMapping;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.internal.artifacts.dependencies.DefaultDependencyArtifact;
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
+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.List;
+import java.util.Set;
+
+import static java.util.Arrays.asList;
+import static org.gradle.util.WrapUtil.toMap;
+import static org.gradle.util.WrapUtil.toSet;
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultPomDependenciesConverterTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+    
+    private DefaultPomDependenciesConverter dependenciesConverter;
+    private Conf2ScopeMappingContainer conf2ScopeMappingContainerMock = context.mock(Conf2ScopeMappingContainer.class);
+    private ExcludeRuleConverter excludeRuleConverterMock = context.mock(ExcludeRuleConverter.class);
+
+    private ModuleDependency dependency1;
+    private ModuleDependency dependency2;
+    private ModuleDependency dependency31;
+    private ModuleDependency dependency32;
+    private Configuration compileConfStub;
+    private Configuration testCompileConfStub;
+
+    @Before
+    public void setUp() {
+        setUpCommonDependenciesAndConfigurations();
+        dependenciesConverter = new DefaultPomDependenciesConverter(excludeRuleConverterMock);
+    }
+
+    private void setUpCommonDependenciesAndConfigurations() {
+        dependency1 = createDependency("org1", "name1", "rev1");
+        dependency2 = createDependency("org2", "name2", "rev2");
+        dependency2.addArtifact(new DefaultDependencyArtifact("name2", null, null, null, null));
+        dependency31 = createDependency("org3", "name3", "rev3");
+        dependency32 = createDependency("org3", "name3", "rev3");
+        dependency32.addArtifact(new DefaultDependencyArtifact("artifactName32", "type32", "ext", "classifier32", null));
+        compileConfStub = createNamedConfigurationStubWithDependencies("compile", dependency1, dependency31);
+        testCompileConfStub = createNamedConfigurationStubWithDependencies("testCompile", dependency2, dependency32);
+        context.checking(new Expectations() {{
+            allowing(conf2ScopeMappingContainerMock).getMapping(toSet(testCompileConfStub, compileConfStub)); will(returnValue(createMapping(testCompileConfStub, "test")));
+            allowing(conf2ScopeMappingContainerMock).getMapping(toSet(compileConfStub, testCompileConfStub)); will(returnValue(createMapping(testCompileConfStub, "test")));
+            allowing(conf2ScopeMappingContainerMock).getMapping(toSet(testCompileConfStub)); will(returnValue(createMapping(testCompileConfStub, "test")));
+            allowing(conf2ScopeMappingContainerMock).getMapping(toSet(compileConfStub)); will(returnValue(createMapping(compileConfStub, "compile")));
+        }});
+    }
+
+    private Conf2ScopeMapping createMapping(Configuration configuration, String scope) {
+        return new Conf2ScopeMapping(10, configuration, scope);
+    }
+
+    private Configuration createNamedConfigurationStubWithDependencies(final String confName, final ModuleDependency... dependencies) {
+        final Configuration configurationStub = context.mock(Configuration.class, confName);
+        context.checking(new Expectations() {{
+            allowing(configurationStub).getName();
+            will(returnValue(confName));
+            allowing(configurationStub).getDependencies(ModuleDependency.class);
+            will(returnValue(toSet(dependencies)));
+        }});
+        return configurationStub;
+    }
+
+    private ModuleDependency createDependency(final String group, final String name, final String version) {
+        return new DefaultExternalModuleDependency(group, name, version);
+    }
+
+    @Test
+    public void init() {
+        assertSame(excludeRuleConverterMock, dependenciesConverter.getExcludeRuleConverter());
+    }
+
+    @Test
+    public void convert() {
+        Set<Configuration> configurations = toSet(compileConfStub, testCompileConfStub);
+        context.checking(new Expectations() {{
+            allowing(conf2ScopeMappingContainerMock).isSkipUnmappedConfs(); will(returnValue(false));
+        }});
+        List<org.apache.maven.model.Dependency> actualMavenDependencies = dependenciesConverter.convert(conf2ScopeMappingContainerMock, configurations);
+        assertEquals(3, actualMavenDependencies.size());
+        checkCommonMavenDependencies(actualMavenDependencies);
+    }
+
+    @Test
+    public void convertWithUnMappedConfAndSkipTrue() {
+        final Dependency dependency4 = createDependency("org4", "name4", "rev4");
+        final Configuration unmappedConfigurationStub = createNamedConfigurationStubWithDependencies("unmappedConf");
+        context.checking(new Expectations() {{
+            allowing(unmappedConfigurationStub).getDependencies();
+            will(returnValue(toSet(dependency4)));
+        }});
+        context.checking(new Expectations() {{
+            allowing(conf2ScopeMappingContainerMock).isSkipUnmappedConfs(); will(returnValue(true));
+            allowing(conf2ScopeMappingContainerMock).getMapping(asList(unmappedConfigurationStub)); will(returnValue(null));
+        }});
+        List<org.apache.maven.model.Dependency> actualMavenDependencies = dependenciesConverter.convert(conf2ScopeMappingContainerMock, toSet(
+                compileConfStub, testCompileConfStub, unmappedConfigurationStub));
+        assertEquals(3, actualMavenDependencies.size());
+        checkCommonMavenDependencies(actualMavenDependencies);
+    }
+
+    @Test
+    public void convertWithUnMappedConfAndSkipFalse() {
+        final ModuleDependency dependency4 = createDependency("org4", "name4", "rev4");
+        final Configuration unmappedConfigurationStub = createNamedConfigurationStubWithDependencies("unmappedConf", dependency4);
+        context.checking(new Expectations() {{
+            allowing(conf2ScopeMappingContainerMock).isSkipUnmappedConfs(); will(returnValue(false));
+            allowing(conf2ScopeMappingContainerMock).getMapping(toSet(unmappedConfigurationStub)); will(returnValue(new Conf2ScopeMapping(null, unmappedConfigurationStub, null)));
+        }});
+        List<org.apache.maven.model.Dependency> actualMavenDependencies = dependenciesConverter.convert(conf2ScopeMappingContainerMock, toSet(
+                compileConfStub, testCompileConfStub, unmappedConfigurationStub));
+        assertEquals(4, actualMavenDependencies.size());
+        checkCommonMavenDependencies(actualMavenDependencies);
+        assertTrue(hasDependency(actualMavenDependencies, "org4", "name4", "rev4", null, null, null, false));
+    }
+
+    private void checkCommonMavenDependencies(List<org.apache.maven.model.Dependency> actualMavenDependencies) {
+        assertTrue(hasDependency(actualMavenDependencies, "org1", "name1", "rev1", null, "compile", null, false));
+        assertTrue(hasDependency(actualMavenDependencies, "org2", "name2", "rev2", null, "test", null, false));
+        assertTrue(hasDependency(actualMavenDependencies, "org3", "artifactName32", "rev3", "type32", "test", "classifier32", false));
+    }
+
+    private boolean hasDependency(List<org.apache.maven.model.Dependency> mavenDependencies,
+                                  String group, String artifactId, String version, String type, String scope,
+                                  String classifier, boolean optional) {
+        org.apache.maven.model.Dependency expectedDependency = new org.apache.maven.model.Dependency();
+        expectedDependency.setGroupId(group);
+        expectedDependency.setArtifactId(artifactId);
+        expectedDependency.setVersion(version);
+        expectedDependency.setType(type);
+        expectedDependency.setScope(scope);
+        expectedDependency.setClassifier(classifier);
+        expectedDependency.setOptional(optional);
+        for (org.apache.maven.model.Dependency mavenDependency : mavenDependencies) {
+            if (equals(mavenDependency, expectedDependency)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean equals(org.apache.maven.model.Dependency lhs, org.apache.maven.model.Dependency rhs) {
+        if (!lhs.getGroupId().equals(lhs.getGroupId())) {
+            return false;
+        }
+        if (!lhs.getArtifactId().equals(lhs.getArtifactId())) {
+            return false;
+        }
+        if (!lhs.getVersion().equals(lhs.getVersion())) {
+            return false;
+        }
+        if (lhs.getType() != null ? !lhs.getType().equals(lhs.getType()) : rhs.getType() != null) {
+            return false;
+        }
+        if (lhs.getScope() != null ? !lhs.getScope().equals(lhs.getScope()) : rhs.getScope() != null) {
+            return false;
+        }
+        if (!lhs.isOptional() == lhs.isOptional()) {
+            return false;
+        }
+        if (lhs.getClassifier() != null ? !lhs.getClassifier().equals(rhs.getClassifier()) : rhs.getClassifier() != null) {
+            return false;
+        }
+        return true;
+    }
+
+    @Test
+    public void convertWithConvertableExcludes() {
+        final Configuration someConfigurationStub = createNamedConfigurationStubWithDependencies("someConfiguration", dependency1);
+        final Exclusion mavenExclude = new Exclusion();
+        mavenExclude.setGroupId("a");
+        mavenExclude.setArtifactId("b");
+        dependency1.exclude(toMap("key", "value"));
+        context.checking(new Expectations() {{
+           allowing(conf2ScopeMappingContainerMock).getMapping(toSet(someConfigurationStub)); will(returnValue(createMapping(compileConfStub, "compile")));
+           allowing(excludeRuleConverterMock).convert(dependency1.getExcludeRules().iterator().next()); will(returnValue(mavenExclude));
+        }});
+        List<org.apache.maven.model.Dependency> actualMavenDependencies = dependenciesConverter.convert(conf2ScopeMappingContainerMock, toSet(someConfigurationStub));
+        assertEquals(1, actualMavenDependencies.size());
+        assertTrue(hasDependency(actualMavenDependencies, "org1", "name1", "rev1", null, "compile", null, false));
+        org.apache.maven.model.Dependency mavenDependency = (org.apache.maven.model.Dependency) actualMavenDependencies.get(0);
+        assertThat(mavenDependency.getExclusions().size(), equalTo(1));
+        assertThat(((Exclusion) mavenDependency.getExclusions().get(0)).getGroupId(), equalTo(mavenExclude.getGroupId()));
+        assertThat(((Exclusion) mavenDependency.getExclusions().get(0)).getArtifactId(), equalTo(mavenExclude.getArtifactId()));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/AbstractMavenResolverTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/AbstractMavenResolverTest.java
new file mode 100644
index 0000000..ee02a78
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/AbstractMavenResolverTest.java
@@ -0,0 +1,248 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.apache.maven.artifact.ant.AttachedArtifact;
+import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
+import org.apache.maven.artifact.ant.Pom;
+import org.apache.maven.settings.Settings;
+import org.apache.tools.ant.Project;
+import org.codehaus.plexus.PlexusContainerException;
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.artifacts.maven.MavenResolver;
+import org.gradle.api.artifacts.maven.PomFilterContainer;
+import org.gradle.api.artifacts.maven.PublishFilter;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.util.AntUtil;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.gradle.util.WrapUtil;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.jmock.Expectations;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractMavenResolverTest {
+    public static final String TEST_NAME = "name";
+    private static final Artifact TEST_IVY_ARTIFACT = DefaultArtifact.newIvyArtifact(ModuleRevisionId.newInstance("org", TEST_NAME, "1.0"), null);
+    private static final File TEST_IVY_FILE = new File("somepom.xml");
+    private static final File TEST_JAR_FILE = new File("somejar.jar");
+    private static final Artifact TEST_ARTIFACT = new DefaultArtifact(ModuleRevisionId.newInstance("org", TEST_NAME, "1.0"), null, TEST_NAME, "jar", "jar");
+    protected ArtifactPomContainer artifactPomContainerMock;
+    protected PomFilterContainer pomFilterContainerMock;
+    protected LoggingManagerInternal loggingManagerMock;
+
+    protected JUnit4GroovyMockery context = new JUnit4GroovyMockery() {
+        {
+            setImposteriser(ClassImposteriser.INSTANCE);
+        }
+    };
+    protected MavenPom pomMock;
+
+    protected Settings mavenSettingsMock;
+
+    protected abstract MavenResolver getMavenResolver();
+
+    protected abstract InstallDeployTaskSupport getInstallDeployTask();
+
+    protected abstract PomFilterContainer createPomFilterContainerMock();
+
+    @Before
+    public void setUp() {
+        pomFilterContainerMock = createPomFilterContainerMock();
+        artifactPomContainerMock = context.mock(ArtifactPomContainer.class);
+        pomMock = context.mock(MavenPom.class);
+        mavenSettingsMock = context.mock(Settings.class);
+        loggingManagerMock = context.mock(LoggingManagerInternal.class);
+    }
+
+    @Test
+    public void deployOrInstall() throws IOException, PlexusContainerException {
+        ClassifierArtifact classifierArtifact = new ClassifierArtifact("someClassifier",
+                "someType", new File("someClassifierFile"));
+        final Set<DeployableFilesInfo> testDeployableFilesInfos = WrapUtil.toSet(
+                new DeployableFilesInfo(new File("pom1.xml"), new File("artifact1.jar"), Collections.<ClassifierArtifact>emptySet()),
+                new DeployableFilesInfo(new File("pom2.xml"), new File("artifact2.jar"), WrapUtil.toSet(classifierArtifact))
+        );
+        final AttachedArtifact attachedArtifact = new AttachedArtifact();
+        context.checking(new Expectations() {
+            {
+                allowing((CustomInstallDeployTaskSupport) getInstallDeployTask()).getSettings();
+                will(returnValue(mavenSettingsMock));
+                allowing((CustomInstallDeployTaskSupport) getInstallDeployTask()).getProject();
+                will(returnValue(AntUtil.createProject()));
+                allowing((CustomInstallDeployTaskSupport) getInstallDeployTask()).createAttach();
+                will(returnValue(attachedArtifact));
+                one(artifactPomContainerMock).addArtifact(TEST_ARTIFACT, TEST_JAR_FILE);
+                allowing(artifactPomContainerMock).createDeployableFilesInfos();
+                will(returnValue(testDeployableFilesInfos));
+            }
+        });
+        getMavenResolver().publish(TEST_IVY_ARTIFACT, TEST_IVY_FILE, true);
+        getMavenResolver().publish(TEST_ARTIFACT, TEST_JAR_FILE, true);
+        checkTransaction(testDeployableFilesInfos, attachedArtifact, classifierArtifact);
+        assertSame(mavenSettingsMock, getMavenResolver().getSettings());
+    }
+
+    protected void checkTransaction(final Set<DeployableFilesInfo> deployableFilesInfos, final AttachedArtifact attachedArtifact, ClassifierArtifact classifierArtifact) throws IOException, PlexusContainerException {
+        final GrabSettingsFileAction grabSettingsFileAction = new GrabSettingsFileAction();
+        context.checking(new Expectations() {
+            {
+                one(getInstallDeployTask()).setProject(with(any(Project.class)));
+                one(getInstallDeployTask()).setSettingsFile(with(any(File.class)));
+                will(grabSettingsFileAction);
+                for (DeployableFilesInfo deployableFilesInfo : deployableFilesInfos) {
+                    one(getInstallDeployTask()).setFile(deployableFilesInfo.getArtifactFile());
+                    one(getInstallDeployTask()).addPom(with(pomMatcher(deployableFilesInfo.getPomFile(), getInstallDeployTask().getProject())));
+                    one(loggingManagerMock).captureStandardOutput(LogLevel.INFO);
+                    will(returnValue(loggingManagerMock));
+                    one(loggingManagerMock).start();
+                    one(getInstallDeployTask()).execute();
+                    one(loggingManagerMock).stop();
+                    will(returnValue(loggingManagerMock));
+                }
+            }
+        });
+        getMavenResolver().commitPublishTransaction();
+        assertThat(attachedArtifact.getFile(), equalTo(classifierArtifact.getFile()));
+        assertThat(attachedArtifact.getType(), equalTo(classifierArtifact.getType()));
+        assertThat(attachedArtifact.getClassifier(), equalTo(classifierArtifact.getClassifier()));
+        assertThat(grabSettingsFileAction.getSettingsFile().exists(), equalTo(false));
+        assertThat(grabSettingsFileAction.getSettingsFileContent(), equalTo(AbstractMavenResolver.SETTINGS_XML));
+    }
+
+    private static Matcher<Pom> pomMatcher(final File expectedPomFile, final Project expectedAntProject) {
+        return new BaseMatcher<Pom>() {
+            public void describeTo(Description description) {
+                description.appendText("matching pom");
+            }
+
+            public boolean matches(Object actual) {
+                Pom actualPom = (Pom) actual;
+                return actualPom.getFile().equals(expectedPomFile) && actualPom.getProject().equals(expectedAntProject);
+            }
+        };
+    }
+
+    @Test
+    public void setFilter() {
+        final PublishFilter publishFilterMock = context.mock(PublishFilter.class);
+        context.checking(new Expectations() {{
+            one(pomFilterContainerMock).setFilter(publishFilterMock);
+        }});
+        getMavenResolver().setFilter(publishFilterMock);
+    }
+
+    @Test
+    public void getFilter() {
+        final PublishFilter publishFilterMock = context.mock(PublishFilter.class);
+        context.checking(new Expectations() {{
+            allowing(pomFilterContainerMock).getFilter();
+            will(returnValue(publishFilterMock));
+        }});
+        assertSame(publishFilterMock, getMavenResolver().getFilter());
+    }
+
+    @Test
+    public void setPom() {
+        context.checking(new Expectations() {{
+            one(pomFilterContainerMock).setPom(pomMock);
+        }});
+        getMavenResolver().setPom(pomMock);
+    }
+
+    @Test
+    public void getPom() {
+        context.checking(new Expectations() {{
+            allowing(pomFilterContainerMock).getPom();
+            will(returnValue(pomMock));
+        }});
+        assertSame(pomMock, getMavenResolver().getPom());
+    }
+
+    @Test
+    public void addFilter() {
+        final String testName = "somename";
+        final PublishFilter publishFilterMock = context.mock(PublishFilter.class);
+        context.checking(new Expectations() {{
+            one(pomFilterContainerMock).addFilter(testName, publishFilterMock);
+        }});
+        getMavenResolver().addFilter(testName, publishFilterMock);
+    }
+
+    @Test
+    public void filter() {
+        final String testName = "somename";
+        final PublishFilter publishFilterMock = context.mock(PublishFilter.class);
+        context.checking(new Expectations() {{
+            one(pomFilterContainerMock).filter(testName);
+            will(returnValue(publishFilterMock));
+        }});
+        assertSame(publishFilterMock, getMavenResolver().filter(testName));
+    }
+
+    @Test
+    public void pom() {
+        final String testName = "somename";
+        context.checking(new Expectations() {{
+            one(pomFilterContainerMock).pom(testName);
+            will(returnValue(pomMock));
+        }});
+        assertSame(pomMock, getMavenResolver().pom(testName));
+    }
+
+    private static class GrabSettingsFileAction implements Action {
+        private File settingsFile;
+        private String settingsFileContent;
+
+        public void describeTo(Description description) {
+        }
+
+        public Object invoke(Invocation invocation) throws Throwable {
+            settingsFile = (File) invocation.getParameter(0);
+            settingsFileContent = FileUtils.readFileToString(settingsFile);
+            return null;
+        }
+
+        public File getSettingsFile() {
+            return settingsFile;
+        }
+
+        public String getSettingsFileContent() {
+            return settingsFileContent;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenDeployerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenDeployerTest.java
new file mode 100644
index 0000000..025bc86
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenDeployerTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.apache.maven.artifact.ant.AttachedArtifact;
+import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
+import org.apache.maven.artifact.ant.RemoteRepository;
+import org.codehaus.plexus.PlexusContainer;
+import org.codehaus.plexus.PlexusContainerException;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.maven.MavenResolver;
+import org.gradle.api.artifacts.maven.PomFilterContainer;
+import org.gradle.util.WrapUtil;
+import org.jmock.Expectations;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Set;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(org.jmock.integration.junit4.JMock.class)
+public class BaseMavenDeployerTest extends AbstractMavenResolverTest {
+
+    private BaseMavenDeployer mavenDeployer = createMavenDeployer();
+
+    private DeployTaskFactory deployTaskFactoryMock = context.mock(DeployTaskFactory.class);
+    private CustomDeployTask deployTaskMock = context.mock(CustomDeployTask.class);
+
+    private PlexusContainer plexusContainerMock = context.mock(PlexusContainer.class);
+    private RemoteRepository testRepository = new RemoteRepository();
+    private RemoteRepository testSnapshotRepository = new RemoteRepository();
+
+    private Configuration configurationStub = context.mock(Configuration.class);
+
+    protected BaseMavenDeployer createMavenDeployer() {
+        return new BaseMavenDeployer(TEST_NAME, pomFilterContainerMock, artifactPomContainerMock, loggingManagerMock);
+    }
+
+    protected MavenResolver getMavenResolver() {
+        return mavenDeployer;
+    }
+
+    protected InstallDeployTaskSupport getInstallDeployTask() {
+        return deployTaskMock;
+    }
+
+    protected PomFilterContainer createPomFilterContainerMock() {
+        return context.mock(PomFilterContainer.class);
+    }
+
+    public void setUp() {
+        super.setUp();
+        mavenDeployer = createMavenDeployer();
+        mavenDeployer.setDeployTaskFactory(deployTaskFactoryMock);
+        mavenDeployer.setRepository(testRepository);
+        mavenDeployer.setSnapshotRepository(testSnapshotRepository);
+        mavenDeployer.setConfiguration(configurationStub);
+        mavenDeployer.setUniqueVersion(false);
+    }
+
+    protected void checkTransaction(final Set<DeployableFilesInfo> deployableFilesInfos, AttachedArtifact attachedArtifact, ClassifierArtifact classifierArtifact) throws IOException, PlexusContainerException {
+        final Set<File> protocolJars = WrapUtil.toLinkedSet(new File("jar1"), new File("jar1"));
+        context.checking(new Expectations() {{
+                allowing(configurationStub).resolve();
+                will(returnValue(protocolJars));
+                allowing(deployTaskFactoryMock).createDeployTask();
+                will(returnValue(getInstallDeployTask()));
+                allowing(deployTaskMock).getContainer();
+                will(returnValue(plexusContainerMock));
+                for (File protocolProviderJar : protocolJars) {
+                    one(plexusContainerMock).addJarResource(protocolProviderJar);
+                }
+                one(deployTaskMock).setUniqueVersion(mavenDeployer.isUniqueVersion());
+                one(deployTaskMock).addRemoteRepository(testRepository);
+                one(deployTaskMock).addRemoteSnapshotRepository(testSnapshotRepository);
+        }});
+        super.checkTransaction(deployableFilesInfos, attachedArtifact, classifierArtifact);
+    }
+
+    @Test
+    public void init() {
+        mavenDeployer = new BaseMavenDeployer(TEST_NAME, pomFilterContainerMock, artifactPomContainerMock, loggingManagerMock);
+        assertTrue(mavenDeployer.isUniqueVersion());
+    }
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenInstallerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenInstallerTest.java
new file mode 100644
index 0000000..38f8dbc
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BaseMavenInstallerTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.apache.maven.artifact.ant.AttachedArtifact;
+import org.apache.maven.artifact.ant.InstallDeployTaskSupport;
+import org.codehaus.plexus.PlexusContainerException;
+import org.gradle.api.artifacts.maven.MavenResolver;
+import org.gradle.api.artifacts.maven.PomFilterContainer;
+import org.jmock.Expectations;
+
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class BaseMavenInstallerTest extends AbstractMavenResolverTest {
+    private BaseMavenInstaller mavenInstaller;
+
+    private InstallTaskFactory installTaskFactoryMock;
+    private CustomInstallTask installTaskMock;
+
+    protected BaseMavenInstaller createMavenInstaller() {
+        return new BaseMavenInstaller(TEST_NAME, pomFilterContainerMock, artifactPomContainerMock, loggingManagerMock);
+    }
+
+    protected PomFilterContainer createPomFilterContainerMock() {
+        return context.mock(PomFilterContainer.class);
+    }
+
+    protected MavenResolver getMavenResolver() {
+        return mavenInstaller;
+    }
+
+    protected InstallDeployTaskSupport getInstallDeployTask() {
+        return installTaskMock;
+    }
+
+    public void setUp() {
+        super.setUp();
+        installTaskFactoryMock = context.mock(InstallTaskFactory.class);
+        installTaskMock = context.mock(CustomInstallTask.class);
+        mavenInstaller = createMavenInstaller();
+        mavenInstaller.setInstallTaskFactory(installTaskFactoryMock);
+    }
+
+    protected void checkTransaction(final Set<DeployableFilesInfo> deployableUnits, AttachedArtifact attachedArtifact, ClassifierArtifact classifierArtifact) throws IOException, PlexusContainerException {
+        context.checking(new Expectations() {
+            {
+                allowing(installTaskFactoryMock).createInstallTask();
+                will(returnValue(getInstallDeployTask()));
+            }
+        });
+        super.checkTransaction(deployableUnits, attachedArtifact, classifierArtifact);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BasePomFilterContainerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BasePomFilterContainerTest.java
new file mode 100644
index 0000000..0afc093
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/BasePomFilterContainerTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.artifacts.maven.PublishFilter;
+import org.gradle.api.internal.artifacts.publish.maven.MavenPomFactory;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class BasePomFilterContainerTest {
+    private static final String TEST_NAME = "testName";
+    
+    private BasePomFilterContainer pomFilterContainer;
+    protected MavenPomFactory mavenPomFactoryMock;
+    protected MavenPom pomMock;
+    protected PomFilter pomFilterMock;
+    protected PublishFilter publishFilterMock;
+
+
+    protected BasePomFilterContainer createPomFilterContainer() {
+        return new BasePomFilterContainer(mavenPomFactoryMock);
+    }
+
+    protected JUnit4GroovyMockery context = new JUnit4GroovyMockery();
+
+    @Before
+    public void setUp() {
+        pomFilterMock = context.mock(PomFilter.class);
+        mavenPomFactoryMock = context.mock(MavenPomFactory.class);
+        pomMock = context.mock(MavenPom.class);
+        publishFilterMock = context.mock(PublishFilter.class);
+        context.checking(new Expectations() {
+            {
+                allowing(mavenPomFactoryMock).createMavenPom();
+                will(returnValue(pomMock));
+            }
+        });
+        pomFilterContainer = createPomFilterContainer();
+        pomFilterContainer.setDefaultPomFilter(pomFilterMock);
+    }
+
+    @Test
+    public void init() {
+        pomFilterContainer = new BasePomFilterContainer(mavenPomFactoryMock);
+        assertNotNull(pomFilterContainer.getPom());
+        assertSame(PublishFilter.ALWAYS_ACCEPT, pomFilterContainer.getFilter());
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void getFilterWithNullName() {
+        pomFilterContainer.filter(null);
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void getPomWithNullName() {
+        pomFilterContainer.pom(null);
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void addFilterWithNullName() {
+        pomFilterContainer.addFilter(null, PublishFilter.ALWAYS_ACCEPT);
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void addFilterWithNullFilter() {
+        pomFilterContainer.addFilter("somename", null);
+    }
+
+    @Test
+    public void getFilter() {
+        context.checking(new Expectations() {{
+            allowing(pomFilterMock).getFilter(); will(returnValue(publishFilterMock));
+        }});
+        assertSame(publishFilterMock, pomFilterContainer.getFilter());
+    }
+
+    @Test
+    public void setFilter() {
+        context.checking(new Expectations() {{
+            one(pomFilterMock).setFilter(publishFilterMock);
+        }});
+        pomFilterContainer.setFilter(publishFilterMock);
+    }
+
+    @Test
+    public void getPom() {
+        context.checking(new Expectations() {{
+            allowing(pomFilterMock).getPomTemplate(); will(returnValue(pomMock));
+        }});
+        assertSame(pomMock, pomFilterContainer.getPom());
+    }
+
+    @Test
+    public void setPom() {
+        context.checking(new Expectations() {{
+            allowing(pomFilterMock).setPomTemplate(pomMock);
+        }});
+        pomFilterContainer.setPom(pomMock);
+    }
+
+
+    @Test
+    public void addFilter() {
+        MavenPom pom = pomFilterContainer.addFilter(TEST_NAME, publishFilterMock);
+        assertSame(pom, pomMock);
+        assertSame(pomMock, pomFilterContainer.pom(TEST_NAME));
+        assertSame(publishFilterMock, pomFilterContainer.filter(TEST_NAME));
+    }
+
+    @Test
+    public void getActivePomFiltersWithDefault() {
+        Iterator<PomFilter> pomFilterIterator = pomFilterContainer.getActivePomFilters().iterator();
+        assertSame(pomFilterMock, pomFilterIterator.next());
+        assertFalse(pomFilterIterator.hasNext());
+    }
+
+    @Test
+    public void getActivePomFiltersWithAdditionalFilters() {
+        PublishFilter filter1 = context.mock(PublishFilter.class, "filter1");
+        PublishFilter filter2 = context.mock(PublishFilter.class, "filter2");
+        String testName1 = "name1";
+        String testName2 = "name2";
+        pomFilterContainer.addFilter(testName1, filter1);
+        pomFilterContainer.addFilter(testName2, filter2);
+        Set actualActiveFilters = getSetFromIterator(pomFilterContainer.getActivePomFilters());
+        assertEquals(2, actualActiveFilters.size());
+        checkIfInSet(testName1, filter1, actualActiveFilters);
+        checkIfInSet(testName2, filter2, actualActiveFilters);
+    }
+
+    private void checkIfInSet(String expectedName, PublishFilter expectedPublishFilter, Set<PomFilter> filters) {
+        for (PomFilter pomFilter : filters) {
+            if (areEqualPomFilter(expectedName, expectedPublishFilter, pomFilter)) {
+                return;
+            }
+        }
+        fail("Not in Set");
+    }
+
+    private Set getSetFromIterator(Iterable<PomFilter> pomFilterIterable) {
+        HashSet<PomFilter> filters = new HashSet<PomFilter>();
+        for (PomFilter pomFilter : pomFilterIterable) {
+            filters.add(pomFilter);
+        }
+        return filters;
+    }
+
+
+    private boolean areEqualPomFilter(String expectedName, PublishFilter expectedPublishFilter, PomFilter pomFilter) {
+        if (!expectedName.equals(pomFilter.getName())) {
+            return false;
+        }
+        if (!(expectedPublishFilter == pomFilter.getFilter())) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainerTest.java
new file mode 100644
index 0000000..8fd0e71
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomContainerTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.artifacts.maven.PomFilterContainer;
+import org.gradle.api.artifacts.maven.PublishFilter;
+import org.gradle.api.internal.artifacts.publish.maven.MavenPomMetaInfoProvider;
+import org.gradle.util.WrapUtil;
+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.util.HashSet;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultArtifactPomContainerTest {
+    private static final File TEST_POM_DIR = new File("pomDir");
+
+    private DefaultArtifactPomContainer artifactPomContainer;
+
+    private MavenPomMetaInfoProvider metaInfoProviderMock;
+    private PomFilterContainer pomFilterContainerMock;
+    private PomFilter pomFilterMock;
+    private PublishFilter publishFilterMock;
+    private ArtifactPomFactory artifactPomFactoryMock;
+    private ArtifactPom artifactPomMock;
+    private MavenPom mavenPomMock;
+    private MavenPom mavenTemplatePomMock;
+
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    private File expectedFile;
+    private File expectedPomFile;
+    private Artifact expectedArtifact;
+    private static final String POMFILTER_NAME = "somename";
+
+    @Before
+    public void setUp() {
+        expectedPomFile = new File(TEST_POM_DIR, "pom-" + POMFILTER_NAME + ".xml");
+        expectedFile = new File("somePath");
+        expectedArtifact = createTestArtifact("someName");
+        pomFilterContainerMock = context.mock(PomFilterContainer.class);
+        pomFilterMock = context.mock(PomFilter.class);
+        artifactPomMock = context.mock(ArtifactPom.class);
+        artifactPomFactoryMock = context.mock(ArtifactPomFactory.class);
+        publishFilterMock = context.mock(PublishFilter.class);
+        mavenPomMock = context.mock(MavenPom.class);
+        mavenTemplatePomMock = context.mock(MavenPom.class, "templatePom");
+        metaInfoProviderMock = context.mock(MavenPomMetaInfoProvider.class);
+
+        artifactPomContainer = new DefaultArtifactPomContainer(metaInfoProviderMock, pomFilterContainerMock,
+                artifactPomFactoryMock);
+    }
+
+    @Test
+    public void addArtifact() {
+        context.checking(new Expectations() {{
+            allowing(pomFilterContainerMock).getActivePomFilters(); will(returnValue(WrapUtil.toList(pomFilterMock)));
+            allowing(pomFilterMock).getName(); will(returnValue(POMFILTER_NAME));
+            allowing(pomFilterMock).getFilter(); will(returnValue(publishFilterMock));
+            allowing(pomFilterMock).getPomTemplate(); will(returnValue(mavenTemplatePomMock));
+            allowing(publishFilterMock).accept(expectedArtifact, expectedFile); will(returnValue(true));
+            allowing(artifactPomFactoryMock).createArtifactPom(mavenTemplatePomMock); will(returnValue(artifactPomMock));
+            one(artifactPomMock).addArtifact(expectedArtifact, expectedFile);
+            allowing(artifactPomMock).getPom(); will(returnValue(mavenPomMock));
+            allowing(artifactPomMock).getArtifactFile(); will(returnValue(expectedFile));
+            allowing(artifactPomMock).getClassifiers(); will(returnValue(new HashSet<ClassifierArtifact>()));
+            allowing(metaInfoProviderMock).getMavenPomDir(); will(returnValue(TEST_POM_DIR));
+            one(artifactPomMock).writePom(expectedPomFile);
+        }});
+        artifactPomContainer.addArtifact(expectedArtifact, expectedFile);
+        Set<DeployableFilesInfo> deployableFilesInfos = artifactPomContainer.createDeployableFilesInfos();
+        assertEquals(1, deployableFilesInfos.size());
+        assertEquals(expectedFile, deployableFilesInfos.iterator().next().getArtifactFile());
+        assertEquals(expectedPomFile, deployableFilesInfos.iterator().next().getPomFile());
+    }
+
+    @Test
+    public void addArtifactNotAcceptedByFilter() {
+        context.checking(new Expectations() {{
+            allowing(pomFilterContainerMock).getActivePomFilters(); will(returnValue(WrapUtil.toList(pomFilterMock)));
+            allowing(pomFilterMock).getName(); will(returnValue(POMFILTER_NAME));
+            allowing(pomFilterMock).getFilter(); will(returnValue(publishFilterMock));
+            allowing(publishFilterMock).accept(expectedArtifact, expectedFile); will(returnValue(false));
+        }});
+        artifactPomContainer.addArtifact(expectedArtifact, expectedFile);
+        assertTrue(artifactPomContainer.getArtifactPoms().isEmpty());
+    }
+
+    @Test(expected= InvalidUserDataException.class)
+    public void addArtifactWithNullArtifact() {
+        artifactPomContainer.addArtifact(null, expectedFile);
+    }
+
+    @Test(expected= InvalidUserDataException.class)
+    public void addArtifactWithNullFile() {
+        artifactPomContainer.addArtifact(expectedArtifact, null);
+    }
+
+    private Artifact createTestArtifact(String name) {
+        return new DefaultArtifact(ModuleRevisionId.newInstance("org", name, "1.0"), null, name, "jar", "jar");
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomTest.java
new file mode 100644
index 0000000..b40e5c9
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultArtifactPomTest.java
@@ -0,0 +1,186 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.apache.ivy.core.module.descriptor.Artifact;
+import org.apache.ivy.core.module.descriptor.DefaultArtifact;
+import org.apache.ivy.core.module.id.ModuleRevisionId;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.Dependency;
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.internal.artifacts.publish.maven.DefaultMavenPom;
+import org.gradle.api.internal.artifacts.publish.maven.dependencies.DefaultConf2ScopeMappingContainer;
+import org.gradle.api.internal.artifacts.publish.maven.dependencies.PomDependenciesConverter;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.util.TemporaryFolder;
+import org.gradle.util.WrapUtil;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasItem;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultArtifactPomTest {
+    private DefaultArtifactPom artifactPom;
+    private MavenPom testPom;
+    private File expectedFile;
+    private Artifact expectedArtifact;
+
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    Mockery context = new JUnit4Mockery();
+
+    @Before
+    public void setUp() {
+        expectedFile = new File("somePath");
+        expectedArtifact = createTestArtifact("someName");
+        testPom = new DefaultMavenPom(context.mock(ConfigurationContainer.class), new DefaultConf2ScopeMappingContainer(),
+                context.mock(PomDependenciesConverter.class), context.mock(FileResolver.class));
+        artifactPom = new DefaultArtifactPom(testPom);
+    }
+
+    @Test
+    public void pomWithMainArtifact() {
+        artifactPom.addArtifact(expectedArtifact, expectedFile);
+
+        assertEquals(expectedArtifact, artifactPom.getArtifact());
+        assertEquals(expectedFile, artifactPom.getArtifactFile());
+        checkPom(expectedArtifact.getModuleRevisionId().getOrganisation(), expectedArtifact.getName(),
+                expectedArtifact.getType(), expectedArtifact.getModuleRevisionId().getRevision());
+    }
+
+    @Test
+    public void pomWithMainArtifactAndClassifierArtifacts() {
+        File classifierFile = new File("someClassifierFile");
+        Artifact classifierArtifact = createTestArtifact(expectedArtifact.getName(), "javadoc", "zip");
+
+        artifactPom.addArtifact(classifierArtifact, classifierFile);
+        artifactPom.addArtifact(expectedArtifact, expectedFile);
+
+        assertThat(artifactPom.getClassifiers(),
+                hasItem(new ClassifierArtifact("javadoc", "sometype", new File("someFile"))));
+
+        assertEquals(expectedArtifact, artifactPom.getArtifact());
+        assertEquals(expectedFile, artifactPom.getArtifactFile());
+        checkPom(expectedArtifact.getModuleRevisionId().getOrganisation(), expectedArtifact.getName(),
+                expectedArtifact.getType(), expectedArtifact.getModuleRevisionId().getRevision());
+    }
+    
+    @Test
+    public void pomWithClassifierArtifacts() {
+        File classifierFile = new File("someClassifierFile");
+        Artifact classifierArtifact = createTestArtifact(expectedArtifact.getName(), "javadoc", "zip");
+
+        artifactPom.addArtifact(classifierArtifact, classifierFile);
+
+        assertThat(artifactPom.getClassifiers(),
+                hasItem(new ClassifierArtifact("javadoc", "sometype", new File("someFile"))));
+        checkPom(classifierArtifact.getModuleRevisionId().getOrganisation(),
+                classifierArtifact.getName(), "jar",
+                classifierArtifact.getModuleRevisionId().getRevision());
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void addClassifierTwiceShouldThrowInvalidUserDataEx() {
+        File classifierFile = new File("someClassifierFile");
+        Artifact classifierArtifact = createTestArtifact(expectedArtifact.getName(), "javadoc");
+        artifactPom.addArtifact(classifierArtifact, classifierFile);
+        artifactPom.addArtifact(classifierArtifact, classifierFile);
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void addMainArtifactTwiceShouldThrowInvalidUserDataEx() {
+        artifactPom.addArtifact(expectedArtifact, expectedFile);
+        artifactPom.addArtifact(expectedArtifact, expectedFile);
+    }
+
+    @Test
+    public void initWithCustomPomSettings() {
+        testPom.setArtifactId(expectedArtifact.getName() + "X");
+        testPom.setGroupId(expectedArtifact.getModuleRevisionId().getOrganisation() + "X");
+        testPom.setVersion(expectedArtifact.getModuleRevisionId().getRevision() + "X");
+        testPom.setPackaging(expectedArtifact.getType() + "X");
+        artifactPom = new DefaultArtifactPom(testPom);
+        artifactPom.addArtifact(expectedArtifact, expectedFile);
+        assertEquals(expectedArtifact, artifactPom.getArtifact());
+        assertEquals(expectedFile, artifactPom.getArtifactFile());
+        checkPom(testPom.getGroupId(), testPom.getArtifactId(), testPom.getPackaging(), testPom.getVersion());
+    }
+
+    private void checkPom(String organisation, String name, String type, String revision) {
+        assertEquals(organisation, testPom.getGroupId());
+        assertEquals(name, testPom.getArtifactId());
+        assertEquals(type, testPom.getPackaging());
+        assertEquals(revision, testPom.getVersion());
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void addArtifactWithArtifactSrcNull() {
+        new DefaultArtifactPom(testPom).addArtifact(expectedArtifact, null);
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void addArtifactWithArtifactNull() {
+        new DefaultArtifactPom(testPom).addArtifact(null, expectedFile);
+    }
+    
+    private Artifact createTestArtifact(String name) {
+        return createTestArtifact(name, null);
+    }
+
+    private Artifact createTestArtifact(String name, String classifier) {
+        return createTestArtifact(name, classifier, "jar");
+    }
+
+    private Artifact createTestArtifact(String name, String classifier, String type) {
+        Map<String, String> extraAttributes = new HashMap<String, String>();
+        if (classifier != null) {
+            extraAttributes.put(Dependency.CLASSIFIER, classifier);
+        }
+        return new DefaultArtifact(ModuleRevisionId.newInstance("org", name, "1.0"), null, name, type, type, extraAttributes);
+    }
+
+    @Test
+    public void writePom() {
+        final MavenPom mavenPomMock = context.mock(MavenPom.class);
+        DefaultArtifactPom artifactPom = new DefaultArtifactPom(mavenPomMock);
+        final File somePomFile = new File(tmpDir.getDir(), "someDir/somePath");
+        final Set<Configuration> configurations = WrapUtil.toSet(context.mock(Configuration.class));
+        context.checking(new Expectations() {{
+            one(mavenPomMock).writeTo(with(any(FileWriter.class)));       
+        }});
+        artifactPom.writePom(somePomFile);
+        assertThat(somePomFile.getParentFile().isDirectory(), equalTo(true));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultPomFilterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultPomFilterTest.java
new file mode 100644
index 0000000..308f067
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/DefaultPomFilterTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.publish.maven.deploy;
+
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.artifacts.maven.PublishFilter;
+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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultPomFilterTest {
+    private static final String TEST_NAME = "TEST_NAME";
+
+    private DefaultPomFilter pomFilter;
+    private MavenPom mavenPomMock;
+
+    private PublishFilter publishFilterMock;
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    @Before
+    public void setUp() {
+        mavenPomMock = context.mock(MavenPom.class);
+        publishFilterMock = context.mock(PublishFilter.class);
+        pomFilter = new DefaultPomFilter(TEST_NAME, mavenPomMock, publishFilterMock);
+    }
+
+    @Test
+    public void testGetName() {
+        assertEquals(TEST_NAME, pomFilter.getName());
+        assertSame(mavenPomMock, pomFilter.getPomTemplate());
+        assertSame(publishFilterMock, pomFilter.getFilter());
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenDeployerTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenDeployerTest.groovy
new file mode 100644
index 0000000..908663a
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenDeployerTest.groovy
@@ -0,0 +1,122 @@
+/*
+ * 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.publish.maven.deploy.groovy
+
+import java.lang.reflect.Proxy
+import org.gradle.api.artifacts.maven.MavenPom
+import org.gradle.api.artifacts.maven.PublishFilter
+import org.gradle.api.internal.artifacts.publish.maven.deploy.BasePomFilterContainer
+import org.gradle.api.internal.artifacts.publish.maven.deploy.BasePomFilterContainerTest
+import org.hamcrest.BaseMatcher
+import org.hamcrest.Description
+import org.hamcrest.Matcher
+import org.jmock.integration.junit4.JMock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.junit.Assert.assertSame
+import org.hamcrest.Factory
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock)
+class DefaultGroovyPomFilterContainerTest extends BasePomFilterContainerTest {
+    static final String TEST_NAME = "somename"
+    DefaultGroovyPomFilterContainer groovyPomFilterContainer
+
+
+    @Before
+    public void setUp() {
+        super.setUp()
+    }
+
+    protected BasePomFilterContainer createPomFilterContainer() {
+        return groovyPomFilterContainer = new DefaultGroovyPomFilterContainer(mavenPomFactoryMock);
+    }
+
+    @Test
+    public void addFilterWithClosure() {
+        Closure closureFilter = {}
+        MavenPom pom = groovyPomFilterContainer.addFilter(TEST_NAME, closureFilter)
+        assertSame(pomMock, pom);
+        assertSame(pomMock, groovyPomFilterContainer.pom(TEST_NAME));
+        assertSame(closureFilter, getClosureFromProxy(groovyPomFilterContainer.filter(TEST_NAME)));
+    }
+
+    private Closure getClosureFromProxy(PublishFilter filter) {
+        Proxy.getInvocationHandler(filter).delegate
+    }
+
+    @Test
+    public void filterWithClosure() {
+        Closure closureFilter = {}
+        context.checking {
+            one(pomFilterMock).setFilter(withParam(FilterMatcher.equalsFilter(closureFilter)))
+        }
+        MavenPom pom = groovyPomFilterContainer.filter(closureFilter)
+    }
+
+    @Test
+    public void defaultPomWithClosure() {
+        String testGroup = "testGroup"
+        context.checking {
+            one(pomFilterMock).getPomTemplate(); will(returnValue(pomMock))
+            one(pomMock).setGroupId(testGroup);
+        }
+        groovyPomFilterContainer.pom {
+            groupId = testGroup
+        }
+    }
+
+    @Test
+    public void pomWithClosure() {
+        groovyPomFilterContainer.addFilter(TEST_NAME, {})
+        String testGroup = "testGroup"
+        context.checking {
+            one(pomMock).setGroupId(testGroup);
+        }
+        groovyPomFilterContainer.pom(TEST_NAME) {
+            groupId = testGroup
+        }
+    }
+}
+
+public class FilterMatcher extends BaseMatcher {
+    Closure filter
+
+    public void describeTo(Description description) {
+        description.appendText("matching filter");
+    }
+
+    public boolean matches(Object actual) {
+        return getClosureFromProxy(actual) == filter;
+    }
+
+    private Closure getClosureFromProxy(PublishFilter filter) {
+        Proxy.getInvocationHandler(filter).delegate
+    }
+
+
+    @Factory
+    public static Matcher<PublishFilter> equalsFilter(Closure filter) {
+        return new FilterMatcher(filter: filter);
+    }
+
+}
+
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenInstallerTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenInstallerTest.groovy
new file mode 100644
index 0000000..1928129
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenInstallerTest.groovy
@@ -0,0 +1,85 @@
+/*
+ * 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.publish.maven.deploy.groovy
+
+import org.gradle.api.artifacts.maven.GroovyPomFilterContainer
+import org.gradle.api.artifacts.maven.PomFilterContainer
+import org.gradle.api.internal.artifacts.publish.maven.deploy.BaseMavenInstaller
+import org.gradle.api.internal.artifacts.publish.maven.deploy.BaseMavenInstallerTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith (org.jmock.integration.junit4.JMock.class)
+class DefaultGroovyMavenInstallerTest extends BaseMavenInstallerTest {
+
+    private DefaultGroovyMavenInstaller groovyMavenInstaller;
+
+    protected PomFilterContainer createPomFilterContainerMock() {
+        context.mock(GroovyPomFilterContainer.class);
+    }
+
+    protected BaseMavenInstaller createMavenInstaller() {
+        groovyMavenInstaller = new DefaultGroovyMavenInstaller(TEST_NAME, pomFilterContainerMock, artifactPomContainerMock, loggingManagerMock)
+    }
+
+    @Before
+    void setUp() {
+        super.setUp();
+    }
+
+    @Test
+    void filter() {
+        Closure testClosure = {}
+        context.checking {
+            one(pomFilterContainerMock).filter(testClosure)
+        }
+        groovyMavenInstaller.filter(testClosure)
+    }
+
+    @Test
+    void pom() {
+        Closure testClosure = {}
+        context.checking {
+            one(pomFilterContainerMock).pom(testClosure)
+        }
+        groovyMavenInstaller.pom(testClosure)
+    }
+
+    @Test
+    void pomWithName() {
+        Closure testClosure = {}
+        String testName = 'somename'
+        context.checking {
+            one(pomFilterContainerMock).pom(testName, testClosure)
+        }
+        groovyMavenInstaller.pom(testName, testClosure)
+    }
+
+    @Test
+    void addFilter() {
+        Closure testClosure = {}
+        String testName = 'somename'
+        context.checking {
+            one(pomFilterContainerMock).addFilter(testName, testClosure)
+        }
+        groovyMavenInstaller.addFilter(testName, testClosure)
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenUploaderTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenUploaderTest.groovy
new file mode 100644
index 0000000..076cc35
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/artifacts/publish/maven/deploy/groovy/DefaultGroovyMavenUploaderTest.groovy
@@ -0,0 +1,119 @@
+/*
+ * 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.publish.maven.deploy.groovy
+
+import org.gradle.api.artifacts.maven.GroovyPomFilterContainer
+import org.gradle.api.artifacts.maven.PomFilterContainer
+import org.gradle.api.internal.artifacts.publish.maven.deploy.BaseMavenDeployer
+import org.gradle.api.internal.artifacts.publish.maven.deploy.BaseMavenDeployerTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.junit.Assert.assertEquals
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith (org.jmock.integration.junit4.JMock.class)
+class DefaultGroovyMavenDeployerTest extends BaseMavenDeployerTest {
+    private DefaultGroovyMavenDeployer groovyMavenDeployer;
+    private DefaultGroovyPomFilterContainerTest groovyMavenResolverHelper
+
+    protected PomFilterContainer createPomFilterContainerMock() {
+        context.mock(GroovyPomFilterContainer.class);
+    }
+
+    protected BaseMavenDeployer createMavenDeployer() {
+        groovyMavenDeployer = new DefaultGroovyMavenDeployer(TEST_NAME, pomFilterContainerMock, artifactPomContainerMock, loggingManagerMock)
+    }
+
+    @Before
+    void setUp() {
+        super.setUp();
+    }
+
+    @Test
+    void repositoryBuilder() {
+        checkRepositoryBuilder(DefaultGroovyMavenDeployer.REPOSITORY_BUILDER)
+    }
+
+    @Test
+    void snapshotRepositoryBuilder() {
+        checkRepositoryBuilder(DefaultGroovyMavenDeployer.SNAPSHOT_REPOSITORY_BUILDER)
+    }
+
+
+    void checkRepositoryBuilder(String repositoryName) {
+        String testUrl = 'testUrl'
+        String testProxyHost = 'hans'
+        String testUserName = 'userId'
+        String testSnapshotUpdatePolicy = 'always'
+        String testReleaseUpdatePolicy = 'never'
+        groovyMavenDeployer."$repositoryName"(url: testUrl) {
+            authentication(userName: testUserName)
+            proxy(host: testProxyHost)
+            releases(updatePolicy: testReleaseUpdatePolicy)
+            snapshots(updatePolicy: testSnapshotUpdatePolicy)
+        }
+        assertEquals(testUrl, groovyMavenDeployer."$repositoryName".url)
+        assertEquals(testUserName, groovyMavenDeployer."$repositoryName".authentication.userName)
+        assertEquals(testProxyHost, groovyMavenDeployer."$repositoryName".proxy.host)
+        assertEquals(testReleaseUpdatePolicy, groovyMavenDeployer."$repositoryName".releases.updatePolicy)
+        assertEquals(testSnapshotUpdatePolicy, groovyMavenDeployer."$repositoryName".snapshots.updatePolicy)
+    }
+
+    @Test
+    void filter() {
+        Closure testClosure = {}
+        context.checking {
+            one(pomFilterContainerMock).filter(testClosure)
+        }
+        groovyMavenDeployer.filter(testClosure)
+    }
+
+    @Test
+    void pom() {
+        Closure testClosure = {}
+        context.checking {
+            one(pomFilterContainerMock).pom(testClosure)
+        }
+        groovyMavenDeployer.pom(testClosure)
+    }
+
+    @Test
+    void pomWithName() {
+        Closure testClosure = {}
+        String testName = 'somename'
+        context.checking {
+            one(pomFilterContainerMock).pom(testName, testClosure)
+        }
+        groovyMavenDeployer.pom(testName, testClosure)
+    }
+
+    @Test
+    void addFilter() {
+        Closure testClosure = {}
+        String testName = 'somename'
+        context.checking {
+            one(pomFilterContainerMock).addFilter(testName, testClosure)
+        }
+        groovyMavenDeployer.addFilter(testName, testClosure)
+    }
+}
+
+
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/changedetection/CachingHasherTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/changedetection/CachingHasherTest.java
new file mode 100644
index 0000000..76c6ca5
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/changedetection/CachingHasherTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.changedetection;
+
+import org.gradle.cache.*;
+
+import static org.gradle.util.Matchers.*;
+import org.gradle.util.TemporaryFolder;
+import static org.hamcrest.Matchers.*;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+ at RunWith(JMock.class)
+public class CachingHasherTest {
+    @Rule
+    TemporaryFolder tmpDir = new TemporaryFolder();
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final Hasher delegate = context.mock(Hasher.class);
+    private final PersistentIndexedCache<File, CachingHasher.FileInfo> cache = context.mock(
+            PersistentIndexedCache.class);
+    private final CacheRepository cacheRepository = context.mock(CacheRepository.class);
+    private final byte[] hash = "hash".getBytes();
+    private final File file = tmpDir.createFile("testfile").write("content");
+    private CachingHasher hasher;
+
+    @Before
+    public void setup() {
+        context.checking(new Expectations(){{
+            CacheBuilder cacheBuilder = context.mock(CacheBuilder.class);
+            PersistentCache persistentCache = context.mock(PersistentCache.class);
+
+            one(cacheRepository).cache("fileHashes");
+            will(returnValue(cacheBuilder));
+
+            one(cacheBuilder).open();
+            will(returnValue(persistentCache));
+
+            one(persistentCache).openIndexedCache(with(notNullValue(Serializer.class)));
+            will(returnValue(cache));
+        }});
+        hasher = new CachingHasher(delegate, cacheRepository);
+    }
+
+    @Test
+    public void hashesFileWhenHashNotCached() {
+        context.checking(new Expectations() {{
+            one(cache).get(file);
+            will(returnValue(null));
+            one(delegate).hash(file);
+            will(returnValue(hash));
+            one(cache).put(with(equalTo(file)), with(reflectionEquals(new CachingHasher.FileInfo(hash, file.length(),
+                    file.lastModified()))));
+        }});
+
+        assertThat(hasher.hash(file), sameInstance(hash));
+    }
+
+    @Test
+    public void hashesFileWhenLengthHasChanged() {
+        context.checking(new Expectations() {{
+            one(cache).get(file);
+            will(returnValue(new CachingHasher.FileInfo(hash, 1078, file.lastModified())));
+            one(delegate).hash(file);
+            will(returnValue(hash));
+            one(cache).put(with(equalTo(file)), with(reflectionEquals(new CachingHasher.FileInfo(hash, file.length(),
+                    file.lastModified()))));
+        }});
+
+        assertThat(hasher.hash(file), sameInstance(hash));
+    }
+
+    @Test
+    public void hashesFileWhenTimestampHasChanged() {
+        context.checking(new Expectations() {{
+            one(cache).get(file);
+            will(returnValue(new CachingHasher.FileInfo(hash, file.length(), 12)));
+            one(delegate).hash(file);
+            will(returnValue(hash));
+            one(cache).put(with(equalTo(file)), with(reflectionEquals(new CachingHasher.FileInfo(hash, file.length(),
+                    file.lastModified()))));
+        }});
+
+        assertThat(hasher.hash(file), sameInstance(hash));
+    }
+
+    @Test
+    public void doesNotHashFileWhenTimestampAndLengthHaveNotChanged() {
+        context.checking(new Expectations() {{
+            one(cache).get(file);
+            will(returnValue(new CachingHasher.FileInfo(hash, file.length(), file.lastModified())));
+        }});
+
+        assertThat(hasher.hash(file), sameInstance(hash));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultFileSnapshotterTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultFileSnapshotterTest.groovy
new file mode 100644
index 0000000..9af10d8
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultFileSnapshotterTest.groovy
@@ -0,0 +1,330 @@
+/*
+ * 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.changedetection
+
+
+import static org.junit.Assert.*
+import static org.hamcrest.Matchers.*
+
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.runner.RunWith
+import org.junit.Test
+import org.junit.Rule
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.gradle.api.file.FileCollection
+import org.gradle.util.ChangeListener
+import org.gradle.api.file.FileTree
+
+ at RunWith(JMock.class)
+public class DefaultFileSnapshotterTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final Hasher hasher = new DefaultHasher()
+    private int counter
+    private ChangeListener listener = context.mock(ChangeListener.class)
+    private final DefaultFileSnapshotter snapshotter = new DefaultFileSnapshotter(hasher)
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder()
+
+    @Test
+    public void getFilesReturnsOnlyTheFilesWhichExisted() {
+        TestFile file = tmpDir.createFile('file1')
+        TestFile dir = tmpDir.createDir('file2')
+        TestFile noExist = tmpDir.file('file3')
+
+        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file, dir, noExist))
+
+        assertThat(snapshot.files.files as List, equalTo([file]))
+    }
+    
+    @Test
+    public void notifiesListenerWhenFileAdded() {
+        TestFile file1 = tmpDir.createFile('file1')
+        TestFile file2 = tmpDir.createFile('file2')
+
+        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file1))
+
+        context.checking {
+            one(listener).added(file2)
+        }
+        snapshotter.snapshot(files(file1, file2)).changesSince(snapshot, listener)
+    }
+
+    @Test
+    public void notifiesListenerWhenFileRemoved() {
+        TestFile file1 = tmpDir.createFile('file1')
+        TestFile file2 = tmpDir.createFile('file2')
+
+        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file1, file2))
+
+        context.checking {
+            one(listener).removed(file2)
+        }
+        snapshotter.snapshot(files(file1)).changesSince(snapshot, listener)
+    }
+
+    @Test
+    public void fileHasNotChangedWhenTypeAndHashHaveNotChanged() {
+        TestFile file = tmpDir.createFile('file')
+
+        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file))
+        assertThat(snapshot, notNullValue())
+
+        snapshotter.snapshot(files(file)).changesSince(snapshot, listener)
+    }
+
+    @Test
+    public void fileHasChangedWhenTypeHasChanged() {
+        TestFile file = tmpDir.createFile('file')
+
+        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file))
+
+        file.delete()
+        file.createDir()
+
+        context.checking {
+            one(listener).changed(file)
+        }
+        snapshotter.snapshot(files(file)).changesSince(snapshot, listener)
+    }
+
+    @Test
+    public void fileHasChangedWhenHashHasChanged() {
+        TestFile file = tmpDir.createFile('file')
+
+        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file))
+
+        file.write('new content')
+
+        context.checking {
+            one(listener).changed(file)
+        }
+        snapshotter.snapshot(files(file)).changesSince(snapshot, listener)
+    }
+
+    @Test
+    public void directoryHasNotChangedWhenTypeHasNotChanged() {
+        TestFile dir = tmpDir.createDir('dir')
+
+        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(dir))
+
+        snapshotter.snapshot(files(dir)).changesSince(snapshot, listener)
+    }
+
+    @Test
+    public void directoryHasChangedWhenTypeHasChanged() {
+        TestFile dir = tmpDir.createDir('dir')
+
+        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(dir))
+
+        dir.deleteDir()
+        dir.createFile()
+
+        context.checking {
+            one(listener).changed(dir)
+        }
+        snapshotter.snapshot(files(dir)).changesSince(snapshot, listener)
+    }
+
+    @Test
+    public void nonExistentFileUnchangedWhenTypeHasNotChanged() {
+        TestFile file = tmpDir.file('unknown')
+
+        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file))
+
+        snapshotter.snapshot(files(file)).changesSince(snapshot, listener)
+    }
+
+    @Test
+    public void nonExistentFileIsChangedWhenTypeHasChanged() {
+        TestFile file = tmpDir.file('unknown')
+
+        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file))
+
+        file.createFile()
+
+        context.checking {
+            one(listener).changed(file)
+        }
+        snapshotter.snapshot(files(file)).changesSince(snapshot, listener)
+    }
+
+    @Test
+    public void ignoresDuplicatesInFileCollection() {
+        TestFile file1 = tmpDir.createFile('file')
+        TestFile file2 = tmpDir.createFile('file')
+
+        FileCollectionSnapshot snapshot = snapshotter.snapshot(files(file1, file2))
+
+        snapshotter.snapshot(files(file1)).changesSince(snapshot, listener)
+    }
+
+    @Test
+    public void canCreateEmptySnapshot() {
+        TestFile file = tmpDir.createFile('file')
+        FileCollectionSnapshot snapshot = snapshotter.snapshot()
+
+        FileCollectionSnapshot newSnapshot = snapshotter.snapshot(files(file))
+
+        context.checking {
+            one(listener).added(file)
+        }
+
+        newSnapshot.changesSince(snapshot, listener)
+    }
+
+    @Test
+    public void diffAddsAddedFilesToSnapshot() {
+        TestFile file = tmpDir.createFile('file')
+        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = context.mock(ChangeListener.class)
+
+        FileCollectionSnapshot original = snapshotter.snapshot()
+        FileCollectionSnapshot modified = snapshotter.snapshot(files(file))
+
+        context.checking {
+            one(mergeListener).added(withParam(notNullValue()))
+        }
+
+        FileCollectionSnapshot target = modified.changesSince(original).applyTo(snapshotter.snapshot(), mergeListener)
+
+        context.checking {
+            one(listener).added(file)
+        }
+        target.changesSince(original, listener)
+    }
+
+    @Test
+    public void canIgnoreAddedFileInDiff() {
+        TestFile file = tmpDir.createFile('file')
+        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = context.mock(ChangeListener.class)
+
+        FileCollectionSnapshot original = snapshotter.snapshot()
+        FileCollectionSnapshot modified = snapshotter.snapshot(files(file))
+
+        context.checking {
+            one(mergeListener).added(withParam(notNullValue()))
+            will {merge -> merge.ignore()}
+        }
+
+        FileCollectionSnapshot target = modified.changesSince(original).applyTo(snapshotter.snapshot(), mergeListener)
+
+        target.changesSince(original, listener)
+    }
+
+    @Test
+    public void diffAddsChangedFilesToSnapshot() {
+        TestFile file = tmpDir.createFile('file')
+        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = context.mock(ChangeListener.class)
+
+        FileCollectionSnapshot original = snapshotter.snapshot(files(file))
+        file.write('new content')
+        FileCollectionSnapshot modified = snapshotter.snapshot(files(file))
+
+        context.checking {
+            one(mergeListener).changed(withParam(notNullValue()))
+        }
+
+        FileCollectionSnapshot target = modified.changesSince(original).applyTo(snapshotter.snapshot(), mergeListener)
+
+        context.checking {
+            one(listener).changed(file)
+        }
+        target.changesSince(original, listener)
+    }
+
+    @Test
+    public void canIgnoreChangedFileInDiff() {
+        TestFile file = tmpDir.createFile('file')
+        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = context.mock(ChangeListener.class)
+
+        FileCollectionSnapshot original = snapshotter.snapshot(files(file))
+        FileCollectionSnapshot target = snapshotter.snapshot(files(file))
+        file.write('new content')
+        FileCollectionSnapshot modified = snapshotter.snapshot(files(file))
+
+        context.checking {
+            one(mergeListener).changed(withParam(notNullValue()))
+            will {merge -> merge.ignore()}
+        }
+
+        target = modified.changesSince(original).applyTo(target, mergeListener)
+
+        target.changesSince(original, listener)
+    }
+
+    @Test
+    public void diffRemovesDeletedFilesFromSnapshot() {
+        TestFile file = tmpDir.createFile('file')
+        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = context.mock(ChangeListener.class)
+
+        FileCollectionSnapshot original = snapshotter.snapshot(files(file))
+        FileCollectionSnapshot modified = snapshotter.snapshot()
+
+        context.checking {
+            one(mergeListener).removed(withParam(notNullValue()))
+        }
+
+        FileCollectionSnapshot target = modified.changesSince(original).applyTo(snapshotter.snapshot(files(file)), mergeListener)
+
+        context.checking {
+            one(listener).removed(file)
+        }
+        target.changesSince(original, listener)
+    }
+
+    @Test
+    public void canIgnoreRemovedFileInDiff() {
+        TestFile file = tmpDir.createFile('file')
+        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = context.mock(ChangeListener.class)
+
+        FileCollectionSnapshot original = snapshotter.snapshot(files(file))
+        FileCollectionSnapshot modified = snapshotter.snapshot()
+
+        context.checking {
+            one(mergeListener).removed(withParam(notNullValue()))
+            will{merge -> merge.ignore()}
+        }
+
+        FileCollectionSnapshot target = modified.changesSince(original).applyTo(snapshotter.snapshot(files(file)), mergeListener)
+
+        target.changesSince(original, listener)
+    }
+
+    @Test
+    public void diffIgnoresUnchangedFilesInSnapshot() {
+        TestFile file = tmpDir.createFile('file')
+        ChangeListener<FileCollectionSnapshot.Merge> mergeListener = context.mock(ChangeListener.class)
+
+        FileCollectionSnapshot original = snapshotter.snapshot(files(file))
+        FileCollectionSnapshot modified = snapshotter.snapshot(files(file))
+        FileCollectionSnapshot target = modified.changesSince(original).applyTo(snapshotter.snapshot(), mergeListener)
+
+        target.changesSince(snapshotter.snapshot(), listener)
+    }
+
+    private FileCollection files(File... files) {
+        FileTree collection = context.mock(FileTree.class)
+        context.checking {
+            allowing(collection).getAsFileTree()
+            will(returnValue(collection))
+            allowing(collection).iterator()
+            will(returnIterator(files as List))
+        }
+        return collection
+    }
+    
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepositoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepositoryTest.java
new file mode 100644
index 0000000..9ccb703
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/changedetection/DefaultTaskArtifactStateRepositoryTest.java
@@ -0,0 +1,687 @@
+/*
+ * 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.changedetection;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.cache.CacheBuilder;
+import org.gradle.cache.CacheRepository;
+import org.gradle.cache.PersistentCache;
+import org.gradle.cache.PersistentIndexedCache;
+import org.gradle.util.*;
+import org.hamcrest.Matcher;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.*;
+
+import static org.gradle.util.Matchers.*;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class DefaultTaskArtifactStateRepositoryTest {
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private final CacheRepository cacheRepository = context.mock(CacheRepository.class);
+    private final ProjectInternal project = HelperUtil.createRootProject();
+    private final Gradle gradle = project.getGradle();
+    private final TestFile outputFile = tmpDir.file("output-file");
+    private final TestFile outputDir = tmpDir.file("output-dir");
+    private final TestFile outputDirFile = outputDir.file("some-file");
+    private final TestFile outputDirFile2 = outputDir.file("some-file-2");
+    private final TestFile emptyOutputDir = tmpDir.file("empty-output-dir");
+    private final TestFile missingOutputFile = tmpDir.file("missing-output-file");
+    private final TestFile inputFile = tmpDir.createFile("input-file");
+    private final TestFile inputDir = tmpDir.createDir("input-dir");
+    private final TestFile inputDirFile = inputDir.file("input-file2").createFile();
+    private final TestFile missingInputFile = tmpDir.file("missing-input-file");
+    private final Set<TestFile> inputFiles = toSet(inputFile, inputDir, missingInputFile);
+    private final Set<TestFile> outputFiles = toSet(outputFile, outputDir, emptyOutputDir, missingOutputFile);
+    private final Set<TestFile> createFiles = toSet(outputFile, outputDirFile, outputDirFile2);
+    private final PersistentCache persistentCache = context.mock(PersistentCache.class);
+    private DefaultTaskArtifactStateRepository repository;
+
+    @Before
+    public void setup() {
+        context.checking(new Expectations(){{
+            CacheBuilder builder = context.mock(CacheBuilder.class);
+
+            one(cacheRepository).cache("outputFileStates");
+            will(returnValue(builder));
+
+            one(builder).open();
+            will(returnValue(persistentCache));
+
+            one(persistentCache).openIndexedCache();
+            will(returnValue(new TestIndexedCache()));
+        }});
+
+        FileSnapshotter inputFilesSnapshotter = new DefaultFileSnapshotter(new DefaultHasher());
+        FileSnapshotter outputFilesSnapshotter = new OutputFilesSnapshotter(inputFilesSnapshotter, new RandomLongIdGenerator(), cacheRepository);
+        repository = new DefaultTaskArtifactStateRepository(cacheRepository, inputFilesSnapshotter, outputFilesSnapshotter);
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenCacheIsEmpty() {
+        expectEmptyCacheLocated();
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertNotNull(state);
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenAnyOutputFileNoLongerExists() {
+        execute();
+
+        outputFile.delete();
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenAnyFileInOutputDirNoLongerExists() {
+        execute();
+
+        outputDirFile.delete();
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenAnyOutputFileHasChangedType() {
+        execute();
+
+        outputFile.delete();
+        outputFile.createDir();
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenAnyFileInOutputDirHasChangedType() {
+        execute();
+
+        outputDirFile.delete();
+        outputDirFile.createDir();
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenAnyOutputFileHasChangedHash() {
+        execute();
+
+        outputFile.write("new content");
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenAnyFileInOutputDirHasChangedHash() {
+        execute();
+
+        outputDirFile.write("new content");
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenAnyOutputFilesAddedToSet() {
+        execute();
+
+        TaskInternal task = builder().withOutputFiles(outputFile, outputDir, tmpDir.createFile("output-file-2"), emptyOutputDir, missingOutputFile).task();
+
+        TaskArtifactState state = repository.getStateFor(task);
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenAnyOutputFilesRemovedFromSet() {
+        execute();
+
+        TaskInternal task = builder().withOutputFiles(outputFile, emptyOutputDir, missingOutputFile).task();
+
+        TaskArtifactState state = repository.getStateFor(task);
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenTaskWithDifferentTypeGeneratedAnyOutputFiles() {
+        TaskInternal task1 = builder().withOutputFiles(outputFile).task();
+        TaskInternal task2 = builder().withType(TaskSubType.class).withOutputFiles(outputFile).task();
+
+        execute(task1, task2);
+
+        TaskArtifactState state = repository.getStateFor(task1);
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenAnyInputFilesAddedToSet() {
+        execute();
+
+        TaskInternal task = builder().withInputFiles(inputFile, inputDir, tmpDir.createFile("other-input"), missingInputFile).task();
+        TaskArtifactState state = repository.getStateFor(task);
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenAnyInputFilesRemovedFromSet() {
+        execute();
+
+        TaskInternal task = builder().withInputFiles(inputFile).task();
+        TaskArtifactState state = repository.getStateFor(task);
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenAnyInputFileHasChangedHash() {
+        execute();
+
+        inputFile.write("some new content");
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenAnyInputFileHasChangedType() {
+        execute();
+
+        inputFile.delete();
+        inputFile.createDir();
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenAnyInputFileNoLongerExists() {
+        execute();
+
+        inputFile.delete();
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenAnyFileCreatedInInputDir() {
+        execute();
+
+        inputDir.file("other-file").createFile();
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenAnyFileDeletedFromInputDir() {
+        execute();
+
+        inputDirFile.delete();
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenAnyFileInInputDirChangesHash() {
+        execute();
+
+        inputDirFile.writelns("new content");
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertFalse(state.isUpToDate());
+    }
+    
+    @Test
+    public void artifactsAreNotUpToDateWhenAnyFileInInputDirChangesType() {
+        execute();
+
+        inputDirFile.delete();
+        inputDirFile.mkdir();
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenAnyInputPropertyValueChanged() {
+        execute();
+
+        TaskArtifactState state = repository.getStateFor(builder().withProperty("prop", "new value").task());
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void inputPropertyValueCanBeNull() {
+        TaskInternal task = builder().withProperty("prop", null).task();
+        execute(task);
+
+        TaskArtifactState state = repository.getStateFor(task);
+        assertTrue(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenAnyInputPropertyAdded() {
+        execute();
+
+        TaskArtifactState state = repository.getStateFor(builder().withProperty("prop2", "value").task());
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenAnyInputPropertyRemoved() {
+        execute(builder().withProperty("prop2", "value").task());
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenStateHasNotBeenUpdated() {
+        expectEmptyCacheLocated();
+        repository.getStateFor(task());
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenOutputDirWhichUsedToExistHasBeenDeleted() {
+        // Output dir already exists before first execution of task
+        outputDirFile.createFile();
+        expectEmptyCacheLocated();
+
+        TaskInternal task1 = builder().withOutputFiles(outputDir).createsFiles(outputDirFile).task();
+        TaskInternal task2 = builder().withPath("other").withOutputFiles(outputDir).createsFiles(outputDirFile2).task();
+
+        TaskArtifactState state = repository.getStateFor(task1);
+        assertFalse(state.isUpToDate());
+        state.update();
+
+        outputDir.deleteDir();
+
+        // Another task creates dir
+        state = repository.getStateFor(task2);
+        assertFalse(state.isUpToDate());
+        task2.execute();
+        state.update();
+
+        // Task should be out-of-date
+        state = repository.getStateFor(task1);
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreUpToDateWhenNothingHasChangedSinceOutputFilesWereGenerated() {
+        execute();
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertTrue(state.isUpToDate());
+
+        state = repository.getStateFor(task());
+        assertTrue(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreUpToDateWhenOutputFileWhichDidNotExistNowExists() {
+        execute();
+
+        missingOutputFile.touch();
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertTrue(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreUpToDateWhenOutputDirWhichWasEmptyIsNoLongerEmpty() {
+        execute();
+
+        emptyOutputDir.file("some-file").touch();
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertTrue(state.isUpToDate());
+    }
+
+    @Test
+    public void hasEmptyTaskHistoryWhenTaskHasNeverBeenExecuted() {
+        expectEmptyCacheLocated();
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertThat(state.getOutputFiles().getFiles(), isEmpty());
+    }
+    
+    @Test
+    public void hasTaskHistoryFromPreviousExecution() {
+        execute();
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertThat(state.getOutputFiles().getFiles(), equalTo(toLinkedSet((File) outputFile, outputDirFile, outputDirFile2)));
+    }
+
+    @Test
+    public void multipleTasksCanProduceFilesIntoTheSameOutputDirectory() {
+        TaskInternal task1 = task();
+        TaskInternal task2 = builder().withPath("other").withOutputFiles(outputDir).createsFiles(outputDir.file("output2")).task();
+        execute(task1, task2);
+
+        TaskArtifactState state = repository.getStateFor(task1);
+        assertTrue(state.isUpToDate());
+
+        state = repository.getStateFor(task2);
+        assertTrue(state.isUpToDate());
+    }
+
+    @Test
+    public void multipleTasksCanProduceTheSameFileWithTheSameContents() {
+        TaskInternal task1 = builder().withOutputFiles(outputFile).task();
+        TaskInternal task2 = builder().withPath("other").withOutputFiles(outputFile).task();
+        execute(task1, task2);
+
+        TaskArtifactState state = repository.getStateFor(task1);
+        assertTrue(state.isUpToDate());
+
+        state = repository.getStateFor(task2);
+        assertTrue(state.isUpToDate());
+    }
+
+    @Test
+    public void multipleTasksCanProduceTheSameEmptyDir() {
+        TaskInternal task1 = task();
+        TaskInternal task2 = builder().withPath("other").withOutputFiles(outputDir).task();
+        execute(task1, task2);
+
+        TaskArtifactState state = repository.getStateFor(task1);
+        assertTrue(state.isUpToDate());
+
+        state = repository.getStateFor(task2);
+        assertTrue(state.isUpToDate());
+    }
+
+    @Test
+    public void doesNotConsiderExistingFilesInOutputDirectoryAsProducedByTask() {
+        TestFile otherFile = outputDir.file("other").createFile();
+
+        execute();
+
+        otherFile.delete();
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertTrue(state.isUpToDate());
+        assertThat(state.getOutputFiles().getFiles(), (Matcher) not(hasItem(otherFile)));
+    }
+
+    @Test
+    public void considersExistingFileInOutputDirectoryWhichIsUpdatedByTheTaskAsProducedByTask() {
+        expectEmptyCacheLocated();
+        
+        TestFile otherFile = outputDir.file("other").createFile();
+
+        TaskInternal task = task();
+        TaskArtifactState state = repository.getStateFor(task);
+        assertFalse(state.isUpToDate());
+
+        task.execute();
+        otherFile.write("new content");
+
+        state.update();
+
+        otherFile.delete();
+
+        state = repository.getStateFor(task());
+        assertFalse(state.isUpToDate());
+        assertThat(state.getOutputFiles().getFiles(), (Matcher) hasItem(otherFile));
+    }
+
+    @Test
+    public void fileIsNoLongerConsideredProducedByTaskOnceItIsDeleted() {
+        execute();
+
+        outputDirFile.delete();
+
+        TaskArtifactState state = repository.getStateFor(task());
+        assertFalse(state.isUpToDate());
+        state.update();
+
+        outputDirFile.write("ignore me");
+
+        state = repository.getStateFor(task());
+        assertTrue(state.isUpToDate());
+        assertThat(state.getOutputFiles().getFiles(), (Matcher) not(hasItem(outputDirFile)));
+        state.update();
+    }
+
+    @Test
+    public void artifactsAreUpToDateWhenTaskDoesNotAcceptAnyInputs() {
+        TaskInternal task = builder().doesNotAcceptInput().task();
+        execute(task);
+
+        TaskArtifactState state = repository.getStateFor(task);
+        assertTrue(state.isUpToDate());
+
+        outputDirFile.delete();
+
+        state = repository.getStateFor(task);
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreNotUpToDateWhenTaskDoesNotProduceAnyOutputs() {
+        TaskInternal task = builder().doesNotProduceOutput().task();
+        execute(task);
+
+        TaskArtifactState state = repository.getStateFor(task);
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void taskHistoryIsEmptyWhenTaskDoesNotProduceAnyOutout() {
+        TaskInternal task = builder().doesNotProduceOutput().task();
+        execute(task);
+
+        TaskArtifactState state = repository.getStateFor(task);
+        assertFalse(state.isUpToDate());
+        assertThat(state.getOutputFiles(), isEmpty());
+    }
+
+    @Test
+    public void artifactsAreUpToDateWhenTaskHasNoInputFiles() {
+        TaskInternal task = builder().withInputFiles().task();
+        execute(task);
+
+        TaskArtifactState state = repository.getStateFor(task);
+        assertTrue(state.isUpToDate());
+    }
+
+    @Test
+    public void artifactsAreUpToDateWhenTaskHasNoOutputs() {
+        TaskInternal task = builder().withOutputFiles().task();
+        execute(task);
+
+        TaskArtifactState state = repository.getStateFor(task);
+        assertTrue(state.isUpToDate());
+    }
+
+    @Test
+    public void taskCanProduceIntoDifferentSetsOfOutputFiles() {
+        TestFile outputDir2 = tmpDir.createDir("output-dir-2");
+        TestFile outputDirFile2 = outputDir2.file("output-file-2");
+        TaskInternal instance1 = builder().withOutputFiles(outputDir).createsFiles(outputDirFile).task();
+        TaskInternal instance2 = builder().withOutputFiles(outputDir2).createsFiles(outputDirFile2).task();
+
+        execute(instance1, instance2);
+
+        TaskArtifactState state = repository.getStateFor(instance1);
+        assertTrue(state.isUpToDate());
+        assertThat(state.getOutputFiles().getFiles(), equalTo(toLinkedSet((File) outputDirFile)));
+
+        state = repository.getStateFor(instance2);
+        assertTrue(state.isUpToDate());
+        assertThat(state.getOutputFiles().getFiles(), equalTo(toLinkedSet((File) outputDirFile2)));
+    }
+
+    private void execute() {
+        execute(task());
+    }
+
+    private void execute(TaskInternal... tasks) {
+        expectEmptyCacheLocated();
+        for (TaskInternal task : tasks) {
+            TaskArtifactState state = repository.getStateFor(task);
+            state.isUpToDate();
+            task.execute();
+            state.update();
+        }
+    }
+    
+    private void expectEmptyCacheLocated() {
+        context.checking(new Expectations(){{
+            CacheBuilder builder = context.mock(CacheBuilder.class);
+
+            one(cacheRepository).cache("taskArtifacts");
+            will(returnValue(builder));
+
+            one(builder).forObject(gradle);
+            will(returnValue(builder));
+
+            one(builder).open();
+            will(returnValue(persistentCache));
+            
+            one(persistentCache).openIndexedCache();
+            will(returnValue(new TestIndexedCache()));
+        }});
+    }
+
+    private TaskInternal task() {
+        return builder().task();
+    }
+
+    private TaskBuilder builder() {
+        return new TaskBuilder();
+    }
+
+    private class TaskBuilder {
+        private String path = "task";
+        private Collection<? extends File> inputs = inputFiles;
+        private Collection<? extends File> outputs = outputFiles;
+        private Collection<? extends TestFile> create = createFiles;
+        private Class<? extends TaskInternal> type = TaskInternal.class;
+        private Map<String, Object> inputProperties = new HashMap<String, Object>(toMap("prop", "value"));
+
+        TaskBuilder withInputFiles(File... inputFiles) {
+            inputs = Arrays.asList(inputFiles);
+            return this;
+        }
+
+        TaskBuilder withOutputFiles(File... outputFiles) {
+            outputs = Arrays.asList(outputFiles);
+            return this;
+        }
+
+        TaskBuilder createsFiles(TestFile... outputFiles) {
+            create = Arrays.asList(outputFiles);
+            return this;
+        }
+
+        TaskBuilder withPath(String path) {
+            this.path = path;
+            return this;
+        }
+
+        TaskBuilder withType(Class<? extends TaskInternal> type) {
+            this.type = type;
+            return this;
+        }
+
+        TaskBuilder doesNotAcceptInput() {
+            inputs = null;
+            inputProperties = null;
+            return this;
+        }
+
+        public TaskBuilder doesNotProduceOutput() {
+            outputs = null;
+            return this;
+        }
+
+        public TaskBuilder withProperty(String name, Object value) {
+            inputProperties.put(name, value);
+            return this;
+        }
+
+        TaskInternal task() {
+            final TaskInternal task = HelperUtil.createTask(type, project, path);
+            if (inputs != null) {
+                task.getInputs().files(inputs);
+            }
+            if (inputProperties != null) {
+                task.getInputs().properties(inputProperties);
+            }
+            if (outputs != null) {
+                task.getOutputs().files(outputs);
+            }
+            task.doLast(new org.gradle.api.Action<Object>() {
+                public void execute(Object o) {
+                    for (TestFile file : create) {
+                        file.createFile();
+                    }
+                }
+            });
+
+            return task;
+        }
+    }
+
+    public static class TaskSubType extends DefaultTask {
+    }
+
+    public static class TestIndexedCache implements PersistentIndexedCache<Object, Object> {
+        Map<Object, Object> entries = new HashMap<Object, Object>();
+
+        public Object get(Object key) {
+            return entries.get(key);
+        }
+
+        public void put(Object key, Object value) {
+            entries.put(key, value);
+        }
+
+        public void remove(Object key) {
+            entries.remove(key);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepositoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepositoryTest.java
new file mode 100644
index 0000000..73919af
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/changedetection/ShortCircuitTaskArtifactStateRepositoryTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.changedetection;
+
+import org.gradle.StartParameter;
+import org.gradle.api.Task;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.TaskOutputsInternal;
+import org.gradle.api.specs.Spec;
+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 static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class ShortCircuitTaskArtifactStateRepositoryTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final StartParameter startParameter = new StartParameter();
+    private final TaskArtifactStateRepository delegate = context.mock(TaskArtifactStateRepository.class);
+    private final TaskInternal task = context.mock(TaskInternal.class);
+    private final TaskArtifactState taskArtifactState = context.mock(TaskArtifactState.class);
+    private final TaskOutputsInternal taskOutputsInternal = context.mock(TaskOutputsInternal.class);
+    private final Spec<Task> upToDateSpec = context.mock(Spec.class);
+    private final ShortCircuitTaskArtifactStateRepository repository = new ShortCircuitTaskArtifactStateRepository(startParameter, delegate);
+
+    @Before
+    public void setup() {
+        context.checking(new Expectations() {{
+            allowing(task).getOutputs();
+            will(returnValue(taskOutputsInternal));
+            allowing(taskOutputsInternal).getUpToDateSpec();
+            will(returnValue(upToDateSpec));
+        }});
+    }
+
+    @Test
+    public void delegatesToBackingRepositoryToCreateStateObject() {
+        expectTaskStateCreated();
+
+        TaskArtifactState state = repository.getStateFor(task);
+        assertNotNull(state);
+
+        final FileCollection outputFiles = context.mock(FileCollection.class);
+
+        context.checking(new Expectations() {{
+            one(taskArtifactState).getOutputFiles();
+            will(returnValue(outputFiles));
+            one(taskArtifactState).update();
+        }});
+
+        assertThat(state.getOutputFiles(), sameInstance(outputFiles));
+        state.update();
+    }
+
+    @Test
+    public void taskArtifactsAreOutOfDateWhenStartParameterOverrideIsSet() {
+        expectTaskStateCreated();
+
+        TaskArtifactState state = repository.getStateFor(task);
+
+        startParameter.setNoOpt(true);
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void taskArtifactsAreOutOfDateWhenUpToDateSpecIsFalse() {
+        expectTaskStateCreated();
+
+        TaskArtifactState state = repository.getStateFor(task);
+
+        context.checking(new Expectations() {{
+            one(upToDateSpec).isSatisfiedBy(task);
+            will(returnValue(false));
+        }});
+
+        assertFalse(state.isUpToDate());
+    }
+
+    @Test
+    public void determinesWhetherTaskArtifactsAreUpToDateUsingBackingRepository() {
+        expectTaskStateCreated();
+
+        TaskArtifactState state = repository.getStateFor(task);
+
+        context.checking(new Expectations() {{
+            one(upToDateSpec).isSatisfiedBy(task);
+            will(returnValue(true));
+            one(taskArtifactState).isUpToDate();
+            will(returnValue(true));
+        }});
+
+        assertTrue(state.isUpToDate());
+    }
+
+    private void expectTaskStateCreated() {
+        context.checking(new Expectations() {{
+            one(delegate).getStateFor(task);
+            will(returnValue(taskArtifactState));
+        }});
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/AbstractFileCollectionTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/AbstractFileCollectionTest.java
new file mode 100644
index 0000000..4ac3a4f
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/AbstractFileCollectionTest.java
@@ -0,0 +1,340 @@
+/*
+ * 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 org.gradle.api.Task;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.StopExecutionException;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.util.TestFile;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.TemporaryFolder;
+import org.hamcrest.Matcher;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.*;
+
+import static org.gradle.api.tasks.AntBuilderAwareUtil.*;
+import static org.gradle.util.Matchers.*;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+public class AbstractFileCollectionTest {
+    @Rule
+    public TemporaryFolder testDir = new TemporaryFolder();
+
+    @Test
+    public void usesDisplayNameAsToString() {
+        TestFileCollection collection = new TestFileCollection();
+        assertThat(collection.toString(), equalTo("collection-display-name"));
+    }
+
+    @Test
+    public void canIterateOverFiles() {
+        File file1 = new File("f1");
+        File file2 = new File("f2");
+
+        TestFileCollection collection = new TestFileCollection(file1, file2);
+        Iterator<File> iterator = collection.iterator();
+        assertThat(iterator.next(), sameInstance(file1));
+        assertThat(iterator.next(), sameInstance(file2));
+        assertFalse(iterator.hasNext());
+    }
+
+    @Test
+    public void canGetSingleFile() {
+        File file = new File("f1");
+
+        TestFileCollection collection = new TestFileCollection(file);
+        assertThat(collection.getSingleFile(), sameInstance(file));
+    }
+
+    @Test
+    public void failsToGetSingleFileWhenCollectionContainsMultipleFiles() {
+        File file1 = new File("f1");
+        File file2 = new File("f2");
+
+        TestFileCollection collection = new TestFileCollection(file1, file2);
+        try {
+            collection.getSingleFile();
+            fail();
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(), equalTo(
+                    "Expected collection-display-name to contain exactly one file, however, it contains 2 files."));
+        }
+    }
+
+    @Test
+    public void failsToGetSingleFileWhenCollectionIsEmpty() {
+        TestFileCollection collection = new TestFileCollection();
+        try {
+            collection.getSingleFile();
+            fail();
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(), equalTo(
+                    "Expected collection-display-name to contain exactly one file, however, it contains no files."));
+        }
+    }
+
+    @Test
+    public void containsFile() {
+        File file1 = new File("f1");
+
+        TestFileCollection collection = new TestFileCollection(file1);
+        assertTrue(collection.contains(file1));
+        assertFalse(collection.contains(new File("f2")));
+    }
+
+    @Test
+    public void canGetFilesAsAPath() {
+        File file1 = new File("f1");
+        File file2 = new File("f2");
+
+        TestFileCollection collection = new TestFileCollection(file1, file2);
+        assertThat(collection.getAsPath(), equalTo(file1 + File.pathSeparator + file2));
+    }
+
+    @Test
+    public void canAddCollectionsTogether() {
+        File file1 = new File("f1");
+        File file2 = new File("f2");
+        File file3 = new File("f3");
+
+        TestFileCollection collection1 = new TestFileCollection(file1, file2);
+        TestFileCollection collection2 = new TestFileCollection(file2, file3);
+        FileCollection sum = collection1.plus(collection2);
+        assertThat(sum, instanceOf(UnionFileCollection.class));
+        assertThat(sum.getFiles(), equalTo(toLinkedSet(file1, file2, file3)));
+    }
+
+    @Test
+    public void canSubtractCollections() {
+        File file1 = new File("f1");
+        File file2 = new File("f2");
+        File file3 = new File("f3");
+
+        TestFileCollection collection1 = new TestFileCollection(file1, file2);
+        TestFileCollection collection2 = new TestFileCollection(file2, file3);
+        FileCollection sum = collection1.minus(collection2);
+        assertThat(sum.getFiles(), equalTo(toLinkedSet(file1)));
+    }
+
+    @Test
+    public void cannotAddCollectionToThisCollection() {
+        try {
+            new TestFileCollection().add(new TestFileCollection());
+            fail();
+        } catch (UnsupportedOperationException e) {
+            assertThat(e.getMessage(), equalTo("Collection-display-name does not allow modification."));
+        }
+    }
+
+    @Test
+    public void canAddToAntBuilderAsResourceCollection() {
+        File file1 = new File("f1");
+        File file2 = new File("f2");
+
+        TestFileCollection collection = new TestFileCollection(file1, file2);
+        assertSetContains(collection, toSet("f1", "f2"));
+    }
+
+    @Test
+    public void includesOnlyExistingFilesWhenAddedToAntBuilderAsAFileSetOrMatchingTask() {
+        TestFile testDir = this.testDir.getDir();
+        TestFile file1 = testDir.file("f1").touch();
+        TestFile dir1 = testDir.file("dir1").createDir();
+        TestFile file2 = dir1.file("f2").touch();
+        TestFile missing = testDir.file("f3");
+        testDir.file("f2").touch();
+        testDir.file("ignored1").touch();
+        dir1.file("f1").touch();
+        dir1.file("ignored1").touch();
+
+        TestFileCollection collection = new TestFileCollection(file1, file2, dir1, missing);
+        assertSetContainsForFileSet(collection, toSet("f1", "f2"));
+        assertSetContainsForMatchingTask(collection, toSet("f1", "f2"));
+    }
+
+    @Test
+    public void isEmptyWhenFilesIsEmpty() {
+        assertTrue(new TestFileCollection().isEmpty());
+        assertFalse(new TestFileCollection(new File("f1")).isEmpty());
+    }
+    
+    @Test
+    public void throwsStopExceptionWhenEmpty() {
+        TestFileCollection collection = new TestFileCollection();
+        try {
+            collection.stopExecutionIfEmpty();
+            fail();
+        } catch (StopExecutionException e) {
+            assertThat(e.getMessage(), equalTo("Collection-display-name does not contain any files."));
+        }
+    }
+
+    @Test
+    public void doesNotThrowStopExceptionWhenNotEmpty() {
+        TestFileCollection collection = new TestFileCollection(new File("f1"));
+        collection.stopExecutionIfEmpty();
+    }
+
+    @Test
+    public void canConvertToCollectionTypes() {
+        File file = new File("f1");
+        TestFileCollection collection = new TestFileCollection(file);
+
+        assertThat(collection.asType(Collection.class), equalTo((Object) toLinkedSet(file)));
+        assertThat(collection.asType(Set.class), equalTo((Object) toLinkedSet(file)));
+        assertThat(collection.asType(List.class), equalTo((Object) toList(file)));
+    }
+
+    @Test
+    public void canConvertToArray() {
+        File file = new File("f1");
+        TestFileCollection collection = new TestFileCollection(file);
+
+        assertThat(collection.asType(File[].class), equalTo((Object) toArray(file)));
+    }
+
+    @Test
+    public void canConvertCollectionWithSingleFileToFile() {
+        File file = new File("f1");
+        TestFileCollection collection = new TestFileCollection(file);
+
+        assertThat(collection.asType(File.class), equalTo((Object) file));
+    }
+
+    @Test
+    public void canConvertToFileTree() {
+        TestFileCollection collection = new TestFileCollection();
+        assertThat(collection.asType(FileTree.class), notNullValue());
+    }
+
+    @Test
+    public void throwsUnsupportedOperationExceptionWhenConvertingToUnsupportedType() {
+        try {
+            new TestFileCollection().asType(Integer.class);
+            fail();
+        } catch (UnsupportedOperationException e) {
+            assertThat(e.getMessage(), equalTo(
+                    "Cannot convert collection-display-name to type Integer, as this type is not supported."));
+        }
+    }
+
+    @Test
+    public void toFileTreeReturnsSingletonTreeForEachFileInCollection() {
+        File file = new File("f1");
+
+        TestFileCollection collection = new TestFileCollection(file);
+        FileTree tree = collection.getAsFileTree();
+        assertThat(tree, instanceOf(CompositeFileTree.class));
+        CompositeFileTree compositeTree = (CompositeFileTree) tree;
+        assertThat(compositeTree.getSourceCollections(), hasItems((Matcher) instanceOf(SingletonFileTree.class)));
+    }
+
+    @Test
+    public void canFilterContentsOfCollectionUsingSpec() {
+        File file1 = new File("f1");
+        File file2 = new File("f2");
+
+        TestFileCollection collection = new TestFileCollection(file1, file2);
+        FileCollection filtered = collection.filter(new Spec<File>() {
+            public boolean isSatisfiedBy(File element) {
+                return element.getName().equals("f1");
+            }
+        });
+        assertThat(filtered.getFiles(), equalTo(toSet(file1)));
+    }
+
+    @Test
+    public void canFilterContentsOfCollectionUsingClosure() {
+        File file1 = new File("f1");
+        File file2 = new File("f2");
+
+        TestFileCollection collection = new TestFileCollection(file1, file2);
+        FileCollection filtered = collection.filter(HelperUtil.toClosure("{f -> f.name == 'f1'}"));
+        assertThat(filtered.getFiles(), equalTo(toSet(file1)));
+    }
+    
+    @Test
+    public void filteredCollectionIsLive() {
+        File file1 = new File("f1");
+        File file2 = new File("f2");
+        File file3 = new File("dir/f1");
+
+        TestFileCollection collection = new TestFileCollection(file1, file2);
+        FileCollection filtered = collection.filter(HelperUtil.toClosure("{f -> f.name == 'f1'}"));
+        assertThat(filtered.getFiles(), equalTo(toSet(file1)));
+
+        collection.files.add(file3);
+        assertThat(filtered.getFiles(), equalTo(toSet(file1, file3)));
+    }
+
+    @Test
+    public void hasNoDependencies() {
+        assertThat(new TestFileCollection().getBuildDependencies().getDependencies(null), isEmpty());
+    }
+
+    @Test
+    public void fileTreeHasSameDependenciesAsThis() {
+        TestFileCollectionWithDependency collection = new TestFileCollectionWithDependency();
+        collection.files.add(new File("f1"));
+
+        assertThat(collection.getAsFileTree().getBuildDependencies(), sameInstance(collection.dependency));
+        assertThat(collection.getAsFileTree().matching(HelperUtil.TEST_CLOSURE).getBuildDependencies(), sameInstance(collection.dependency));
+    }
+
+    @Test
+    public void filteredCollectionHasSameDependenciesAsThis() {
+        TestFileCollectionWithDependency collection = new TestFileCollectionWithDependency();
+
+        assertThat(collection.filter(HelperUtil.toClosure("{true}")).getBuildDependencies(), sameInstance( collection.dependency));
+    }
+
+    private class TestFileCollection extends AbstractFileCollection {
+        Set<File> files = new LinkedHashSet<File>();
+
+        private TestFileCollection(File... files) {
+            this.files.addAll(Arrays.asList(files));
+        }
+
+        public String getDisplayName() {
+            return "collection-display-name";
+        }
+
+        public Set<File> getFiles() {
+            return files;
+        }
+    }
+
+    private class TestFileCollectionWithDependency extends TestFileCollection {
+        TaskDependency dependency = new TaskDependency() {
+            public Set<? extends Task> getDependencies(Task task) {
+                throw new UnsupportedOperationException();
+            }
+        };
+
+        @Override
+        public TaskDependency getBuildDependencies() {
+            return dependency;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/AbstractFileTreeElementTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/AbstractFileTreeElementTest.java
new file mode 100644
index 0000000..dc788fc
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/AbstractFileTreeElementTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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 org.gradle.api.file.RelativePath;
+import org.gradle.util.TestFile;
+import org.gradle.util.TemporaryFolder;
+import org.gradle.util.GFileUtils;
+
+import static org.junit.Assert.*;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class AbstractFileTreeElementTest {
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder();
+
+    @Test
+    public void testNeedsCopy() throws IOException {
+        TestFile source = tmpDir.createFile("src");
+        TestFile dest = tmpDir.getDir().file("dest");
+        dest.assertDoesNotExist();
+
+        TestFileTreeElement element = new TestFileTreeElement(source);
+
+        assertTrue(element.needsCopy(dest));
+
+        element.copyTo(dest);
+
+        assertFalse(element.needsCopy(dest));
+
+        dest.setLastModified(source.lastModified() - 1000);
+        assertTrue(element.needsCopy(dest));
+    }
+
+    private class TestFileTreeElement extends AbstractFileTreeElement {
+        private final TestFile file;
+
+        public TestFileTreeElement(TestFile file) {
+            this.file = file;
+        }
+
+        public String getDisplayName() {
+            return "display name";
+        }
+
+        public File getFile() {
+            return file;
+        }
+
+        public long getLastModified() {
+            return file.lastModified();
+        }
+
+        public boolean isDirectory() {
+            return file.isDirectory();
+        }
+
+        public long getSize() {
+            return file.length();
+        }
+
+        public RelativePath getRelativePath() {
+            throw new UnsupportedOperationException();
+        }
+
+        public InputStream open() {
+            return GFileUtils.openInputStream(file);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/AbstractFileTreeTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/AbstractFileTreeTest.groovy
new file mode 100644
index 0000000..7d39b32
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/AbstractFileTreeTest.groovy
@@ -0,0 +1,71 @@
+/*
+ * 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 static org.junit.Assert.*
+import static org.hamcrest.Matchers.*
+import static org.gradle.util.Matchers.*
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.runner.RunWith
+import org.junit.Test
+import org.gradle.api.file.FileTree
+import org.gradle.api.file.FileVisitor
+import org.gradle.api.file.FileVisitDetails
+
+ at RunWith(JMock.class)
+public class AbstractFileTreeTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+
+    @Test
+    public void isEmptyWhenVisitsNoFiles() {
+        def tree = new TestFileTree([])
+        assertTrue(tree.empty)
+    }
+
+    @Test
+    public void isNotEmptyWhenVisitsFirstFile() {
+        FileVisitDetails file = context.mock(FileVisitDetails.class)
+        def tree = new TestFileTree([file])
+
+        context.checking {
+            one(file).stopVisiting()
+        }
+
+        assertFalse(tree.empty)
+    }
+}
+
+class TestFileTree extends AbstractFileTree {
+    List contents
+
+    def TestFileTree(List files) {
+        this.contents = files
+    }
+
+    String getDisplayName() {
+        throw new UnsupportedOperationException();
+    }
+
+    FileTree visit(FileVisitor visitor) {
+        contents.each {FileVisitDetails details ->
+            visitor.visitFile(details)
+        }
+        this
+    }
+}
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/BaseDirConverterTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/BaseDirConverterTest.groovy
new file mode 100644
index 0000000..1e93c2f
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/BaseDirConverterTest.groovy
@@ -0,0 +1,333 @@
+/*
+ * 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 org.gradle.api.InvalidUserDataException
+import org.gradle.api.PathValidation
+import org.gradle.api.file.FileCollection
+import org.gradle.util.TemporaryFolder
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import java.util.concurrent.Callable
+import org.gradle.util.OperatingSystem
+
+/**
+ * @author Hans Dockter
+ */
+class BaseDirConverterTest {
+    static final String TEST_PATH = 'testpath'
+
+    File baseDir
+    File testFile
+    File testDir
+
+    BaseDirConverter baseDirConverter
+    @Rule public TemporaryFolder rootDir = new TemporaryFolder();
+
+    @Before public void setUp() {
+        baseDir = rootDir.dir
+        baseDirConverter = new BaseDirConverter(baseDir)
+        testFile = new File(baseDir, 'testfile')
+        testDir = new File(baseDir, 'testdir')
+    }
+
+    @Test(expected = IllegalArgumentException) public void testWithNullPath() {
+        baseDirConverter.resolve(null)
+    }
+
+    @Test public void testWithNoPathValidation() {
+        // No exceptions means test has passed
+        baseDirConverter.resolve(TEST_PATH)
+        baseDirConverter.resolve(TEST_PATH, PathValidation.NONE)
+    }
+
+    @Test public void testPathValidationWithNonExistingFile() {
+        try {
+            baseDirConverter.resolve(testFile.name, PathValidation.FILE)
+            fail()
+        } catch (InvalidUserDataException e) {
+            assertThat(e.message, equalTo("File '$testFile.canonicalFile' does not exist.".toString()))
+        }
+    }
+
+    @Test public void testPathValidationForFileWithDirectory() {
+        testDir.mkdir()
+        try {
+            baseDirConverter.resolve(testDir.name, PathValidation.FILE)
+            fail()
+        } catch (InvalidUserDataException e) {
+            assertThat(e.message, equalTo("File '$testDir.canonicalFile' is not a file.".toString()))
+        }
+    }
+
+    @Test public void testWithValidFile() {
+        testFile.createNewFile()
+        baseDirConverter.resolve(testFile.name, PathValidation.FILE)
+    }
+
+    @Test public void testPathValidationWithNonExistingDirectory() {
+        try {
+            baseDirConverter.resolve(testDir.name, PathValidation.DIRECTORY)
+            fail()
+        } catch (InvalidUserDataException e) {
+            assertThat(e.message, equalTo("Directory '$testDir.canonicalFile' does not exist.".toString()))
+        }
+    }
+
+    @Test public void testPathValidationWithValidDirectory() {
+        testDir.mkdir()
+        baseDirConverter.resolve(testDir.name, PathValidation.DIRECTORY)
+    }
+
+    @Test public void testPathValidationForDirectoryWithFile() {
+        testFile.createNewFile()
+        try {
+            baseDirConverter.resolve(testFile.name, PathValidation.DIRECTORY)
+            fail()
+        } catch (InvalidUserDataException e) {
+            assertThat(e.message, equalTo("Directory '$testFile.canonicalFile' is not a directory.".toString()))
+        }
+    }
+
+    @Test public void testPathValidationForExistingDirAndFile() {
+        testDir.mkdir()
+        testFile.createNewFile()
+        baseDirConverter.resolve(testDir.name, PathValidation.EXISTS)
+        baseDirConverter.resolve(testFile.name, PathValidation.EXISTS)
+    }
+
+    @Test public void testExistsPathValidationWithNonExistingDir() {
+        try {
+            baseDirConverter.resolve(testDir.name, PathValidation.EXISTS)
+            fail()
+        } catch (InvalidUserDataException e) {
+            assertThat(e.message, equalTo("File '$testDir.canonicalFile' does not exist.".toString()))
+        }
+    }
+
+    @Test public void testExistsPathValidationWithNonExistingFile() {
+        try {
+            baseDirConverter.resolve(testFile.name, PathValidation.EXISTS)
+            fail()
+        } catch (InvalidUserDataException e) {
+            assertThat(e.message, equalTo("File '$testFile.canonicalFile' does not exist.".toString()))
+        }
+    }
+
+    @Test public void testResolveAbsolutePath() {
+        File absoluteFile = new File('nonRelative').canonicalFile
+        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.path))
+    }
+
+    @Test public void testResolveRelativePath() {
+        String relativeFileName = "relative"
+        assertEquals(new File(baseDir, relativeFileName), baseDirConverter.resolve(relativeFileName))
+    }
+
+    @Test public void testResolveFileWithAbsolutePath() {
+        File absoluteFile = new File('nonRelative').canonicalFile
+        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile))
+    }
+
+    @Test public void testResolveRelativeObject() {
+        assertEquals(new File(baseDir, "12"), baseDirConverter.resolve(12))
+    }
+
+    @Test public void testResolveFileWithRelativePath() {
+        File relativeFile = new File('relative')
+        assertEquals(new File(baseDir, 'relative'), baseDirConverter.resolve(relativeFile))
+    }
+
+    @Test public void testResolveAbsolutePathOnCaseInsensitiveFileSystemToUri() {
+        if (OperatingSystem.current().isCaseSensitiveFileSystem()) {
+            return
+        }
+
+        String path = baseDir.absolutePath.toLowerCase()
+        assertEquals(baseDir, baseDirConverter.resolve(path))
+    }
+
+    @Test public void testResolveRelativeFileURIString() {
+        assertEquals(new File(baseDir, 'relative'), baseDirConverter.resolve('file:relative'))
+        assertEquals(new File(baseDir.parentFile, 'relative'), baseDirConverter.resolve('file:../relative'))
+    }
+
+    @Test public void testResolveAbsoluteFileURIString() {
+        File absoluteFile = new File('nonRelative').canonicalFile
+        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.toURI().toString()))
+    }
+
+    @Test public void testResolveAbsoluteFileURI() {
+        File absoluteFile = new File('nonRelative').canonicalFile
+        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.toURI()))
+    }
+
+    @Test public void testResolveAbsoluteFileURL() {
+        File absoluteFile = new File('nonRelative').canonicalFile
+        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.toURI().toURL()))
+    }
+
+    @Test public void testResolveFilePathWithURIEncodedAndReservedCharacters() {
+        File absoluteFile = new File('white%20space').canonicalFile
+        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.absolutePath))
+        absoluteFile = new File('white space').canonicalFile
+        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.absolutePath))
+    }
+
+    @Test public void testResolveURIStringWithEncodedAndReservedCharacters() {
+        assertEquals(new File(baseDir, 'white space'), baseDirConverter.resolve('file:white%20space'))
+        assertEquals(new File(baseDir, 'not%encoded'), baseDirConverter.resolve('file:not%encoded'))
+        assertEquals(new File(baseDir, 'bad%1'), baseDirConverter.resolve('file:bad%1'))
+        assertEquals(new File(baseDir, 'white space'), baseDirConverter.resolve('file:white space'))
+    }
+
+    @Test public void testResolveURIWithReservedCharacters() {
+        File absoluteFile = new File('white space').canonicalFile
+        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.toURI()))
+    }
+
+    @Test public void testResolveURLWithReservedCharacters() {
+        File absoluteFile = new File('white space').canonicalFile
+        assertEquals(absoluteFile, baseDirConverter.resolve(absoluteFile.toURI().toURL()))
+    }
+
+    @Test public void testCannotResolveNonFileURI() {
+        try {
+            baseDirConverter.resolve("http://www.gradle.org")
+            fail()
+        } catch (InvalidUserDataException e) {
+            assertThat(e.message, equalTo('Cannot convert URL \'http://www.gradle.org\' to a file.'))
+        }
+    }
+
+    @Test public void testResolveClosure() {
+        assertEquals(new File(baseDir, 'relative'), baseDirConverter.resolve({'relative'}))
+    }
+
+    @Test public void testResolveCallable() {
+        assertEquals(new File(baseDir, 'relative'), baseDirConverter.resolve({'relative'} as Callable))
+    }
+
+    @Test public void testResolveFileSource() {
+        assertEquals(new File(baseDir, 'relative'), baseDirConverter.resolve(baseDirConverter.resolveLater('relative')))
+    }
+
+    @Test public void testResolveNestedClosuresAndCallables() {
+        Callable callable = {'relative'} as Callable
+        Closure closure = {callable}
+        assertEquals(new File(baseDir, 'relative'), baseDirConverter.resolve(closure))
+    }
+
+    @Test public void testFiles() {
+        FileCollection collection = baseDirConverter.resolveFiles('a', 'b')
+        assertThat(collection, instanceOf(PathResolvingFileCollection))
+        assertThat(collection.sources, equalTo(['a', 'b']))
+    }
+
+    @Test public void testFilesReturnsSourceFileCollection() {
+        FileCollection source = baseDirConverter.resolveFiles('a')
+        FileCollection collection = baseDirConverter.resolveFiles(source)
+        assertThat(collection, sameInstance(source))
+    }
+
+    @Test public void testResolveAbsolutePathToUri() {
+        File absoluteFile = new File('nonRelative').canonicalFile
+        assertEquals(absoluteFile.toURI(), baseDirConverter.resolveUri(absoluteFile.path))
+    }
+
+    @Test public void testResolveRelativePathToUri() {
+        assertEquals(new File(baseDir, 'relative').toURI(), baseDirConverter.resolveUri('relative'))
+    }
+
+    @Test public void testResolveFileWithAbsolutePathToUri() {
+        File absoluteFile = new File('nonRelative').canonicalFile
+        assertEquals(absoluteFile.toURI(), baseDirConverter.resolveUri(absoluteFile))
+    }
+
+    @Test public void testResolveFileWithRelativePathToUri() {
+        File relativeFile = new File('relative')
+        assertEquals(new File(baseDir, 'relative').toURI(), baseDirConverter.resolveUri(relativeFile))
+    }
+
+    @Test public void testResolveUriStringToUri() {
+        assertEquals(new URI("http://www.gradle.org"), baseDirConverter.resolveUri("http://www.gradle.org"))
+    }
+
+    @Test public void testResolveUriObjectToUri() {
+        URI uri = new URI("http://www.gradle.org")
+        assertEquals(uri, baseDirConverter.resolveUri(uri))
+    }
+
+    @Test public void testResolveUrlObjectToUri() {
+        assertEquals(new URI("http://www.gradle.org"), baseDirConverter.resolveUri(new URL("http://www.gradle.org")))
+    }
+
+    @Test public void testResolveAbsolutePathWithReservedCharsToUri() {
+        assertEquals(new File(baseDir, 'with white%20space').toURI(), baseDirConverter.resolveUri('with white%20space'))
+        assertEquals('with white%20space', baseDirConverter.resolve(baseDirConverter.resolveUri('with white%20space')).name)
+    }
+
+    @Test public void testResolveUriStringWithEncodedCharsToUri() {
+        assertEquals(new URI("http://www.gradle.org/white%20space"), baseDirConverter.resolveUri("http://www.gradle.org/white%20space"))
+    }
+    
+    @Test public void testResolveRelativePathToRelativePath() {
+        assertEquals("relative", baseDirConverter.resolveAsRelativePath("relative"))
+    }
+
+    @Test public void testResolveAbsoluteChildPathToRelativePath() {
+        def absoluteFile = new File(baseDir, 'child').absoluteFile
+        assertEquals('child', baseDirConverter.resolveAsRelativePath(absoluteFile))
+        assertEquals('child', baseDirConverter.resolveAsRelativePath(absoluteFile.absolutePath))
+    }
+
+    @Test public void testResolveAbsoluteSiblingPathToRelativePath() {
+        def absoluteFile = new File(baseDir, '../sibling').absoluteFile
+        assertEquals("..${File.separator}sibling".toString(), baseDirConverter.resolveAsRelativePath(absoluteFile))
+        assertEquals("..${File.separator}sibling".toString(), baseDirConverter.resolveAsRelativePath(absoluteFile.absolutePath))
+    }
+
+    @Test public void testResolveBaseDirToRelativePath() {
+        assertEquals('.', baseDirConverter.resolveAsRelativePath(baseDir))
+        assertEquals('.', baseDirConverter.resolveAsRelativePath(baseDir.absolutePath))
+        assertEquals('.', baseDirConverter.resolveAsRelativePath('.'))
+        assertEquals('.', baseDirConverter.resolveAsRelativePath("../$baseDir.name"))
+    }
+
+    @Test public void testResolveParentDirToRelativePath() {
+        assertEquals('..', baseDirConverter.resolveAsRelativePath(baseDir.parentFile))
+        assertEquals('..', baseDirConverter.resolveAsRelativePath('..'))
+    }
+
+    @Test public void testResolveLater() {
+        String src;
+        Closure cl = { src }
+        FileSource source = baseDirConverter.resolveLater(cl)
+        src = 'file1'
+        assertEquals(new File(baseDir, 'file1'), source.get())
+    }
+    
+    @Test public void testCreateFileResolver() {
+        File newBaseDir = new File(baseDir, 'subdir')
+        assertEquals(new File(newBaseDir, 'file'), baseDirConverter.withBaseDir('subdir').resolve('file'))
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/CompositeFileCollectionTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/CompositeFileCollectionTest.java
new file mode 100644
index 0000000..5be94b7
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/CompositeFileCollectionTest.java
@@ -0,0 +1,278 @@
+/*
+ * 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 org.gradle.api.Task;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.TaskDependency;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.*;
+
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class CompositeFileCollectionTest {
+    private final JUnit4Mockery context = new JUnit4Mockery(){{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+    private final AbstractFileCollection source1 = context.mock(AbstractFileCollection.class, "source1");
+    private final AbstractFileCollection source2 = context.mock(AbstractFileCollection.class, "source2");
+    private final TestCompositeFileCollection collection = new TestCompositeFileCollection(source1, source2);
+
+    @Test
+    public void containsUnionOfAllSourceCollections() {
+        final File file1 = new File("1");
+        final File file2 = new File("2");
+        final File file3 = new File("3");
+
+        context.checking(new Expectations() {{
+            one(source1).getFiles();
+            will(returnValue(toSet(file1, file2)));
+            one(source2).getFiles();
+            will(returnValue(toSet(file2, file3)));
+        }});
+
+        assertThat(collection.getFiles(), equalTo(toLinkedSet(file1, file2, file3)));
+    }
+
+    @Test
+    public void contentsTrackContentsOfSourceCollections() {
+        final File file1 = new File("1");
+        final File file2 = new File("2");
+        final File file3 = new File("3");
+
+        context.checking(new Expectations() {{
+            allowing(source1).getFiles();
+            will(returnValue(toSet(file1)));
+            exactly(2).of(source2).getFiles();
+            will(onConsecutiveCalls(returnValue(toSet(file2, file3)), returnValue(toSet(file3))));
+        }});
+
+        assertThat(collection.getFiles(), equalTo(toLinkedSet(file1, file2, file3)));
+        assertThat(collection.getFiles(), equalTo(toLinkedSet(file1, file3)));
+    }
+
+    @Test
+    public void containsFileWhenAtLeastOneSourceCollectionContainsFile() {
+        final File file1 = new File("1");
+
+        context.checking(new Expectations() {{
+            one(source1).contains(file1);
+            will(returnValue(false));
+            one(source2).contains(file1);
+            will(returnValue(true));
+        }});
+
+        assertTrue(collection.contains(file1));
+    }
+
+    @Test
+    public void doesNotContainFileWhenNoSourceCollectionsContainFile() {
+        final File file1 = new File("1");
+
+        context.checking(new Expectations() {{
+            one(source1).contains(file1);
+            will(returnValue(false));
+            one(source2).contains(file1);
+            will(returnValue(false));
+        }});
+
+        assertFalse(collection.contains(file1));
+    }
+    
+    @Test
+    public void isEmptyWhenHasNoSets() {
+        CompositeFileCollection set = new TestCompositeFileCollection();
+        assertTrue(set.isEmpty());
+    }
+
+    @Test
+    public void isEmptyWhenAllSetsAreEmpty() {
+        context.checking(new Expectations() {{
+            one(source1).isEmpty();
+            will(returnValue(true));
+            one(source2).isEmpty();
+            will(returnValue(true));
+        }});
+
+        assertTrue(collection.isEmpty());
+    }
+
+    @Test
+    public void isNotEmptyWhenAnySetIsNotEmpty() {
+        context.checking(new Expectations() {{
+            one(source1).isEmpty();
+            will(returnValue(false));
+        }});
+
+        assertFalse(collection.isEmpty());
+    }
+
+    @Test
+    public void addToAntBuilderDelegatesToEachSet() {
+        context.checking(new Expectations() {{
+            one(source1).addToAntBuilder("node", "name", FileCollection.AntType.ResourceCollection);
+            one(source2).addToAntBuilder("node", "name", FileCollection.AntType.ResourceCollection);
+        }});
+
+        collection.addToAntBuilder("node", "name", FileCollection.AntType.ResourceCollection);
+    }
+
+    @Test
+    public void getAsFileSetsReturnsUnionOfFileSets() {
+        final DefaultConfigurableFileTree set1 = new DefaultConfigurableFileTree(new File("dir1").getAbsoluteFile(), null, null);
+        final DefaultConfigurableFileTree set2 = new DefaultConfigurableFileTree(new File("dir2").getAbsoluteFile(), null, null);
+
+        context.checking(new Expectations() {{
+            one(source1).getAsFileTrees();
+            will(returnValue(toList((Object) set1)));
+            one(source2).getAsFileTrees();
+            will(returnValue(toList((Object) set2)));
+        }});
+        assertThat(collection.getAsFileTrees(), equalTo((Collection) toList(set1, set2)));
+    }
+    
+    @Test
+    public void getAsFileTreeDelegatesToEachSet() {
+        final FileTree tree1 = context.mock(FileTree.class, "tree1");
+        final FileTree tree2 = context.mock(FileTree.class, "tree2");
+
+        context.checking(new Expectations() {{
+            one(source1).getAsFileTree();
+            will(returnValue(tree1));
+            one(source2).getAsFileTree();
+            will(returnValue(tree2));
+        }});
+
+        FileTree fileTree = collection.getAsFileTree();
+        assertThat(fileTree, instanceOf(CompositeFileTree.class));
+        assertThat(((CompositeFileTree) fileTree).getSourceCollections(), equalTo((Iterable) toList(tree1, tree2)));
+    }
+
+    @Test
+    public void fileTreeIsLive() {
+        final FileTree tree1 = context.mock(FileTree.class, "tree1");
+        final FileTree tree2 = context.mock(FileTree.class, "tree2");
+        final FileCollection source3 = context.mock(FileCollection.class);
+        final FileTree tree3 = context.mock(FileTree.class);
+
+        context.checking(new Expectations() {{
+            one(source1).getAsFileTree();
+            will(returnValue(tree1));
+            one(source2).getAsFileTree();
+            will(returnValue(tree2));
+        }});
+
+        FileTree fileTree = collection.getAsFileTree();
+        assertThat(fileTree, instanceOf(CompositeFileTree.class));
+        assertThat(((CompositeFileTree) fileTree).getSourceCollections(), equalTo((Iterable) toList(tree1, tree2)));
+
+        collection.sourceCollections.add(source3);
+
+        context.checking(new Expectations() {{
+            one(source1).getAsFileTree();
+            will(returnValue(tree1));
+            one(source2).getAsFileTree();
+            will(returnValue(tree2));
+            one(source3).getAsFileTree();
+            will(returnValue(tree3));
+        }});
+
+        assertThat(((CompositeFileTree) fileTree).getSourceCollections(), equalTo((Iterable) toList(tree1, tree2, tree3)));
+    }
+
+    @Test
+    public void filterDelegatesToEachSet() {
+        final FileCollection filtered1 = context.mock(FileCollection.class, "filtered1");
+        final FileCollection filtered2 = context.mock(FileCollection.class, "filtered2");
+        final Spec spec = context.mock(Spec.class);
+
+        context.checking(new Expectations() {{
+            one(source1).filter(spec);
+            will(returnValue(filtered1));
+            one(source2).filter(spec);
+            will(returnValue(filtered2));
+        }});
+
+        FileCollection filtered = collection.filter(spec);
+        assertThat(filtered, instanceOf(CompositeFileCollection.class));
+        assertThat(((CompositeFileCollection) filtered).getSourceCollections(), equalTo((Iterable) toList(filtered1, filtered2)));
+    }
+
+    @Test
+    public void dependsOnLiveUnionOfAllDependencies() {
+        final Task target = context.mock(Task.class, "target");
+        final Task task1 = context.mock(Task.class, "task1");
+        final Task task2 = context.mock(Task.class, "task2");
+        final Task task3 = context.mock(Task.class, "task3");
+        final FileCollection source3 = context.mock(FileCollection.class, "source3");
+
+        context.checking(new Expectations(){{
+            TaskDependency dependency1 = context.mock(TaskDependency.class, "dep1");
+            TaskDependency dependency2 = context.mock(TaskDependency.class, "dep2");
+            TaskDependency dependency3 = context.mock(TaskDependency.class, "dep3");
+
+            allowing(source1).getBuildDependencies();
+            will(returnValue(dependency1));
+            allowing(dependency1).getDependencies(target);
+            will(returnValue(toSet(task1)));
+            allowing(source2).getBuildDependencies();
+            will(returnValue(dependency2));
+            allowing(dependency2).getDependencies(target);
+            will(returnValue(toSet(task2)));
+            allowing(source3).getBuildDependencies();
+            will(returnValue(dependency3));
+            allowing(dependency3).getDependencies(target);
+            will(returnValue(toSet(task3)));
+        }});
+
+        TaskDependency dependency = collection.getBuildDependencies();
+        assertThat(dependency.getDependencies(target), equalTo((Set) toSet(task1, task2)));
+
+        collection.sourceCollections.add(source3);
+
+        assertThat(dependency.getDependencies(target), equalTo((Set) toSet(task1, task2, task3)));
+    }
+
+    private class TestCompositeFileCollection extends CompositeFileCollection {
+        private List<FileCollection> sourceCollections;
+
+        public TestCompositeFileCollection(FileCollection... sourceCollections) {
+            this.sourceCollections = new ArrayList<FileCollection>(Arrays.asList(sourceCollections));
+        }
+
+        @Override
+        public String getDisplayName() {
+            return "<display name>";
+        }
+
+        @Override
+        protected void addSourceCollections(Collection<FileCollection> sources) {
+            sources.addAll(sourceCollections);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/CompositeFileTreeTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/CompositeFileTreeTest.java
new file mode 100644
index 0000000..a219764
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/CompositeFileTreeTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.file;
+
+import groovy.lang.Closure;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.FileVisitor;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.util.HelperUtil;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collection;
+
+ at RunWith(JMock.class)
+public class CompositeFileTreeTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final FileTree source1 = context.mock(FileTree.class, "source1");
+    private final FileTree source2 = context.mock(FileTree.class, "source2");
+    private final CompositeFileTree tree = new CompositeFileTree() {
+        @Override
+        public String getDisplayName() {
+            return "<display-name>";
+        }
+
+        @Override
+        protected void addSourceCollections(Collection<FileCollection> sources) {
+            sources.add(source1);
+            sources.add(source2);
+        }
+    };
+
+    @Test
+    public void matchingWithClosureReturnsUnionOfFilteredSets() {
+        final Closure closure = HelperUtil.TEST_CLOSURE;
+        final FileTree filtered1 = context.mock(FileTree.class, "filtered1");
+        final FileTree filtered2 = context.mock(FileTree.class, "filtered2");
+
+        context.checking(new Expectations() {{
+            one(source1).matching(closure);
+            will(returnValue(filtered1));
+            one(source2).matching(closure);
+            will(returnValue(filtered2));
+        }});
+
+        FileTree filtered = tree.matching(closure);
+        assertThat(filtered, instanceOf(CompositeFileTree.class));
+        CompositeFileTree filteredCompositeSet = (CompositeFileTree) filtered;
+
+        assertThat(toList(filteredCompositeSet.getSourceCollections()), equalTo(toList(filtered1, filtered2)));
+    }
+
+    @Test
+    public void matchingWithPatternSetReturnsUnionOfFilteredSets() {
+        final PatternSet patternSet = new PatternSet();
+        final FileTree filtered1 = context.mock(FileTree.class, "filtered1");
+        final FileTree filtered2 = context.mock(FileTree.class, "filtered2");
+
+        context.checking(new Expectations() {{
+            one(source1).matching(patternSet);
+            will(returnValue(filtered1));
+            one(source2).matching(patternSet);
+            will(returnValue(filtered2));
+        }});
+
+        FileTree filtered = tree.matching(patternSet);
+        assertThat(filtered, instanceOf(CompositeFileTree.class));
+        CompositeFileTree filteredCompositeSet = (CompositeFileTree) filtered;
+
+        assertThat(toList(filteredCompositeSet.getSourceCollections()), equalTo(toList(filtered1, filtered2)));
+    }
+
+    @Test
+    public void plusReturnsUnionOfThisTreeAndSourceTree() {
+        FileTree other = context.mock(FileTree.class, "other");
+
+        FileTree sum = tree.plus(other);
+        assertThat(sum, instanceOf(CompositeFileTree.class));
+        UnionFileTree sumCompositeTree = (UnionFileTree) sum;
+        assertThat(sumCompositeTree.getSourceCollections(), equalTo((Iterable) toList(tree, other)));
+    }
+
+    @Test
+    public void visitsEachTreeWithVisitor() {
+        final FileVisitor visitor = context.mock(FileVisitor.class);
+
+        context.checking(new Expectations() {{
+            one(source1).visit(visitor);
+            one(source2).visit(visitor);
+        }});
+
+        tree.visit(visitor);
+    }
+
+    @Test
+    public void visitsEachTreeWithClosure() {
+        final Closure visitor = HelperUtil.TEST_CLOSURE;
+
+        context.checking(new Expectations() {{
+            one(source1).visit(visitor);
+            one(source2).visit(visitor);
+        }});
+
+        tree.visit(visitor);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/DefaultDirectoryWalkerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/DefaultDirectoryWalkerTest.java
new file mode 100644
index 0000000..f71c309
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/DefaultDirectoryWalkerTest.java
@@ -0,0 +1,288 @@
+/*
+ * 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 org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.internal.file.copy.CopySpecVisitor;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.util.PatternSet;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.Sequence;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+ at RunWith(JMock.class)
+public class DefaultDirectoryWalkerTest {
+    private JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+    private CopySpecVisitor visitor;
+    private DirectoryWalker walker;
+
+    @Before
+    public void setUp() {
+        visitor = context.mock(CopySpecVisitor.class);
+    }
+
+    @Test public void rootDirEmpty() throws IOException {
+        final MockFile root = new MockFile(context, "root", false);
+
+        walker = new DefaultDirectoryWalker(visitor);
+        root.setExpectations();
+
+        walker.start(root.getMock());
+    }
+
+    @Test public void testUsesSpecFromPatternSetToMatchFilesAndDirs() {
+        final PatternSet patternSet = context.mock(PatternSet.class);
+        final Spec spec = context.mock(Spec.class);
+
+        context.checking(new Expectations(){{
+            one(patternSet).getAsSpec();
+            will(returnValue(spec));
+        }});
+
+        walker = new DefaultDirectoryWalker(visitor);
+        walker.match(patternSet);
+    }
+
+    @Test public void walkSingleFile() throws IOException {
+        walker = new DefaultDirectoryWalker(visitor);
+
+        final MockFile root = new MockFile(context, "root", false);
+        final MockFile fileToCopy = root.addFile("file.txt");
+
+        fileToCopy.setExpectations();
+
+        context.checking(new Expectations() {{
+            one(visitor).visitFile(with(file(fileToCopy)));
+        }});
+
+        walker.start(fileToCopy.getMock());
+    }
+
+    /*
+    mock file structure:
+    root
+        rootFile1
+        dir1
+           dirFile1
+           dirFile2
+        rootFile2
+
+        Test that the files are really walked breadth first
+     */
+    @Test public void walkBreadthFirst() throws IOException {
+
+        walker = new DefaultDirectoryWalker(visitor);
+
+        final MockFile root = new MockFile(context, "root", false);
+        final MockFile rootFile1 = root.addFile("rootFile1");
+        final MockFile dir1 = root.addDir("dir1");
+        final MockFile dirFile1 = dir1.addFile("dirFile1");
+        final MockFile dirFile2 = dir1.addFile("dirFile2");
+        final MockFile rootFile2 = root.addFile("rootFile2");
+        root.setExpectations();
+
+        final Sequence visiting = context.sequence("visiting");
+        context.checking(new Expectations() {{
+            one(visitor).visitFile(with(file(rootFile1))); inSequence(visiting);
+            one(visitor).visitFile(with(file(rootFile2))); inSequence(visiting);
+            one(visitor).visitDir(with(file(dir1))); inSequence(visiting);
+            one(visitor).visitFile(with(file(dirFile1))); inSequence(visiting);
+            one(visitor).visitFile(with(file(dirFile2))); inSequence(visiting);
+        }});
+
+        walker.start(root.getMock());
+    }
+
+    @Test public void walkDepthFirst() throws IOException {
+
+        walker = new DefaultDirectoryWalker(visitor).depthFirst();
+
+        final MockFile root = new MockFile(context, "root", false);
+        final MockFile rootFile1 = root.addFile("rootFile1");
+        final MockFile dir1 = root.addDir("dir1");
+        final MockFile dirFile1 = dir1.addFile("dirFile1");
+        final MockFile dirFile2 = dir1.addFile("dirFile2");
+        final MockFile rootFile2 = root.addFile("rootFile2");
+        root.setExpectations();
+
+        final Sequence visiting = context.sequence("visiting");
+        context.checking(new Expectations() {{
+            one(visitor).visitFile(with(file(rootFile1))); inSequence(visiting);
+            one(visitor).visitFile(with(file(rootFile2))); inSequence(visiting);
+            one(visitor).visitFile(with(file(dirFile1))); inSequence(visiting);
+            one(visitor).visitFile(with(file(dirFile2))); inSequence(visiting);
+            one(visitor).visitDir(with(file(dir1))); inSequence(visiting);
+        }});
+
+        walker.start(root.getMock());
+    }
+
+    @Test public void canVisitorCanStopVisit() throws IOException {
+
+        walker = new DefaultDirectoryWalker(visitor);
+
+        final MockFile root = new MockFile(context, "root", false);
+        final MockFile rootFile1 = root.addFile("rootFile1");
+        final MockFile dir1 = root.addDir("dir1");
+        final MockFile dirFile1 = dir1.addFile("dirFile1");
+        dir1.addFile("dirFile2");
+        dir1.addDir("dir1Dir").addFile("dir1Dir1File1");
+        final MockFile rootFile2 = root.addFile("rootFile2");
+        root.setExpectations();
+
+        context.checking(new Expectations() {{
+            one(visitor).visitFile(with(file(rootFile1))); will(stopVisiting());
+        }});
+
+        walker.start(root.getMock());
+
+        final Sequence visiting = context.sequence("visiting");
+        context.checking(new Expectations() {{
+            one(visitor).visitFile(with(file(rootFile1))); inSequence(visiting);
+            one(visitor).visitFile(with(file(rootFile2))); inSequence(visiting);
+            one(visitor).visitDir(with(file(dir1))); inSequence(visiting);
+            one(visitor).visitFile(with(file(dirFile1))); will(stopVisiting()); inSequence(visiting);
+        }});
+
+        walker.start(root.getMock());
+    }
+
+    private Action stopVisiting() {
+        return new Action() {
+            public void describeTo(Description description) {
+                description.appendText("stop visiting");
+            }
+
+            public Object invoke(Invocation invocation) throws Throwable {
+                FileVisitDetails details = (FileVisitDetails) invocation.getParameter(0);
+                details.stopVisiting();
+                return null;
+            }
+        };
+    }
+
+    // test excludes, includes
+
+    private Matcher<FileVisitDetails> file(final MockFile file) {
+        return new BaseMatcher<FileVisitDetails>() {
+            public boolean matches(Object o) {
+                FileVisitDetails details = (FileVisitDetails) o;
+                return details.getFile().equals(file.getMock()) && details.getRelativePath().equals(file.getRelativePath());
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("details match file ").appendValue(file.getMock()).appendText(" with path ")
+                        .appendValue(file.getRelativePath());
+            }
+        };
+    }
+
+    public class MockFile {
+        private boolean isFile;
+        private String name;
+        private Mockery context;
+        private List<MockFile> children;
+        private File mock;
+        private MockFile parent;
+
+        public MockFile(Mockery context, String name, boolean isFile) {
+            this.context = context;
+            this.name = name;
+            this.isFile = isFile;
+            children = new ArrayList<MockFile>();
+            mock = context.mock(File.class, name);
+        }
+
+        public File getMock() {
+            return mock;
+        }
+
+        public MockFile addFile(String name) {
+            MockFile child = new MockFile(context, name, true);
+            child.setParent(this);
+            children.add(child);
+            return child;
+        }
+
+        public MockFile addDir(String name) {
+            MockFile child = new MockFile(context, name, false);
+            child.setParent(this);
+            children.add(child);
+            return child;
+        }
+
+        public void setParent(MockFile parent) {
+            this.parent = parent;
+        }
+
+        public RelativePath getRelativePath() {
+            if (parent == null) {
+                return new RelativePath(isFile);
+            } else {
+                return parent.getRelativePath().append(isFile, name);
+            }
+        }
+
+        public void setExpectations() {
+            Expectations expectations = new Expectations();
+            setExpectations(expectations);
+            context.checking(expectations);
+        }
+
+        public void setExpectations(Expectations expectations) {
+            try {
+                expectations.allowing(mock).getCanonicalFile();
+                expectations.will(expectations.returnValue(mock));
+            } catch (IOException th) {
+                // ignore
+            }
+            expectations.allowing(mock).isFile();
+            expectations.will(expectations.returnValue(isFile));
+            expectations.allowing(mock).getName();
+            expectations.will(expectations.returnValue(name));
+            expectations.allowing(mock).exists();
+            expectations.will(expectations.returnValue(true));
+
+            ArrayList<File> mockChildren = new ArrayList<File>(children.size());
+            for (MockFile child : children) {
+                mockChildren.add(child.getMock());
+                child.setExpectations(expectations);
+            }
+            expectations.allowing(mock).listFiles();
+            expectations.will(expectations.returnValue(mockChildren.toArray(new File[mockChildren.size()])));
+        }
+    }
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/DefaultFileOperationsTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/DefaultFileOperationsTest.groovy
new file mode 100644
index 0000000..9d75a94
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/DefaultFileOperationsTest.groovy
@@ -0,0 +1,326 @@
+/*
+ * 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 org.apache.commons.io.FileUtils
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.PathValidation
+import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.FileTree
+import org.gradle.api.internal.file.archive.TarFileTree
+import org.gradle.api.internal.file.archive.ZipFileTree
+import org.gradle.api.internal.file.copy.CopyActionImpl
+import org.gradle.api.internal.file.copy.CopySpecImpl
+import org.gradle.api.internal.tasks.TaskResolver
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.gradle.process.internal.ExecException
+import org.gradle.process.ExecResult
+import org.junit.Rule
+import org.junit.Test
+import spock.lang.Specification
+import org.gradle.util.OperatingSystem
+import org.gradle.util.ClasspathUtil
+
+public class DefaultFileOperationsTest extends Specification {
+    private final FileResolver resolver = Mock()
+    private final TaskResolver taskResolver = Mock()
+    private final TemporaryFileProvider temporaryFileProvider = Mock()
+    private DefaultFileOperations fileOperations = new DefaultFileOperations(resolver, taskResolver, temporaryFileProvider)
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder()
+
+    def resolvesFile() {
+        when:
+        TestFile file = expectPathResolved('path')
+
+        then:
+        fileOperations.file('path') == file
+    }
+
+    def resolvesFileWithValidation() {
+        TestFile file = tmpDir.file('path')
+        resolver.resolve('path', PathValidation.EXISTS) >> file
+
+        expect:
+        fileOperations.file('path', PathValidation.EXISTS) == file
+    }
+
+    def resolvesURI() {
+        when:
+        URI uri = expectPathResolvedToUri('path')
+
+        then:
+        fileOperations.uri('path') == uri
+    }
+
+    def resolvesFiles() {
+        when:
+        def fileCollection = fileOperations.files('a', 'b')
+
+        then:
+        fileCollection instanceof PathResolvingFileCollection
+        fileCollection.sources == ['a', 'b']
+        fileCollection.resolver.is(resolver)
+        fileCollection.buildDependency.resolver.is(taskResolver)
+    }
+
+    def createsFileTree() {
+        TestFile baseDir = expectPathResolved('base')
+
+        when:
+        def fileTree = fileOperations.fileTree('base')
+
+        then:
+        fileTree instanceof FileTree
+        fileTree.dir == baseDir
+        fileTree.resolver.is(resolver)
+    }
+
+    def createsFileTreeFromMap() {
+        TestFile baseDir = expectPathResolved('base')
+
+        when:
+        def fileTree = fileOperations.fileTree(dir: 'base')
+
+        then:
+        fileTree instanceof FileTree
+        fileTree.dir == baseDir
+        fileTree.resolver.is(resolver)
+    }
+
+    @Test
+    public void createsFileTreeFromClosure() {
+        TestFile baseDir = expectPathResolved('base')
+
+        when:
+        def fileTree = fileOperations.fileTree { from 'base' }
+
+        then:
+        fileTree instanceof FileTree
+        fileTree.dir == baseDir
+        fileTree.resolver.is(resolver)
+    }
+
+    def createsZipFileTree() {
+        expectPathResolved('path')
+        expectTempFileCreated()
+        when:
+        def zipTree = fileOperations.zipTree('path')
+
+        then:
+        zipTree instanceof ZipFileTree
+    }
+
+    def createsTarFileTree() {
+        expectPathResolved('path')
+        expectTempFileCreated()
+
+        when:
+        def tarTree = fileOperations.tarTree('path')
+
+        then:
+        tarTree instanceof TarFileTree
+    }
+
+    def copiesFiles() {
+        FileTree fileTree = Mock(FileTree)
+        resolver.resolveFilesAsTree(_) >> fileTree
+        // todo we should make this work so that we can be more specific
+//        resolver.resolveFilesAsTree(['file'] as Object[]) >> fileTree
+//        resolver.resolveFilesAsTree(['file'] as Set) >> fileTree
+        fileTree.matching(_) >> fileTree
+        resolver.resolve('dir') >> tmpDir.getDir()
+
+        when:
+        def result = fileOperations.copy { from 'file'; into 'dir' }
+
+        then:
+        result instanceof CopyActionImpl
+        !result.didWork
+    }
+
+    def deletes() {
+        TestFile fileToBeDeleted = tmpDir.file("file")
+        ConfigurableFileCollection fileCollection = new PathResolvingFileCollection(resolver, null, "file")
+        resolver.resolveFiles(["file"] as Object[]) >> fileCollection
+        resolver.resolve("file") >> fileToBeDeleted
+        fileToBeDeleted.touch();
+
+        expect:
+        fileOperations.delete('file') == true
+        fileToBeDeleted.isFile() == false
+    }
+
+    def makesDir() {
+        TestFile dirToBeCreated = tmpDir.file("parentDir", "dir")
+        resolver.resolve('parentDir/dir') >> dirToBeCreated
+
+        when:
+        File actualDir = fileOperations.mkdir('parentDir/dir')
+
+        then:
+        actualDir == dirToBeCreated
+        actualDir.isDirectory() == true
+    }
+
+    def makesDirThrowsExceptionIfPathPointsToFile() {
+        TestFile dirToBeCreated = tmpDir.file("parentDir", "dir")
+        dirToBeCreated.touch();
+        resolver.resolve('parentDir/dir') >> dirToBeCreated
+
+        when:
+        fileOperations.mkdir('parentDir/dir')
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def createsCopySpec() {
+        when:
+        def spec = fileOperations.copySpec { include 'pattern'}
+
+        then:
+        spec instanceof CopySpecImpl
+        spec.includes == ['pattern'] as Set
+    }
+
+    private TestFile expectPathResolved(String path) {
+        TestFile file = tmpDir.file(path)
+        resolver.resolve(path) >> file
+        return file
+    }
+
+    private URI expectPathResolvedToUri(String path) {
+        TestFile file = tmpDir.file(path)
+        resolver.resolveUri(path) >> file.toURI()
+        return file.toURI()
+    }
+
+    private TestFile expectTempFileCreated() {
+        TestFile file = tmpDir.file('expandedArchives')
+        temporaryFileProvider.newTemporaryFile('expandedArchives') >> file
+        return file
+    }
+
+    def javaexec() {
+        File testFile = tmpDir.file("someFile")
+        fileOperations = new DefaultFileOperations(new IdentityFileResolver(), taskResolver, temporaryFileProvider)
+        List files = ClasspathUtil.getClasspath(getClass().classLoader)
+        println "Using classpath:"
+        files.each {println it}
+        println "==="
+
+        when:
+        ExecResult result = fileOperations.javaexec {
+            classpath(files as Object[])
+            main = 'org.gradle.api.internal.file.SomeMain'
+            args testFile.absolutePath
+        }
+
+        then:
+        testFile.isFile()
+        result.exitValue == 0
+    }
+
+    def javaexecWithNonZeroExitValueShouldThrowException() {
+        fileOperations = new DefaultFileOperations(new IdentityFileResolver(), taskResolver, temporaryFileProvider)
+
+        when:
+        fileOperations.javaexec {
+            main = 'org.gradle.UnknownMain'
+        }
+
+        then:
+        thrown(ExecException)
+    }
+
+    def javaexecWithNonZeroExitValueAndIgnoreExitValueShouldNotThrowException() {
+        fileOperations = new DefaultFileOperations(new IdentityFileResolver(), taskResolver, temporaryFileProvider)
+
+        when:
+        ExecResult result = fileOperations.javaexec {
+            main = 'org.gradle.UnknownMain'
+            ignoreExitValue = true
+        }
+
+        then:
+        result.exitValue != 0
+    }
+
+    def exec() {
+        if (OperatingSystem.current().isWindows()) {
+            return
+        }
+
+        fileOperations = new DefaultFileOperations(new IdentityFileResolver(), taskResolver, temporaryFileProvider)
+        File testFile = tmpDir.file("someFile")
+
+        when:
+        ExecResult result = fileOperations.exec {
+            executable = "touch"
+            workingDir = tmpDir.getDir()
+            args testFile.name
+        }
+
+        then:
+        testFile.isFile()
+        result.exitValue == 0
+    }
+
+    def execWithNonZeroExitValueShouldThrowException() {
+        if (OperatingSystem.current().isWindows()) {
+            return
+        }
+        fileOperations = new DefaultFileOperations(new IdentityFileResolver(), taskResolver, temporaryFileProvider)
+
+        when:
+        fileOperations.exec {
+            executable = "touch"
+            workingDir = tmpDir.getDir()
+            args tmpDir.dir.name + "/nonExistingDir/someFile"
+        }
+
+        then:
+        thrown(ExecException)
+    }
+
+    def execWithNonZeroExitValueAndIgnoreExitValueShouldNotThrowException() {
+        if (OperatingSystem.current().isWindows()) {
+            return
+        }
+        fileOperations = new DefaultFileOperations(new IdentityFileResolver(), taskResolver, temporaryFileProvider)
+
+        when:
+        ExecResult result = fileOperations.exec {
+            ignoreExitValue = true
+            executable = "touch"
+            workingDir = tmpDir.getDir()
+            args tmpDir.dir.name + "/nonExistingDir/someFile"
+        }
+
+        then:
+        result.exitValue != 0
+    }
+}
+
+class SomeMain {
+    static void main(String[] args) {
+        FileUtils.touch(new File(args[0]))
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/DefaultSourceDirectorySetTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/DefaultSourceDirectorySetTest.groovy
new file mode 100644
index 0000000..cb4486d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/DefaultSourceDirectorySetTest.groovy
@@ -0,0 +1,238 @@
+/*
+ * 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 org.gradle.api.InvalidUserDataException
+import org.gradle.api.file.FileTree
+import org.gradle.api.tasks.StopExecutionException
+import org.gradle.util.GFileUtils
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.TemporaryFolder
+import org.jmock.integration.junit4.JMock
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.gradle.api.tasks.AntBuilderAwareUtil.*
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*;
+
+ at RunWith (JMock)
+public class DefaultSourceDirectorySetTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    @Rule public TemporaryFolder tmpDir = new TemporaryFolder()
+    private final File testDir = tmpDir.dir
+    private FileResolver resolver
+    private DefaultSourceDirectorySet set
+
+    @Before
+    public void setUp() {
+        resolver = {src -> src instanceof File ? src : new File(testDir, src as String)} as FileResolver
+        set = new DefaultSourceDirectorySet('<display-name>', resolver)
+    }
+
+    @Test
+    public void addsResolvedSourceDirectoryToSet() {
+        set.srcDir 'dir1'
+
+        assertThat(set.srcDirs, equalTo([new File(testDir, 'dir1')] as Set))
+    }
+
+    @Test
+    public void addsResolvedSourceDirectoriesToSet() {
+        set.srcDir { -> ['dir1', 'dir2'] }
+
+        assertThat(set.srcDirs, equalTo([new File(testDir, 'dir1'), new File(testDir, 'dir2')] as Set))
+    }
+
+    @Test
+    public void canSetSourceDirectories() {
+        set.srcDir 'ignore me'
+        set.srcDirs = ['dir1', 'dir2']
+
+        assertThat(set.srcDirs, equalTo([new File(testDir, 'dir1'), new File(testDir, 'dir2')] as Set))
+    }
+
+    @Test
+    public void addsFilesetForEachSourceDirectory() {
+        File srcDir1 = new File(testDir, 'dir1')
+        GFileUtils.touch(new File(srcDir1, 'subdir/file1.txt'))
+        GFileUtils.touch(new File(srcDir1, 'subdir/file2.txt'))
+        File srcDir2 = new File(testDir, 'dir2')
+        GFileUtils.touch(new File(srcDir2, 'subdir2/file1.txt'))
+
+        set.srcDir 'dir1'
+        set.srcDir 'dir2'
+
+        assertSetContainsForAllTypes(set, 'subdir/file1.txt', 'subdir/file2.txt', 'subdir2/file1.txt')
+    }
+
+    @Test
+    public void canUsePatternsToFilterCertainFiles() {
+        File srcDir1 = new File(testDir, 'dir1')
+        GFileUtils.touch(new File(srcDir1, 'subdir/file1.txt'))
+        GFileUtils.touch(new File(srcDir1, 'subdir/file2.txt'))
+        GFileUtils.touch(new File(srcDir1, 'subdir/ignored.txt'))
+        File srcDir2 = new File(testDir, 'dir2')
+        GFileUtils.touch(new File(srcDir2, 'subdir2/file1.txt'))
+        GFileUtils.touch(new File(srcDir2, 'subdir2/file2.txt'))
+        GFileUtils.touch(new File(srcDir2, 'subdir2/ignored.txt'))
+
+        set.srcDir 'dir1'
+        set.srcDir 'dir2'
+        set.include '**/file*'
+        set.exclude '**/file2*'
+
+        assertSetContainsForAllTypes(set, 'subdir/file1.txt', 'subdir2/file1.txt')
+    }
+
+    @Test
+    public void canUseFilterPatternsToFilterCertainFiles() {
+        File srcDir1 = new File(testDir, 'dir1')
+        GFileUtils.touch(new File(srcDir1, 'subdir/file1.txt'))
+        GFileUtils.touch(new File(srcDir1, 'subdir/file2.txt'))
+        GFileUtils.touch(new File(srcDir1, 'subdir/ignored.txt'))
+        File srcDir2 = new File(testDir, 'dir2')
+        GFileUtils.touch(new File(srcDir2, 'subdir2/file1.txt'))
+        GFileUtils.touch(new File(srcDir2, 'subdir2/file2.txt'))
+        GFileUtils.touch(new File(srcDir2, 'subdir2/ignored.txt'))
+
+        set.srcDir 'dir1'
+        set.srcDir 'dir2'
+        set.filter.include '**/file*'
+        set.filter.exclude '**/file2*'
+
+        assertSetContainsForAllTypes(set, 'subdir/file1.txt', 'subdir2/file1.txt')
+    }
+
+    @Test
+    public void ignoresSourceDirectoriesWhichDoNotExist() {
+        File srcDir1 = new File(testDir, 'dir1')
+        GFileUtils.touch(new File(srcDir1, 'subdir/file1.txt'))
+
+        set.srcDir 'dir1'
+        set.srcDir 'dir2'
+
+        assertSetContainsForAllTypes(set, 'subdir/file1.txt')
+    }
+
+    @Test
+    public void failsWhenSourceDirectoryIsNotADirectory() {
+        File srcDir = new File(testDir, 'dir1')
+        GFileUtils.touch(srcDir)
+
+        set.srcDir 'dir1'
+        try {
+            set.addToAntBuilder("node", "fileset")
+            fail()
+        } catch (InvalidUserDataException e) {
+            assertThat(e.message, equalTo("Source directory '$srcDir' is not a directory." as String))
+        }
+    }
+
+    @Test
+    public void throwsStopExceptionWhenNoSourceDirectoriesExist() {
+        set.srcDir 'dir1'
+        set.srcDir 'dir2'
+
+        try {
+            set.stopExecutionIfEmpty()
+            fail()
+        } catch (StopExecutionException e) {
+            assertThat(e.message, equalTo('<display-name> does not contain any files.'))
+        }
+    }
+
+    @Test
+    public void throwsStopExceptionWhenNoSourceDirectoryHasMatches() {
+        set.srcDir 'dir1'
+        File srcDir = new File(testDir, 'dir1')
+        srcDir.mkdirs()
+
+        try {
+            set.stopExecutionIfEmpty()
+            fail()
+        } catch (StopExecutionException e) {
+            assertThat(e.message, equalTo('<display-name> does not contain any files.'))
+        }
+    }
+
+    @Test
+    public void doesNotThrowStopExceptionWhenSomeSourceDirectoriesAreNotEmpty() {
+        set.srcDir 'dir1'
+        GFileUtils.touch(new File(testDir, 'dir1/file1.txt'))
+        set.srcDir 'dir2'
+
+        set.stopExecutionIfEmpty()
+    }
+
+    @Test
+    public void canUseMatchingMethodToFilterCertainFiles() {
+        File srcDir1 = new File(testDir, 'dir1')
+        GFileUtils.touch(new File(srcDir1, 'subdir/file1.txt'))
+        GFileUtils.touch(new File(srcDir1, 'subdir/file2.txt'))
+        GFileUtils.touch(new File(srcDir1, 'subdir2/file1.txt'))
+
+        set.srcDir 'dir1'
+
+        FileTree filteredSet = set.matching {
+            include '**/file1.txt'
+            exclude 'subdir2/**'
+        }
+
+        assertSetContainsForAllTypes(filteredSet, 'subdir/file1.txt')
+    }
+
+    @Test
+    public void canUsePatternsAndFilterPatternsAndMatchingMethodToFilterSourceFiles() {
+        File srcDir1 = new File(testDir, 'dir1')
+        GFileUtils.touch(new File(srcDir1, 'subdir/file1.txt'))
+        GFileUtils.touch(new File(srcDir1, 'subdir/file1.other'))
+        GFileUtils.touch(new File(srcDir1, 'subdir/file2.txt'))
+        GFileUtils.touch(new File(srcDir1, 'subdir/ignored.txt'))
+        GFileUtils.touch(new File(srcDir1, 'subdir2/file1.txt'))
+
+        set.srcDir 'dir1'
+        set.include '**/*file?.*'
+        set.filter.include '**/*.txt'
+
+        FileTree filteredSet = set.matching {
+            include 'subdir/**'
+            exclude '**/file2.txt'
+        }
+
+        assertSetContainsForAllTypes(filteredSet, 'subdir/file1.txt')
+    }
+
+    @Test
+    public void filteredSetIsLive() {
+        File srcDir1 = new File(testDir, 'dir1')
+        GFileUtils.touch(new File(srcDir1, 'subdir/file1.txt'))
+        GFileUtils.touch(new File(srcDir1, 'subdir/file2.txt'))
+        File srcDir2 = new File(testDir, 'dir2')
+        GFileUtils.touch(new File(srcDir2, 'subdir2/file1.txt'))
+
+        set.srcDir 'dir1'
+
+        FileTree filteredSet = set.matching { include '**/file1.txt' }
+        assertSetContainsForAllTypes(filteredSet, 'subdir/file1.txt')
+
+        set.srcDir 'dir2'
+
+        assertSetContainsForAllTypes(filteredSet, 'subdir/file1.txt', 'subdir2/file1.txt')
+    }
+}
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProviderTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProviderTest.groovy
new file mode 100644
index 0000000..ba91b0f
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/DefaultTemporaryFileProviderTest.groovy
@@ -0,0 +1,40 @@
+/*
+ * 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 org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.TemporaryFolder
+import org.jmock.integration.junit4.JMock
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+ at RunWith(JMock.class)
+public class DefaultTemporaryFileProviderTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder()
+
+    @Test
+    public void allocatesTempFile() {
+        DefaultTemporaryFileProvider provider = new DefaultTemporaryFileProvider({tmpDir.getDir()} as FileSource)
+        assertThat(provider.newTemporaryFile('a', 'b'), equalTo(tmpDir.file('a', 'b')))
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/FileSetTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/FileSetTest.groovy
new file mode 100644
index 0000000..ea69d4e
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/FileSetTest.groovy
@@ -0,0 +1,304 @@
+/*
+ * 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.api.internal.file
+
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.Task
+import org.gradle.api.file.FileTree
+import org.gradle.api.internal.tasks.TaskResolver
+import org.gradle.api.tasks.StopExecutionException
+import org.gradle.api.tasks.util.AbstractTestForPatternSet
+import org.gradle.api.tasks.util.PatternFilterable
+import org.gradle.api.tasks.util.PatternSet
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.WrapUtil
+import org.jmock.integration.junit4.JUnit4Mockery
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import static org.gradle.api.file.FileVisitorUtil.assertCanStopVisiting
+import static org.gradle.api.file.FileVisitorUtil.assertVisits
+import static org.gradle.api.tasks.AntBuilderAwareUtil.assertSetContainsForAllTypes
+import static org.gradle.util.Matchers.isEmpty
+import static org.hamcrest.Matchers.equalTo
+import static org.hamcrest.Matchers.instanceOf
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+class FileSetTest extends AbstractTestForPatternSet {
+    JUnit4Mockery context = new JUnit4GroovyMockery();
+    TaskResolver taskResolverStub = context.mock(TaskResolver.class);
+    DefaultConfigurableFileTree fileSet
+    FileResolver fileResolverStub = [resolve: {it as File}] as FileResolver
+    @Rule public TemporaryFolder tmpDir = new TemporaryFolder();
+    File testDir = tmpDir.dir
+
+    PatternFilterable getPatternSet() {
+        return fileSet
+    }
+
+    Class getPatternSetType() {
+        DefaultConfigurableFileTree
+    }
+
+    @Before public void setUp() {
+        super.setUp()
+        fileSet = patternSetType.newInstance(testDir, fileResolverStub, taskResolverStub)
+    }
+
+    @Test public void testFileSetConstructionWithBaseDir() {
+        fileSet = new DefaultConfigurableFileTree(testDir, fileResolverStub, taskResolverStub)
+        assertEquals(testDir, fileSet.dir)
+    }
+
+    @Test public void testFileSetConstructionFromMap() {
+        fileSet = new DefaultConfigurableFileTree(fileResolverStub, taskResolverStub, dir: testDir, includes: ['include'])
+        assertEquals(testDir, fileSet.dir)
+        assertEquals(['include'] as Set, fileSet.includes)
+    }
+
+    @Test(expected = InvalidUserDataException) public void testFileSetConstructionWithNoBaseDirSpecified() {
+        DefaultConfigurableFileTree fileSet = new DefaultConfigurableFileTree([:], fileResolverStub, taskResolverStub)
+        fileSet.matching {}
+    }
+
+    @Test public void testFileSetConstructionWithBaseDirAsString() {
+        DefaultConfigurableFileTree fileSet = new DefaultConfigurableFileTree(fileResolverStub, taskResolverStub, dir: 'dirname')
+        assertEquals(new File('dirname'), fileSet.dir);
+    }
+
+    @Test public void testCanScanForFiles() {
+        File included1 = new File(testDir, 'subDir/included1')
+        File included2 = new File(testDir, 'subDir2/included2')
+        [included1, included2].each {File file ->
+            file.parentFile.mkdirs()
+            file.text = 'some text'
+        }
+
+        assertThat(fileSet.files, equalTo([included1, included2] as Set))
+    }
+
+    @Test public void testCanVisitFiles() {
+        File included1 = new File(testDir, 'subDir/included1')
+        File included2 = new File(testDir, 'subDir2/included2')
+        [included1, included2].each {File file ->
+            file.parentFile.mkdirs()
+            file.text = 'some text'
+        }
+
+        assertVisits(fileSet, ['subDir/included1', 'subDir2/included2'], ['subDir', 'subDir2'])
+    }
+
+    @Test public void testCanStopVisitingFiles() {
+        File included1 = new File(testDir, 'subDir/included1')
+        File included2 = new File(testDir, 'subDir/otherDir/included2')
+        [included1, included2].each {File file ->
+            file.parentFile.mkdirs()
+            file.text = 'some text'
+        }
+
+        assertCanStopVisiting(fileSet)
+    }
+
+    @Test public void testContainsFiles() {
+        File included1 = new File(testDir, 'subDir/included1')
+        File included2 = new File(testDir, 'subDir2/included2')
+        [included1, included2].each {File file ->
+            file.parentFile.mkdirs()
+            file.text = 'some text'
+        }
+
+        assertTrue(fileSet.contains(included1))
+        assertTrue(fileSet.contains(included2))
+        assertFalse(fileSet.contains(testDir))
+        assertFalse(fileSet.contains(included1.parentFile))
+        assertFalse(fileSet.contains(included2.parentFile))
+        assertFalse(fileSet.contains(new File(testDir, 'does not exist')))
+        assertFalse(fileSet.contains(testDir.parentFile))
+        assertFalse(fileSet.contains(new File('something')))
+    }
+
+    @Test public void testCanAddToAntTask() {
+        File included1 = new File(testDir, 'subDir/included1')
+        File included2 = new File(testDir, 'subDir2/included2')
+        [included1, included2].each {File file ->
+            file.parentFile.mkdirs()
+            file.text = 'some text'
+        }
+
+        assertSetContainsForAllTypes(fileSet, 'subDir/included1', 'subDir2/included2')
+    }
+
+    @Test public void testIsEmptyWhenBaseDirDoesNotExist() {
+        fileSet.dir = new File(testDir, 'does not exist')
+
+        assertThat(fileSet.files, isEmpty())
+        assertSetContainsForAllTypes(fileSet)
+        assertVisits(fileSet, [], [])
+    }
+
+    @Test public void testCanSelectFilesUsingPatterns() {
+        File included1 = new File(testDir, 'subDir/included1')
+        File included2 = new File(testDir, 'subDir2/included2')
+        File excluded1 = new File(testDir, 'subDir/notincluded')
+        File ignored1 = new File(testDir, 'ignored')
+        [included1, included2, excluded1, ignored1].each {File file ->
+            file.parentFile.mkdirs()
+            file.text = 'some text'
+        }
+
+        fileSet.include('*/*included*')
+        fileSet.exclude('**/not*')
+
+        assertThat(fileSet.files, equalTo([included1, included2] as Set))
+        assertSetContainsForAllTypes(fileSet, 'subDir/included1', 'subDir2/included2')
+        assertVisits(fileSet, ['subDir/included1', 'subDir2/included2'], ['subDir', 'subDir2'])
+        assertTrue(fileSet.contains(included1))
+        assertFalse(fileSet.contains(excluded1))
+        assertFalse(fileSet.contains(ignored1))
+    }
+
+    @Test public void testCanFilterMatchingFilesUsingConfigureClosure() {
+        File included1 = new File(testDir, 'subDir/included1')
+        File included2 = new File(testDir, 'subDir2/included2')
+        File excluded1 = new File(testDir, 'subDir/notincluded')
+        File ignored1 = new File(testDir, 'ignored')
+        [included1, included2, excluded1, ignored1].each {File file ->
+            file.parentFile.mkdirs()
+            file.text = 'some text'
+        }
+
+        DefaultConfigurableFileTree filtered = fileSet.matching {
+            include('*/*included*')
+            exclude('**/not*')
+        }
+
+        assertThat(filtered.files, equalTo([included1, included2] as Set))
+        assertSetContainsForAllTypes(filtered, 'subDir/included1', 'subDir2/included2')
+        assertVisits(filtered, ['subDir/included1', 'subDir2/included2'], ['subDir', 'subDir2'])
+        assertTrue(filtered.contains(included1))
+        assertFalse(filtered.contains(excluded1))
+        assertFalse(filtered.contains(ignored1))
+    }
+
+    @Test public void testCanFilterMatchingFilesUsingPatternSet() {
+        File included1 = new File(testDir, 'subDir/included1')
+        File included2 = new File(testDir, 'subDir2/included2')
+        File excluded1 = new File(testDir, 'subDir/notincluded')
+        File ignored1 = new File(testDir, 'ignored')
+        [included1, included2, excluded1, ignored1].each {File file ->
+            file.parentFile.mkdirs()
+            file.text = 'some text'
+        }
+
+        PatternSet patternSet = new PatternSet(includes: ['*/*included*'], excludes: ['**/not*'])
+        DefaultConfigurableFileTree filtered = fileSet.matching(patternSet)
+
+        assertThat(filtered.files, equalTo([included1, included2] as Set))
+        assertSetContainsForAllTypes(filtered, 'subDir/included1', 'subDir2/included2')
+        assertVisits(filtered, ['subDir/included1', 'subDir2/included2'], ['subDir', 'subDir2'])
+        assertTrue(filtered.contains(included1))
+        assertFalse(filtered.contains(excluded1))
+        assertFalse(filtered.contains(ignored1))
+    }
+
+    @Test public void testCanFilterAndSelectFiles() {
+        File included1 = new File(testDir, 'subDir/included1')
+        File included2 = new File(testDir, 'subDir2/included2')
+        File excluded1 = new File(testDir, 'subDir/notincluded')
+        File excluded2 = new File(testDir, 'subDir/excluded')
+        File ignored1 = new File(testDir, 'ignored')
+        [included1, included2, excluded1, excluded2, ignored1].each {File file ->
+            file.parentFile.mkdirs()
+            file.text = 'some text'
+        }
+
+        fileSet.exclude '**/excluded*'
+
+        DefaultConfigurableFileTree filtered = fileSet.matching {
+            include('*/*included*')
+            exclude('**/not*')
+        }
+
+        assertThat(filtered.files, equalTo([included1, included2] as Set))
+        assertSetContainsForAllTypes(filtered, 'subDir/included1', 'subDir2/included2')
+        assertVisits(filtered, ['subDir/included1', 'subDir2/included2'], ['subDir', 'subDir2'])
+        assertTrue(filtered.contains(included1))
+        assertFalse(filtered.contains(excluded1))
+        assertFalse(filtered.contains(ignored1))
+    }
+
+    @Test public void testCanAddFileSetsTogether() {
+        FileTree other = new DefaultConfigurableFileTree(testDir, fileResolverStub, taskResolverStub)
+        FileTree sum = fileSet + other
+        assertThat(sum, instanceOf(UnionFileTree))
+        assertThat(sum.sourceCollections, equalTo([fileSet, other]))
+    }
+
+    @Test public void testDisplayName() {
+        assertThat(fileSet.displayName, equalTo("file set '$testDir'".toString()))
+    }
+
+    @Test public void testStopExecutionIfEmptyWhenNoMatchingFilesFound() {
+        fileSet.include('**/*included')
+        new File(testDir, 'excluded').text = 'some text'
+
+        try {
+            fileSet.stopExecutionIfEmpty()
+            fail()
+        } catch (StopExecutionException e) {
+            assertThat(e.message, equalTo("File set '$testDir' does not contain any files." as String))
+        }
+    }
+
+    @Test public void testStopExecutionIfEmptyWhenMatchingFilesFound() {
+        fileSet.include('**/*included')
+        new File(testDir, 'included').text = 'some text'
+
+        fileSet.stopExecutionIfEmpty()
+    }
+
+    @Test
+    public void canGetAndSetTaskDependencies() {
+        FileResolver fileResolverStub = context.mock(FileResolver.class);
+        fileSet = patternSetType.newInstance(testDir, fileResolverStub, taskResolverStub)
+        
+        assertThat(fileSet.getBuiltBy(), isEmpty());
+
+        fileSet.builtBy("a");
+        fileSet.builtBy("b");
+        fileSet.from("f");
+
+        assertThat(fileSet.getBuiltBy(), equalTo(WrapUtil.toSet((Object) "a", "b")));
+
+        fileSet.setBuiltBy(WrapUtil.toList("c"));
+
+        assertThat(fileSet.getBuiltBy(), equalTo(WrapUtil.toSet((Object) "c")));
+        final Task task = context.mock(Task.class);
+        context.checking {
+            allowing(fileResolverStub).resolve("f");
+            will(returnValue(new File("f")));
+            allowing(taskResolverStub).resolveTask('c');
+            will(returnValue(task));
+        }
+
+        assertThat(fileSet.getBuildDependencies().getDependencies(null), equalTo((Set) WrapUtil.toSet(task)));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/MapFileTreeTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/MapFileTreeTest.java
new file mode 100644
index 0000000..62f076d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/MapFileTreeTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.file;
+
+import groovy.lang.Closure;
+import static org.gradle.api.file.FileVisitorUtil.*;
+import static org.gradle.api.tasks.AntBuilderAwareUtil.*;
+import org.gradle.util.TestFile;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.TemporaryFolder;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.List;
+
+public class MapFileTreeTest {
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    private TestFile rootDir = tmpDir.getDir();
+    private final MapFileTree tree = new MapFileTree(rootDir);
+
+    @Test
+    public void isEmptyByDefault() {
+        List<String> emptyList = toList();
+        assertVisits(tree, emptyList, emptyList);
+        assertSetContainsForAllTypes(tree, emptyList);
+    }
+    
+    @Test
+    public void canAddAnElementUsingAClosureToGeneratedContent() {
+        Closure closure = HelperUtil.toClosure("{it.write('content'.getBytes())}");
+        tree.add("path/file.txt", closure);
+
+        assertVisits(tree, toList("path/file.txt"), toList("path"));
+        assertSetContainsForAllTypes(tree, toList("path/file.txt"));
+
+        rootDir.file("path").assertIsDir();
+        rootDir.file("path/file.txt").assertContents(equalTo("content"));
+    }
+
+    @Test
+    public void canAddMultipleElementsInDifferentDirs() {
+        Closure closure = HelperUtil.toClosure("{it.write('content'.getBytes())}");
+        tree.add("path/file.txt", closure);
+        tree.add("file.txt", closure);
+        tree.add("path/subdir/file.txt", closure);
+
+        assertVisits(tree, toList("path/file.txt", "file.txt", "path/subdir/file.txt"), toList("path", "path/subdir"));
+        assertSetContainsForAllTypes(tree, toList("path/file.txt", "file.txt", "path/subdir/file.txt"));
+    }
+
+    @Test
+    public void canStopVisitingElements() {
+        Closure closure = HelperUtil.toClosure("{it.write('content'.getBytes())}");
+        tree.add("path/file.txt", closure);
+        tree.add("file.txt", closure);
+        assertCanStopVisiting(tree);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/PathResolvingFileCollectionTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/PathResolvingFileCollectionTest.java
new file mode 100644
index 0000000..be25b98
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/PathResolvingFileCollectionTest.java
@@ -0,0 +1,351 @@
+/*
+ * 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.file;
+
+import groovy.lang.Closure;
+import org.gradle.api.Task;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.tasks.TaskResolver;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.TemporaryFolder;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+import static org.gradle.util.Matchers.*;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class PathResolvingFileCollectionTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+    private final File testDir = tmpDir.getDir();
+    private final FileResolver resolverMock = context.mock(FileResolver.class);
+    private final TaskResolver taskResolverStub = context.mock(TaskResolver.class);
+    private final PathResolvingFileCollection collection = new PathResolvingFileCollection(resolverMock,
+            taskResolverStub);
+
+    @Test
+    public void resolvesSpecifiedFilesUseFileResolver() {
+        final File file1 = new File("1");
+        final File file2 = new File("2");
+
+        PathResolvingFileCollection collection = new PathResolvingFileCollection(resolverMock, taskResolverStub, file1,
+                file2);
+
+        context.checking(new Expectations() {{
+            one(resolverMock).resolve(file1);
+            will(returnValue(file1));
+            one(resolverMock).resolve(file2);
+            will(returnValue(file2));
+        }});
+
+        assertThat(collection.getFiles(), equalTo(toLinkedSet(file1, file2)));
+    }
+
+    @Test
+    public void canAddPathsToTheCollection() {
+        final File file1 = new File("1");
+        final File file2 = new File("2");
+
+        collection.from("src1", "src2");
+
+        context.checking(new Expectations() {{
+            one(resolverMock).resolve("src1");
+            will(returnValue(file1));
+            one(resolverMock).resolve("src2");
+            will(returnValue(file2));
+        }});
+
+        assertThat(collection.getFiles(), equalTo(toLinkedSet(file1, file2)));
+    }
+
+    @Test
+    public void resolvesSpecifiedPathsUseFileResolver() {
+        final File file1 = new File("1");
+        final File file2 = new File("2");
+
+        PathResolvingFileCollection collection = new PathResolvingFileCollection(resolverMock, taskResolverStub, "src1",
+                "src2");
+
+        context.checking(new Expectations() {{
+            one(resolverMock).resolve("src1");
+            will(returnValue(file1));
+            one(resolverMock).resolve("src2");
+            will(returnValue(file2));
+        }});
+
+        assertThat(collection.getFiles(), equalTo(toLinkedSet(file1, file2)));
+    }
+
+    @Test
+    public void canUseAClosureToSpecifyTheContentsOfTheCollection() {
+        final File file1 = new File("1");
+        final File file2 = new File("2");
+
+        context.checking(new Expectations() {{
+            allowing(resolverMock).resolve('a');
+            will(returnValue(file1));
+            allowing(resolverMock).resolve('b');
+            will(returnValue(file2));
+        }});
+
+        List<Character> files = toList('a');
+        Closure closure = HelperUtil.returns(files);
+        collection.from(closure);
+
+        assertThat(collection.getFiles(), equalTo(toLinkedSet(file1)));
+
+        files.add('b');
+
+        assertThat(collection.getFiles(), equalTo(toLinkedSet(file1, file2)));
+    }
+
+    @Test
+    public void canUseAClosureToSpecifyASingleFile() {
+        Closure closure = HelperUtil.returns('a');
+        final File file = new File("1");
+
+        collection.from(closure);
+
+        context.checking(new Expectations() {{
+            one(resolverMock).resolve('a');
+            will(returnValue(file));
+        }});
+
+        assertThat(collection.getFiles(), equalTo(toLinkedSet(file)));
+    }
+
+    @Test
+    public void closureCanReturnNull() {
+        Closure closure = HelperUtil.returns(null);
+
+        collection.from(closure);
+
+        assertThat(collection.getFiles(), isEmpty());
+    }
+
+    @Test
+    public void canUseACollectionToSpecifyTheContentsOfTheCollection() {
+        final File file1 = new File("1");
+        final File file2 = new File("2");
+
+        context.checking(new Expectations() {{
+            allowing(resolverMock).resolve("src1");
+            will(returnValue(file1));
+            allowing(resolverMock).resolve("src2");
+            will(returnValue(file2));
+        }});
+
+        List<String> files = toList("src1");
+        collection.from(files);
+
+        assertThat(collection.getFiles(), equalTo(toLinkedSet(file1)));
+
+        files.add("src2");
+
+        assertThat(collection.getFiles(), equalTo(toLinkedSet(file1, file2)));
+    }
+
+    @Test
+    public void canUseAnArrayToSpecifyTheContentsOfTheCollection() {
+        final File file1 = new File("1");
+        final File file2 = new File("2");
+
+        context.checking(new Expectations() {{
+            allowing(resolverMock).resolve("src1");
+            will(returnValue(file1));
+            allowing(resolverMock).resolve("src2");
+            will(returnValue(file2));
+        }});
+
+        collection.from((Object) toArray("src1", "src2"));
+        assertThat(collection.getFiles(), equalTo(toLinkedSet(file1, file2)));
+    }
+
+    @Test
+    public void canUseNestedObjectsToSpecifyTheContentsOfTheCollection() {
+        final File file1 = new File("1");
+        final File file2 = new File("2");
+
+        context.checking(new Expectations() {{
+            allowing(resolverMock).resolve("src1");
+            will(returnValue(file1));
+            allowing(resolverMock).resolve("src2");
+            will(returnValue(file2));
+        }});
+
+        collection.from(HelperUtil.toClosure("{[{['src1', { ['src2'] as String[] }]}]}"));
+        assertThat(collection.getFiles(), equalTo(toLinkedSet(file1, file2)));
+    }
+
+    @Test
+    public void canUseAFileCollectionToSpecifyTheContentsOfTheCollection() {
+        final File file1 = new File("1");
+        final File file2 = new File("2");
+
+        final FileCollection src = context.mock(FileCollection.class);
+
+        collection.from(src);
+
+        context.checking(new Expectations() {{
+            one(src).getFiles();
+            will(returnValue(toLinkedSet(file1)));
+        }});
+
+        assertThat(collection.getFiles(), equalTo(toLinkedSet(file1)));
+
+        context.checking(new Expectations() {{
+            one(src).getFiles();
+            will(returnValue(toLinkedSet(file1, file2)));
+        }});
+
+        assertThat(collection.getFiles(), equalTo(toLinkedSet(file1, file2)));
+    }
+
+    @Test
+    public void canUseACallableToSpecifyTheContentsOfTheCollection() throws Exception {
+        final File file1 = new File("1");
+        final File file2 = new File("2");
+        final Callable callable = context.mock(Callable.class);
+
+        context.checking(new Expectations() {{
+            one(callable).call();
+            will(returnValue(toList("src1", "src2")));
+            allowing(resolverMock).resolve("src1");
+            will(returnValue(file1));
+            allowing(resolverMock).resolve("src2");
+            will(returnValue(file2));
+        }});
+
+        collection.from(callable);
+        assertThat(collection.getFiles(), equalTo(toLinkedSet(file1, file2)));
+    }
+
+    @Test
+    public void callableCanReturnNull() throws Exception {
+        final Callable callable = context.mock(Callable.class);
+
+        context.checking(new Expectations() {{
+            one(callable).call();
+            will(returnValue(null));
+        }});
+
+        collection.from(callable);
+        assertThat(collection.getFiles(), isEmpty());
+    }
+
+    @Test
+    public void treatsEachFileAsASingletonFileCollection() {
+        final File file = new File(testDir, "f");
+        GFileUtils.touch(file);
+
+        context.checking(new Expectations() {{
+            one(resolverMock).resolve("file");
+            will(returnValue(file));
+        }});
+
+        collection.from("file");
+        assertThat(collection.getSourceCollections().get(0), instanceOf(SingletonFileCollection.class));
+    }
+
+    @Test
+    public void treatsEachFileCollectionAsFileCollection() {
+        final FileCollection fileCollectionMock = context.mock(FileCollection.class);
+
+        collection.from(fileCollectionMock);
+        assertThat((Iterable<FileCollection>) collection.getSourceCollections(), hasItem(fileCollectionMock));
+    }
+
+    @Test
+    public void canGetAndSetTaskDependencies() {
+        assertThat(collection.getBuiltBy(), isEmpty());
+
+        collection.builtBy("a");
+        collection.builtBy("b");
+        collection.from("f");
+
+        assertThat(collection.getBuiltBy(), equalTo(toSet((Object) "a", "b")));
+
+        collection.setBuiltBy(toList("c"));
+
+        assertThat(collection.getBuiltBy(), equalTo(toSet((Object) "c")));
+
+        final Task task = context.mock(Task.class);
+        context.checking(new Expectations() {{
+            allowing(resolverMock).resolve("f");
+            will(returnValue(new File("f")));
+            allowing(taskResolverStub).resolveTask("c");
+            will(returnValue(task));
+        }});
+
+        assertThat(collection.getBuildDependencies().getDependencies(null), equalTo((Set) toSet(task)));
+    }
+
+    @Test
+    public void taskDependenciesContainsUnionOfDependenciesOfNestedFileCollectionsPlusOwnDependencies() {
+        final FileCollection fileCollectionMock = context.mock(FileCollection.class);
+
+        collection.from(fileCollectionMock);
+        collection.from("f");
+        collection.builtBy('b');
+
+        final Task taskA = context.mock(Task.class, "a");
+        final Task taskB = context.mock(Task.class, "b");
+        context.checking(new Expectations() {{
+            allowing(resolverMock).resolve("f");
+            will(returnValue(new File("f")));
+            TaskDependency dependency = context.mock(TaskDependency.class);
+            allowing(fileCollectionMock).getBuildDependencies();
+            will(returnValue(dependency));
+            allowing(dependency).getDependencies(null);
+            will(returnValue(toSet(taskA)));
+            allowing(taskResolverStub).resolveTask('b');
+            will(returnValue(taskB));
+        }});
+
+        assertThat(collection.getBuildDependencies().getDependencies(null), equalTo((Set) toSet(taskA, taskB)));
+    }
+
+    @Test
+    public void hasSpecifiedDependenciesWhenEmpty() {
+        collection.builtBy("task");
+
+        final Task task = context.mock(Task.class);
+        context.checking(new Expectations(){{
+            allowing(taskResolverStub).resolveTask("task");
+            will(returnValue(task));
+        }});
+
+        assertThat(collection.getBuildDependencies().getDependencies(null), equalTo((Set) toSet(task)));
+        assertThat(collection.getAsFileTree().getBuildDependencies().getDependencies(null), equalTo((Set) toSet(task)));
+        assertThat(collection.getAsFileTree().matching(HelperUtil.TEST_CLOSURE).getBuildDependencies().getDependencies(null), equalTo((Set) toSet(task)));
+    }
+    
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/RelativePathTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/RelativePathTest.java
new file mode 100644
index 0000000..e538d39
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/RelativePathTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.file;
+
+import org.gradle.api.file.RelativePath;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.gradle.util.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+public class RelativePathTest {
+
+    private void assertPathContains(RelativePath path, boolean isFile, String... expectedSegments) {
+        String[] actualPaths = path.getSegments();
+        assertArrayEquals(expectedSegments, actualPaths);
+        assertEquals(isFile, path.isFile());
+    }
+
+    @Test
+    public void testConstructors() {
+        RelativePath path, childPath;
+        path = new RelativePath(true, "one");
+        assertPathContains(path, true, "one");
+
+        path = new RelativePath(false, "one", "two");
+        assertPathContains(path, false, "one", "two");
+    }
+
+    @Test
+    public void appendPath() {
+        RelativePath childPath = new RelativePath(false, "one", "two").append(new RelativePath(true, "three", "four"));
+        assertPathContains(childPath, true, "one", "two", "three", "four");
+
+        childPath = new RelativePath(false, "one", "two").append(new RelativePath(true));
+        assertPathContains(childPath, true, "one", "two");
+
+        childPath = new RelativePath(false, "one", "two").plus(new RelativePath(true, "three"));
+        assertPathContains(childPath, true, "one", "two", "three");
+    }
+
+    @Test
+    public void appendNames() {
+        RelativePath childPath = new RelativePath(false, "one", "two").append(true, "three", "four");
+        assertPathContains(childPath, true, "one", "two", "three", "four");
+
+        childPath = new RelativePath(false, "one", "two").append(true);
+        assertPathContains(childPath, true, "one", "two");
+    }
+
+    @Test
+    public void prependNames() {
+        RelativePath childPath = new RelativePath(false, "one", "two").prepend("three", "four");
+        assertPathContains(childPath, false, "three", "four", "one", "two");
+
+        childPath = new RelativePath(false, "one", "two").prepend();
+        assertPathContains(childPath, false, "one", "two");
+    }
+
+    @Test
+    public void hasWellBehavedEqualsAndHashCode() {
+        assertThat(new RelativePath(true), strictlyEqual(new RelativePath(true)));
+        assertThat(new RelativePath(true, "one"), strictlyEqual(new RelativePath(true, "one")));
+        assertThat(new RelativePath(false, "one", "two"), strictlyEqual(new RelativePath(false, "one", "two")));
+
+        assertThat(new RelativePath(true, "one"), not(equalTo(new RelativePath(true, "two"))));
+        assertThat(new RelativePath(true, "one"), not(equalTo(new RelativePath(true, "one", "two"))));
+        assertThat(new RelativePath(true, "one"), not(equalTo(new RelativePath(false, "one"))));
+    }
+
+    @Test
+    public void canParsePathIntoRelativePath() {
+        RelativePath path;
+
+        path = RelativePath.parse(true, "one");
+        assertPathContains(path, true, "one");
+
+        path = RelativePath.parse(true, "one/two");
+        assertPathContains(path, true, "one", "two");
+
+        path = RelativePath.parse(true, "one/two/");
+        assertPathContains(path, true, "one", "two");
+
+        path = RelativePath.parse(true, String.format("one%stwo%s", File.separator, File.separator));
+        assertPathContains(path, true, "one", "two");
+
+        path = RelativePath.parse(false, "");
+        assertPathContains(path, false);
+
+        path = RelativePath.parse(false, "/");
+        assertPathContains(path, false);
+
+        path = RelativePath.parse(true, "/one");
+        assertPathContains(path, true, "one");
+
+        path = RelativePath.parse(true, "/one/two");
+        assertPathContains(path, true, "one", "two");
+    }
+
+    @Test
+    public void canGetParentOfPath() {
+        assertThat(new RelativePath(true, "a", "b").getParent(), equalTo(new RelativePath(false, "a")));
+        assertThat(new RelativePath(false, "a", "b").getParent(), equalTo(new RelativePath(false, "a")));
+        assertThat(new RelativePath(false, "a").getParent(), equalTo(new RelativePath(false)));
+        assertThat(new RelativePath(false).getParent(), nullValue());
+    }
+
+    @Test
+    public void canReplaceLastName() {
+        assertPathContains(new RelativePath(true, "old").replaceLastName("new"), true, "new");
+        assertPathContains(new RelativePath(false, "old").replaceLastName("new"), false, "new");
+        assertPathContains(new RelativePath(true, "a", "b", "old").replaceLastName("new"), true, "a", "b", "new");
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/SimpleFileCollectionTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/SimpleFileCollectionTest.groovy
new file mode 100644
index 0000000..dcb300d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/SimpleFileCollectionTest.groovy
@@ -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.api.internal.file
+
+import spock.lang.Specification
+
+class SimpleFileCollectionTest extends Specification {
+    def collectionContainsFixedSetOfFiles() {
+        File file1 = new File('file1')
+        File file2 = new File('file2')
+
+        expect:
+        SimpleFileCollection collection = new SimpleFileCollection(file1, file2)
+        collection.files as List == [file1, file2]
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/SingletonFileCollectionTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/SingletonFileCollectionTest.groovy
new file mode 100644
index 0000000..8ba1703
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/SingletonFileCollectionTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * 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.file
+
+import org.gradle.api.tasks.TaskDependency
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+class SingletonFileCollectionTest {
+    private final File testFile = new File('test-file')
+    private final SingletonFileCollection collection = new SingletonFileCollection(testFile, [:] as TaskDependency)
+
+    @Test
+    public void hasUsefulDisplayName() {
+        assertThat(collection.displayName, equalTo("file '$testFile'".toString()));
+    }
+
+    @Test
+    public void containsASingleFile() {
+        assertThat(collection.files, equalTo([testFile] as Set))
+    }
+
+    @Test
+    public void convertsSelfToSingletonFileTree() {
+        assertThat(collection.asFileTree, instanceOf(SingletonFileTree))
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/SingletonFileTreeTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/SingletonFileTreeTest.groovy
new file mode 100644
index 0000000..f94824d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/SingletonFileTreeTest.groovy
@@ -0,0 +1,162 @@
+/*
+ * 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.file
+
+import org.gradle.api.file.FileTree
+import org.gradle.api.tasks.TaskDependency
+import org.gradle.util.TestFile
+import org.gradle.util.TemporaryFolder
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import static org.gradle.api.file.FileVisitorUtil.*
+import static org.gradle.api.tasks.AntBuilderAwareUtil.*
+import static org.gradle.util.Matchers.*
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.gradle.api.Task
+
+class SingletonFileTreeTest {
+    @Rule public TemporaryFolder rootDir = new TemporaryFolder();
+    private final TestFile testFile = rootDir.dir.file('test.txt')
+    private final TestFile missingFile = rootDir.dir.file('missing')
+    private final TestFile testDir = rootDir.dir.file('test-dir')
+    private final TaskDependency dependency = [:] as TaskDependency
+
+    @Before
+    public void setUp() {
+        testFile.touch()
+        testDir.create {
+            subdir1 {
+                file 'file1.txt'
+                file 'file2.txt'
+            }
+            subdir2 {
+                file 'file1.txt'
+            }
+        }
+    }
+
+    @Test
+    public void hasUsefulDisplayName() {
+        SingletonFileTree tree = new SingletonFileTree(testFile, dependency)
+        assertThat(tree.displayName, equalTo("file '$testFile'".toString()));
+    }
+
+    @Test
+    public void containsASingleFileWhenSourceIsAFile() {
+        SingletonFileTree tree = new SingletonFileTree(testFile, dependency)
+        assertThat(tree.files, equalTo([testFile] as Set))
+    }
+
+    @Test
+    public void containsDescendentFilesWhenSourceIsADirectory() {
+        SingletonFileTree tree = new SingletonFileTree(testDir, dependency)
+        assertThat(tree.files, equalTo(testDir.files('subdir1/file1.txt', 'subdir1/file2.txt', 'subdir2/file1.txt') as Set))
+    }
+
+    @Test
+    public void isEmptyWhenSourceDoesNotExist() {
+        SingletonFileTree tree = new SingletonFileTree(missingFile, dependency)
+        assertThat(tree.files, isEmpty())
+    }
+
+    @Test
+    public void addsToAntBuilderWhenSourceIsAFile() {
+        SingletonFileTree tree = new SingletonFileTree(testFile, dependency)
+        assertSetContains(tree, 'test.txt')
+    }
+
+    @Test
+    public void addsToAntBuilderWhenSourceIsADirectory() {
+        SingletonFileTree tree = new SingletonFileTree(testDir, dependency)
+        assertSetContains(tree, 'subdir1/file1.txt', 'subdir1/file2.txt', 'subdir2/file1.txt')
+    }
+
+    @Test
+    public void addsToAntBuilderWhenSourceDoesNotExist() {
+        SingletonFileTree tree = new SingletonFileTree(missingFile, dependency)
+        assertSetContains(tree)
+    }
+
+    @Test
+    public void visitsASingleFileWhenSourceIsAFile() {
+        SingletonFileTree tree = new SingletonFileTree(testFile, dependency)
+        assertVisits(tree, ['test.txt'], [])
+    }
+
+    @Test
+    public void visitsDescendentFilesWhenSourceIsADirectory() {
+        SingletonFileTree tree = new SingletonFileTree(testDir, dependency)
+        assertVisits(tree, ['subdir1/file1.txt', 'subdir1/file2.txt', 'subdir2/file1.txt'], ['subdir1', 'subdir2'])
+    }
+
+    @Test
+    public void visitsNothingWhenSourceDoesNotExist() {
+        SingletonFileTree tree = new SingletonFileTree(missingFile, dependency)
+        assertVisits(tree, [], [])
+    }
+
+    @Test
+    public void canRestrictContentsWhenSourceIsAFile() {
+        SingletonFileTree tree = new SingletonFileTree(testFile, dependency)
+
+        FileTree filtered = tree.matching { exclude '*.txt' }
+        assertThat(filtered.files, isEmpty())
+        assertSetContains(filtered)
+        assertVisits(filtered, [], [])
+
+        filtered = tree.matching { include '*.txt' }
+        assertThat(filtered.files, equalTo([testFile] as Set))
+        assertSetContains(filtered, 'test.txt')
+        assertVisits(filtered, ['test.txt'], [])
+    }
+
+    @Test
+    public void canRestrictContentsWhenSourceIsADirectory() {
+        SingletonFileTree tree = new SingletonFileTree(testDir, dependency)
+
+        FileTree filtered = tree.matching { exclude '**/file1.txt' }
+        assertThat(filtered.files, equalTo([testDir.file('subdir1/file2.txt')] as Set))
+        assertSetContains(filtered, 'subdir1/file2.txt')
+        assertVisits(filtered, ['subdir1/file2.txt'], ['subdir1', 'subdir2'])
+
+        filtered = tree.matching { include '**/file2.txt' }
+        assertThat(filtered.files, equalTo([testDir.file('subdir1/file2.txt')] as Set))
+        assertSetContains(filtered, 'subdir1/file2.txt')
+        assertVisits(filtered, ['subdir1/file2.txt'], ['subdir1', 'subdir2'])
+    }
+
+    @Test
+    public void canRestrictContentsWhenSourceDoesNotExist() {
+        SingletonFileTree tree = new SingletonFileTree(missingFile, dependency)
+
+        FileTree filtered = tree.matching { exclude '*.txt' }
+        assertThat(filtered.files, isEmpty())
+        assertSetContains(filtered)
+        assertVisits(filtered, [], [])
+    }
+
+    @Test
+    public void hasSpecifiedDependencies() {
+        Task task = [:] as Task
+        SingletonFileTree tree = new SingletonFileTree(missingFile, [getDependencies: { [task] as Set}] as TaskDependency)
+        assertThat(tree.buildDependencies.getDependencies(null), equalTo([task] as Set))
+        assertThat(tree.matching{}.buildDependencies.getDependencies(null), equalTo([task] as Set))
+    }
+}
+
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/UnionFileCollectionTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/UnionFileCollectionTest.java
new file mode 100644
index 0000000..b8392fb
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/UnionFileCollectionTest.java
@@ -0,0 +1,77 @@
+/*
+ * 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.file;
+
+import org.gradle.api.file.FileCollection;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+ at RunWith(JMock.class)
+public class UnionFileCollectionTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final FileCollection source1 = context.mock(FileCollection.class, "source1");
+    private final FileCollection source2 = context.mock(FileCollection.class, "source2");
+
+    @Test
+    public void containsUnionOfAllSourceCollections() {
+        final File file1 = new File("1");
+        final File file2 = new File("2");
+        final File file3 = new File("3");
+
+        context.checking(new Expectations() {{
+            one(source1).getFiles();
+            will(returnValue(toSet(file1, file2)));
+            one(source2).getFiles();
+            will(returnValue(toSet(file2, file3)));
+        }});
+
+        UnionFileCollection collection = new UnionFileCollection(source1, source2);
+        assertThat(collection.getFiles(), equalTo(toLinkedSet(file1, file2, file3)));
+    }
+
+    @Test
+    public void contentsTrackContentsOfSourceCollections() {
+        final File file1 = new File("1");
+        final File file2 = new File("2");
+        final File file3 = new File("3");
+
+        context.checking(new Expectations() {{
+            allowing(source1).getFiles();
+            will(returnValue(toSet(file1)));
+            exactly(2).of(source2).getFiles();
+            will(onConsecutiveCalls(returnValue(toSet(file2, file3)), returnValue(toSet(file3))));
+        }});
+
+        UnionFileCollection collection = new UnionFileCollection(source1, source2);
+        assertThat(collection.getFiles(), equalTo(toLinkedSet(file1, file2, file3)));
+        assertThat(collection.getFiles(), equalTo(toLinkedSet(file1, file3)));
+    }
+
+    @Test
+    public void canAddCollection() {
+        final UnionFileCollection collection = new UnionFileCollection();
+        collection.add(source1);
+        assertThat(collection.getSourceCollections(), equalTo((Iterable) toList((Object) source1)));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/UnionFileTreeTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/UnionFileTreeTest.java
new file mode 100644
index 0000000..2c6aa91
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/UnionFileTreeTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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.file;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+ at RunWith(JMock.class)
+public class UnionFileTreeTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final UnionFileTree set = new UnionFileTree("<display name>");
+
+    @Test
+    public void canAddFileTree() {
+        FileTree set1 = context.mock(FileTree.class, "set1");
+
+        set.add(set1);
+        assertThat(set.getSourceCollections(), equalTo((Iterable) toList((Object) set1)));
+    }
+
+    @Test
+    public void cannotAddFileCollection() {
+        try {
+            set.add(context.mock(FileCollection.class));
+            fail();
+        } catch (UnsupportedOperationException e) {
+            assertThat(e.getMessage(), equalTo("Can only add FileTree instances to <display name>."));
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitorTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitorTest.java
new file mode 100644
index 0000000..52f2531
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/archive/TarCopySpecVisitorTest.java
@@ -0,0 +1,235 @@
+/*
+ * 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.archive;
+
+import org.apache.commons.io.IOUtils;
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.file.copy.ReadableCopySpec;
+import org.gradle.api.tasks.bundling.Compression;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.RelativePath;
+import org.gradle.util.TestFile;
+import org.gradle.util.TemporaryFolder;
+import org.hamcrest.Description;
+import static org.hamcrest.Matchers.*;
+import org.jmock.Expectations;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+import java.io.OutputStream;
+
+ at RunWith(JMock.class)
+public class TarCopySpecVisitorTest {
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final TarCopyAction copyAction = context.mock(TarCopyAction.class);
+    private final ReadableCopySpec copySpec = context.mock(ReadableCopySpec.class);
+    private final TarCopySpecVisitor visitor = new TarCopySpecVisitor();
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations(){{
+            allowing(copySpec).getFileMode();
+            will(returnValue(1));
+            allowing(copySpec).getDirMode();
+            will(returnValue(2));
+        }});
+    }
+    
+    @Test
+    public void createsTarFile() {
+        final TestFile tarFile = tmpDir.getDir().file("test.tar");
+
+        context.checking(new Expectations() {{
+            allowing(copyAction).getArchivePath();
+            will(returnValue(tarFile));
+            allowing(copyAction).getCompression();
+            will(returnValue(Compression.NONE));
+        }});
+
+        visitor.startVisit(copyAction);
+        visitor.visitSpec(copySpec);
+
+        visitor.visitFile(file("dir/file1"));
+        visitor.visitFile(file("file2"));
+
+        visitor.endVisit();
+
+        TestFile expandDir = tmpDir.getDir().file("expanded");
+        tarFile.untarTo(expandDir);
+        expandDir.file("dir/file1").assertContents(equalTo("contents of dir/file1"));
+        expandDir.file("file2").assertContents(equalTo("contents of file2"));
+    }
+
+    @Test
+    public void createsGzipCompressedTarFile() {
+        final TestFile tarFile = tmpDir.getDir().file("test.tgz");
+
+        context.checking(new Expectations(){{
+            allowing(copyAction).getArchivePath();
+            will(returnValue(tarFile));
+            allowing(copyAction).getCompression();
+            will(returnValue(Compression.GZIP));
+        }});
+
+        visitor.startVisit(copyAction);
+        visitor.visitSpec(copySpec);
+
+        visitor.visitFile(file("dir/file1"));
+        visitor.visitFile(file("file2"));
+
+        visitor.endVisit();
+
+        TestFile expandDir = tmpDir.getDir().file("expanded");
+        tarFile.untarTo(expandDir);
+        expandDir.file("dir/file1").assertContents(equalTo("contents of dir/file1"));
+        expandDir.file("file2").assertContents(equalTo("contents of file2"));
+    }
+
+    @Test
+    public void createsBzip2CompressedTarFile() {
+        final TestFile tarFile = tmpDir.getDir().file("test.tbz2");
+
+        context.checking(new Expectations(){{
+            allowing(copyAction).getArchivePath();
+            will(returnValue(tarFile));
+            allowing(copyAction).getCompression();
+            will(returnValue(Compression.BZIP2));
+        }});
+
+        visitor.startVisit(copyAction);
+        visitor.visitSpec(copySpec);
+
+        visitor.visitFile(file("dir/file1"));
+        visitor.visitFile(file("file2"));
+
+        visitor.endVisit();
+
+        TestFile expandDir = tmpDir.getDir().file("expanded");
+        tarFile.untarTo(expandDir);
+        expandDir.file("dir/file1").assertContents(equalTo("contents of dir/file1"));
+        expandDir.file("file2").assertContents(equalTo("contents of file2"));
+    }
+
+    @Test
+    public void wrapsFailureToOpenOutputFile() {
+        final TestFile tarFile = tmpDir.createDir("test.tar");
+
+        context.checking(new Expectations(){{
+            allowing(copyAction).getArchivePath();
+            will(returnValue(tarFile));
+        }});
+
+        try {
+            visitor.startVisit(copyAction);
+            fail();
+        } catch (GradleException e) {
+            assertThat(e.getMessage(), equalTo(String.format("Could not create TAR '%s'.", tarFile)));
+        }
+    }
+
+    @Test
+    public void wrapsFailureToAddElement() {
+        final TestFile tarFile = tmpDir.getDir().file("test.tar");
+
+        context.checking(new Expectations(){{
+            allowing(copyAction).getArchivePath();
+            will(returnValue(tarFile));
+
+            allowing(copyAction).getCompression();
+            will(returnValue(Compression.NONE));
+        }});
+
+        visitor.startVisit(copyAction);
+        visitor.visitSpec(copySpec);
+
+        Throwable failure = new RuntimeException("broken");
+        try {
+            visitor.visitFile(brokenFile("dir/file1", failure));
+            fail();
+        } catch (GradleException e) {
+            assertThat(e.getMessage(), equalTo(String.format("Could not add [dir/file1] to TAR '%s'.", tarFile)));
+            assertThat(e.getCause(), sameInstance(failure));
+        }
+    }
+
+    private FileVisitDetails file(final String path) {
+        final FileVisitDetails details = context.mock(FileVisitDetails.class, path);
+        final String content = String.format("contents of %s", path);
+
+        context.checking(new Expectations() {{
+            allowing(details).getRelativePath();
+            will(returnValue(RelativePath.parse(true, path)));
+
+            allowing(details).getLastModified();
+            will(returnValue(1000L));
+
+            allowing(details).getSize();
+            will(returnValue((long)content.getBytes().length));
+
+            allowing(details).copyTo(with(notNullValue(OutputStream.class)));
+            will(new Action() {
+                public void describeTo(Description description) {
+                    description.appendText("write content");
+                }
+
+                public Object invoke(Invocation invocation) throws Throwable {
+                    IOUtils.write(content, (OutputStream) invocation.getParameter(0));
+                    return null;
+                }
+            });
+        }});
+
+        return details;
+    }
+
+    private FileVisitDetails brokenFile(final String path, final Throwable failure) {
+        final FileVisitDetails details = context.mock(FileVisitDetails.class, String.format("[%s]", path));
+
+        context.checking(new Expectations() {{
+            allowing(details).getRelativePath();
+            will(returnValue(RelativePath.parse(true, path)));
+
+            allowing(details).getLastModified();
+            will(returnValue(1000L));
+
+            allowing(details).getSize();
+            will(returnValue(1000L));
+
+            allowing(details).copyTo(with(notNullValue(OutputStream.class)));
+            will(new Action() {
+                public void describeTo(Description description) {
+                    description.appendText("write content");
+                }
+
+                public Object invoke(Invocation invocation) throws Throwable {
+                    failure.fillInStackTrace();
+                    throw failure;
+                }
+            });
+        }});
+
+        return details;
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/archive/TarFileTreeTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/archive/TarFileTreeTest.java
new file mode 100644
index 0000000..005f923
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/archive/TarFileTreeTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.file.archive;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.InvalidUserDataException;
+import static org.gradle.api.file.FileVisitorUtil.*;
+import static org.gradle.api.tasks.AntBuilderAwareUtil.*;
+import org.gradle.util.TestFile;
+import org.gradle.util.TemporaryFolder;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import static java.util.Collections.*;
+
+public class TarFileTreeTest {
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    private final TestFile tarFile = tmpDir.getDir().file("test.tar");
+    private final TestFile rootDir = tmpDir.getDir().file("root");
+    private final TestFile expandDir = tmpDir.getDir().file("tmp");
+    private final TarFileTree tree = new TarFileTree(tarFile, expandDir);
+
+    @Test
+    public void displayName() {
+        assertThat(tree.getDisplayName(), equalTo("TAR '" + tarFile + "'"));
+    }
+
+    @Test
+    public void visitsContentsOfTarFile() {
+        rootDir.file("subdir/file1.txt").write("content");
+        rootDir.file("subdir2/file2.txt").write("content");
+        rootDir.tarTo(tarFile);
+
+        assertVisits(tree, toList("subdir/file1.txt", "subdir2/file2.txt"), toList("subdir", "subdir2"));
+        assertSetContainsForAllTypes(tree, toList("subdir/file1.txt", "subdir2/file2.txt"));
+    }
+
+    @Test
+    public void canStopVisitingFiles() {
+        rootDir.file("subdir/file1.txt").write("content");
+        rootDir.file("subdir/other/file2.txt").write("content");
+        rootDir.tarTo(tarFile);
+
+        assertCanStopVisiting(tree);
+    }
+
+    @Test
+    public void isEmptyWhenTarFileDoesNotExist() {
+        assertVisits(tree, EMPTY_LIST, EMPTY_LIST);
+        assertSetContainsForAllTypes(tree, EMPTY_LIST);
+    }
+
+    @Test
+    public void failsWhenTarFileIsADirectory() {
+        tarFile.createDir();
+
+        try {
+            tree.getFiles();
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), equalTo("Cannot expand TAR '" + tarFile + "' as it is not a file."));
+        }
+    }
+
+    @Test
+    public void wrapsFailureToUntarFile() {
+        expandDir.write("not a directory");
+        tarFile.write("not a tar file");
+
+        try {
+            tree.getFiles();
+            fail();
+        } catch (GradleException e) {
+            assertThat(e.getMessage(), equalTo("Could not expand TAR '" + tarFile + "'."));
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitorTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitorTest.java
new file mode 100644
index 0000000..5064a61
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/archive/ZipCopySpecVisitorTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.archive;
+
+import org.apache.commons.io.IOUtils;
+import org.gradle.api.GradleException;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.internal.file.copy.ArchiveCopyAction;
+import org.gradle.api.internal.file.copy.ReadableCopySpec;
+import org.gradle.util.TestFile;
+import org.gradle.util.TemporaryFolder;
+import org.hamcrest.Description;
+import static org.hamcrest.Matchers.*;
+import org.jmock.Expectations;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+import java.io.OutputStream;
+
+ at RunWith(JMock.class)
+public class ZipCopySpecVisitorTest {
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final ArchiveCopyAction copyAction = context.mock(ArchiveCopyAction.class);
+    private final ReadableCopySpec copySpec = context.mock(ReadableCopySpec.class);
+    private final ZipCopySpecVisitor visitor = new ZipCopySpecVisitor();
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations(){{
+            allowing(copySpec).getFileMode();
+            will(returnValue(1));
+            allowing(copySpec).getDirMode();
+            will(returnValue(2));
+        }});
+    }
+
+    @Test
+    public void createsZipFile() {
+        final TestFile zipFile = tmpDir.getDir().file("test.zip");
+
+        context.checking(new Expectations(){{
+            allowing(copyAction).getArchivePath();
+            will(returnValue(zipFile));
+        }});
+
+        visitor.startVisit(copyAction);
+        visitor.visitSpec(copySpec);
+
+        visitor.visitDir(dir("dir"));
+        visitor.visitFile(file("dir/file1"));
+        visitor.visitFile(file("file2"));
+
+        visitor.endVisit();
+
+        TestFile expandDir = tmpDir.getDir().file("expanded");
+        zipFile.unzipTo(expandDir);
+        expandDir.file("dir/file1").assertContents(equalTo("contents of dir/file1"));
+        expandDir.file("file2").assertContents(equalTo("contents of file2"));
+    }
+
+    @Test
+    public void wrapsFailureToOpenOutputFile() {
+        final TestFile zipFile = tmpDir.createDir("test.zip");
+
+        context.checking(new Expectations(){{
+            allowing(copyAction).getArchivePath();
+            will(returnValue(zipFile));
+        }});
+
+        try {
+            visitor.startVisit(copyAction);
+            fail();
+        } catch (GradleException e) {
+            assertThat(e.getMessage(), equalTo(String.format("Could not create ZIP '%s'.", zipFile)));
+        }
+    }
+
+    @Test
+    public void wrapsFailureToAddElement() {
+        final TestFile zipFile = tmpDir.getDir().file("test.zip");
+
+        context.checking(new Expectations(){{
+            allowing(copyAction).getArchivePath();
+            will(returnValue(zipFile));
+        }});
+
+        visitor.startVisit(copyAction);
+        visitor.visitSpec(copySpec);
+
+        Throwable failure = new RuntimeException("broken");
+        try {
+            visitor.visitFile(brokenFile("dir/file1", failure));
+            fail();
+        } catch (GradleException e) {
+            assertThat(e.getMessage(), equalTo(String.format("Could not add [dir/file1] to ZIP '%s'.", zipFile)));
+            assertThat(e.getCause(), sameInstance(failure));
+        }
+    }
+
+    private FileVisitDetails file(final String path) {
+        final FileVisitDetails details = context.mock(FileVisitDetails.class, path);
+
+        context.checking(new Expectations() {{
+            allowing(details).getRelativePath();
+            will(returnValue(RelativePath.parse(true, path)));
+
+            allowing(details).getLastModified();
+            will(returnValue(1000L));
+
+            allowing(details).copyTo(with(notNullValue(OutputStream.class)));
+            will(new Action() {
+                public void describeTo(Description description) {
+                    description.appendText("write content");
+                }
+
+                public Object invoke(Invocation invocation) throws Throwable {
+                    IOUtils.write(String.format("contents of %s", path), (OutputStream) invocation.getParameter(0));
+                    return null;
+                }
+            });
+        }});
+
+        return details;
+    }
+
+    private FileVisitDetails dir(final String path) {
+        final FileVisitDetails details = context.mock(FileVisitDetails.class, path);
+
+        context.checking(new Expectations() {{
+            allowing(details).getRelativePath();
+            will(returnValue(RelativePath.parse(false, path)));
+
+            allowing(details).getLastModified();
+            will(returnValue(1000L));
+        }});
+
+        return details;
+    }
+
+    private FileVisitDetails brokenFile(final String path, final Throwable failure) {
+        final FileVisitDetails details = context.mock(FileVisitDetails.class, String.format("[%s]", path));
+
+        context.checking(new Expectations() {{
+            allowing(details).getRelativePath();
+            will(returnValue(RelativePath.parse(true, path)));
+
+            allowing(details).getLastModified();
+            will(returnValue(1000L));
+
+            allowing(details).copyTo(with(notNullValue(OutputStream.class)));
+            will(new Action() {
+                public void describeTo(Description description) {
+                    description.appendText("write content");
+                }
+
+                public Object invoke(Invocation invocation) throws Throwable {
+                    failure.fillInStackTrace();
+                    throw failure;
+                }
+            });
+        }});
+
+        return details;
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/archive/ZipFileTreeTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/archive/ZipFileTreeTest.java
new file mode 100644
index 0000000..3b3f6ba
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/archive/ZipFileTreeTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.file.archive;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import org.gradle.util.TestFile;
+import org.junit.Test;
+import org.junit.Rule;
+import org.gradle.util.TemporaryFolder;
+import static org.gradle.util.WrapUtil.*;
+import static org.gradle.api.tasks.AntBuilderAwareUtil.*;
+import static org.gradle.api.file.FileVisitorUtil.*;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.GradleException;
+
+import static java.util.Collections.*;
+
+public class ZipFileTreeTest {
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    private final TestFile zipFile = tmpDir.getDir().file("test.zip");
+    private final TestFile rootDir = tmpDir.getDir().file("root");
+    private final TestFile expandDir = tmpDir.getDir().file("tmp");
+    private final ZipFileTree tree = new ZipFileTree(zipFile, expandDir);
+
+    @Test
+    public void displayName() {
+        assertThat(tree.getDisplayName(), equalTo("ZIP '" + zipFile + "'"));
+    }
+
+    @Test
+    public void visitsContentsOfZipFile() {
+        rootDir.file("subdir/file1.txt").write("content");
+        rootDir.file("subdir2/file2.txt").write("content");
+        rootDir.zipTo(zipFile);
+
+        assertVisits(tree, toList("subdir/file1.txt", "subdir2/file2.txt"), toList("subdir", "subdir2"));
+        assertSetContainsForAllTypes(tree, toList("subdir/file1.txt", "subdir2/file2.txt"));
+    }
+
+    @Test
+    public void canStopVisitingFiles() {
+        rootDir.file("subdir/file1.txt").write("content");
+        rootDir.file("subdir/other/file2.txt").write("content");
+        rootDir.zipTo(zipFile);
+
+        assertCanStopVisiting(tree);
+    }
+
+    @Test
+    public void isEmptyWhenZipFileDoesNotExist() {
+        assertVisits(tree, EMPTY_LIST, EMPTY_LIST);
+        assertSetContainsForAllTypes(tree, EMPTY_LIST);
+    }
+
+    @Test
+    public void failsWhenZipFileIsADirectory() {
+        zipFile.createDir();
+
+        try {
+            tree.getFiles();
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), equalTo("Cannot expand ZIP '" + zipFile + "' as it is not a file."));
+        }
+    }
+
+    @Test
+    public void wrapsFailureToUnzipFile() {
+        zipFile.write("not a zip file");
+
+        try {
+            tree.getFiles();
+            fail();
+        } catch (GradleException e) {
+            assertThat(e.getMessage(), equalTo("Could not expand ZIP '" + zipFile + "'."));
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/CopyActionImplTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/CopyActionImplTest.groovy
new file mode 100644
index 0000000..d2024f5
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/CopyActionImplTest.groovy
@@ -0,0 +1,95 @@
+/*
+ * 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.copy
+
+import org.gradle.api.file.FileTree
+import org.gradle.api.internal.file.FileResolver
+import spock.lang.Specification
+
+public class CopyActionImplTest extends Specification {
+    FileCopySpecVisitor visitor = Mock()
+    FileResolver resolver = Mock()
+    FileTree sourceFileTree = Mock()
+    CopyActionImpl copyAction = new CopyActionImpl(resolver, visitor)
+
+    def delegatesToMainSpecRootSpec() {
+        when:
+        copyAction.include 'a'
+
+        then:
+        copyAction.includes == ['a'] as Set
+        copyAction.mainSpec.includes == ['a'] as Set
+        copyAction.rootSpec.includes == [] as Set
+        copyAction.rootSpec.childSpecs.contains(copyAction.mainSpec)
+    }
+
+    def didWorkDelegatesToVisitor() {
+        when:
+        def didWork = copyAction.didWork
+
+        then:
+        1 * visitor.didWork >> true
+        didWork
+    }
+
+    def visitsAndCopiesEachSpec() {
+        FileTree source = Mock()
+        _ * source.matching(_) >> source
+
+        copyAction.from('source1')
+        def child = copyAction.from('source2') { }
+
+        when:
+        copyAction.execute()
+
+        then:
+        1 * visitor.startVisit(copyAction)
+        1 * visitor.visitSpec(copyAction.rootSpec)
+        1 * resolver.resolveFilesAsTree([[] as Set] as Object[]) >> source
+        1 * visitor.visitSpec(copyAction.mainSpec)
+        1 * resolver.resolveFilesAsTree([['source1'] as Set] as Object[]) >> source
+        1 * visitor.visitSpec(child)
+        1 * resolver.resolveFilesAsTree([['source2'] as Set] as Object[]) >> source
+        1 * visitor.endVisit()
+        0 * resolver._
+        0 * visitor._
+    }
+   
+    def allSourceIncludesSourceFromAllSpecs() {
+        FileTree mainSource = Mock()
+        _ * mainSource.matching(_) >> mainSource
+        FileTree rootSource = Mock()
+        _ * rootSource.matching(_) >> rootSource
+        FileTree childSource = Mock()
+        _ * childSource.matching(_) >> childSource
+        FileTree allSource = Mock()
+
+        copyAction.from('source1')
+        copyAction.from('source2') { }
+
+        when:
+        def source = copyAction.allSource
+
+        then:
+        source == allSource
+        1 * resolver.resolveFilesAsTree([[] as Set] as Object[]) >> rootSource
+        1 * resolver.resolveFilesAsTree([['source1'] as Set] as Object[]) >> mainSource
+        1 * resolver.resolveFilesAsTree([['source2'] as Set] as Object[]) >> childSource
+        1 * resolver.resolveFilesAsTree([[rootSource, mainSource, childSource]] as Object[]) >> allSource
+        0 * resolver._
+        0 * visitor._
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecImplTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecImplTest.groovy
new file mode 100644
index 0000000..e2abc16
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/CopySpecImplTest.groovy
@@ -0,0 +1,333 @@
+/*
+ * 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.copy
+
+import org.apache.tools.ant.filters.HeadFilter
+import org.apache.tools.ant.filters.StripJavaComments
+import org.gradle.api.file.RelativePath
+import org.gradle.util.TestFile
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.TemporaryFolder
+import org.jmock.integration.junit4.JMock
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.apache.tools.zip.UnixStat
+import org.gradle.api.specs.Spec
+import org.gradle.api.tasks.util.PatternSet
+import org.gradle.api.file.FileTree
+import org.gradle.api.Action
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.file.CopySpec
+
+ at RunWith(JMock)
+public class CopySpecImplTest {
+
+    @Rule public TemporaryFolder testDir = new TemporaryFolder();
+    private TestFile baseFile = testDir.dir
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery();
+    private final FileResolver fileResolver = context.mock(FileResolver);
+    private final CopySpecImpl spec = new CopySpecImpl(fileResolver)
+
+    private List<String> getTestSourceFileNames() {
+        ['first', 'second']
+    }
+
+    private List<File> getAbsoluteTestSources() {
+        testSourceFileNames.collect { new File(baseFile, it) }
+    }
+
+    @Test public void testAbsoluteFromList() {
+        List<File> sources = getAbsoluteTestSources();
+        spec.from(sources);
+        assertEquals([sources], spec.sourcePaths as List);
+    }
+
+    @Test public void testFromArray() {
+        List<File> sources = getAbsoluteTestSources();
+        spec.from(sources as File[]);
+        assertEquals(sources, spec.sourcePaths as List);
+    }
+
+    @Test public void testSourceWithClosure() {
+        CopySpecImpl child = spec.from('source') {
+        }
+
+        assertThat(child, not(sameInstance(spec)))
+        assertEquals(['source'], child.sourcePaths as List);
+    }
+
+    @Test public void testMultipleSourcesWithClosure() {
+        CopySpecImpl child = spec.from(['source1', 'source2']) {
+        }
+
+        assertThat(child, not(sameInstance(spec)))
+        assertEquals(['source1', 'source2'], child.sourcePaths.flatten() as List);
+    }
+
+    @Test public void testDefaultDestinationPathForRootSpec() {
+        assertThat(spec.destPath, equalTo(new RelativePath(false)))
+    }
+
+    @Test public void testInto() {
+        spec.into 'spec'
+        assertThat(spec.destPath, equalTo(new RelativePath(false, 'spec')))
+        spec.into '/'
+        assertThat(spec.destPath, equalTo(new RelativePath(false)))
+    }
+
+    @Test public void testWithSpec() {
+        CopySpecImpl other1 = new CopySpecImpl(fileResolver)
+        CopySpecImpl other2 = new CopySpecImpl(fileResolver)
+
+        spec.with other1, other2
+        assertTrue(spec.sourcePaths.empty)
+        assertThat(spec.childSpecs.size(), equalTo(2))
+    }
+
+    @Test public void testWithSpecSource() {
+        CopyActionImpl source = new CopyActionImpl(fileResolver, null)
+
+        spec.with source
+        assertTrue(spec.sourcePaths.empty)
+        assertThat(spec.childSpecs.size(), equalTo(1))
+    }
+
+    @Test public void testWithSpecInheritsDestinationPathFromParent() {
+        CopySpecImpl other = new CopySpecImpl(fileResolver)
+        other.into 'other'
+
+        spec.into 'spec'
+        spec.with other
+
+        ReadableCopySpec child = spec.childSpecs[0]
+        assertThat(child.destPath, equalTo(new RelativePath(false, 'spec', 'other')))
+    }
+
+    @Test public void testDestinationWithClosure() {
+        CopySpecImpl child = spec.into('target') {
+        }
+
+        assertThat(child, not(sameInstance(spec)))
+        assertThat(child.destPath, equalTo(new RelativePath(false, 'target')))
+    }
+
+    @Test public void testGetAllSpecsReturnsBreadthwiseTraverseOfSpecs() {
+        CopySpecImpl child = spec.into('somedir') { }
+        CopySpecImpl grandchild = child.into('somedir') { }
+        CopySpecImpl child2 = spec.into('somedir') { }
+
+        assertThat(spec.allSpecs, equalTo([spec, child, grandchild, child2]))
+    }
+
+    @Test public void testRootSpecHasRootPathAsDestination() {
+        assertThat(spec.destPath, equalTo(new RelativePath(false)))
+    }
+
+    @Test public void testChildSpecResolvesIntoArgRelativeToParentDestinationDir() {
+        CopySpecImpl child = spec.from('somedir') { into 'child' }
+        assertThat(child.destPath, equalTo(new RelativePath(false, 'child')))
+
+        CopySpecImpl grandchild = child.from('somedir') { into 'grandchild'}
+        assertThat(grandchild.destPath, equalTo(new RelativePath(false, 'child', 'grandchild')))
+
+        grandchild.into '/grandchild'
+        assertThat(grandchild.destPath, equalTo(new RelativePath(false, 'grandchild')))
+    }
+
+    @Test public void testChildSpecUsesParentDestinationPathAsDefault() {
+        CopySpecImpl child = spec.from('somedir') { }
+        assertThat(child.destPath, equalTo(spec.destPath))
+
+        child.into 'child'
+
+        CopySpecImpl grandchild = child.from('somedir') { }
+        assertThat(grandchild.destPath, equalTo(child.destPath))
+    }
+
+    @Test public void testSourceIsFilteredTreeOfSources() {
+        spec.from 'a'
+        spec.from 'b'
+
+        def filteredTree = context.mock(FileTree, 'filtered')
+
+        context.checking {
+            one(fileResolver).resolveFilesAsTree(['a', 'b'] as Set)
+            def tree = context.mock(FileTree, 'source')
+            will(returnValue(tree))
+            one(tree).matching(withParam(equalTo(spec.patternSet)))
+            will(returnValue(filteredTree))
+        }
+
+        assertThat(spec.source, sameInstance(filteredTree))
+    }
+
+    @Test public void testChildUsesPatternsFromParent() {
+        CopySpecImpl child = spec.from('dir') {}
+        Spec specInclude = [:] as Spec
+        Spec specExclude = [:] as Spec
+        Spec childInclude = [:] as Spec
+        Spec childExclude = [:] as Spec
+
+        spec.include('parent-include')
+        spec.exclude('parent-exclude')
+        spec.include(specInclude)
+        spec.exclude(specExclude)
+        child.include('child-include')
+        child.exclude('child-exclude')
+        child.include(childInclude)
+        child.exclude(childExclude)
+
+        PatternSet patterns = child.patternSet
+        assertThat(patterns.includes, equalTo(['parent-include', 'child-include'] as Set))
+        assertThat(patterns.excludes, equalTo(['parent-exclude', 'child-exclude'] as Set))
+        assertThat(patterns.includeSpecs, equalTo([specInclude, childInclude] as Set))
+        assertThat(patterns.excludeSpecs, equalTo([specExclude, childExclude] as Set))
+    }
+
+    @Test public void testChildUsesParentPatternsAsDefault() {
+        CopySpecImpl child = spec.from('dir') {}
+        Spec specInclude = [:] as Spec
+        Spec specExclude = [:] as Spec
+
+        spec.include('parent-include')
+        spec.exclude('parent-exclude')
+        spec.include(specInclude)
+        spec.exclude(specExclude)
+
+        PatternSet patterns = child.patternSet
+        assertThat(patterns.includes, equalTo(['parent-include'] as Set))
+        assertThat(patterns.excludes, equalTo(['parent-exclude'] as Set))
+        assertThat(patterns.includeSpecs, equalTo([specInclude] as Set))
+        assertThat(patterns.excludeSpecs, equalTo([specExclude] as Set))
+    }
+
+    @Test public void testChildUsesCaseSensitiveFlagFromParentAsDefault() {
+        CopySpecImpl child = spec.from('dir') {}
+        assertTrue(child.caseSensitive)
+        assertTrue(child.patternSet.caseSensitive)
+
+        spec.caseSensitive = false
+        assertFalse(child.caseSensitive)
+        assertFalse(child.patternSet.caseSensitive)
+
+        child.caseSensitive = true
+        assertTrue(child.caseSensitive)
+        assertTrue(child.patternSet.caseSensitive)
+    }
+
+    @Test public void testNoArgFilter() {
+        spec.filter(StripJavaComments)
+        assertThat(spec.allCopyActions.size(), equalTo(1))
+    }
+
+    @Test public void testArgFilter() {
+        spec.filter(HeadFilter, lines: 15, skip: 2)
+        assertThat(spec.allCopyActions.size(), equalTo(1))
+    }
+
+    @Test public void testExpand() {
+        spec.expand(version: '1.2', skip: 2)
+        assertThat(spec.allCopyActions.size(), equalTo(1))
+    }
+
+    @Test public void testTwoFilters() {
+        spec.filter(StripJavaComments)
+        spec.filter(HeadFilter, lines: 15, skip: 2)
+
+        assertThat(spec.allCopyActions.size(), equalTo(2))
+    }
+
+    @Test public void testAddsStringNameTransformerToActions() {
+        spec.rename("regexp", "replacement")
+
+        assertThat(spec.allCopyActions.size(), equalTo(1))
+        assertThat(spec.allCopyActions[0], instanceOf(RenamingCopyAction))
+        assertThat(spec.allCopyActions[0].transformer, instanceOf(RegExpNameMapper))
+    }
+
+    @Test public void testAddsPatternNameTransformerToActions() {
+        spec.rename(/regexp/, "replacement")
+
+        assertThat(spec.allCopyActions.size(), equalTo(1))
+        assertThat(spec.allCopyActions[0], instanceOf(RenamingCopyAction))
+        assertThat(spec.allCopyActions[0].transformer, instanceOf(RegExpNameMapper))
+    }
+
+    @Test public void testAddsClosureToActions() {
+        spec.rename {}
+
+        assertThat(spec.allCopyActions.size(), equalTo(1))
+        assertThat(spec.allCopyActions[0], instanceOf(RenamingCopyAction))
+    }
+
+    @Test public void testAddAction() {
+        def action = context.mock(Action)
+        spec.eachFile(action)
+
+        assertThat(spec.allCopyActions, equalTo([action]))
+    }
+
+    @Test public void testAddActionAsClosure() {
+        def action = {}
+        spec.eachFile(action)
+
+        assertThat(spec.allCopyActions.size(), equalTo(1))
+    }
+
+    @Test public void testSpecInheritsActionsFromParent() {
+        Action parentAction = context.mock(Action, 'parent')
+        Action childAction = context.mock(Action, 'child')
+
+        spec.eachFile parentAction
+        CopySpecImpl childSpec = spec.from('src') {
+            eachFile childAction
+        }
+
+        assertThat(childSpec.allCopyActions, equalTo([parentAction, childAction]))
+    }
+
+    @Test public void testDefaultPermissions() {
+        org.junit.Assert.assertEquals(UnixStat.DEFAULT_FILE_PERM, spec.fileMode)
+        org.junit.Assert.assertEquals(UnixStat.DEFAULT_DIR_PERM, spec.dirMode)
+    }
+
+    @Test public void testInheritsPermissionsFromParent() {
+        spec.fileMode = 0x1
+        spec.dirMode = 0x2
+
+        CopySpecImpl child = spec.from('src') { }
+        org.junit.Assert.assertEquals(0x1, child.fileMode)
+        org.junit.Assert.assertEquals(0x2, child.dirMode)
+    }
+
+    @Test public void testHasNoSourceByDefault() {
+        assertFalse(spec.hasSource())
+    }
+
+    @Test public void testHasSourceWhenSpecHasSource() {
+        spec.from 'source'
+        assertTrue(spec.hasSource())
+    }
+
+    @Test public void testHasSourceWhenChildSpecHasSource() {
+        spec.from('source') {}
+        assertTrue(spec.hasSource())
+    }
+}
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/DeleteActionImplTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/DeleteActionImplTest.groovy
new file mode 100644
index 0000000..7a37201
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/DeleteActionImplTest.groovy
@@ -0,0 +1,98 @@
+/*
+ * 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.copy
+
+import org.gradle.api.internal.file.BaseDirConverter
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+class DeleteActionImplTest extends Specification {
+    @Rule
+    TemporaryFolder tmpDir = new TemporaryFolder();
+
+    FileResolver fileResolver = new BaseDirConverter(tmpDir.getDir())
+    
+    DeleteActionImpl delete = new DeleteActionImpl(fileResolver);
+
+    def deletesDirectory() {
+        TestFile dir = tmpDir.getDir();
+        dir.file("somefile").createFile();
+
+        when:
+        boolean didWork = delete.delete(dir);
+
+        then:
+        dir.assertDoesNotExist();
+        didWork
+    }
+
+    def deletesFile() {
+        TestFile dir = tmpDir.getDir();
+        TestFile file = dir.file("somefile");
+        file.createFile();
+
+        when:
+        boolean didWork = delete.delete(file);
+
+        then:
+        file.assertDoesNotExist();
+        didWork
+    }
+
+    def deletesFileByPath() {
+        TestFile dir = tmpDir.getDir();
+        TestFile file = dir.file("somefile");
+        file.createFile();
+
+        when:
+        boolean didWork = delete.delete('somefile');
+
+        then:
+        file.assertDoesNotExist();
+        didWork
+    }
+
+    def deletesMultipleTargets() {
+        TestFile file = tmpDir.getDir().file("somefile").createFile();
+        TestFile dir = tmpDir.getDir().file("somedir").createDir();
+        dir.file("sub/child").createFile();
+
+        when:
+        boolean didWork = delete.delete(file, dir);
+
+        then:
+        file.assertDoesNotExist();
+        dir.assertDoesNotExist();
+        didWork
+    }
+
+    def didWorkIsFalseWhenNothingDeleted() {
+        TestFile dir = tmpDir.file("unknown");
+        dir.assertDoesNotExist();
+
+        when:
+        boolean didWork = delete.delete(dir);
+
+        then:
+        !didWork
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/FileCopyActionImplTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/FileCopyActionImplTest.java
new file mode 100644
index 0000000..afca533
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/FileCopyActionImplTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.file.copy;
+
+import org.gradle.api.internal.file.FileResolver;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertThat;
+
+ at RunWith(JMock.class)
+public class FileCopyActionImplTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final FileResolver fileResolver = context.mock(FileResolver.class);
+    private final FileCopyActionImpl spec = new FileCopyActionImpl(fileResolver, context.mock(CopySpecVisitor.class));
+
+    @Test public void testRootSpecResolvesItsIntoArgAsDestinationDir() {
+        final File file = new File("base dir");
+
+        spec.into("somedir");
+
+        context.checking(new Expectations() {{
+            allowing(fileResolver).resolve("somedir");
+            will(returnValue(file));
+        }});
+
+        assertThat(spec.getDestinationDir(), equalTo(file));
+    }
+
+    @Test public void testRootSpecHasNoDefaultDestinationDir() {
+        assertThat(spec.getDestinationDir(), nullValue());
+    }
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/FileCopySpecVisitorTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/FileCopySpecVisitorTest.java
new file mode 100644
index 0000000..83468cf
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/FileCopySpecVisitorTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.file.copy;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.RelativePath;
+import org.gradle.util.TestFile;
+import org.gradle.util.TemporaryFolder;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class FileCopySpecVisitorTest {
+    private File destDir;
+    private TestFile sourceDir;
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final FileCopySpecVisitor visitor = new FileCopySpecVisitor();
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    @Before
+    public void setUp() throws IOException {
+        destDir = tmpDir.getDir().file("dest");
+        sourceDir = tmpDir.getDir().file("src").createDir();
+    }
+
+    @Test
+    public void plainCopy() {
+        visitor.startVisit(action(destDir));
+
+        visitor.visitDir(file(new RelativePath(false)));
+
+        visitor.visitFile(file(new RelativePath(true, "rootfile.txt"), new File(destDir, "rootfile.txt")));
+
+        RelativePath subDirPath = new RelativePath(false, "subdir");
+        visitor.visitDir(file(subDirPath));
+
+        visitor.visitFile(file(new RelativePath(true, "subdir", "anotherfile.txt"), new File(destDir, "subdir/anotherfile.txt")));
+    }
+
+    @Test
+    public void testThrowsExceptionWhenNoDestinationSet() {
+        try {
+            visitor.startVisit(action(null));
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), equalTo("No copy destination directory has been specified, use 'into' to specify a target directory."));
+        }
+    }
+
+    private FileCopyAction action(final File destDir) {
+        final FileCopyAction action = context.mock(FileCopyAction.class);
+        context.checking(new Expectations(){{
+            allowing(action).getDestinationDir();
+            will(returnValue(destDir));
+        }});
+        return action;
+    }
+
+    private FileVisitDetails file(final RelativePath relativePath) {
+        final FileVisitDetails details = context.mock(FileVisitDetails.class, relativePath.getPathString());
+        context.checking(new Expectations(){{
+            allowing(details).getRelativePath();
+            will(returnValue(relativePath));
+        }});
+        return details;
+    }
+
+    private FileVisitDetails file(final RelativePath relativePath, final File targetFile) {
+        final FileVisitDetails details = file(relativePath);
+        context.checking(new Expectations(){{
+            one(details).copyTo(targetFile);
+        }});
+        return details;
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/FilterChainTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/FilterChainTest.java
new file mode 100644
index 0000000..019ec32
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/FilterChainTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.file.copy;
+
+import org.apache.commons.io.IOUtils;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.WrapUtil;
+import org.junit.Test;
+
+import java.io.FilterReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+public class FilterChainTest {
+    private final FilterChain filterChain = new FilterChain();
+    private final Reader originalReader = new StringReader("string");
+
+    @Test
+    public void usesOriginalReaderByDefault() {
+        assertThat(filterChain.transform(originalReader), sameInstance(originalReader));
+    }
+
+    @Test
+    public void canAddFilterReaderToEndOfChain() {
+        filterChain.add(TestFilterReader.class);
+        Reader transformedReader = filterChain.transform(originalReader);
+        assertThat(transformedReader, instanceOf(TestFilterReader.class));
+        TestFilterReader reader = (TestFilterReader) transformedReader;
+        assertThat(reader.getIn(), sameInstance(originalReader));
+    }
+
+    @Test
+    public void canAddFilterReaderWithParametersToEndOfChain() {
+        filterChain.add(TestFilterReader.class, toMap("property", "value"));
+        Reader transformedReader = filterChain.transform(originalReader);
+        assertThat(transformedReader, instanceOf(TestFilterReader.class));
+        TestFilterReader reader = (TestFilterReader) transformedReader;
+        assertThat(reader.getIn(), sameInstance(originalReader));
+        assertThat(reader.property, equalTo("value"));
+    }
+
+    @Test
+    public void canAddLineFilterReaderToEndOfChain() {
+        filterChain.add(HelperUtil.TEST_CLOSURE);
+        Reader transformedReader = filterChain.transform(originalReader);
+        assertThat(transformedReader, instanceOf(LineFilter.class));
+    }
+
+    @Test
+    public void canAddExpandFilterToEndOfChain() throws IOException {
+        filterChain.expand(WrapUtil.toMap("prop", 1));
+        Reader transformedReader = filterChain.transform(new StringReader("[$prop][${prop+1}][<%= prop+2 %>]"));
+        assertThat(IOUtils.toString(transformedReader), equalTo("[1][2][3]"));
+    }
+
+    public static class TestFilterReader extends FilterReader {
+        String property;
+
+        public TestFilterReader(Reader reader) {
+            super(reader);
+        }
+
+        public Reader getIn() {
+            return in;
+        }
+
+        public void setProperty(String property) {
+            this.property = property;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/LineFilterTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/LineFilterTest.groovy
new file mode 100644
index 0000000..8f3bc87
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/LineFilterTest.groovy
@@ -0,0 +1,74 @@
+/*
+ * 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.file.copy
+
+import org.junit.Test
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+public class LineFilterTest {
+    @Test public void testEmptyInput() {
+        Reader input = new StringReader("");
+        int lineCount = 1;
+        LineFilter filter = new LineFilter(input, { "${lineCount++} - $it" as String })
+
+        assertThat(filter.text, equalTo(""))
+    }
+
+    @Test public void testEmptyLinesWithTrailingEOL() {
+        Reader input = new StringReader("\n\n");
+        int lineCount = 1;
+        LineFilter filter = new LineFilter(input, { "${lineCount++} - $it" as String })
+
+        assertThat(filter.text, equalTo(lines("1 - ", "2 - ", "")))
+    }
+    
+    @Test public void testSingleLine() {
+        Reader input = new StringReader("one");
+        int lineCount = 1;
+        LineFilter filter = new LineFilter(input, { "${lineCount++} - $it" as String })
+
+        assertThat(filter.text, equalTo("1 - one"))
+    }
+    
+    @Test public void testCRLFWithTrailingEOL() {
+        Reader input = new StringReader("one\r\ntwo\r\nthree\r\n");
+        int lineCount = 1;
+        LineFilter filter = new LineFilter(input,  { "${lineCount++} - $it" as String })
+
+        assertThat(filter.text, equalTo(lines("1 - one", "2 - two", "3 - three", "")))
+    }
+
+    @Test public void testLfWithNoTrailingEOL() {
+        Reader input = new StringReader("one\ntwo\nthree");
+        int lineCount = 1;
+        LineFilter filter = new LineFilter(input,  { "${lineCount++} - $it" as String })
+
+        assertThat(filter.text, equalTo(lines("1 - one", "2 - two", "3 - three")))
+    }
+    
+    @Test public void testCRWithNoTrailingEOL() {
+        Reader input = new StringReader("one\rtwo\rthree");
+        int lineCount = 1;
+        LineFilter filter = new LineFilter(input, { "${lineCount++} - $it" as String })
+
+        assertThat(filter.text, equalTo(lines("1 - one", "2 - two", "3 - three")))
+    }
+
+    private String lines(String ... lines) {
+        return (lines as List).join(System.getProperty('line.separator'))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitorTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitorTest.java
new file mode 100644
index 0000000..a156048
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/MappingCopySpecVisitorTest.java
@@ -0,0 +1,318 @@
+/*
+ * 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.file.copy;
+
+import org.gradle.api.Action;
+import org.gradle.api.file.CopyAction;
+import org.gradle.api.file.FileCopyDetails;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.RelativePath;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.TemporaryFolder;
+import org.gradle.util.TestFile;
+import org.hamcrest.Description;
+import org.jmock.Expectations;
+import org.jmock.Sequence;
+import org.jmock.api.Invocation;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+
+import static org.gradle.util.Matchers.*;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at 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);
+
+    @Test
+    public void delegatesStartAndEndVisitMethods() {
+        final CopyAction action = context.mock(CopyAction.class);
+
+        context.checking(new Expectations() {{
+            one(delegate).startVisit(action);
+            one(delegate).endVisit();
+        }});
+
+        visitor.startVisit(action);
+        visitor.endVisit();
+    }
+
+    @Test
+    public void delegatesDidWork() {
+        context.checking(new Expectations() {{
+            allowing(delegate).getDidWork();
+            will(onConsecutiveCalls(returnValue(true), returnValue(false)));
+        }});
+
+        assertTrue(visitor.getDidWork());
+        assertFalse(visitor.getDidWork());
+    }
+
+    @Test
+    public void visitFileInvokesEachCopyAction() {
+        final Action<FileCopyDetails> action1 = context.mock(Action.class, "action1");
+        final Action<FileCopyDetails> action2 = context.mock(Action.class, "action2");
+        final Collector<FileCopyDetails> collectDetails1 = collector();
+        final Collector<Object> collectDetails2 = collector();
+        final Collector<Object> collectDetails3 = collector();
+
+        context.checking(new Expectations(){{
+            Sequence seq = context.sequence("seq");
+            one(delegate).visitSpec(spec);
+            inSequence(seq);
+
+            allowing(spec).getAllCopyActions();
+            will(returnValue(toList(action1, action2)));
+
+            one(action1).execute(with(notNullValue(FileCopyDetails.class)));
+            inSequence(seq);
+            will(collectTo(collectDetails1));
+
+            one(action2).execute(with(notNullValue(FileCopyDetails.class)));
+            inSequence(seq);
+            will(collectTo(collectDetails2));
+
+            one(delegate).visitFile(with(not(sameInstance(details))));
+            inSequence(seq);
+            will(collectTo(collectDetails3));
+        }});
+
+        visitor.visitSpec(spec);
+        visitor.visitFile(details);
+
+        assertThat(collectDetails1.get(), sameInstance(collectDetails2.get()));
+        assertThat(collectDetails1.get(), sameInstance(collectDetails3.get()));
+    }
+
+    @Test
+    public void initialRelativePathForFileIsSpecPathPlusFilePath() {
+        FileCopyDetails copyDetails = expectActionExecutedWhenFileVisited();
+
+        context.checking(new Expectations(){{
+            allowing(spec).getDestPath();
+            will(returnValue(new RelativePath(false, "spec")));
+            allowing(details).getRelativePath();
+            will(returnValue(new RelativePath(true, "file")));
+        }});
+
+        assertThat(copyDetails.getRelativePath(), equalTo(new RelativePath(true, "spec", "file")));
+    }
+    
+    @Test
+    public void relativePathForDirIsSpecPathPlusFilePath() {
+        FileVisitDetails visitDetails = expectSpecAndDirVisited();
+
+        context.checking(new Expectations(){{
+            allowing(spec).getDestPath();
+            will(returnValue(new RelativePath(false, "spec")));
+            allowing(details).getRelativePath();
+            will(returnValue(new RelativePath(false, "dir")));
+        }});
+
+        assertThat(visitDetails.getRelativePath(), equalTo(new RelativePath(false, "spec", "dir")));
+    }
+
+    @Test
+    public void copyActionCanChangeFileDestinationPath() {
+        FileCopyDetails copyDetails = expectActionExecutedWhenFileVisited();
+
+        RelativePath newPath = new RelativePath(true, "new");
+        copyDetails.setRelativePath(newPath);
+        assertThat(copyDetails.getRelativePath(), equalTo(newPath));
+
+        copyDetails.setPath("/a/b");
+        assertThat(copyDetails.getRelativePath(), equalTo(new RelativePath(true, "a", "b")));
+
+        copyDetails.setName("new name");
+        assertThat(copyDetails.getRelativePath(), equalTo(new RelativePath(true, "a", "new name")));
+    }
+
+    @Test
+    public void copyActionCanExcludeFile() {
+        final Action<FileCopyDetails> action1 = context.mock(Action.class, "action1");
+        final Action<FileCopyDetails> action2 = context.mock(Action.class, "action2");
+
+        context.checking(new Expectations(){{
+            Sequence seq = context.sequence("seq");
+            one(delegate).visitSpec(spec);
+            inSequence(seq);
+
+            allowing(spec).getAllCopyActions();
+            will(returnValue(toList(action1, action2)));
+
+            one(action1).execute(with(notNullValue(FileCopyDetails.class)));
+            inSequence(seq);
+            will(excludeFile());
+        }});
+
+        visitor.visitSpec(spec);
+        visitor.visitFile(details);
+    }
+
+    @Test
+    public void copyActionCanFilterContentWhenFileIsCopiedToStream() {
+        final FileCopyDetails mappedDetails = expectActionExecutedWhenFileVisited();
+
+        context.checking(new Expectations() {{
+            one(details).open();
+            will(returnValue(new ByteArrayInputStream("content".getBytes())));
+        }});
+
+        mappedDetails.filter(HelperUtil.toClosure("{ 'PREFIX: ' + it } "));
+
+        ByteArrayOutputStream outstr = new ByteArrayOutputStream();
+        mappedDetails.copyTo(outstr);
+        assertThat(new String(outstr.toByteArray()), equalTo("PREFIX: content"));
+    }
+
+    @Test
+    public void copyActionCanFilterContentWhenFileIsCopiedToFile() {
+        final FileCopyDetails mappedDetails = expectActionExecutedWhenFileVisited();
+
+        context.checking(new Expectations() {{
+            one(details).open();
+            will(returnValue(new ByteArrayInputStream("content".getBytes())));
+            one(details).isDirectory();
+            will(returnValue(false));
+            one(details).getLastModified();
+            will(returnValue(90L));
+        }});
+
+        mappedDetails.filter(HelperUtil.toClosure("{ 'PREFIX: ' + it } "));
+
+        TestFile destDir = tmpDir.getDir().file("test.txt");
+        mappedDetails.copyTo(destDir);
+        destDir.assertContents(equalTo("PREFIX: content"));
+    }
+
+    @Test
+    public void getSizeReturnsSizeOfFilteredContent() {
+        final FileCopyDetails mappedDetails = expectActionExecutedWhenFileVisited();
+
+        context.checking(new Expectations() {{
+            one(details).open();
+            will(returnValue(new ByteArrayInputStream("content".getBytes())));
+        }});
+
+        mappedDetails.filter(HelperUtil.toClosure("{ 'PREFIX: ' + it } "));
+
+        assertThat(mappedDetails.getSize(), equalTo(15L));
+    }
+
+    @Test
+    public void wrappedFileElementDelegatesToSourceForRemainingMethods() {
+        final FileVisitDetails mappedDetails = expectSpecAndFileVisited();
+        final File file = new File("file");
+
+        context.checking(new Expectations() {{
+            one(details).getFile();
+            will(returnValue(file));
+        }});
+
+        assertThat(mappedDetails.getFile(), sameInstance(file));
+    }
+
+    private FileVisitDetails expectSpecAndFileVisited() {
+        final Collector<FileVisitDetails> collector = collector();
+
+        context.checking(new Expectations() {{
+            one(delegate).visitSpec(spec);
+
+            one(spec).getAllCopyActions();
+            will(returnValue(toList()));
+
+            one(delegate).visitFile(with(not(sameInstance(details))));
+            will(collectTo(collector));
+        }});
+
+        visitor.visitSpec(spec);
+        visitor.visitFile(details);
+        return collector.get();
+    }
+
+    private FileCopyDetails expectActionExecutedWhenFileVisited() {
+        final Collector<FileCopyDetails> collectDetails = collector();
+        final Action<FileCopyDetails> action = context.mock(Action.class, "action1");
+
+        context.checking(new Expectations(){{
+            Sequence seq = context.sequence("seq");
+            one(delegate).visitSpec(spec);
+            inSequence(seq);
+
+            allowing(spec).getAllCopyActions();
+            will(returnValue(toList(action)));
+
+            one(action).execute(with(notNullValue(FileCopyDetails.class)));
+            inSequence(seq);
+            will(collectTo(collectDetails));
+
+            one(delegate).visitFile(with(not(sameInstance(details))));
+            inSequence(seq);
+        }});
+
+        visitor.visitSpec(spec);
+        visitor.visitFile(details);
+
+        FileCopyDetails copyDetails = collectDetails.get();
+        return copyDetails;
+    }
+
+    private FileVisitDetails expectSpecAndDirVisited() {
+        final Collector<FileVisitDetails> collector = collector();
+
+        context.checking(new Expectations() {{
+            one(delegate).visitSpec(spec);
+            one(delegate).visitDir(with(not(sameInstance(details))));
+
+            will(collectTo(collector));
+        }});
+
+        visitor.visitSpec(spec);
+        visitor.visitDir(details);
+
+        return collector.get();
+    }
+
+    private org.jmock.api.Action excludeFile() {
+        return new org.jmock.api.Action() {
+            public void describeTo(Description description) {
+                description.appendText("exclude file");
+            }
+
+            public Object invoke(Invocation invocation) throws Throwable {
+                FileCopyDetails details = (FileCopyDetails) invocation.getParameter(0);
+                details.exclude();
+                return null;
+            }
+        };
+    }
+    
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/NormalizingCopySpecVisitorTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/NormalizingCopySpecVisitorTest.java
new file mode 100644
index 0000000..d6db593
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/NormalizingCopySpecVisitorTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.file.copy;
+
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.RelativePath;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.integration.junit4.JMock;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+ at RunWith(JMock.class)
+public class NormalizingCopySpecVisitorTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final CopySpecVisitor delegate = context.mock(CopySpecVisitor.class);
+    private final NormalizingCopySpecVisitor visitor = new NormalizingCopySpecVisitor(delegate);
+
+    @Test
+    public void doesNotVisitADirectoryWhichHasBeenVisitedBefore() {
+        final FileVisitDetails details = file("dir");
+        final FileVisitDetails file = file("dir/file");
+
+        context.checking(new Expectations() {{
+            one(delegate).visitDir(details);
+            one(delegate).visitFile(file);
+        }});
+
+        visitor.visitDir(details);
+        visitor.visitFile(file);
+        visitor.visitDir(details);
+    }
+
+    @Test
+    public void doesNotVisitADirectoryUntilAChildFileIsVisited() {
+        final FileVisitDetails dir = file("dir");
+        final FileVisitDetails file = file("dir/file");
+
+        visitor.visitDir(dir);
+
+        context.checking(new Expectations() {{
+            one(delegate).visitDir(dir);
+            one(delegate).visitFile(file);
+        }});
+
+        visitor.visitFile(file);
+    }
+
+    @Test
+    public void doesNotVisitADirectoryUntilAChildDirIsVisited() {
+        final FileVisitDetails dir = file("dir");
+        final FileVisitDetails subdir = file("dir/sub");
+        final FileVisitDetails file = file("dir/sub/file");
+
+        visitor.visitDir(dir);
+        visitor.visitDir(subdir);
+
+        context.checking(new Expectations() {{
+            one(delegate).visitDir(dir);
+            one(delegate).visitDir(subdir);
+            one(delegate).visitFile(file);
+        }});
+
+        visitor.visitFile(file);
+    }
+
+    @Test
+    public void visitsDirectoryAncestorsWhichHaveNotBeenVisited() {
+        final FileVisitDetails dir1 = file("a/b/c");
+        final FileVisitDetails file1 = file("a/b/c/file");
+
+        context.checking(new Expectations() {{
+            one(delegate).visitDir(with(hasPath("a")));
+            one(delegate).visitDir(with(hasPath("a/b")));
+            one(delegate).visitDir(dir1);
+            one(delegate).visitFile(file1);
+        }});
+
+        visitor.visitDir(dir1);
+        visitor.visitFile(file1);
+
+        final FileVisitDetails dir2 = file("a/b/d/e");
+        final FileVisitDetails file2 = file("a/b/d/e/file");
+
+        context.checking(new Expectations() {{
+            one(delegate).visitDir(with(hasPath("a/b/d")));
+            one(delegate).visitDir(dir2);
+            one(delegate).visitFile(file2);
+        }});
+
+        visitor.visitDir(dir2);
+        visitor.visitFile(file2);
+    }
+
+    @Test
+    public void visitsFileAncestorsWhichHaveNotBeenVisited() {
+        final FileVisitDetails details = file("a/b/c");
+
+        context.checking(new Expectations() {{
+            one(delegate).visitDir(with(hasPath("a")));
+            one(delegate).visitDir(with(hasPath("a/b")));
+            one(delegate).visitFile(details);
+        }});
+
+        visitor.visitFile(details);
+    }
+
+    @Test
+    public void visitSpecDelegatesToVisitor() {
+        final ReadableCopySpec spec = context.mock(ReadableCopySpec.class);
+
+        context.checking(new Expectations() {{
+            one(delegate).visitSpec(spec);
+        }});
+
+        visitor.visitSpec(spec);
+    }
+
+    private FileVisitDetails file(final String path) {
+        final FileVisitDetails details = context.mock(FileVisitDetails.class, path);
+        context.checking(new Expectations(){{
+            allowing(details).getRelativePath();
+            will(returnValue(RelativePath.parse(false, path)));
+        }});
+        return details;
+    }
+
+    private Matcher<FileVisitDetails> hasPath(final String path) {
+        return new BaseMatcher<FileVisitDetails>() {
+            public void describeTo(Description description) {
+                description.appendText("has path ").appendValue(path);
+            }
+
+            public boolean matches(Object o) {
+                FileVisitDetails details = (FileVisitDetails) o;
+                return details.getRelativePath().getPathString().equals(path);
+            }
+        };
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/RegExpNameMapperTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/RegExpNameMapperTest.java
new file mode 100644
index 0000000..2651966
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/RegExpNameMapperTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.file.copy;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class RegExpNameMapperTest {
+    @Test public void testRenameWithCapture() {
+        RegExpNameMapper mapper = new RegExpNameMapper("(.+).java","$1Test.java");
+        assertEquals("SourceTest.java", mapper.transform("Source.java"));
+        assertEquals("SecondTest.java", mapper.transform("Second.java"));
+    }
+
+    @Test public void testRenameNoMatch() {
+        RegExpNameMapper mapper = new RegExpNameMapper("(.+).java","$1Test.java");
+        String noMatch = "NoMatch";
+        assertEquals(noMatch, mapper.transform(noMatch));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/RenamingCopyActionTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/RenamingCopyActionTest.java
new file mode 100644
index 0000000..76ae2b9
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/RenamingCopyActionTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.file.copy;
+
+import org.gradle.api.Transformer;
+import org.gradle.api.file.FileCopyDetails;
+import org.gradle.api.file.RelativePath;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+ at RunWith(JMock.class)
+public class RenamingCopyActionTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final Transformer<String> transformer = context.mock(Transformer.class);
+    private final FileCopyDetails details = context.mock(FileCopyDetails.class);
+    private final RenamingCopyAction action = new RenamingCopyAction(transformer);
+
+    @Test
+    public void transformsLastSegmentOfPath() {
+        context.checking(new Expectations() {{
+            allowing(details).getRelativePath();
+            will(returnValue(new RelativePath(true, "a", "b")));
+            one(transformer).transform("b");
+            will(returnValue("c"));
+            one(details).setRelativePath(new RelativePath(true, "a", "c"));
+        }});
+
+        action.execute(details);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitorTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitorTest.java
new file mode 100644
index 0000000..fe4f0e1
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/copy/SyncCopySpecVisitorTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.copy;
+
+import org.gradle.api.file.CopyAction;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.file.RelativePath;
+import org.gradle.util.TestFile;
+import org.gradle.util.TemporaryFolder;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class SyncCopySpecVisitorTest {
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final CopySpecVisitor delegate = context.mock(CopySpecVisitor.class);
+    private final SyncCopySpecVisitor visitor = new SyncCopySpecVisitor(delegate);
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations(){{
+            allowing(delegate).startVisit(with(notNullValue(CopyAction.class)));
+            allowing(delegate).visitFile(with(notNullValue(FileVisitDetails.class)));
+            allowing(delegate).visitDir(with(notNullValue(FileVisitDetails.class)));
+            allowing(delegate).endVisit();
+        }});
+    }
+    
+    @Test
+    public void deletesExtraFilesFromDestinationDirectoryAtTheEndOfVisit() {
+        TestFile destDir = tmpDir.createDir("dest");
+        destDir.file("subdir/included.txt").createFile();
+        destDir.file("subdir/extra.txt").createFile();
+        destDir.file("included.txt").createFile();
+        destDir.file("extra.txt").createFile();
+
+        visitor.startVisit(action(destDir));
+        visitor.visitDir(dir("subdir"));
+        visitor.visitFile(file("subdir/included.txt"));
+        visitor.visitFile(file("included.txt"));
+        visitor.endVisit();
+
+        destDir.assertHasDescendants("subdir/included.txt", "included.txt");
+    }
+
+    @Test
+    public void deletesExtraDirectoriesFromDestinationDirectoryAtTheEndOfVisit() {
+        TestFile destDir = tmpDir.createDir("dest");
+        destDir.file("included.txt").createFile();
+        destDir.file("extra/extra.txt").createFile();
+
+        visitor.startVisit(action(destDir));
+        visitor.visitFile(file("included.txt"));
+        visitor.endVisit();
+
+        destDir.assertHasDescendants("included.txt");
+    }
+
+    @Test
+    public void doesNotDeleteDestDirectoryWhenNothingCopied() {
+        TestFile destDir = tmpDir.createDir("dest");
+        destDir.file("extra.txt").createFile();
+        destDir.file("extra/extra.txt").createFile();
+
+        visitor.startVisit(action(destDir));
+        visitor.endVisit();
+
+        destDir.assertHasDescendants();
+    }
+
+    @Test
+    public void didWorkWhenDelegateDidWork() {
+        context.checking(new Expectations() {{
+            allowing(delegate).getDidWork();
+            will(returnValue(true));
+        }});
+
+        assertTrue(visitor.getDidWork());
+    }
+
+    @Test
+    public void didWorkWhenFilesDeleted() {
+        TestFile destDir = tmpDir.createDir("dest");
+        destDir.file("extra.txt").createFile();
+
+        visitor.startVisit(action(destDir));
+        visitor.endVisit();
+
+        assertTrue(visitor.getDidWork());
+    }
+
+    private FileCopyAction action(final File destDir) {
+        final FileCopyAction action = context.mock(FileCopyAction.class);
+
+        context.checking(new Expectations() {{
+            allowing(action).getDestinationDir();
+            will(returnValue(destDir));
+        }});
+
+        return action;
+    }
+
+    private FileVisitDetails file(final String path) {
+        return file(RelativePath.parse(true, path));
+    }
+
+    private FileVisitDetails dir(final String path) {
+        return file(RelativePath.parse(false, path));
+    }
+
+    private FileVisitDetails file(final RelativePath relativePath) {
+        final FileVisitDetails details = context.mock(FileVisitDetails.class, relativePath.toString());
+
+        context.checking(new Expectations(){{
+            allowing(details).getRelativePath();
+            will(returnValue(relativePath));
+        }});
+
+        return details;
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/pattern/DefaultPatternMatcherTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/pattern/DefaultPatternMatcherTest.java
new file mode 100644
index 0000000..81499bd
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/pattern/DefaultPatternMatcherTest.java
@@ -0,0 +1,218 @@
+/*
+ * 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.file.pattern;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.gradle.api.file.RelativePath;
+
+import java.util.List;
+
+public class DefaultPatternMatcherTest {
+    private DefaultPatternMatcher matcher;
+    private RelativePath path;
+
+
+    @Test public void testParsing() {
+        List<PatternStep> steps;
+        PatternStep step;
+
+        // parse forward slash pattern
+        matcher = new DefaultPatternMatcher(true, true, "a", "b", "c");
+        steps = matcher.getStepsForTest();
+        assertEquals(3, steps.size());
+        step = steps.get(2);
+        assertTrue(step.matches("c", true));
+        //assertFalse(step.matches("c", false));
+
+        // try matching a wrong literal string
+        assertFalse(step.matches("somethingelse", true));
+
+        // check greedy
+        matcher = new DefaultPatternMatcher(true, true, "a", "**", "c");
+        steps = matcher.getStepsForTest();
+        step = steps.get(1);
+        assertTrue(step.isGreedy());
+    }
+
+    @Test public void testEmpty() {
+        DefaultPatternMatcher matcher = new DefaultPatternMatcher(true, true);
+        List<PatternStep> steps = matcher.getStepsForTest();
+        assertEquals(0, steps.size());
+
+        // both empty
+        RelativePath path = new RelativePath(true);
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        // empty matcher, non-empty path
+        path = new RelativePath(true, "a");
+        assertFalse(matcher.isSatisfiedBy(path));
+
+        // non-empty matcher, empty path
+        matcher = new DefaultPatternMatcher(true, true, "a");
+        path = new RelativePath(true);
+        assertFalse(matcher.isSatisfiedBy(path));
+
+    }
+
+    @Test public void testLiterals() {
+        matcher = new DefaultPatternMatcher(true, true, "a");
+        path = new RelativePath(true, "a");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        path = new RelativePath(true, "b");
+        assertFalse(matcher.isSatisfiedBy(path));
+        
+        matcher = new DefaultPatternMatcher(true, true, "a", "b");
+        path = new RelativePath(true, "a", "b");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        path = new RelativePath(true, "a", "c");
+        assertFalse(matcher.isSatisfiedBy(path));
+
+        path = new RelativePath(true, "b", "c");
+        assertFalse(matcher.isSatisfiedBy(path));
+
+        // short path
+        path = new RelativePath(true, "a");
+        assertFalse(matcher.isSatisfiedBy(path));
+
+        // long path
+        path = new RelativePath(true, "a", "b", "c");
+        assertFalse(matcher.isSatisfiedBy(path));
+    }
+
+    @Test public void testPartials() {
+        matcher = new DefaultPatternMatcher(true, true, "a", "b", "c");
+        path = new RelativePath(false, "a", "b");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        path = new RelativePath(true, "a", "b");
+        assertFalse(matcher.isSatisfiedBy(path));
+
+        matcher = new DefaultPatternMatcher(false, true, "a", "b", "c");
+        path = new RelativePath(false, "a", "b");
+        assertFalse(matcher.isSatisfiedBy(path));
+
+    }
+
+    @Test public void testWildCards() {
+        matcher = new DefaultPatternMatcher(true, true, "*");
+        path = new RelativePath(true, "anything");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        path = new RelativePath(true, "anything", "b");
+        assertFalse(matcher.isSatisfiedBy(path));
+
+        matcher = new DefaultPatternMatcher(true, true, "any??ing");
+        path = new RelativePath(true, "anything");
+        assertTrue(matcher.isSatisfiedBy(path));
+    }
+
+    @Test public void testGreedy() {
+        matcher = new DefaultPatternMatcher(true, true, "a","**");
+        path = new RelativePath(true, "a", "b", "c");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        //leading greedy
+        matcher = new DefaultPatternMatcher(true, true, "**", "c");
+        path = new RelativePath(true, "a", "b", "c");
+        assertTrue(matcher.isSatisfiedBy(path));
+        
+        path = new RelativePath(true, "a", "b", "d");
+        assertFalse(matcher.isSatisfiedBy(path));
+
+        // inner greedy
+        matcher = new DefaultPatternMatcher(true, true, "a", "**", "c");
+        path = new RelativePath(true, "a", "b", "c");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        path = new RelativePath(true, "a", "aa", "bb", "c");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        path = new RelativePath(false, "a", "aa", "bb", "d");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        path = new RelativePath(true, "a", "aa", "bb", "d");
+        assertFalse(matcher.isSatisfiedBy(path));
+
+        // fake trail
+        matcher = new DefaultPatternMatcher(true, true, "a", "**", "c", "d");
+        path = new RelativePath(true, "a", "b", "c", "e", "c", "d");
+        assertTrue(matcher.isSatisfiedBy(path));
+        
+        // multiple greedies
+        matcher = new DefaultPatternMatcher(true, true, "a", "**", "c", "**", "e");
+        path = new RelativePath(true, "a", "b", "c", "d", "e");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        path = new RelativePath(true, "a", "b", "bb", "c", "d", "e");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        path = new RelativePath(true, "a", "q", "bb", "c", "d", "c", "d", "e");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        // Missing greedy
+        matcher = new DefaultPatternMatcher(true, true, "a", "**", "c");
+        path = new RelativePath(true, "a", "c");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        path = new RelativePath(true, "a", "d");
+        assertFalse(matcher.isSatisfiedBy(path));
+    }
+
+    @Test public void testTypical() {
+        matcher = new DefaultPatternMatcher(true, true, "**", "CVS", "*");
+        path = new RelativePath(true, "CVS", "Repository");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        path = new RelativePath(true, "org", "gradle", "CVS", "Entries");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        path = new RelativePath(true, "org", "gradle", "CVS", "foo", "bar", "Entries");
+        assertFalse(matcher.isSatisfiedBy(path));
+
+        matcher = new DefaultPatternMatcher(true, true, "src", "main", "**");
+        path = new RelativePath(true, "src", "main", "groovy", "org");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        path = new RelativePath(true, "src", "test", "groovy", "org");
+        assertFalse(matcher.isSatisfiedBy(path));
+
+        matcher = new DefaultPatternMatcher(true, true, "**", "test", "**");
+        // below fails, trailing ** not ignored
+        path = new RelativePath(true, "src", "main", "test");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        path = new RelativePath(true, "src", "test", "main");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        path = new RelativePath(true, "src", "main", "fred");
+        assertFalse(matcher.isSatisfiedBy(path));
+    }
+
+    @Test public void testCase() {
+        matcher = new DefaultPatternMatcher(true, true, "a", "b");
+        assertTrue(matcher.isSatisfiedBy(new RelativePath(true, "a", "b")));
+        assertFalse(matcher.isSatisfiedBy(new RelativePath(true, "A", "B")));
+
+        matcher = new DefaultPatternMatcher(true, false, "a", "b");
+        assertTrue(matcher.isSatisfiedBy(new RelativePath(true, "a", "b")));
+        assertTrue(matcher.isSatisfiedBy(new RelativePath(true, "A", "B")));
+    }
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/pattern/NameOnlyPatternMatcherTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/pattern/NameOnlyPatternMatcherTest.java
new file mode 100644
index 0000000..3d4af16
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/pattern/NameOnlyPatternMatcherTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.file.pattern;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.specs.Spec;
+
+public class NameOnlyPatternMatcherTest {
+
+    @Test public void testLiteralName() {
+        Spec<RelativePath> matcher;
+        RelativePath path;
+
+        matcher = new NameOnlyPatternMatcher(true, true, "fred.txt");
+
+        path = new RelativePath(true, "fred.txt");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        path = new RelativePath(true, "something.else");
+        assertFalse(matcher.isSatisfiedBy(path));
+    }
+
+    @Test public void testPartialMatch() {
+        Spec<RelativePath> matcher;
+        RelativePath path;
+
+        matcher = new NameOnlyPatternMatcher(true, true, "fred");
+        path = new RelativePath(true, "fred");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        path = new RelativePath(false, "subdir");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+
+        matcher = new NameOnlyPatternMatcher(false, true, "fred");
+        path = new RelativePath(true, "fred");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        path = new RelativePath(false, "subdir");
+        assertFalse(matcher.isSatisfiedBy(path));
+    }
+
+    @Test public void testWildcardInName() {
+        Spec<RelativePath> matcher;
+        RelativePath path;
+
+        matcher = new NameOnlyPatternMatcher(true, true, "*.jsp");
+        path = new RelativePath(true, "fred.jsp");
+        assertTrue(matcher.isSatisfiedBy(path));
+
+        path = new RelativePath(true, "fred.java");
+        assertFalse(matcher.isSatisfiedBy(path));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/pattern/PatternMatcherFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/pattern/PatternMatcherFactoryTest.java
new file mode 100644
index 0000000..09e78e2
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/pattern/PatternMatcherFactoryTest.java
@@ -0,0 +1,222 @@
+/*
+ * 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.file.pattern;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import org.gradle.api.file.RelativePath;
+import org.gradle.api.specs.Spec;
+
+import java.util.List;
+
+public class PatternMatcherFactoryTest {
+    private Spec<RelativePath> matcher;
+    private RelativePath path;
+    List<PatternStep> steps;
+    PatternStep step;
+
+    @Test public void testSlashDirection() {
+        matcher = PatternMatcherFactory.getPatternMatcher(true, true, "a/b/c");
+        assertTrue(matcher instanceof DefaultPatternMatcher);
+        steps = ((DefaultPatternMatcher) matcher).getStepsForTest();
+        assertEquals(3, steps.size());
+        step = steps.get(2);
+        assertTrue(step.matches("c", true));
+
+
+        matcher = PatternMatcherFactory.getPatternMatcher(true, true, "a\\b\\c");
+        assertTrue(matcher instanceof DefaultPatternMatcher);
+        steps = ((DefaultPatternMatcher) matcher).getStepsForTest();
+        assertEquals(3, steps.size());
+        step = steps.get(2);
+        assertTrue(step.matches("c", true));
+    }
+
+    /**
+     * Test that trailing slash gets ** added automatically
+     */
+    @Test public void testAddGreedy() {
+        matcher = PatternMatcherFactory.getPatternMatcher(true, true, "a/b/");
+        assertTrue(matcher instanceof DefaultPatternMatcher);
+        steps = ((DefaultPatternMatcher) matcher).getStepsForTest();
+        assertEquals(3, steps.size());
+        step = steps.get(2);
+        assertTrue(step.isGreedy());
+
+        matcher = PatternMatcherFactory.getPatternMatcher(true, true, "a\\b\\");
+        assertTrue(matcher instanceof DefaultPatternMatcher);
+        steps = ((DefaultPatternMatcher) matcher).getStepsForTest();
+        assertEquals(3, steps.size());
+        step = steps.get(2);
+        assertTrue(step.isGreedy());
+    }
+
+    @Test public void testNameOnly() {
+        matcher = PatternMatcherFactory.getPatternMatcher(true, true, "**/*.jsp");
+        assertTrue(matcher instanceof NameOnlyPatternMatcher);
+        path = new RelativePath(true, "fred.jsp");
+        assertTrue(matcher.isSatisfiedBy(path));
+    }
+
+    @Test public void testShortenedGreedy() {
+        matcher = PatternMatcherFactory.getPatternMatcher(true, true, "**/");
+        assertTrue(matcher instanceof DefaultPatternMatcher);
+        steps = ((DefaultPatternMatcher) matcher).getStepsForTest();
+        assertEquals(1, steps.size());
+        step = steps.get(0);
+        assertTrue(step.isGreedy());
+    }
+
+    @Test public void testGreedyPatternsMatchingFiles() {
+        matcher = PatternMatcherFactory.getPatternMatcher(false, true, "**");
+        assertThat(matcher, matchesFile());
+        assertThat(matcher, matchesFile("a"));
+        assertThat(matcher, matchesFile("a", "b", "c"));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(false, true, "**/a");
+        assertThat(matcher, matchesFile("a"));
+        assertThat(matcher, matchesFile("b", "a"));
+        assertThat(matcher, matchesFile("a", "b", "a"));
+        assertThat(matcher, not(matchesFile()));
+        assertThat(matcher, not(matchesFile("b")));
+        assertThat(matcher, not(matchesFile("a", "b")));
+        assertThat(matcher, not(matchesFile("b", "a", "c")));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(false, true, "**/a/b/**");
+        assertThat(matcher, matchesFile("a", "b"));
+        assertThat(matcher, matchesFile("c", "a", "b"));
+        assertThat(matcher, matchesFile("a", "b", "c"));
+        assertThat(matcher, matchesFile("c", "a", "b", "d"));
+        assertThat(matcher, matchesFile("a", "b", "a", "b"));
+        assertThat(matcher, not(matchesFile()));
+        assertThat(matcher, not(matchesFile("a")));
+        assertThat(matcher, not(matchesFile("b")));
+        assertThat(matcher, not(matchesFile("a", "c", "b")));
+        assertThat(matcher, not(matchesFile("c", "d")));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(false, true, "**/a/**/b");
+        assertThat(matcher, matchesFile("a", "b"));
+        assertThat(matcher, matchesFile("a", "c", "b"));
+        assertThat(matcher, matchesFile("c", "a", "b"));
+        assertThat(matcher, matchesFile("c", "a", "d", "b"));
+        assertThat(matcher, not(matchesFile()));
+        assertThat(matcher, not(matchesFile("a")));
+        assertThat(matcher, not(matchesFile("a", "b", "c")));
+        assertThat(matcher, not(matchesFile("c", "d")));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(false, true, "a/b/**");
+        assertThat(matcher, matchesFile("a", "b"));
+        assertThat(matcher, matchesFile("a", "b", "c"));
+        assertThat(matcher, not(matchesFile()));
+        assertThat(matcher, not(matchesFile("a")));
+        assertThat(matcher, not(matchesFile("a", "c", "b")));
+        assertThat(matcher, not(matchesFile("c", "a", "b")));
+        assertThat(matcher, not(matchesFile("c", "d")));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(false, true, "a/b/**/c");
+        assertThat(matcher, matchesFile("a", "b", "c"));
+        assertThat(matcher, matchesFile("a", "b", "d", "c"));
+        assertThat(matcher, not(matchesFile()));
+        assertThat(matcher, not(matchesFile("a")));
+        assertThat(matcher, not(matchesFile("a", "b")));
+        assertThat(matcher, not(matchesFile("a", "b", "c", "d")));
+        assertThat(matcher, not(matchesFile("a", "c", "b", "c")));
+        assertThat(matcher, not(matchesFile("d", "a", "b")));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(false, true, "a/b/**/c/**");
+        assertThat(matcher, matchesFile("a", "b", "c"));
+        assertThat(matcher, matchesFile("a", "b", "d", "c"));
+        assertThat(matcher, matchesFile("a", "b", "c", "d"));
+        assertThat(matcher, matchesFile("a", "b", "d", "c", "d"));
+        assertThat(matcher, not(matchesFile()));
+        assertThat(matcher, not(matchesFile("a")));
+        assertThat(matcher, not(matchesFile("a", "b")));
+        assertThat(matcher, not(matchesFile("d", "a", "b")));
+    }
+    
+    @Test public void testGreedyPatternsPartialMatchingDirs() {
+        matcher = PatternMatcherFactory.getPatternMatcher(true, true, "**");
+        assertThat(matcher, matchesDir());
+        assertThat(matcher, matchesDir("a"));
+        assertThat(matcher, matchesDir("a", "b", "c"));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(true, true, "**/a");
+        assertThat(matcher, matchesDir());
+        assertThat(matcher, matchesDir("a"));
+        assertThat(matcher, matchesDir("b", "a"));
+        assertThat(matcher, matchesDir("a", "b", "a"));
+        assertThat(matcher, matchesDir("d"));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(true, true, "**/a/b/**");
+        assertThat(matcher, matchesDir());
+        assertThat(matcher, matchesDir("a", "b"));
+        assertThat(matcher, matchesDir("c", "a", "b"));
+        assertThat(matcher, matchesDir("a", "b", "c"));
+        assertThat(matcher, matchesDir("c", "a", "b", "d"));
+        assertThat(matcher, matchesDir("a", "b", "a", "b"));
+        assertThat(matcher, matchesDir("a"));
+        assertThat(matcher, matchesDir("c"));
+        assertThat(matcher, matchesDir("c", "a"));
+        assertThat(matcher, matchesDir("c", "a", "a", "b"));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(true, true, "a/b/**");
+        assertThat(matcher, matchesDir());
+        assertThat(matcher, matchesDir("a", "b"));
+        assertThat(matcher, matchesDir("a", "b", "c"));
+        assertThat(matcher, matchesDir("a"));
+        assertThat(matcher, not(matchesDir("b")));
+        assertThat(matcher, not(matchesDir("d")));
+        assertThat(matcher, not(matchesDir("c", "a", "b")));
+
+        matcher = PatternMatcherFactory.getPatternMatcher(true, true, "a/b/**/c");
+        assertThat(matcher, matchesDir());
+        assertThat(matcher, matchesDir("a", "b", "c"));
+        assertThat(matcher, matchesDir("a", "b", "d", "c"));
+        assertThat(matcher, matchesDir("a"));
+        assertThat(matcher, matchesDir("a", "b"));
+        assertThat(matcher, matchesDir("a", "b", "d"));
+        assertThat(matcher, matchesDir("a", "b", "c", "d"));
+        assertThat(matcher, not(matchesDir("a", "c", "b", "c")));
+        assertThat(matcher, not(matchesDir("d", "a", "b")));
+    }
+
+    private Matcher<Spec<RelativePath>> matchesFile(String... paths) {
+        return matches(new RelativePath(true, paths));
+    }
+
+    private Matcher<Spec<RelativePath>> matchesDir(String... paths) {
+        return matches(new RelativePath(false, paths));
+    }
+
+    private Matcher<Spec<RelativePath>> matches(final RelativePath path) {
+        return new BaseMatcher<Spec<RelativePath>>() {
+            public void describeTo(Description description) {
+                description.appendText("matches ").appendValue(path);
+            }
+
+            public boolean matches(Object o) {
+                Spec<RelativePath> matcher = (Spec<RelativePath>) o;
+                return matcher.isSatisfiedBy(path);
+            }
+        };
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/pattern/PatternStepFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/pattern/PatternStepFactoryTest.java
new file mode 100644
index 0000000..89d2878
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/pattern/PatternStepFactoryTest.java
@@ -0,0 +1,40 @@
+/*
+ * 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.file.pattern;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class PatternStepFactoryTest {
+    @Test public void testGreedy() {
+        PatternStep step = PatternStepFactory.getStep("**", true, true);
+        assertTrue(step.isGreedy());
+        assertTrue(step.matches("anything", true));
+        assertTrue(step.matches("anything", false));
+    }
+
+    @Test public void testNormal() {
+        PatternStep step = PatternStepFactory.getStep("*.jsp", true, true);
+        assertFalse(step.isGreedy());
+        assertTrue(step.matches("fred.jsp", true));
+
+        // check case sensitivity param
+        assertFalse(step.matches("fred.JSP", true));
+        step = PatternStepFactory.getStep("*.jsp", true, false);
+        assertTrue(step.matches("fred.JSP", true));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/pattern/RegExpPatternStepTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/pattern/RegExpPatternStepTest.java
new file mode 100644
index 0000000..fffdd9f
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/file/pattern/RegExpPatternStepTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.file.pattern;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ * @author Steve Appling
+ */
+public class RegExpPatternStepTest {
+
+    private void testPatternEscape(String expected, String pattern) {
+        assertEquals(expected, RegExpPatternStep.getRegExPattern(pattern));
+    }
+
+
+    @Test public void testGetRegExpPattern() {
+        testPatternEscape("literal", "literal");
+        testPatternEscape("dotq.?", "dotq?");
+        testPatternEscape("star.*stuff", "star*stuff");
+        testPatternEscape("\\\\\\[\\]\\^\\-\\&\\.\\{\\}\\(\\)\\$\\+\\|\\<\\=\\!", "\\[]^-&.{}()$+|<=!");
+        testPatternEscape("\\$\\&time", "$&time");
+    }
+
+    @Test public void testEscapeSet() {
+        String testChars = "`~!@#$%^&*()-_=+[]{}\\|;:'\"<>,/";
+        RegExpPatternStep step = new RegExpPatternStep(testChars, true);
+        assertTrue(step.matches(testChars, true));
+    }
+
+    @Test public void testMatches() {
+        RegExpPatternStep step = new RegExpPatternStep("literal", true);
+        assertTrue(step.matches("literal", true));
+        assertFalse(step.matches("Literal", true));
+        assertFalse(step.matches("literally", true));
+        assertFalse(step.matches("aliteral", true));
+
+        step = new RegExpPatternStep("a?c", true);
+        assertTrue(step.matches("abc", true));
+        assertFalse(step.matches("abcd", true));
+        assertTrue(step.matches("a$c", true));
+
+        step = new RegExpPatternStep("a*c", true);
+        assertTrue(step.matches("abc", true));
+        assertTrue(step.matches("abrac", true));
+        assertFalse(step.matches("abcd", true));
+
+        step = new RegExpPatternStep("*", true);
+        assertTrue(step.matches("asd;flkj", true));
+    }
+
+
+    @Test public void testCase() {
+        RegExpPatternStep step = new RegExpPatternStep("MiXeD", true);
+        assertTrue(step.matches("MiXeD", true));
+        assertFalse(step.matches("mixed", true));
+
+        step = new RegExpPatternStep("MiXeD", false);
+        assertTrue(step.matches("MiXeD", true));
+        assertTrue(step.matches("mixed", true));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactoryTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactoryTest.groovy
new file mode 100644
index 0000000..7bb9961
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerFactoryTest.groovy
@@ -0,0 +1,89 @@
+/*
+ * 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.initialization
+
+import spock.lang.Specification
+import org.gradle.api.artifacts.dsl.RepositoryHandlerFactory
+import org.gradle.api.internal.artifacts.ConfigurationContainerFactory
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider
+import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory
+import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.api.artifacts.dsl.RepositoryHandler
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.util.ObservableUrlClassLoader
+
+class DefaultScriptHandlerFactoryTest extends Specification {
+    private final RepositoryHandlerFactory repositoryHandlerFactory = Mock()
+    private final ConfigurationContainerFactory configurationContainerFactory = Mock()
+    private final DependencyMetaDataProvider metaDataProvider = Mock()
+    private final DependencyFactory dependencyFactory = Mock()
+    private final ClassLoader parentClassLoader = Mock()
+    private final RepositoryHandler repositoryHandler = Mock()
+    private final ConfigurationContainer configurationContainer = Mock()
+    private final DefaultScriptHandlerFactory factory = new DefaultScriptHandlerFactory(repositoryHandlerFactory, configurationContainerFactory, metaDataProvider, dependencyFactory)
+
+    def createsScriptHandler() {
+        ScriptSource script = scriptSource()
+        expectConfigContainerCreated()
+
+        when:
+        def handler = factory.create(script, parentClassLoader)
+
+        then:
+        handler instanceof DefaultScriptHandler
+        handler.classLoader instanceof ObservableUrlClassLoader
+        handler.classLoader.parent == parentClassLoader
+    }
+
+    def reusesClassLoaderForGivenScriptClassAndParentClassLoader() {
+        ScriptSource script = scriptSource('script')
+        ScriptSource other = scriptSource('script')
+        expectConfigContainerCreated()
+
+        when:
+        def handler1 = factory.create(script, parentClassLoader)
+        def handler2 = factory.create(other, parentClassLoader)
+
+        then:
+        handler1.classLoader == handler2.classLoader
+        handler2 instanceof NoClassLoaderUpdateScriptHandler
+    }
+
+    def doesNotReuseClassLoaderForDifferentScriptClass() {
+        ScriptSource script = scriptSource('script')
+        ScriptSource other = scriptSource('other')
+        expectConfigContainerCreated()
+
+        when:
+        def handler1 = factory.create(script, parentClassLoader)
+        def handler2 = factory.create(other, parentClassLoader)
+
+        then:
+        handler1.classLoader != handler2.classLoader
+        handler2 instanceof DefaultScriptHandler
+    }
+
+    private def expectConfigContainerCreated() {
+        _ * repositoryHandlerFactory.createRepositoryHandler(_) >> repositoryHandler
+        _ * configurationContainerFactory.createConfigurationContainer(repositoryHandler, metaDataProvider, _) >> configurationContainer
+    }
+
+    private def scriptSource(String className = 'script') {
+        ScriptSource script = Mock()
+        _ * script.className >> className
+        script
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerTest.groovy
new file mode 100644
index 0000000..639b10d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerTest.groovy
@@ -0,0 +1,100 @@
+/*
+ * 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.initialization
+
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.artifacts.dsl.DependencyHandler
+import org.gradle.api.artifacts.dsl.RepositoryHandler
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.WrapUtil
+import org.jmock.integration.junit4.JMock
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.gradle.util.Matchers.*
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.util.ObservableUrlClassLoader
+
+ at RunWith(JMock)
+public class DefaultScriptHandlerTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final RepositoryHandler repositoryHandler = context.mock(RepositoryHandler.class)
+    private final DependencyHandler dependencyHandler = context.mock(DependencyHandler.class)
+    private final ConfigurationContainer configurationContainer = context.mock(ConfigurationContainer.class)
+    private final Configuration configuration = context.mock(Configuration.class)
+    private final ScriptSource scriptSource = context.mock(ScriptSource.class)
+    private final ObservableUrlClassLoader classLoader = context.mock(ObservableUrlClassLoader.class)
+
+    @Test void addsClasspathConfiguration() {
+        context.checking {
+            one(configurationContainer).add('classpath')
+        }
+
+        new DefaultScriptHandler(scriptSource, repositoryHandler, dependencyHandler, configurationContainer, classLoader)
+    }
+
+    @Test void createsAClassLoaderAndAddsContentsOfClassPathConfiguration() {
+        DefaultScriptHandler handler = handler()
+
+        ClassLoader classLoader = handler.classLoader
+        assertThat(classLoader, sameInstance(this.classLoader))
+
+        File file1 = new File('a')
+        File file2 = new File('b')
+        context.checking {
+            one(configuration).getFiles()
+            will(returnValue(WrapUtil.toSet(file1, file2)))
+            one(classLoader).addURL(file1.toURI().toURL())
+            one(classLoader).addURL(file2.toURI().toURL())
+        }
+
+        handler.updateClassPath()
+    }
+
+    @Test void canConfigureRepositories() {
+        DefaultScriptHandler handler = handler()
+
+        context.checking {
+            one(repositoryHandler).mavenCentral()
+        }
+
+        handler.repositories {
+            mavenCentral()
+        }
+    }
+
+    @Test void canConfigureDependencies() {
+        DefaultScriptHandler handler = handler()
+
+        context.checking {
+            one(dependencyHandler).add('config', 'dep')
+        }
+
+        handler.dependencies {
+            add('config', 'dep')
+        }
+    }
+
+    private DefaultScriptHandler handler() {
+        context.checking {
+            one(configurationContainer).add('classpath')
+            will(returnValue(configuration))
+        }
+        return new DefaultScriptHandler(scriptSource, repositoryHandler, dependencyHandler, configurationContainer, classLoader)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/plugins/DefaultConventionTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/plugins/DefaultConventionTest.groovy
new file mode 100644
index 0000000..fa856d3
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/plugins/DefaultConventionTest.groovy
@@ -0,0 +1,131 @@
+/*
+ * 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.plugins
+
+import org.gradle.api.plugins.Convention
+import org.gradle.api.plugins.TestPluginConvention1
+import org.gradle.api.plugins.TestPluginConvention2
+import org.junit.Before
+import org.junit.Test
+import static org.junit.Assert.*
+import static org.hamcrest.Matchers.*
+
+/**
+ * @author Hans Dockter
+ */
+class DefaultConventionTest {
+    Convention convention
+
+    TestPluginConvention1 convention1
+    TestPluginConvention2 convention2
+
+    @Before public void setUp() {
+        convention = new DefaultConvention()
+        convention1 = new TestPluginConvention1()
+        convention2 = new TestPluginConvention2()
+        convention.plugins.plugin1 = convention1
+        convention.plugins.plugin2 = convention2
+    }
+
+    @Test public void testGetProperty() {
+        assertEquals(convention1.a, convention.plugins.plugin1.a)
+        assertEquals(convention1.a, convention.a)
+    }
+
+    @Test public void testGetPropertiesWithAmbiguity() {
+        assertEquals(convention1.a, convention.plugins.plugin1.a)
+        assertEquals(convention2.a, convention.plugins.plugin2.a)
+        assertEquals(convention1.a, convention.a)
+    }
+
+    @Test public void testGetAllProperties() {
+        assertEquals(convention1.a, convention.properties.a)
+        assertEquals(convention1.b, convention.properties.b)
+        assertEquals(convention1.c, convention.properties.c)
+    }
+
+    @Test public void testSetProperties() {
+        convention.b = 'newvalue'
+        assertEquals('newvalue', convention.plugins.plugin1.b)
+    }
+
+    @Test public void testSetPropertiesWithAmbiguity() {
+        convention.a = 'newvalue'
+        assertEquals('newvalue', convention1.a)
+    }
+
+    @Test (expected = MissingPropertyException) public void testMissingPropertiesWithGet() {
+        convention.prop
+    }
+
+    @Test(expected = MissingPropertyException) public void testMissingPropertiesWithSet() {
+        convention.prop = 'newvalue'
+    }
+
+    @Test public void testMethods() {
+        assertEquals(convention1.meth('somearg'), convention.plugins.plugin1.meth('somearg'))
+        assertEquals(convention1.meth('somearg'), convention.meth('somearg'))
+    }
+
+    @Test public void testMethodsWithAmbiguity() {
+        assertEquals(convention1.meth(), convention.plugins.plugin1.meth())
+        assertEquals(convention2.meth(), convention.plugins.plugin2.meth())
+        assertEquals(convention.meth(), convention1.meth())
+    }
+
+    @Test (expected = MissingMethodException) public void testMissingMethod() {
+        convention.methUnknown()
+    }
+
+    @Test public void testCanLocateConventionObjectByType() {
+        assertSame(convention1, convention.getPlugin(TestPluginConvention1))
+        assertSame(convention2, convention.getPlugin(TestPluginConvention2))
+        assertSame(convention1, convention.findPlugin(TestPluginConvention1))
+        assertSame(convention2, convention.findPlugin(TestPluginConvention2))
+    }
+    
+    @Test public void testGetPluginFailsWhenMultipleConventionObjectsWithCompatibleType() {
+        try {
+            convention.getPlugin(Object)
+            fail()
+        } catch (java.lang.IllegalStateException e) {
+            assertThat(e.message, equalTo('Found multiple convention objects of type Object.'))
+        }
+    }
+
+    @Test public void testFindPluginFailsWhenMultipleConventionObjectsWithCompatibleType() {
+        try {
+            convention.getPlugin(Object)
+            fail()
+        } catch (java.lang.IllegalStateException e) {
+            assertThat(e.message, equalTo('Found multiple convention objects of type Object.'))
+        }
+    }
+
+    @Test public void testGetPluginFailsWhenNoConventionObjectsWithCompatibleType() {
+        try {
+            convention.getPlugin(String)
+            fail()
+        } catch (java.lang.IllegalStateException e) {
+            assertThat(e.message, equalTo('Could not find any convention object of type String.'))
+        }
+    }
+    
+    @Test public void testFindPluginReturnsNullWhenNoConventionObjectsWithCompatibleType() {
+        assertNull(convention.findPlugin(String))
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationActionTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationActionTest.groovy
new file mode 100644
index 0000000..45a8f30
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/plugins/DefaultObjectConfigurationActionTest.groovy
@@ -0,0 +1,102 @@
+/*
+ * 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
+
+import static org.hamcrest.Matchers.*
+
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.runner.RunWith
+import org.junit.Test
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.configuration.ScriptPluginFactory
+import org.gradle.configuration.ScriptPlugin
+
+ at RunWith(JMock.class)
+public class DefaultObjectConfigurationActionTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final Object target = new Object()
+    private final URI file = new URI('script:something')
+    private final FileResolver resolver = context.mock(FileResolver.class)
+    private final ScriptPluginFactory factory = context.mock(ScriptPluginFactory.class)
+    private final ScriptPlugin configurer = context.mock(ScriptPlugin.class)
+    private final DefaultObjectConfigurationAction action = new DefaultObjectConfigurationAction(resolver, factory, target)
+
+    @Test
+    public void doesNothingWhenNothingSpecified() {
+        action.execute()
+    }
+
+    @Test
+    public void appliesScriptsToDefaultTargetObject() {
+        context.checking {
+            one(resolver).resolveUri('script')
+            will(returnValue(file))
+
+            one(factory).create(withParam(notNullValue()))
+            will(returnValue(configurer))
+
+            one(configurer).apply(target)
+        }
+
+        action.from('script')
+        action.execute()
+    }
+
+    @Test
+    public void appliesScriptsToTargetObjects() {
+        Object target1 = new Object()
+        Object target2 = new Object()
+
+        context.checking {
+            one(resolver).resolveUri('script')
+            will(returnValue(file))
+
+            one(factory).create(withParam(notNullValue()))
+            will(returnValue(configurer))
+
+            one(configurer).apply(target1)
+            one(configurer).apply(target2)
+        }
+
+        action.from('script')
+        action.to(target1)
+        action.to(target2)
+        action.execute()
+    }
+    
+    @Test
+    public void flattensCollections() {
+        Object target1 = new Object()
+        Object target2 = new Object()
+
+        context.checking {
+            one(resolver).resolveUri('script')
+            will(returnValue(file))
+
+            one(factory).create(withParam(notNullValue()))
+            will(returnValue(configurer))
+
+            one(configurer).apply(target1)
+            one(configurer).apply(target2)
+        }
+
+        action.from('script')
+        action.to([[target1], target2])
+        action.execute()
+    }
+}
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistryTest.java
new file mode 100644
index 0000000..69e626a
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/plugins/DefaultPluginRegistryTest.java
@@ -0,0 +1,221 @@
+/*
+ * 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.plugins;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Plugin;
+import org.gradle.api.internal.project.TestPlugin1;
+import org.gradle.api.internal.project.TestPlugin2;
+import org.gradle.api.plugins.PluginInstantiationException;
+import org.gradle.api.plugins.UnknownPluginException;
+import org.gradle.util.GUtil;
+import org.gradle.util.TemporaryFolder;
+import org.gradle.util.TestFile;
+import org.hamcrest.Matchers;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Properties;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultPluginRegistryTest {
+    private String pluginId = "test";
+    private DefaultPluginRegistry pluginRegistry;
+    private JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+    @Rule
+    public TemporaryFolder testDir = new TemporaryFolder();
+    private ClassLoader classLoader;
+
+    @Before
+    public void setup() throws Exception {
+        classLoader = createClassLoader(pluginId, TestPlugin1.class.getName(), "parent");
+        pluginRegistry = new DefaultPluginRegistry(classLoader);
+    }
+
+    private ClassLoader createClassLoader(final String id, String implClass, String name) throws IOException {
+        TestFile classPath = testDir.createDir(name);
+        Properties props = new Properties();
+        props.setProperty("implementation-class", implClass);
+        final TestFile propertiesFile = classPath.file(id + ".properties");
+        propertiesFile.getParentFile().mkdirs();
+        GUtil.saveProperties(props, propertiesFile);
+        final ClassLoader classLoader = context.mock(ClassLoader.class, name);
+        context.checking(new Expectations() {{
+            allowing(classLoader).getResource("META-INF/gradle-plugins/" + id + ".properties");
+            will(returnValue(propertiesFile.toURI().toURL()));
+        }});
+        return classLoader;
+    }
+
+    @Test
+    public void canLoadPluginByType() {
+        assertThat(pluginRegistry.loadPlugin(TestPlugin2.class), instanceOf(TestPlugin2.class));
+    }
+
+    @Test
+    public void canLookupPluginTypeById() throws ClassNotFoundException {
+        expectClassLoaded(classLoader, TestPlugin1.class);
+
+        assertThat(pluginRegistry.getTypeForId(pluginId), equalTo((Class) TestPlugin1.class));
+    }
+
+    @Test
+    public void failsForUnknownId() {
+        expectResourceNotFound(classLoader, "unknownId");
+
+        try {
+            pluginRegistry.getTypeForId("unknownId");
+            fail();
+        } catch (UnknownPluginException e) {
+            assertThat(e.getMessage(), equalTo("Plugin with id 'unknownId' not found."));
+        }
+    }
+
+    @Test
+    public void failsWhenClassDoesNotImplementPlugin() {
+        try {
+            pluginRegistry.loadPlugin((Class)String.class);
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), equalTo("Cannot create plugin of type 'String' as it does not implement the Plugin interface."));
+        }
+    }
+
+    @Test
+    public void failsWhenNoImplementationClassSpecifiedInPropertiesFile() throws MalformedURLException {
+        Properties properties = new Properties();
+        final TestFile propertiesFile = testDir.file("prop");
+        GUtil.saveProperties(properties, propertiesFile);
+        final URL url = propertiesFile.toURI().toURL();
+
+        context.checking(new Expectations() {{
+            allowing(classLoader).getResource("META-INF/gradle-plugins/noImpl.properties");
+            will(returnValue(url));
+        }});
+
+        try {
+            pluginRegistry.getTypeForId("noImpl");
+            fail();
+        } catch (PluginInstantiationException e) {
+            assertThat(e.getMessage(), equalTo("No implementation class specified for plugin 'noImpl' in " + url + "."));
+        }
+    }
+
+    @Test
+    public void wrapsPluginInstantiationFailure() {
+        try {
+            pluginRegistry.loadPlugin(BrokenPlugin.class);
+            fail();
+        } catch (PluginInstantiationException e) {
+            assertThat(e.getMessage(), equalTo("Could not create plugin of type 'BrokenPlugin'."));
+            assertThat(e.getCause(), Matchers.<Object>nullValue());
+        }
+    }
+
+    @Test
+    public void childDelegatesToParentRegistryToLoadPlugin() throws Exception {
+        ClassLoader childClassLoader = createClassLoader("other", TestPlugin1.class.getName(), "child");
+
+        PluginRegistry child = pluginRegistry.createChild(childClassLoader);
+        assertThat(child.loadPlugin(TestPlugin1.class), instanceOf(TestPlugin1.class));
+    }
+
+    @Test
+    public void childDelegatesToParentRegistryToLookupPluginType() throws Exception {
+        expectClassLoaded(classLoader, TestPlugin1.class);
+
+        ClassLoader childClassLoader = createClassLoader("other", TestPlugin1.class.getName(), "child");
+
+        PluginRegistry child = pluginRegistry.createChild(childClassLoader);
+        assertThat(child.getTypeForId(pluginId), equalTo((Class) pluginRegistry.getTypeForId(pluginId)));
+    }
+
+    @Test
+    public void childClasspathCanContainAdditionalMappingsForPlugins() throws Exception {
+        expectResourceNotFound(classLoader, "other");
+
+        ClassLoader childClassLoader = createClassLoader("other", TestPlugin1.class.getName(), "child");
+        expectClassLoaded(childClassLoader, TestPlugin1.class);
+
+        PluginRegistry child = pluginRegistry.createChild(childClassLoader);
+        assertThat(child.getTypeForId("other"), equalTo((Class) TestPlugin1.class));
+    }
+
+    @Test
+    public void parentIdMappingHasPrecedenceOverChildIdMapping() throws Exception {
+        expectClassLoaded(classLoader, TestPlugin1.class);
+        ClassLoader childClassLoader = createClassLoader(pluginId, "no-such-class", "child");
+
+        PluginRegistry child = pluginRegistry.createChild(childClassLoader);
+        assertThat(child.getTypeForId(pluginId), equalTo((Class) pluginRegistry.getTypeForId(pluginId)));
+    }
+
+    @Test
+    public void childClasspathCanContainAdditionalPlugins() throws Exception {
+        expectClassesNotFound(classLoader);
+        expectResourceNotFound(classLoader, "other");
+
+        ClassLoader childClassLoader = createClassLoader("other", TestPlugin2.class.getName(), "child");
+        expectClassLoaded(childClassLoader, TestPlugin2.class);
+
+        PluginRegistry child = pluginRegistry.createChild(childClassLoader);
+        assertThat(child.getTypeForId("other"), equalTo((Class) TestPlugin2.class));
+    }
+
+    private void expectResourceNotFound(final ClassLoader classLoader, final String id) {
+        context.checking(new Expectations(){{
+            allowing(classLoader).getResource("META-INF/gradle-plugins/" + id + ".properties");
+            will(returnValue(null));
+        }});
+    }
+
+    private void expectClassesNotFound(final ClassLoader classLoader) throws ClassNotFoundException {
+        context.checking(new Expectations() {{
+            allowing(classLoader).loadClass(with(notNullValue(String.class)));
+            will(throwException(new ClassNotFoundException()));
+        }});
+    }
+
+    private void expectClassLoaded(final ClassLoader classLoader, final Class<?> pluginClass) throws ClassNotFoundException {
+        context.checking(new Expectations() {{
+            atLeast(1).of(classLoader).loadClass(pluginClass.getName());
+            will(returnValue(pluginClass));
+        }});
+    }
+
+    private class BrokenPlugin implements Plugin<String> {
+        public void apply(String target) {
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/plugins/DefaultProjectsPluginContainerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/plugins/DefaultProjectsPluginContainerTest.java
new file mode 100644
index 0000000..046f1a3
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/plugins/DefaultProjectsPluginContainerTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.plugins;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.internal.project.DefaultProject;
+import org.gradle.api.internal.project.TestPlugin1;
+import org.gradle.api.internal.project.TestPlugin2;
+import org.gradle.api.plugins.UnknownPluginException;
+import org.gradle.util.HelperUtil;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultProjectsPluginContainerTest {
+    protected String pluginId = "somePluginId";
+    protected JUnit4Mockery context = new JUnit4Mockery();
+    private final DefaultProject project = HelperUtil.createRootProject();
+
+    private PluginRegistry pluginRegistryStub = context.mock(PluginRegistry.class);
+    private DefaultProjectsPluginContainer projectsPluginHandler = new DefaultProjectsPluginContainer(pluginRegistryStub, project);
+
+    private TestPlugin1 pluginWithIdMock = new TestPlugin1();
+    private TestPlugin2 pluginWithoutIdMock = new TestPlugin2();
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations() {{
+            allowing(pluginRegistryStub).getTypeForId(pluginId); will(returnValue(TestPlugin1.class));
+            allowing(pluginRegistryStub).loadPlugin(TestPlugin1.class); will(returnValue(pluginWithIdMock));
+            allowing(pluginRegistryStub).loadPlugin(TestPlugin2.class); will(returnValue(pluginWithoutIdMock));
+        }});
+    }
+
+    @Test
+    public void usePluginById() {
+        Plugin addedPlugin = projectsPluginHandler.apply(pluginId);
+        assertThat(pluginWithIdMock, sameInstance(addedPlugin));
+        assertThat(projectsPluginHandler.apply(pluginId), sameInstance(addedPlugin));
+
+        assertThat(projectsPluginHandler.findPlugin(addedPlugin.getClass()), sameInstance(addedPlugin));
+        assertThat(projectsPluginHandler.findPlugin(pluginId), sameInstance(addedPlugin));
+    }
+
+    @Test
+    public void usePluginWithIdByType() {
+        Class<? extends Plugin> type = pluginWithIdMock.getClass();
+
+        Plugin addedPlugin = projectsPluginHandler.apply(type);
+        assertThat(pluginWithIdMock, sameInstance(addedPlugin));
+        assertThat(projectsPluginHandler.apply(type), sameInstance(addedPlugin));
+        assertThat(projectsPluginHandler.apply(pluginId), sameInstance(addedPlugin));
+
+        assertThat(projectsPluginHandler.findPlugin(type), sameInstance(addedPlugin));
+        assertThat(projectsPluginHandler.findPlugin(pluginId), sameInstance(addedPlugin));
+    }
+
+    @Test
+    public void usePluginWithoutId() {
+        Class<? extends Plugin> type = pluginWithoutIdMock.getClass();
+        Plugin addedPlugin = projectsPluginHandler.apply(type);
+        assertThat(pluginWithoutIdMock, sameInstance(addedPlugin));
+        assertThat(projectsPluginHandler.apply(type), sameInstance(addedPlugin));
+
+        assertThat(projectsPluginHandler.findPlugin(type), sameInstance(addedPlugin));
+    }
+
+    @Test
+    public void hasAndFindForPluginWithId() {
+        projectsPluginHandler.apply(pluginId);
+        assertThat(projectsPluginHandler.hasPlugin(pluginId), equalTo(true));
+        assertThat(projectsPluginHandler.hasPlugin(pluginWithIdMock.getClass()), equalTo(true));
+        assertThat(projectsPluginHandler.findPlugin(pluginId), sameInstance((Plugin) pluginWithIdMock));
+        assertThat(projectsPluginHandler.findPlugin(pluginWithIdMock.getClass()), sameInstance((Plugin) pluginWithIdMock));
+    }
+
+    @Test
+    public void hasAndFindForPluginWithoutId() {
+        Plugin plugin = pluginWithoutIdMock;
+        Class<? extends Plugin> pluginType = plugin.getClass();
+        projectsPluginHandler.apply(pluginType);
+        assertThat(projectsPluginHandler.hasPlugin(pluginType), equalTo(true));
+        assertThat(projectsPluginHandler.findPlugin(pluginType), sameInstance(plugin));
+    }
+
+    @Test
+    public void hasAndFindPluginByTypeWithUnknownPlugin() {
+        assertThat(projectsPluginHandler.hasPlugin(TestPlugin2.class), equalTo(false));
+        assertThat(projectsPluginHandler.findPlugin(TestPlugin2.class), nullValue());
+    }
+
+    @Test(expected = UnknownPluginException.class)
+    public void getNonUsedPluginById() {
+        projectsPluginHandler.getPlugin(pluginId);
+    }
+
+    @Test(expected = UnknownPluginException.class)
+    public void getNonUsedPluginByType() {
+        projectsPluginHandler.getPlugin(TestPlugin1.class);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultAntBuilderFactoryTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultAntBuilderFactoryTest.groovy
new file mode 100644
index 0000000..2d789ff
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultAntBuilderFactoryTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * 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.project
+
+import org.apache.tools.ant.BuildListener
+import org.gradle.api.Project
+import org.gradle.util.HelperUtil
+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.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+ at RunWith (JMock)
+public class DefaultAntBuilderFactoryTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final BuildListener listener = context.mock(BuildListener)
+    private final Project project = HelperUtil.createRootProject()
+    private final DefaultAntBuilderFactory factory = new DefaultAntBuilderFactory(listener, project)
+
+    @Before
+    public void setUp() {
+        context.checking {
+            allowing(listener).messageLogged(withParam(anything()))
+            allowing(listener).taskStarted(withParam(anything()))
+            allowing(listener).taskFinished(withParam(anything()))
+        }
+    }
+
+    @Test
+    public void createsAnAntBuilder() {
+        def ant = factory.createAntBuilder()
+        assertThat(ant, notNullValue())
+    }
+
+    @Test
+    public void setsBaseDirOfAntProject() {
+        def ant = factory.createAntBuilder()
+        assertThat(ant.project.baseDir, equalTo(project.projectDir))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultAntBuilderTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultAntBuilderTest.groovy
new file mode 100644
index 0000000..07a6613
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultAntBuilderTest.groovy
@@ -0,0 +1,127 @@
+/*
+ * 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.project
+
+import groovy.xml.MarkupBuilder
+import org.junit.Test
+import org.gradle.api.Project
+import org.gradle.util.HelperUtil
+import static org.junit.Assert.*
+import static org.hamcrest.Matchers.*
+import static org.gradle.util.Matchers.*
+import org.gradle.api.tasks.ant.AntTarget
+import java.lang.reflect.Field
+import org.apache.tools.ant.Target
+
+class DefaultAntBuilderTest {
+    private final Project project = HelperUtil.createRootProject()
+    private final def ant = new DefaultAntBuilder(project)
+
+    @Test
+    public void antPropertiesAreAvailableAsPropertiesOfBuilder() {
+        ant.property(name: 'prop1', value: 'value1')
+        assertThat(ant.prop1, equalTo('value1'))
+
+        ant.prop2 = 'value2'
+        assertThat(ant.antProject.properties.prop2, equalTo('value2'))
+    }
+
+    @Test
+    public void throwsMissingPropertyExceptionForUnknownProperty() {
+        try {
+            ant.unknown
+            fail()
+        } catch (MissingPropertyException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void antPropertiesAreAvailableAsMap() {
+        ant.property(name: 'prop1', value: 'value1')
+        assertThat(ant.properties.prop1, equalTo('value1'))
+
+        ant.properties.prop2 = 'value2'
+        assertThat(ant.antProject.properties.prop2, equalTo('value2'))
+    }
+
+    @Test
+    public void antReferencesAreAvailableAsMap() {
+        def path = ant.path(id: 'ref1', location: 'path')
+        assertThat(ant.references.ref1, sameInstance(path))
+
+        ant.references.prop2 = 'value2'
+        assertThat(ant.antProject.references.prop2, equalTo('value2'))
+    }
+
+    @Test
+    public void importAddsTaskForEachAntTarget() {
+        File buildFile = new File(project.projectDir, 'build.xml')
+        buildFile.withWriter {Writer writer ->
+            def xml = new MarkupBuilder(writer)
+            xml.project {
+                target(name: 'target1', depends: 'target2, target3')
+                target(name: 'target2')
+                target(name: 'target3')
+            }
+        }
+
+        ant.importBuild(buildFile)
+
+        def task = project.tasks.target1
+        assertThat(task, instanceOf(AntTarget))
+        assertThat(task.target.name, equalTo('target1'))
+
+        task = project.tasks.target2
+        assertThat(task, instanceOf(AntTarget))
+        assertThat(task.target.name, equalTo('target2'))
+
+        task = project.tasks.target3
+        assertThat(task, instanceOf(AntTarget))
+        assertThat(task.target.name, equalTo('target3'))
+    }
+
+    @Test
+    public void canNestElements() {
+        ant.otherProp = 'true'
+        ant.condition(property: 'prop', value: 'someValue') {
+            or {
+                and {
+                    isSet(property: 'otherProp')
+                    not { isSet(property: 'missing') }
+                }
+            }
+        }
+        assertThat(ant.prop, equalTo('someValue'))
+    }
+    
+    @Test
+    public void discardsTasksAfterExecution() {
+        ant.echo(message: 'message')
+        ant.echo(message: 'message')
+        ant.echo(message: 'message')
+
+        assertThat(ant.antProject.targets.size(), equalTo(0))
+
+        Field field = AntBuilder.class.getDeclaredField('collectorTarget')
+        field.accessible = true
+        Target target = field.get(ant)
+        field = target.class.getDeclaredField('children')
+        field.accessible = true
+        List children = field.get(target)
+        assertThat(children, isEmpty())
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilderTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilderTest.groovy
new file mode 100644
index 0000000..9654372
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultIsolatedAntBuilderTest.groovy
@@ -0,0 +1,171 @@
+/*
+ * 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.project
+
+import ch.qos.logback.classic.Level
+import ch.qos.logback.core.AppenderBase
+import org.apache.tools.ant.BuildException
+import org.apache.tools.ant.Project
+import org.apache.tools.ant.taskdefs.ConditionTask
+import org.gradle.api.GradleException
+import org.gradle.api.internal.ClassPathRegistry
+import org.gradle.api.internal.DefaultClassPathRegistry
+import org.gradle.api.internal.project.ant.BasicAntBuilder
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.gradle.logging.LoggingTestHelper
+
+class DefaultIsolatedAntBuilderTest {
+    private final ClassPathRegistry registry = new DefaultClassPathRegistry()
+    private final DefaultIsolatedAntBuilder builder = new DefaultIsolatedAntBuilder(registry)
+    private final TestAppender appender = new TestAppender()
+    private final LoggingTestHelper helper = new LoggingTestHelper(appender)
+    private Collection classpath
+
+    @Before
+    public void attachAppender() {
+        classpath = registry.getClassPathFiles("LOCAL_GROOVY")
+        helper.attachAppender()
+        helper.setLevel(Level.INFO);
+    }
+
+    @After
+    public void detachAppender() {
+        helper.detachAppender()
+    }
+
+    @Test
+    public void executesClosureAgainstDifferentVersionOfAntAndGroovy() {
+        Object antBuilder = null
+        Object antProject = null
+        builder.withGroovy(classpath).execute {
+            antBuilder = delegate.builder
+            antProject = delegate.antProject
+        }
+        assertThat(antBuilder, notNullValue())
+        assertThat(antProject, notNullValue())
+
+        ClassLoader loader = antBuilder.class.classLoader
+        assertThat(loader, not(sameInstance(AntBuilder.classLoader)))
+
+        assertThat(antBuilder, not(instanceOf(BasicAntBuilder)))
+        assertThat(antBuilder, instanceOf(loader.loadClass(BasicAntBuilder.name)))
+
+        assertThat(antBuilder, not(instanceOf(AntBuilder)))
+        assertThat(antBuilder, instanceOf(loader.loadClass(AntBuilder.name)))
+
+        assertThat(antProject, not(instanceOf(Project)))
+        assertThat(antProject, instanceOf(loader.loadClass(Project.name)))
+
+        assertThat(loader.loadClass(AntBuilder.name).classLoader, sameInstance(loader.loadClass(Project.name).classLoader))
+
+        assertThat(loader.loadClass(BuildException.name), not(sameInstance(BuildException)))
+        assertThat(loader.loadClass(Closure.name), not(sameInstance(Closure)))
+    }
+
+    @Test
+    public void executesNestedClosures() {
+        String propertyValue = null
+        Object task = null
+        builder.execute {
+            property(name: 'message', value: 'a message')
+            task = condition(property: 'prop', value: 'a message') {
+                isset(property: 'message')
+            }
+            task = task.proxy
+            propertyValue = project.properties.prop
+        }
+
+        assertThat(propertyValue, equalTo('a message'))
+        assertThat(task.class.name, equalTo(ConditionTask.class.name))
+    }
+
+    @Test
+    public void attachesLogger() {
+        builder.execute {
+            property(name: 'message', value: 'a message')
+            echo('${message}')
+        }
+
+        assertThat(appender.writer.toString(), equalTo('[ant:echo] a message'))
+    }
+
+    @Test
+    public void addsToolsJarToClasspath() {
+        builder.execute {
+            delegate.builder.class.classLoader.loadClass('com.sun.tools.javac.Main')
+        }
+    }
+
+    @Test
+    public void cachesClassloaderForGivenClassPath() {
+        Object antBuilder1 = null
+        builder.execute {
+            antBuilder1 = delegate.builder
+        }
+        Object antBuilder2 = null
+        builder.withGroovy(classpath).execute {
+            antBuilder2 = delegate.builder
+        }
+
+        assertThat(antBuilder1.class, sameInstance(antBuilder2.class))
+    }
+
+    @Test
+    public void setsContextClassLoader() {
+        ClassLoader originalLoader = Thread.currentThread().contextClassLoader
+        ClassLoader contextLoader = null
+        Object antProject = null
+
+        builder.execute {
+            antProject = delegate.antProject
+            contextLoader = Thread.currentThread().contextClassLoader
+        }
+
+        assertThat(contextLoader, sameInstance(antProject.class.classLoader))
+        assertThat(Thread.currentThread().contextClassLoader, sameInstance(originalLoader))
+    }
+
+    @Test
+    public void gradleClassesAreNotVisibleToAnt() {
+        ClassLoader loader = null
+        builder.execute {
+            loader = antProject.getClass().classLoader
+        }
+
+        try {
+            loader.loadClass(GradleException.name)
+            fail()
+        } catch (ClassNotFoundException e) {
+            // expected
+        }
+    }
+}
+
+class TestAppender<LoggingEvent> extends AppenderBase<LoggingEvent> {
+    StringWriter writer = new StringWriter()
+
+    synchronized void doAppend(LoggingEvent e) {
+        append(e)
+    }
+
+    protected void append(LoggingEvent e) {
+        writer.write(e.formattedMessage)
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectRegistryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectRegistryTest.java
new file mode 100644
index 0000000..7b9c0d1
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectRegistryTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.project;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Project;
+import org.gradle.api.specs.Spec;
+import org.gradle.util.HelperUtil;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import static junit.framework.Assert.assertSame;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.*;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultProjectRegistryTest {
+    public static final String CHILD_NAME = "child";
+    public static final String CHILD_CHILD_NAME = "childchild";
+    private DefaultProject rootMock;
+    private DefaultProject childMock;
+    private DefaultProject childChildMock;
+
+    private DefaultProjectRegistry<ProjectInternal> projectRegistry;
+
+    @Before
+    public void setUp() {
+        projectRegistry = new DefaultProjectRegistry<ProjectInternal>();
+        rootMock = HelperUtil.createRootProject();
+        childMock = HelperUtil.createChildProject(rootMock, CHILD_NAME);
+        childChildMock = HelperUtil.createChildProject(childMock, CHILD_CHILD_NAME);
+        projectRegistry.addProject(rootMock);
+        projectRegistry.addProject(childMock);
+        projectRegistry.addProject(childChildMock);
+    }
+
+    @Test
+    public void addProject() {
+        checkAccessMethods(rootMock, toSortedSet(rootMock, childMock, childChildMock), toSortedSet(childMock,
+                childChildMock), rootMock);
+        checkAccessMethods(childMock, toSortedSet(childMock, childChildMock), toSortedSet(childChildMock), childMock);
+        checkAccessMethods(childChildMock, toSortedSet(childChildMock), new TreeSet(), childChildMock);
+    }
+
+    private void checkAccessMethods(Project project, SortedSet<DefaultProject> expectedAllProjects,
+                                    SortedSet<DefaultProject> expectedSubProjects, Project expectedGetProject) {
+        assertSame(expectedGetProject, projectRegistry.getProject(project.getPath()));
+        assertEquals(expectedAllProjects, projectRegistry.getAllProjects(project.getPath()));
+        assertEquals(expectedSubProjects, projectRegistry.getSubProjects(project.getPath()));
+        assertSame(expectedGetProject, projectRegistry.getProject(project.getProjectDir()));
+        assertTrue(projectRegistry.getAllProjects().contains(project));
+    }
+
+    @Test
+    public void cannotLocateProjectsWithAmbiguousProjectDir() {
+        DefaultProject duplicateProjectDirProject = HelperUtil.createChildProject(childMock, "childchild2", childMock.getProjectDir());
+        projectRegistry.addProject(duplicateProjectDirProject);
+
+        try {
+            projectRegistry.getProject(childMock.getProjectDir());
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), startsWith("Found multiple projects with project directory "));
+        }
+    }
+
+    @Test
+    public void accessMethodsForNonExistingsPaths() {
+        projectRegistry = new DefaultProjectRegistry<ProjectInternal>();
+        Project otherRoot = HelperUtil.createRootProject();
+        assertNull(projectRegistry.getProject(otherRoot.getPath()));
+        assertEquals(new TreeSet<ProjectInternal>(), projectRegistry.getAllProjects(otherRoot.getPath()));
+        assertEquals(new TreeSet<ProjectInternal>(), projectRegistry.getSubProjects(otherRoot.getPath()));
+        assertNull(projectRegistry.getProject(otherRoot.getProjectDir()));
+    }
+
+    @Test
+    public void canLocalAllProjects() {
+        assertThat(projectRegistry.getAllProjects(), equalTo(toSet((ProjectInternal) rootMock, childMock,
+                childChildMock)));
+    }
+    
+    @Test
+    public void canLocateAllProjectsWhichMatchSpec() {
+        Spec<Project> spec = new Spec<Project>() {
+            public boolean isSatisfiedBy(Project element) {
+                return element.getName().contains("child");
+            }
+        };
+
+        assertThat(projectRegistry.findAll(spec), equalTo(toSet((ProjectInternal) childMock, childChildMock)));
+    }
+
+    @Test
+    public void canRemoveProject() {
+        String path = childChildMock.getPath();
+        assertThat(projectRegistry.removeProject(path), sameInstance((ProjectInternal) childChildMock));
+        assertThat(projectRegistry.getProject(path), nullValue());
+        assertThat(projectRegistry.getProject(childChildMock.getProjectDir()), nullValue());
+        assertTrue(projectRegistry.getAllProjects(path).isEmpty());
+        assertTrue(projectRegistry.getSubProjects(path).isEmpty());
+        assertFalse(projectRegistry.getAllProjects().contains(childChildMock));
+        assertFalse(projectRegistry.getAllProjects(":").contains(childChildMock));
+        assertFalse(projectRegistry.getSubProjects(":").contains(childChildMock));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy
new file mode 100644
index 0000000..2f859f0
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultProjectTest.groovy
@@ -0,0 +1,1053 @@
+/*
+ * 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.project
+
+import java.awt.Point
+import java.text.FieldPosition
+import org.apache.tools.ant.types.FileSet
+import org.gradle.api.artifacts.ConfigurationContainer
+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.artifacts.dsl.RepositoryHandlerFactory
+import org.gradle.api.artifacts.repositories.InternalRepository
+import org.gradle.api.initialization.dsl.ScriptHandler
+import org.gradle.api.internal.AsmBackedClassGenerator
+import org.gradle.api.internal.BeanDynamicObject
+import org.gradle.api.internal.ClassGenerator
+import org.gradle.api.internal.GradleInternal
+import org.gradle.api.internal.artifacts.ConfigurationContainerFactory
+import org.gradle.api.internal.artifacts.configurations.DefaultConfigurationContainer
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider
+import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandlerFactoryTest
+import org.gradle.api.internal.artifacts.dsl.PublishArtifactFactory
+import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory
+import org.gradle.api.internal.artifacts.ivyservice.ResolverFactory
+import org.gradle.api.internal.file.FileOperations
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.initialization.ScriptClassLoaderProvider
+import org.gradle.api.internal.plugins.DefaultConvention
+import org.gradle.api.internal.tasks.TaskContainerInternal
+import org.gradle.api.invocation.Gradle
+import org.gradle.api.logging.LogLevel
+import org.gradle.api.plugins.Convention
+import org.gradle.api.plugins.PluginContainer
+import org.gradle.api.tasks.Directory
+import org.gradle.configuration.ProjectEvaluator
+import org.gradle.configuration.ScriptPluginFactory
+import org.gradle.groovy.scripts.EmptyScript
+import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.logging.LoggingManagerInternal
+import org.gradle.logging.StandardOutputCapture
+import org.gradle.util.HelperUtil
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.TestClosure
+import org.jmock.integration.junit4.JMock
+import org.jmock.lib.legacy.ClassImposteriser
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.gradle.api.*
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith (JMock.class)
+class DefaultProjectTest {
+    JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+
+    static final String TEST_PROJECT_NAME = 'testproject'
+
+    static final String TEST_BUILD_FILE_NAME = 'build.gradle'
+
+    static final String TEST_TASK_NAME = 'testtask'
+
+    Task testTask;
+
+    DefaultProject project, child1, child2, childchild
+
+    ProjectEvaluator projectEvaluator
+
+    IProjectRegistry projectRegistry
+
+    File rootDir
+
+    groovy.lang.Script testScript
+
+    ScriptSource script;
+
+    ServiceRegistry serviceRegistryMock
+    ServiceRegistryFactory projectServiceRegistryFactoryMock
+    TaskContainerInternal taskContainerMock
+    AntBuilderFactory antBuilderFactoryMock
+    AntBuilder testAntBuilder
+
+    ConfigurationContainerFactory configurationContainerFactoryMock;
+    DefaultConfigurationContainer configurationContainerMock;
+    InternalRepository internalRepositoryDummy = context.mock(InternalRepository)
+    ResolverFactory resolverFactoryMock = context.mock(ResolverFactory.class);
+    RepositoryHandlerFactory repositoryHandlerFactoryMock = context.mock(RepositoryHandlerFactory.class);
+    RepositoryHandler repositoryHandlerMock
+    DependencyFactory dependencyFactoryMock
+    DependencyHandler dependencyHandlerMock = context.mock(DependencyHandler)
+    PluginContainer pluginContainerMock = context.mock(PluginContainer)
+    PublishArtifactFactory publishArtifactFactoryMock = context.mock(PublishArtifactFactory)
+    ScriptHandler scriptHandlerMock = context.mock(ScriptHandler)
+    DependencyMetaDataProvider dependencyMetaDataProviderMock = context.mock(DependencyMetaDataProvider)
+    Gradle build;
+    Convention convention = new DefaultConvention();
+    FileOperations fileOperationsMock
+    LoggingManagerInternal loggingManagerMock;
+
+    @Before
+    void setUp() {
+        rootDir = new File("/path/root").absoluteFile
+
+        context.imposteriser = ClassImposteriser.INSTANCE
+        dependencyFactoryMock = context.mock(DependencyFactory.class)
+        loggingManagerMock = context.mock(LoggingManagerInternal.class)
+        taskContainerMock = context.mock(TaskContainerInternal.class);
+        antBuilderFactoryMock = context.mock(AntBuilderFactory.class)
+        testAntBuilder = new DefaultAntBuilder()
+        context.checking {
+            allowing(antBuilderFactoryMock).createAntBuilder(); will(returnValue(testAntBuilder))
+        }
+        configurationContainerMock = context.mock(DefaultConfigurationContainer.class)
+        configurationContainerFactoryMock = [createConfigurationContainer: {
+          resolverProvider, dependencyMetaDataProvider, projectDependenciesBuildInstruction ->
+            assertSame(build.startParameter.projectDependenciesBuildInstruction, projectDependenciesBuildInstruction)
+            configurationContainerMock}] as ConfigurationContainerFactory
+        repositoryHandlerMock =  context.mock(DefaultRepositoryHandlerFactoryTest.ConventionAwareRepositoryHandler.class);
+        context.checking {
+          allowing(repositoryHandlerFactoryMock).createRepositoryHandler(withParam(any(Convention))); will(returnValue(repositoryHandlerMock))
+        }
+        script = context.mock(ScriptSource.class)
+        context.checking {
+            allowing(script).getDisplayName(); will(returnValue('[build file]'))
+            allowing(script).getClassName(); will(returnValue('scriptClass'))
+            allowing(scriptHandlerMock).getSourceFile(); will(returnValue(new File(rootDir, TEST_BUILD_FILE_NAME)))
+        }
+
+        testScript = new EmptyScript()
+
+        testTask = HelperUtil.createTask(DefaultTask)
+
+        projectEvaluator = context.mock(ProjectEvaluator.class)
+        projectRegistry = new DefaultProjectRegistry()
+
+        projectServiceRegistryFactoryMock = context.mock(ServiceRegistryFactory.class, 'parent')
+        serviceRegistryMock = context.mock(ServiceRegistryFactory.class, 'project')
+        build = context.mock(GradleInternal.class)
+        fileOperationsMock = context.mock(FileOperations.class)
+
+        context.checking {
+            allowing(projectServiceRegistryFactoryMock).createFor(withParam(notNullValue())); will(returnValue(serviceRegistryMock))
+            allowing(serviceRegistryMock).get(TaskContainerInternal); will(returnValue(taskContainerMock))
+            allowing(taskContainerMock).getAsDynamicObject(); will(returnValue(new BeanDynamicObject(new TaskContainerDynamicObject(someTask: testTask))))
+            allowing(serviceRegistryMock).get(RepositoryHandler); will(returnValue(repositoryHandlerMock))
+            allowing(serviceRegistryMock).get(RepositoryHandlerFactory); will(returnValue(repositoryHandlerFactoryMock))
+            allowing(serviceRegistryMock).get(ConfigurationContainer); will(returnValue(configurationContainerMock))
+            allowing(serviceRegistryMock).get(ArtifactHandler); will(returnValue(context.mock(ArtifactHandler)))
+            allowing(serviceRegistryMock).get(DependencyHandler); will(returnValue(dependencyHandlerMock))
+            allowing(serviceRegistryMock).get(Convention); will(returnValue(convention))
+            allowing(serviceRegistryMock).get(ProjectEvaluator); will(returnValue(projectEvaluator))
+            allowing(serviceRegistryMock).get(AntBuilderFactory); will(returnValue(antBuilderFactoryMock))
+            allowing(serviceRegistryMock).get(PluginContainer); will(returnValue(pluginContainerMock))
+            allowing(serviceRegistryMock).get(ScriptHandler); will(returnValue(scriptHandlerMock))
+            allowing(serviceRegistryMock).get(ScriptClassLoaderProvider); will(returnValue(context.mock(ScriptClassLoaderProvider)))
+            allowing(serviceRegistryMock).get(LoggingManagerInternal); will(returnValue(loggingManagerMock))
+            allowing(serviceRegistryMock).get(StandardOutputCapture); will(returnValue(context.mock(StandardOutputCapture)))
+            allowing(serviceRegistryMock).get(IProjectRegistry); will(returnValue(projectRegistry))
+            allowing(serviceRegistryMock).get(DependencyMetaDataProvider); will(returnValue(dependencyMetaDataProviderMock))
+            allowing(serviceRegistryMock).get(FileResolver); will(returnValue([:] as FileResolver))
+            allowing(serviceRegistryMock).get(FileOperations);
+            will(returnValue(fileOperationsMock))
+            allowing(serviceRegistryMock).get(ScriptPluginFactory); will(returnValue([:] as ScriptPluginFactory))
+            Object listener = context.mock(ProjectEvaluationListener)
+            ignoring(listener)
+            allowing(build).getProjectEvaluationBroadcaster();
+            will(returnValue(listener))
+        }
+
+        // TODO - don't decorate the project objects
+        ClassGenerator classGenerator = new AsmBackedClassGenerator()
+        project = classGenerator.newInstance(DefaultProject.class, 'root', null, rootDir, script, build, projectServiceRegistryFactoryMock);
+        child1 = classGenerator.newInstance(DefaultProject.class, "child1", project, new File("child1"), script, build, projectServiceRegistryFactoryMock)
+        project.addChildProject(child1)
+        childchild = classGenerator.newInstance(DefaultProject.class, "childchild", child1, new File("childchild"), script, build, projectServiceRegistryFactoryMock)
+        child1.addChildProject(childchild)
+        child2 = classGenerator.newInstance(DefaultProject.class, "child2", project, new File("child2"), script, build, projectServiceRegistryFactoryMock)
+        project.addChildProject(child2)
+        [project, child1, childchild, child2].each {
+            projectRegistry.addProject(it)
+        }
+    }
+
+  @Test void testRepositories() {
+      context.checking {
+          allowing(repositoryHandlerFactoryMock).createRepositoryHandler(withParam(any(Convention))); will(returnValue(repositoryHandlerMock))
+          ignoring(repositoryHandlerMock)
+      }
+      assertThat(project.createRepositoryHandler(), sameInstance(repositoryHandlerMock))
+  }
+
+  @Ignore void testArtifacts() {
+        boolean called = false;
+        ArtifactHandler artifactHandlerMock = [testMethod: { called = true }] as ArtifactHandler
+        project.artifactHandler = artifactHandlerMock
+        project.artifacts {
+            testMethod()
+        }
+        assertTrue(called)
+  }
+
+  @Test void testDependencies() {
+      context.checking {
+          one(dependencyHandlerMock).add('conf', 'dep')
+      }
+      project.dependencies {
+          add('conf', 'dep')
+      }
+  }
+
+  @Test void testConfigurations() {
+        Closure cl = { }
+        context.checking {
+          one(configurationContainerMock).configure(cl)
+        }
+        project.configurationContainer = configurationContainerMock
+        project.configurations cl
+    }
+
+  @Test void testScriptClasspath() {
+        context.checking {
+            one(scriptHandlerMock).getRepositories()
+        }
+        project.buildscript {
+            repositories
+        }
+    }
+
+    @Test void testProject() {
+        assertSame project, child1.parent
+        assertSame project, child1.rootProject
+        checkProject(project, null, 'root', rootDir)
+    }
+
+    private void checkProject(DefaultProject project, Project parent, String name, File projectDir) {
+        assertSame parent, project.parent
+        assertEquals name, project.name
+        assertEquals Project.DEFAULT_VERSION, project.version
+        assertEquals Project.DEFAULT_STATUS, project.status
+        assertSame(rootDir, project.rootDir)
+        assertSame(projectDir, project.projectDir)
+        assertSame this.project, project.rootProject
+        assertEquals(new File(projectDir, TEST_BUILD_FILE_NAME), project.buildFile)
+        assertSame projectEvaluator, project.projectEvaluator
+        assertSame antBuilderFactoryMock, project.antBuilderFactory
+        assertSame project.gradle, build
+        assertNotNull(project.ant)
+        assertNotNull(project.convention)
+        assertEquals([], project.getDefaultTasks())
+        assert project.configurations.is(configurationContainerMock)
+        assert project.repositoryHandlerFactory.is(repositoryHandlerFactoryMock)
+        assertSame(repositoryHandlerMock, project.repositories)
+        assert projectRegistry.is(project.projectRegistry)
+        assertFalse project.state.executed
+        assertEquals DefaultProject.DEFAULT_BUILD_DIR_NAME, project.buildDirName
+    }
+
+    @Test public void testNullVersionAndStatus() {
+        project.version = 'version'
+        project.status = 'status'
+        assertEquals('version', project.version)
+        assertEquals('status', project.status)
+        project.version = null
+        project.status = null
+        assertEquals(Project.DEFAULT_VERSION, project.version)
+        assertEquals(Project.DEFAULT_STATUS, project.status)
+    }
+
+    @Test void testGetGroup() {
+        assertThat(project.getGroup(), equalTo(''))
+        assertThat(childchild.getGroup(), equalTo('root.child1'))
+
+        child1.group = ''
+        assertThat(child1.getGroup(), equalTo(''))
+
+        child1.group = null
+        assertThat(child1.getGroup(), equalTo('root'))
+    }
+
+    @Test public void testExecutesActionBeforeEvaluation() {
+        Action<Project> listener = context.mock(Action)
+        context.checking {
+            one(listener).execute(project)
+        }
+        project.beforeEvaluate(listener)
+        project.projectEvaluationBroadcaster.beforeEvaluate(project)
+    }
+
+    @Test public void testExecutesActionAfterEvaluation() {
+        Action<Project> listener = context.mock(Action)
+        context.checking {
+            one(listener).execute(project)
+        }
+        project.afterEvaluate(listener)
+        project.projectEvaluationBroadcaster.afterEvaluate(project, null)
+    }
+
+    @Test public void testExecutesClosureBeforeEvaluation() {
+        TestClosure listener = context.mock(TestClosure)
+        context.checking {
+            one(listener).call(project)
+        }
+
+        project.beforeEvaluate(HelperUtil.toClosure(listener))
+        project.projectEvaluationBroadcaster.beforeEvaluate(project)
+    }
+
+    @Test public void testExecutesClosureAfterEvaluation() {
+        TestClosure listener = context.mock(TestClosure)
+        context.checking {
+            one(listener).call(project)
+        }
+
+        project.afterEvaluate(HelperUtil.toClosure(listener))
+        project.projectEvaluationBroadcaster.afterEvaluate(project, null)
+    }
+
+    @Test void testEvaluate() {
+        context.checking {
+            one(projectEvaluator).evaluate(project, project.state)
+        }
+        assertSame(project, project.evaluate())
+    }
+
+    @Test void testUsePluginWithString() {
+        context.checking {
+            one(pluginContainerMock).apply('someplugin'); will(returnValue([:] as Plugin))
+        }
+        project.apply(plugin: 'someplugin')
+    }
+
+    @Test void testUsePluginWithClass() {
+        context.checking {
+            one(pluginContainerMock).apply(Plugin); will(returnValue([:] as Plugin))
+        }
+        project.apply(plugin: Plugin)
+    }
+
+    @Test void testEvaluationDependsOn() {
+        boolean mockReader2Finished = false
+        boolean mockReader1Called = false
+        final ProjectEvaluator mockReader1 = [evaluate: {DefaultProject project, state ->
+            project.evaluationDependsOn(child1.path)
+            assertTrue(mockReader2Finished)
+            mockReader1Called = true
+            testScript
+        }] as ProjectEvaluator
+        final ProjectEvaluator mockReader2 = [
+                evaluate: {DefaultProject project, state ->
+                    mockReader2Finished = true
+                    testScript
+                }] as ProjectEvaluator
+        project.projectEvaluator = mockReader1
+        child1.projectEvaluator = mockReader2
+        project.evaluate()
+        assertTrue mockReader1Called
+    }
+
+    @Test (expected = InvalidUserDataException) void testEvaluationDependsOnWithNullArgument() {
+        project.evaluationDependsOn(null)
+    }
+
+    @Test (expected = InvalidUserDataException) void testEvaluationDependsOnWithEmptyArgument() {
+        project.evaluationDependsOn('')
+    }
+
+    @Test (expected = CircularReferenceException) void testEvaluationDependsOnWithCircularDependency() {
+        final ProjectEvaluator mockReader1 = [evaluate: {DefaultProject project, ProjectState state ->
+            state.executing = true
+            project.evaluationDependsOn(child1.path)
+            testScript
+        }] as ProjectEvaluator
+        final ProjectEvaluator mockReader2 = [evaluate: {DefaultProject project, ProjectState state ->
+            state.executing = true
+            project.evaluationDependsOn(project.path)
+            testScript
+        }] as ProjectEvaluator
+        project.projectEvaluator = mockReader1
+        child1.projectEvaluator = mockReader2
+        project.evaluate()
+    }
+
+    @Test void testDependsOnWithNoEvaluation() {
+        boolean mockReaderCalled = false
+        final ProjectEvaluator mockReader = [evaluateProject: {DefaultProject project ->
+            mockReaderCalled = true
+            testScript
+        }] as ProjectEvaluator
+        child1.projectEvaluator = mockReader
+        project.dependsOn(child1.name, false)
+        assertFalse mockReaderCalled
+        assertEquals([child1] as Set, project.dependsOnProjects)
+        project.dependsOn(child2.path, false)
+        assertEquals([child1, child2] as Set, project.dependsOnProjects)
+    }
+
+    @Test void testDependsOn() {
+        boolean mockReaderCalled = false
+        final ProjectEvaluator mockReader = [evaluate: {DefaultProject project, state ->
+            mockReaderCalled = true
+            testScript
+        }] as ProjectEvaluator
+        child1.projectEvaluator = mockReader
+        project.dependsOn(child1.name)
+        assertTrue mockReaderCalled
+        assertEquals([child1] as Set, project.dependsOnProjects)
+
+    }
+
+    @Test void testChildrenDependsOnMe() {
+        project.childrenDependOnMe()
+        assertTrue(child1.dependsOnProjects.contains(project))
+        assertTrue(child2.dependsOnProjects.contains(project))
+        assertEquals(1, child1.dependsOnProjects.size())
+        assertEquals(1, child2.dependsOnProjects.size())
+    }
+
+    @Test void testDependsOnChildren() {
+        context.checking {
+            never(projectEvaluator).evaluate(child1, child1.state)
+        }
+
+        project.dependsOnChildren()
+        context.assertIsSatisfied()
+        assertTrue(project.dependsOnProjects.contains(child1))
+        assertTrue(project.dependsOnProjects.contains(child2))
+        assertEquals(2, project.dependsOnProjects.size())
+    }
+
+    @Test void testDependsOnChildrenIncludingEvaluate() {
+        context.checking {
+            one(projectEvaluator).evaluate(child1, child1.state)
+            one(projectEvaluator).evaluate(child2, child2.state)
+        }
+        project.dependsOnChildren(true)
+        assertTrue(project.dependsOnProjects.contains(child1))
+        assertTrue(project.dependsOnProjects.contains(child2))
+        assertEquals(2, project.dependsOnProjects.size())
+    }
+
+    @Test (expected = InvalidUserDataException) void testDependsOnWithNullPath() {
+        project.dependsOn(null)
+    }
+
+    @Test (expected = InvalidUserDataException) void testDependsOnWithEmptyPath() {
+        project.dependsOn('')
+    }
+
+    @Test (expected = UnknownProjectException) void testDependsOnWithUnknownParentPath() {
+        project.dependsOn(child1.path + 'XXX')
+    }
+
+    @Test (expected = UnknownProjectException) void testDependsOnWithUnknownProjectPath() {
+        project.dependsOn(child1.name + 'XXX')
+    }
+
+    @Test void testAddAndGetChildProject() {
+        ProjectInternal child1 = ['getName': {-> 'child1'}] as ProjectInternal
+        ProjectInternal child2 = ['getName': {-> 'child2'}] as ProjectInternal
+
+        project.addChildProject(child1)
+        assertEquals(2, project.childProjects.size())
+        assertSame(child1, project.childProjects.child1)
+
+        project.addChildProject(child2)
+        assertEquals(2, project.childProjects.size())
+        assertSame(child2, project.childProjects.child2)
+    }
+
+    @Test public void testDefaultTasks() {
+        project.defaultTasks("a", "b");
+        assertEquals(["a", "b"], project.getDefaultTasks())
+        project.defaultTasks("c");
+        assertEquals(["c"], project.getDefaultTasks())
+    }
+
+    @Test (expected = InvalidUserDataException) public void testDefaultTasksWithNull() {
+        project.defaultTasks(null);
+    }
+
+    @Test (expected = InvalidUserDataException) public void testDefaultTasksWithSingleNullValue() {
+        project.defaultTasks("a", null);
+    }
+
+    @Test public void testCreateTaskWithName() {
+        context.checking {
+            one(taskContainerMock).add([name: TEST_TASK_NAME]); will(returnValue(testTask))
+        }
+        assertSame(testTask, project.createTask(TEST_TASK_NAME));
+    }
+
+    @Test public void testCreateTaskWithNameAndArgs() {
+        Map testArgs = [a: 'b']
+        context.checking {
+            one(taskContainerMock).add(testArgs + [name: TEST_TASK_NAME]); will(returnValue(testTask))
+        }
+        assertSame(testTask, project.createTask(testArgs, TEST_TASK_NAME));
+    }
+
+    @Test public void testCreateTaskWithNameAndAction() {
+        Action<Task> testAction = {} as Action
+        context.checking {
+            one(taskContainerMock).add([name: TEST_TASK_NAME, action: testAction]); will(returnValue(testTask))
+        }
+        assertSame(testTask, project.createTask(TEST_TASK_NAME, testAction));
+    }
+
+    @Test public void testCreateTaskWithNameAndClosureAction() {
+        Closure testAction = {}
+        context.checking {
+            one(taskContainerMock).add([name: TEST_TASK_NAME, action: testAction]); will(returnValue(testTask))
+        }
+        assertSame(testTask, project.createTask(TEST_TASK_NAME, testAction));
+    }
+
+    @Test public void testCreateTaskWithNameArgsAndActions() {
+        Map testArgs = [a: 'b']
+        Action<Task> testAction = {} as Action
+        context.checking {
+            one(taskContainerMock).add(testArgs + [name: TEST_TASK_NAME, action: testAction]); will(returnValue(testTask))
+        }
+        assertSame(testTask, project.createTask(testArgs, TEST_TASK_NAME, testAction));
+    }
+
+    @Test void testCanAccessTaskAsAProjectProperty() {
+        assertThat(project.someTask, sameInstance(testTask))
+    }
+
+    @Test (expected = MissingPropertyException) void testPropertyShortCutForTaskCallWithNonExistingTask() {
+        project.unknownTask
+    }
+
+    @Test (expected = MissingMethodException) void testMethodShortCutForTaskCallWithNonExistingTask() {
+        project.unknownTask([dependsOn: '/task2'])
+    }
+
+    private Set getListWithAllProjects() {
+        [project, child1, child2, childchild]
+    }
+
+    private Set getListWithAllChildProjects() {
+        [child1, child2, childchild]
+
+    }
+
+    @Test void testPath() {
+        assertEquals(Project.PATH_SEPARATOR + "child1", child1.path)
+        assertEquals(Project.PATH_SEPARATOR, project.path)
+    }
+
+    @Test void testGetProject() {
+        assertSame(project, project.project(Project.PATH_SEPARATOR))
+        assertSame(child1, project.project(Project.PATH_SEPARATOR + "child1"))
+        assertSame(child1, project.project("child1"))
+        assertSame(childchild, child1.project('childchild'))
+        assertSame(child1, childchild.project(Project.PATH_SEPARATOR + "child1"))
+    }
+
+    @Test void testGetProjectWithUnknownAbsolutePath() {
+        try {
+            project.project(Project.PATH_SEPARATOR + "unknownchild")
+            fail()
+        } catch (UnknownProjectException e) {
+            assertEquals(e.getMessage(), "Project with path ':unknownchild' could not be found in root project 'root'.")
+        }
+    }
+
+    @Test void testGetProjectWithUnknownRelativePath() {
+        try {
+            project.project("unknownchild")
+            fail()
+        } catch (UnknownProjectException e) {
+            assertEquals(e.getMessage(), "Project with path 'unknownchild' could not be found in root project 'root'.")
+        }
+    }
+
+    @Test (expected = InvalidUserDataException) void testGetProjectWithEmptyPath() {
+        project.project("")
+    }
+
+    @Test (expected = InvalidUserDataException) void testGetProjectWithNullPath() {
+        project.project(null)
+    }
+
+    @Test void testFindProject() {
+        assertSame(project, project.findProject(Project.PATH_SEPARATOR))
+        assertSame(child1, project.findProject(Project.PATH_SEPARATOR + "child1"))
+        assertSame(child1, project.findProject("child1"))
+        assertSame(childchild, child1.findProject('childchild'))
+        assertSame(child1, childchild.findProject(Project.PATH_SEPARATOR + "child1"))
+    }
+
+    @Test void testFindProjectWithUnknownAbsolutePath() {
+        assertNull(project.findProject(Project.PATH_SEPARATOR + "unknownchild"))
+    }
+
+    @Test void testFindProjectWithUnknownRelativePath() {
+        assertNull(project.findProject("unknownChild"))
+    }
+
+    @Test void testGetProjectWithClosure() {
+        String newPropValue = 'someValue'
+        assert child1.is(project.project("child1") {
+            newProp = newPropValue
+        })
+        assertEquals(child1.newProp, newPropValue)
+    }
+
+    @Test void testGetAllTasksRecursive() {
+        Task projectTask = HelperUtil.createTask(DefaultTask.class)
+        Task child1Task = HelperUtil.createTask(DefaultTask.class)
+        Task child2Task = HelperUtil.createTask(DefaultTask.class)
+
+        Map expectedMap = new TreeMap()
+        expectedMap[project] = [projectTask] as TreeSet
+        expectedMap[child1] = [child1Task] as TreeSet
+        expectedMap[child2] = [child2Task] as TreeSet
+        expectedMap[childchild] = [] as TreeSet
+
+        context.checking {
+            one(taskContainerMock).getAll(); will(returnValue([projectTask] as Set))
+            one(taskContainerMock).getAll(); will(returnValue([child1Task] as Set))
+            one(taskContainerMock).getAll(); will(returnValue([child2Task] as Set))
+            one(taskContainerMock).getAll(); will(returnValue([] as Set))
+        }
+
+        assertEquals(expectedMap, project.getAllTasks(true))
+    }
+
+    @Test void testGetAllTasksNonRecursive() {
+        Task projectTask = HelperUtil.createTask(DefaultTask.class)
+
+        Map expectedMap = new TreeMap()
+        expectedMap[project] = [projectTask] as TreeSet
+
+        context.checking {
+            one(taskContainerMock).getAll(); will(returnValue([projectTask] as Set))
+        }
+
+        assertEquals(expectedMap, project.getAllTasks(false))
+    }
+
+    @Test void testGetTasksByNameRecursive() {
+        Task projectTask = HelperUtil.createTask(DefaultTask.class)
+        Task child1Task = HelperUtil.createTask(DefaultTask.class)
+
+        context.checking {
+            one(taskContainerMock).findByName('task'); will(returnValue(projectTask))
+            one(taskContainerMock).findByName('task'); will(returnValue(child1Task))
+            one(taskContainerMock).findByName('task'); will(returnValue(null))
+            one(taskContainerMock).findByName('task'); will(returnValue(null))
+        }
+
+        assertEquals([projectTask, child1Task] as Set, project.getTasksByName('task', true))
+    }
+
+    @Test void testGetTasksByNameNonRecursive() {
+        Task projectTask = HelperUtil.createTask(DefaultTask.class)
+
+        context.checking {
+            one(taskContainerMock).findByName('task'); will(returnValue(projectTask))
+        }
+
+        assertEquals([projectTask] as Set, project.getTasksByName('task', false))
+    }
+
+    @Test (expected = InvalidUserDataException) void testGetTasksWithEmptyName() {
+        project.getTasksByName('', true)
+    }
+
+    @Test (expected = InvalidUserDataException) void testGetTasksWithNullName() {
+        project.getTasksByName(null, true)
+    }
+
+    @Test void testGetTasksWithUnknownName() {
+        context.checking {
+            allowing(taskContainerMock).findByName('task'); will(returnValue(null))
+        }
+
+        assertEquals([] as Set, project.getTasksByName('task', true))
+        assertEquals([] as Set, project.getTasksByName('task', false))
+    }
+
+    private List addTestTaskToAllProjects(String name) {
+        List tasks = []
+        project.allprojects.each {Project project ->
+            tasks << addTestTask(project, name)
+        }
+        tasks
+    }
+
+    private Task addTestTask(Project project, String name) {
+        new DefaultTask(project, name)
+    }
+
+    @Test void testMethodMissing() {
+        boolean closureCalled = false
+        Closure testConfigureClosure = {closureCalled = true}
+        project.someTask(testConfigureClosure)
+        assert closureCalled
+
+        project.convention.plugins.test = new TestConvention()
+        assertEquals(TestConvention.METHOD_RESULT, project.scriptMethod(testConfigureClosure))
+
+        project.script = createScriptForMethodMissingTest('projectScript')
+        assertEquals('projectScript', project.scriptMethod(testConfigureClosure))
+    }
+
+    private groovy.lang.Script createScriptForMethodMissingTest(String returnValue) {
+        String code = """
+def scriptMethod(Closure closure) {
+    "$returnValue"
+}
+"""
+        HelperUtil.createScript(code)
+    }
+
+    @Test void testSetPropertyAndPropertyMissingWithProjectProperty() {
+        String propertyName = 'propName'
+        String expectedValue = 'somevalue'
+
+        project."$propertyName" = expectedValue
+        assertEquals(expectedValue, project."$propertyName")
+        assertEquals(expectedValue, child1."$propertyName")
+    }
+
+    @Test void testPropertyMissingWithExistingConventionProperty() {
+        String propertyName = 'conv'
+        String expectedValue = 'somevalue'
+        project.convention.plugins.test = new TestConvention()
+        project.convention.conv = expectedValue
+        assertEquals(expectedValue, project."$propertyName")
+        assertEquals(expectedValue, project.convention."$propertyName")
+        assertEquals(expectedValue, child1."$propertyName")
+    }
+
+    @Test void testSetPropertyAndPropertyMissingWithConventionProperty() {
+        String propertyName = 'conv'
+        String expectedValue = 'somevalue'
+        project.convention.plugins.test = new TestConvention()
+        project."$propertyName" = expectedValue
+        assertEquals(expectedValue, project."$propertyName")
+        assertEquals(expectedValue, project.convention."$propertyName")
+        assertEquals(expectedValue, child1."$propertyName")
+    }
+
+    @Test void testSetPropertyAndPropertyMissingWithProjectAndConventionProperty() {
+        String propertyName = 'archivesBaseName'
+        String expectedValue = 'somename'
+
+        project.archivesBaseName = expectedValue
+        project.convention.plugins.test = new TestConvention()
+        project.convention.archivesBaseName = 'someothername'
+        project."$propertyName" = expectedValue
+        assertEquals(expectedValue, project."$propertyName")
+        assertEquals('someothername', project.convention."$propertyName")
+    }
+
+    @Test void testPropertyMissingWithNullProperty() {
+        project.nullProp = null
+        assertNull(project.nullProp)
+        assert project.hasProperty('nullProp')
+    }
+
+    @Test (expected = MissingPropertyException)
+    public void testPropertyMissingWithUnknownProperty() {
+        project.unknownProperty
+    }
+
+    @Test void testHasProperty() {
+        assertTrue(project.hasProperty('name'))
+        String propertyName = 'beginIndex'
+        assertFalse(project.hasProperty(propertyName))
+        assertFalse(child1.hasProperty(propertyName))
+
+        project.convention.plugins.test = new FieldPosition(0)
+        project.convention."$propertyName" = 5
+        assertTrue(project.hasProperty(propertyName))
+        assertTrue(child1.hasProperty(propertyName))
+        project.convention = new DefaultConvention()
+        project."$propertyName" = 4
+        assertTrue(project.hasProperty(propertyName))
+        assertTrue(child1.hasProperty(propertyName))
+    }
+
+    @Test void testProperties() {
+        context.checking {
+            allowing(dependencyMetaDataProviderMock).getModule(); will(returnValue([:] as Module))
+            ignoring(fileOperationsMock)
+        }
+        project.additional = 'additional'
+
+        Map properties = project.properties
+        assertEquals(properties.name, 'root')
+        assertEquals(properties.additional, 'additional')
+        assertSame(properties['someTask'], testTask)
+    }
+
+    @Test void testAdditionalProperty() {
+        String expectedPropertyName = 'somename'
+        String expectedPropertyValue = 'somevalue'
+        project.additionalProperties[expectedPropertyName] = expectedPropertyValue
+        assertEquals(project."$expectedPropertyName", expectedPropertyValue)
+    }
+
+    @Test void testAdditionalPropertiesAreInheritable() {
+        project.somename = 'somevalue'
+        assertTrue(project.inheritedScope.hasProperty('somename'))
+        assertEquals(project.inheritedScope.getProperty('somename'), 'somevalue')
+    }
+
+    @Test void testConventionPropertiesAreInheritable() {
+        project.convention.plugins.test = new TestConvention()
+        project.convention.plugins.test.conv = 'somevalue'
+        assertTrue(project.inheritedScope.hasProperty('conv'))
+        assertEquals(project.inheritedScope.getProperty('conv'), 'somevalue')
+    }
+
+    @Test void testInheritedPropertiesAreInheritable() {
+        project.somename = 'somevalue'
+        assertTrue(child1.inheritedScope.hasProperty('somename'))
+        assertEquals(child1.inheritedScope.getProperty('somename'), 'somevalue')
+    }
+
+    @Test void testGetProjectProperty() {
+        assert project.is(project.getProject())
+    }
+
+    @Test void testAllprojectsField() {
+        assertEquals(getListWithAllProjects(), project.allprojects)
+    }
+
+    @Test void testChildren() {
+        assertEquals(getListWithAllChildProjects(), project.subprojects)
+    }
+
+    @Test void testBuildDir() {
+        File dir = new File(rootDir, 'dir')
+        context.checking {
+            one(fileOperationsMock).file(Project.DEFAULT_BUILD_DIR_NAME)
+            will(returnValue(dir))
+        }
+        assertEquals(dir, child1.buildDir)
+
+        child1.buildDir = 12
+        context.checking {
+            one(fileOperationsMock).file(12)
+            will(returnValue(dir))
+        }
+        assertEquals(dir, child1.buildDir)
+    }
+
+    @Test public void testDir() {
+        Task dirTask1 = HelperUtil.createTask(Directory.class)
+        Task dirTask12 = HelperUtil.createTask(Directory.class)
+        Task dirTask123 = HelperUtil.createTask(Directory.class)
+        context.checking {
+            one(taskContainerMock).findByName('dir1'); will(returnValue(null))
+            one(taskContainerMock).add('dir1', Directory); will(returnValue(dirTask1))
+            one(taskContainerMock).findByName('dir1/dir2'); will(returnValue(null))
+            one(taskContainerMock).add('dir1/dir2', Directory); will(returnValue(dirTask12))
+            one(taskContainerMock).findByName('dir1/dir2/dir3'); will(returnValue(null))
+            one(taskContainerMock).add('dir1/dir2/dir3', Directory); will(returnValue(dirTask123))
+        }
+        assertSame(dirTask123, project.dir('dir1/dir2/dir3'));
+    }
+
+    @Test public void testDirWithExistingParentDirTask() {
+        Task dirTask1 = HelperUtil.createTask(Directory.class)
+        context.checking {
+            one(taskContainerMock).findByName('dir1'); will(returnValue(null))
+            one(taskContainerMock).add('dir1', Directory); will(returnValue(dirTask1))
+        }
+        project.dir('dir1')
+
+        Task dirTask14 = HelperUtil.createTask(Directory.class)
+        context.checking {
+            one(taskContainerMock).findByName('dir1'); will(returnValue(dirTask1))
+            one(taskContainerMock).findByName('dir1/dir4'); will(returnValue(null))
+            one(taskContainerMock).add('dir1/dir4', Directory); will(returnValue(dirTask14))
+        }
+        assertSame(dirTask14, project.dir('dir1/dir4'))
+    }
+
+    @Test public void testDirWithConflictingNonDirTask() {
+        Task dirTask14 = HelperUtil.createTask(DefaultTask.class)
+
+        Task dirTask1 = HelperUtil.createTask(Directory.class)
+        context.checking {
+            one(taskContainerMock).findByName('dir1'); will(returnValue(null))
+            one(taskContainerMock).add('dir1', Directory); will(returnValue(dirTask1))
+            one(taskContainerMock).findByName('dir1/dir4'); will(returnValue(dirTask14))
+        }
+
+        try {
+            project.dir('dir1/dir4')
+            fail()
+        } catch (InvalidUserDataException e) {
+            assertThat(e.message, equalTo("Cannot add directory task 'dir1/dir4' as a non-directory task with this name already exists."))
+        }
+    }
+
+    @Test void testCachingOfAnt() {
+        assertSame(testAntBuilder, project.ant)
+        assert project.ant.is(project.ant)
+    }
+
+    @Test void testAnt() {
+        Closure configureClosure = {fileset(dir: 'dir', id: 'fileset')}
+        project.ant(configureClosure)
+        assertThat(project.ant.project.getReference('fileset'), instanceOf(FileSet))
+    }
+
+    @Test void testCreateAntBuilder() {
+        assertSame testAntBuilder, project.createAntBuilder()
+    }
+
+    @Test void testCompareTo() {
+        assertThat(project, lessThan(child1))
+        assertThat(child1, lessThan(child2))
+        assertThat(child1, lessThan(childchild))
+        assertThat(child2, lessThan(childchild))
+    }
+
+    @Test void testDepthCompare() {
+        assertTrue(project.depthCompare(child1) < 0)
+        assertTrue(child1.depthCompare(project) > 0)
+        assertTrue(child1.depthCompare(child2) == 0)
+    }
+
+    @Test void testDepth() {
+        assertTrue(project.depth == 0)
+        assertTrue(child1.depth == 1)
+        assertTrue(child2.depth == 1)
+        assertTrue(childchild.depth == 2)
+    }
+
+    @Test void testSubprojects() {
+        checkConfigureProject('subprojects', listWithAllChildProjects)
+    }
+
+    @Test void testAllprojects() {
+        checkConfigureProject('allprojects', listWithAllProjects)
+    }
+
+    @Test void testConfigureProjects() {
+        checkConfigureProject('configure', [project, child1] as Set)
+    }
+
+    @Test void testHasUsefulToString() {
+        assertEquals('root project \'root\'', project.toString())
+        assertEquals('project \':child1\'', child1.toString())
+        assertEquals('project \':child1:childchild\'', childchild.toString())
+    }
+
+    private void checkConfigureProject(String configureMethod, Set projectsToCheck) {
+        String propValue = 'someValue'
+        if (configureMethod == 'configure') {
+            project."$configureMethod" projectsToCheck as List,
+                    {
+                        testSubProp = propValue
+                    }
+        } else {
+            project."$configureMethod"
+            {
+                testSubProp = propValue
+            }
+        }
+
+        projectsToCheck.each {
+            assertEquals(propValue, it.testSubProp)
+        }
+    }
+
+    @Test
+    void disableStandardOutputCapture() {
+        context.checking {
+            one(loggingManagerMock).disableStandardOutputCapture()
+        }
+        project.disableStandardOutputCapture()
+    }
+
+    @Test
+    void captureStandardOutput() {
+        context.checking {
+            one(loggingManagerMock).captureStandardOutput(LogLevel.DEBUG)
+        }
+        project.captureStandardOutput(LogLevel.DEBUG)
+    }
+
+    @Test
+    void configure() {
+        Point expectedPoint = new Point(4, 3)
+        Point actualPoint = project.configure(new Point()) {
+            setLocation(expectedPoint.x, expectedPoint.y)
+        }
+        assertEquals(expectedPoint, actualPoint)
+    }
+
+    @Test(expected = ReadOnlyPropertyException) void setName() {
+        project.name = "someNewName"
+    }
+
+    @Test void testGetModule() {
+        Module moduleDummyResolve = [:] as Module
+        context.checking {
+            allowing(dependencyMetaDataProviderMock).getModule(); will(returnValue(moduleDummyResolve))
+        }
+        assertThat(project.getModule(), equalTo(moduleDummyResolve))
+    }
+}
+
+class TaskContainerDynamicObject {
+    Task someTask
+    
+    def someTask(Closure closure) {
+        closure.call()
+    }
+}
+
+class TestConvention {
+    final static String METHOD_RESULT = 'methodResult'
+    String name
+    String conv
+    String archivesBaseName
+
+    def scriptMethod(Closure cl) {
+        METHOD_RESULT
+    }
+}
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultServiceRegistryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultServiceRegistryTest.java
new file mode 100644
index 0000000..1d2925b
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/DefaultServiceRegistryTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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.project;
+
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.math.BigDecimal;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class DefaultServiceRegistryTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final TestRegistry registry = new TestRegistry();
+
+    @Test
+    public void throwsExceptionForUnknownService() {
+        try {
+            registry.get(Map.class);
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), equalTo("No service of type Map available in TestRegistry."));
+        }
+    }
+
+    @Test
+    public void delegatesToParentForUnknownService() {
+        final BigDecimal value = BigDecimal.TEN;
+        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
+        TestRegistry registry = new TestRegistry(parent);
+
+        context.checking(new Expectations(){{
+            one(parent).get(BigDecimal.class);
+            will(returnValue(value));
+        }});
+
+        assertThat(registry.get(BigDecimal.class), sameInstance(value));
+    }
+
+    @Test
+    public void throwsExceptionForUnknownParentService() {
+        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
+        TestRegistry registry = new TestRegistry(parent);
+
+        context.checking(new Expectations(){{
+            one(parent).get(Map.class);
+            will(throwException(new DefaultServiceRegistry.UnknownServiceException(Map.class, "fail")));
+        }});
+
+        try {
+            registry.get(Map.class);
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), equalTo("No service of type Map available in TestRegistry."));
+        }
+    }
+
+    @Test
+    public void returnsAddedServiceInstance() {
+        BigDecimal value = BigDecimal.TEN;
+        registry.add(BigDecimal.class, value);
+        assertThat(registry.get(BigDecimal.class), sameInstance(value));
+        assertThat(registry.get(Number.class), sameInstance((Object) value));
+    }
+
+    @Test
+    public void createsAndCachesRegisteredServiceInstance() {
+        final BigDecimal value = BigDecimal.TEN;
+        registry.add(new DefaultServiceRegistry.Service(BigDecimal.class) {
+            @Override
+            protected Object create() {
+                return value;
+            }
+        });
+        assertThat(registry.get(BigDecimal.class), sameInstance(value));
+        assertThat(registry.get(Number.class), sameInstance((Object) value));
+    }
+
+    @Test
+    public void usesFactoryMethodToCreateServiceInstance() {
+        assertThat(registry.get(String.class), equalTo("12"));
+        assertThat(registry.get(Integer.class), equalTo(12));
+    }
+
+    @Test
+    public void usesDecoratorMethodToDecorateParentServiceInstance() {
+        final ServiceRegistry parent = context.mock(ServiceRegistry.class);
+        TestRegistry registry = new TestRegistry(parent);
+
+        context.checking(new Expectations() {{
+            one(parent).get(Long.class);
+            will(returnValue(110L));
+        }});
+
+        assertThat(registry.get(Long.class), equalTo(120L));
+    }
+
+    @Test
+    public void servicesCreatedByFactoryMethodsAreVisibleWhenUsingASubClass() {
+        ServiceRegistry registry = new SubType();
+        assertThat(registry.get(String.class), equalTo("12"));
+        assertThat(registry.get(Integer.class), equalTo(12));
+    }
+    
+    @Test
+    public void closeInvokesCloseMethodOnEachService() {
+        final TestCloseService service = context.mock(TestCloseService.class);
+        registry.add(TestCloseService.class, service);
+
+        context.checking(new Expectations() {{
+            one(service).close();
+        }});
+
+        registry.close();
+    }
+
+    @Test
+    public void closeInvokesStopMethodOnEachService() {
+        final TestStopService service = context.mock(TestStopService.class);
+        registry.add(TestStopService.class, service);
+
+        context.checking(new Expectations() {{
+            one(service).stop();
+        }});
+
+        registry.close();
+    }
+
+    @Test
+    public void ignoresServiceWithNoCloseOrStopMethod() {
+        registry.add(String.class, "service");
+
+        registry.close();
+    }
+
+    @Test
+    public void discardsServicesOnClose() {
+        registry.get(String.class);
+        registry.close();
+        try {
+            registry.get(String.class);
+            fail();
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(), equalTo("Cannot locate service of type String, as TestRegistry has been closed."));
+        }
+    }
+
+    private static class TestRegistry extends DefaultServiceRegistry {
+        public TestRegistry() {
+        }
+
+        public TestRegistry(ServiceRegistry parent) {
+            super(parent);
+        }
+
+        protected String createString() {
+            return get(Integer.class).toString();
+        }
+
+        protected Long createLong(Long value) {
+            return value + 10;
+        }
+
+        protected Integer createInt() {
+            return 12;
+        }
+    }
+
+    private static class SubType extends TestRegistry {
+    }
+
+    public interface TestCloseService {
+        void close();
+    }
+
+    public interface TestStopService {
+        void stop();
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/GlobalServicesRegistryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/GlobalServicesRegistryTest.java
new file mode 100644
index 0000000..2d588a6
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/GlobalServicesRegistryTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.project;
+
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.DefaultClassPathProvider;
+import org.gradle.api.internal.DefaultClassPathRegistry;
+import org.gradle.api.internal.GradleDistributionLocator;
+import org.gradle.cache.AutoCloseCacheFactory;
+import org.gradle.cache.CacheFactory;
+import org.gradle.initialization.ClassLoaderFactory;
+import org.gradle.initialization.CommandLine2StartParameterConverter;
+import org.gradle.initialization.DefaultClassLoaderFactory;
+import org.gradle.initialization.DefaultCommandLine2StartParameterConverter;
+import org.gradle.listener.DefaultListenerManager;
+import org.gradle.listener.ListenerManager;
+import org.gradle.logging.*;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+public class GlobalServicesRegistryTest {
+    private final GlobalServicesRegistry registry = new GlobalServicesRegistry();
+
+    @Test
+    public void providesCommandLineArgsConverter() {
+        assertThat(registry.get(CommandLine2StartParameterConverter.class), instanceOf(
+                DefaultCommandLine2StartParameterConverter.class));
+    }
+
+    @Test
+    public void providesACacheFactory() {
+        assertThat(registry.get(CacheFactory.class), instanceOf(AutoCloseCacheFactory.class));
+    }
+
+    @Test
+    public void providesAClassPathRegistry() {
+        assertThat(registry.get(ClassPathRegistry.class), instanceOf(DefaultClassPathRegistry.class));
+    }
+
+    @Test
+    public void providesAClassLoaderFactory() {
+        assertThat(registry.get(ClassLoaderFactory.class), instanceOf(DefaultClassLoaderFactory.class));
+    }
+
+    @Test
+    public void providesALoggingManagerFactory() {
+        assertThat(registry.get(LoggingManagerFactory.class), instanceOf(DefaultLoggingManagerFactory.class));
+    }
+    
+    @Test
+    public void providesAListenerManager() {
+        assertThat(registry.get(ListenerManager.class), instanceOf(DefaultListenerManager.class));
+    }
+    
+    @Test
+    public void providesAProgressLoggerFactory() {
+        assertThat(registry.get(ProgressLoggerFactory.class), instanceOf(DefaultProgressLoggerFactory.class));
+    }
+    
+    @Test
+    public void providesAGradleDistributionLocator() {
+        assertThat(registry.get(GradleDistributionLocator.class), instanceOf(DefaultClassPathProvider.class));
+    }
+    
+    @Test
+    public void providesAnIsolatedAntBuilder() {
+        assertThat(registry.get(IsolatedAntBuilder.class), instanceOf(DefaultIsolatedAntBuilder.class));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistryTest.java
new file mode 100644
index 0000000..bb577d6
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/GradleInternalServiceRegistryTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.project;
+
+import org.gradle.StartParameter;
+import org.gradle.api.artifacts.repositories.InternalRepository;
+import org.gradle.api.execution.TaskExecutionGraphListener;
+import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.PublishModuleDescriptorConverter;
+import org.gradle.api.internal.artifacts.repositories.DefaultInternalRepository;
+import org.gradle.api.internal.plugins.DefaultPluginRegistry;
+import org.gradle.api.internal.plugins.PluginRegistry;
+import org.gradle.execution.DefaultTaskGraphExecuter;
+import org.gradle.execution.TaskGraphExecuter;
+import org.gradle.listener.ListenerBroadcast;
+import org.gradle.listener.ListenerManager;
+import org.gradle.util.MultiParentClassLoader;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class GradleInternalServiceRegistryTest {
+    private final JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);    
+    }};
+    
+    private final GradleInternal gradle = context.mock(GradleInternal.class);
+    private final ServiceRegistry parent = context.mock(ServiceRegistry.class);
+    private final GradleInternalServiceRegistry registry = new GradleInternalServiceRegistry(parent, gradle);
+    private final StartParameter startParameter = new StartParameter();
+    private final PublishModuleDescriptorConverter publishModuleDescriptorConverter =
+            context.mock(PublishModuleDescriptorConverter.class);
+    private final ListenerManager listenerManager = context.mock(ListenerManager.class);
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations(){{
+            allowing(parent).get(PublishModuleDescriptorConverter.class);
+            will(returnValue(publishModuleDescriptorConverter));
+            allowing(parent).get(ListenerManager.class);
+            will(returnValue(listenerManager));
+            allowing(gradle).getStartParameter();
+            will(returnValue(startParameter));
+            allowing(gradle).getScriptClassLoader();
+            will(returnValue(new MultiParentClassLoader()));
+        }});
+    }
+
+    @Test
+    public void canCreateServicesForAProjectInstance() {
+        ProjectInternal project = context.mock(ProjectInternal.class);
+        ServiceRegistryFactory serviceRegistry = registry.createFor(project);
+        assertThat(serviceRegistry, instanceOf(ProjectInternalServiceRegistry.class));
+    }
+
+    @Test
+    public void providesAProjectRegistry() {
+        assertThat(registry.get(IProjectRegistry.class), instanceOf(DefaultProjectRegistry.class));
+        assertThat(registry.get(IProjectRegistry.class), sameInstance(registry.get(IProjectRegistry.class)));
+    }
+
+    @Test
+    public void providesAPluginRegistry() {
+        assertThat(registry.get(PluginRegistry.class), instanceOf(DefaultPluginRegistry.class));
+        assertThat(registry.get(PluginRegistry.class), sameInstance(registry.get(PluginRegistry.class)));
+    }
+
+    @Test
+    public void providesATaskGraphExecuter() {
+        context.checking(new Expectations(){{
+            one(listenerManager).createAnonymousBroadcaster(TaskExecutionGraphListener.class);
+            will(returnValue(new ListenerBroadcast<TaskExecutionGraphListener>(TaskExecutionGraphListener.class)));
+            one(listenerManager).createAnonymousBroadcaster(TaskExecutionListener.class);
+            will(returnValue(new ListenerBroadcast<TaskExecutionListener>(TaskExecutionListener.class)));
+        }});
+        assertThat(registry.get(TaskGraphExecuter.class), instanceOf(DefaultTaskGraphExecuter.class));
+        assertThat(registry.get(TaskGraphExecuter.class), sameInstance(registry.get(TaskGraphExecuter.class)));
+    }
+
+    @Test
+    public void providesAnInternalRepository() {
+        assertThat(registry.get(InternalRepository.class), instanceOf(DefaultInternalRepository.class));
+        assertThat(registry.get(InternalRepository.class), sameInstance(registry.get(InternalRepository.class)));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.java
new file mode 100644
index 0000000..9f85000
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/ProjectFactoryTest.java
@@ -0,0 +1,219 @@
+/*
+ * 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.project;
+
+import org.apache.commons.io.FileUtils;
+import org.gradle.StartParameter;
+import org.gradle.api.artifacts.dsl.RepositoryHandlerFactory;
+import org.gradle.api.initialization.ProjectDescriptor;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.DomainObjectContext;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.artifacts.ConfigurationContainerFactory;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.configurations.ResolverProvider;
+import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler;
+import org.gradle.api.plugins.Convention;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.groovy.scripts.StringScriptSource;
+import org.gradle.groovy.scripts.UriScriptSource;
+import org.gradle.util.MultiParentClassLoader;
+import org.gradle.util.TemporaryFolder;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class ProjectFactoryTest {
+    private final JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+    private final MultiParentClassLoader buildScriptClassLoader = new MultiParentClassLoader(getClass().getClassLoader());
+    @Rule
+    public TemporaryFolder testDir = new TemporaryFolder();
+    private final File rootDir = testDir.getDir();
+    private final File projectDir = new File(rootDir, "project");
+    private ConfigurationContainerFactory configurationContainerFactory = context.mock(
+            ConfigurationContainerFactory.class);
+    private RepositoryHandlerFactory repositoryHandlerFactory = context.mock(RepositoryHandlerFactory.class);
+    private DefaultRepositoryHandler repositoryHandler = context.mock(DefaultRepositoryHandler.class);
+    private StartParameter startParameterStub = new StartParameter();
+    private ServiceRegistryFactory serviceRegistryFactory = new TopLevelBuildServiceRegistry(new GlobalServicesRegistry(), startParameterStub);
+    private ClassGenerator classGeneratorMock = serviceRegistryFactory.get(ClassGenerator.class);
+    private GradleInternal gradle = context.mock(GradleInternal.class);
+
+    private ProjectFactory projectFactory;
+
+    @Before
+    public void setUp() throws Exception {
+        startParameterStub.setGradleUserHomeDir(testDir.createDir("home"));
+        context.checking(new Expectations() {{
+            allowing(repositoryHandlerFactory).createRepositoryHandler(with(any(Convention.class)));
+            will(returnValue(repositoryHandler));
+        }});
+        final ServiceRegistryFactory gradleServices = serviceRegistryFactory.createFor(gradle);
+        context.checking(new Expectations() {{
+            allowing(gradle).getServiceRegistryFactory();
+            will(returnValue(gradleServices));
+            allowing(gradle).getStartParameter();
+            will(returnValue(startParameterStub));
+            allowing(configurationContainerFactory).createConfigurationContainer(with(any(ResolverProvider.class)),
+                    with(any(DependencyMetaDataProvider.class)), with(any(DomainObjectContext.class)));
+            allowing(gradle).getProjectRegistry();
+            will(returnValue(gradleServices.get(IProjectRegistry.class)));
+            allowing(gradle).getScriptClassLoader();
+            will(returnValue(buildScriptClassLoader));
+            allowing(gradle).getGradleUserHomeDir();
+            will(returnValue(new File("gradleUserHomeDir")));
+            ignoring(gradle).getProjectEvaluationBroadcaster();
+        }});
+
+        projectFactory = new ProjectFactory(null, classGeneratorMock);
+    }
+
+    @Test
+    public void testConstructsRootProjectWithBuildFile() throws IOException {
+        File buildFile = new File(rootDir, "build.gradle");
+        FileUtils.writeStringToFile(buildFile, "build");
+        ProjectDescriptor descriptor = descriptor("somename", rootDir, buildFile, "build.gradle");
+
+        DefaultProject project = projectFactory.createProject(descriptor, null, gradle);
+
+        assertEquals("somename", project.getName());
+        assertEquals(buildFile, project.getBuildFile());
+        assertNull(project.getParent());
+        assertSame(rootDir, project.getRootDir());
+        assertSame(rootDir, project.getProjectDir());
+        assertSame(project, project.getRootProject());
+        checkProjectResources(project);
+
+        assertThat(project.getBuildScriptSource(), instanceOf(UriScriptSource.class));
+        assertThat(project.getBuildScriptSource().getDisplayName(), startsWith("build file "));
+        assertThat(project.getBuildScriptSource().getResource().getFile(), equalTo(buildFile));
+    }
+
+    @Test
+    public void testConstructsChildProjectWithBuildFile() throws IOException {
+        File buildFile = new File(projectDir, "build.gradle");
+        FileUtils.writeStringToFile(buildFile, "build");
+
+        ProjectDescriptor rootDescriptor = descriptor("root");
+        ProjectDescriptor parentDescriptor = descriptor("parent");
+        ProjectDescriptor projectDescriptor = descriptor("somename", projectDir, buildFile, "build.gradle");
+
+        DefaultProject rootProject = projectFactory.createProject(rootDescriptor, null, gradle);
+        DefaultProject parentProject = projectFactory.createProject(parentDescriptor, rootProject, gradle);
+
+        DefaultProject project = projectFactory.createProject(projectDescriptor, parentProject, gradle);
+
+        assertEquals("somename", project.getName());
+        assertEquals(buildFile, project.getBuildFile());
+        assertSame(parentProject, project.getParent());
+        assertSame(rootDir, project.getRootDir());
+        assertSame(projectDir, project.getProjectDir());
+        assertSame(rootProject, project.getRootProject());
+        assertSame(project, parentProject.getChildProjects().get("somename"));
+        checkProjectResources(project);
+
+        assertThat(project.getBuildScriptSource(), instanceOf(UriScriptSource.class));
+        assertThat(project.getBuildScriptSource().getDisplayName(), startsWith("build file "));
+        assertThat(project.getBuildScriptSource().getResource().getFile(), equalTo(buildFile));
+    }
+
+    @Test
+    public void testAddsProjectToProjectRegistry() throws IOException {
+        ProjectDescriptor rootDescriptor = descriptor("root");
+        ProjectDescriptor parentDescriptor = descriptor("somename");
+
+        DefaultProject rootProject = projectFactory.createProject(rootDescriptor, null, gradle);
+        DefaultProject project = projectFactory.createProject(parentDescriptor, rootProject, gradle);
+
+        assertThat(gradle.getProjectRegistry().getProject(":somename"), sameInstance((ProjectIdentifier) project));
+    }
+
+    @Test
+    public void testUsesEmptyBuildFileWhenBuildFileIsMissing() {
+
+        DefaultProject rootProject = projectFactory.createProject(descriptor("root"), null, gradle);
+        DefaultProject project = projectFactory.createProject(descriptor("somename", projectDir), rootProject, gradle);
+
+        assertThat(project.getBuildScriptSource(), instanceOf(StringScriptSource.class));
+        assertThat(project.getBuildScriptSource().getDisplayName(), equalTo("empty build file"));
+        assertThat(project.getBuildScriptSource().getResource().getText(), equalTo(""));
+    }
+
+    @Test
+    public void testConstructsRootProjectWithEmbeddedBuildScript() {
+        ScriptSource expectedScriptSource = new StringScriptSource("script", "content");
+
+        ProjectFactory projectFactory = new ProjectFactory(expectedScriptSource, classGeneratorMock);
+
+        DefaultProject project = projectFactory.createProject(descriptor("somename"), null, gradle);
+
+        assertEquals("somename", project.getName());
+        assertSame(rootDir, project.getRootDir());
+        assertSame(rootDir, project.getProjectDir());
+        assertNull(project.getParent());
+        assertSame(project, project.getRootProject());
+        assertNotNull(project.getConvention());
+        checkProjectResources(project);
+        assertSame(project.getBuildScriptSource(), expectedScriptSource);
+    }
+
+    private ProjectDescriptor descriptor(String name) {
+        return descriptor(name, rootDir);
+    }
+
+    private ProjectDescriptor descriptor(String name, File projectDir) {
+        return descriptor(name, projectDir, new File(projectDir, "build.gradle"), "build.gradle");
+    }
+
+    private ProjectDescriptor descriptor(final String name, final File projectDir, final File buildFile,
+                                         final String buildFileName) {
+        final ProjectDescriptor descriptor = context.mock(ProjectDescriptor.class, name);
+
+        context.checking(new Expectations() {{
+            allowing(descriptor).getName();
+            will(returnValue(name));
+            allowing(descriptor).getProjectDir();
+            will(returnValue(projectDir));
+            allowing(descriptor).getBuildFile();
+            will(returnValue(buildFile));
+            allowing(descriptor).getBuildFileName();
+            will(returnValue(buildFileName));
+        }});
+        return descriptor;
+    }
+
+    private void checkProjectResources(DefaultProject project) {
+        assertSame(gradle, project.getGradle());
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistryTest.java
new file mode 100644
index 0000000..2800b91
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/ProjectInternalServiceRegistryTest.java
@@ -0,0 +1,236 @@
+/*
+ * 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.project;
+
+import org.gradle.api.artifacts.ConfigurationContainer;
+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.artifacts.dsl.RepositoryHandlerFactory;
+import org.gradle.api.initialization.dsl.ScriptHandler;
+import org.gradle.api.internal.AsmBackedClassGenerator;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.artifacts.ConfigurationContainerFactory;
+import org.gradle.api.internal.artifacts.configurations.DependencyMetaDataProvider;
+import org.gradle.api.internal.artifacts.dsl.DefaultArtifactHandler;
+import org.gradle.api.internal.artifacts.dsl.PublishArtifactFactory;
+import org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler;
+import org.gradle.api.internal.artifacts.dsl.dependencies.DependencyFactory;
+import org.gradle.api.internal.file.*;
+import org.gradle.api.internal.initialization.DefaultScriptHandler;
+import org.gradle.api.internal.initialization.ScriptClassLoaderProvider;
+import org.gradle.api.internal.plugins.DefaultConvention;
+import org.gradle.api.internal.plugins.DefaultProjectsPluginContainer;
+import org.gradle.api.internal.plugins.PluginRegistry;
+import org.gradle.api.internal.project.taskfactory.ITaskFactory;
+import org.gradle.api.internal.tasks.DefaultTaskContainer;
+import org.gradle.api.logging.LoggingManager;
+import org.gradle.api.plugins.Convention;
+import org.gradle.api.plugins.PluginContainer;
+import org.gradle.api.tasks.TaskContainer;
+import org.gradle.logging.LoggingManagerFactory;
+import org.gradle.logging.LoggingManagerInternal;
+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 static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class ProjectInternalServiceRegistryTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final ProjectInternal project = context.mock(ProjectInternal.class);
+    private final ConfigurationContainer configurationContainer = context.mock(ConfigurationContainer.class);
+    private final GradleInternal gradle = context.mock(GradleInternal.class);
+    private final ConfigurationContainerFactory configurationContainerFactory = context.mock(
+            ConfigurationContainerFactory.class);
+    private final RepositoryHandlerFactory repositoryHandlerFactory = context.mock(RepositoryHandlerFactory.class);
+    private final ITaskFactory taskFactory = context.mock(ITaskFactory.class);
+    private final PublishArtifactFactory publishArtifactFactory = context.mock(PublishArtifactFactory.class);
+    private final DependencyFactory dependencyFactory = context.mock(DependencyFactory.class);
+    private final ServiceRegistry parent = context.mock(ServiceRegistry.class);
+    private final ProjectInternalServiceRegistry registry = new ProjectInternalServiceRegistry(parent, project);
+    private final PluginRegistry pluginRegistry = context.mock(PluginRegistry.class);
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations() {{
+            allowing(project).getGradle();
+            will(returnValue(gradle));
+            allowing(project).getProjectDir();
+            will(returnValue(new File("project-dir")));
+            allowing(project).getBuildScriptSource();
+            allowing(parent).get(ITaskFactory.class);
+            will(returnValue(taskFactory));
+            allowing(parent).get(RepositoryHandlerFactory.class);
+            will(returnValue(repositoryHandlerFactory));
+            allowing(parent).get(ConfigurationContainerFactory.class);
+            will(returnValue(configurationContainerFactory));
+            allowing(parent).get(PublishArtifactFactory.class);
+            will(returnValue(publishArtifactFactory));
+            allowing(parent).get(DependencyFactory.class);
+            will(returnValue(dependencyFactory));
+            allowing(parent).get(PluginRegistry.class);
+            will(returnValue(pluginRegistry));
+            allowing(parent).get(ClassGenerator.class);
+            will(returnValue(new AsmBackedClassGenerator()));
+        }});
+    }
+
+    @Test
+    public void createsARegistryForATask() {
+        ServiceRegistryFactory taskRegistry = registry.createFor(context.mock(TaskInternal.class));
+        assertThat(taskRegistry, instanceOf(TaskInternalServiceRegistry.class));
+    }
+
+    @Test
+    public void providesAConvention() {
+        assertThat(registry.get(Convention.class), instanceOf(DefaultConvention.class));
+        assertThat(registry.get(Convention.class), sameInstance(registry.get(Convention.class)));
+    }
+
+    @Test
+    public void providesATaskContainer() {
+        assertThat(registry.get(TaskContainer.class), instanceOf(DefaultTaskContainer.class));
+        assertThat(registry.get(TaskContainer.class), sameInstance(registry.get(TaskContainer.class)));
+    }
+
+    @Test
+    public void providesAPluginContainer() {
+        expectScriptClassLoaderProviderCreated();
+        context.checking(new Expectations() {{
+            one(pluginRegistry).createChild(with(notNullValue(ClassLoader.class)));
+        }});
+
+        assertThat(registry.get(PluginContainer.class), instanceOf(DefaultProjectsPluginContainer.class));
+        assertThat(registry.get(PluginContainer.class), sameInstance(registry.get(PluginContainer.class)));
+    }
+
+    @Test
+    public void providesARepositoryHandler() {
+        final RepositoryHandler repositoryHandler = context.mock(RepositoryHandler.class);
+
+        context.checking(new Expectations() {{
+            one(repositoryHandlerFactory).createRepositoryHandler(with(any(Convention.class)));
+            will(returnValue(repositoryHandler));
+        }});
+
+        assertThat(registry.get(RepositoryHandler.class), sameInstance(repositoryHandler));
+        assertThat(registry.get(RepositoryHandler.class), sameInstance(registry.get(RepositoryHandler.class)));
+    }
+
+    @Test
+    public void providesAnArtifactHandler() {
+        expectConfigurationHandlerCreated();
+
+        assertThat(registry.get(ArtifactHandler.class), instanceOf(DefaultArtifactHandler.class));
+        assertThat(registry.get(ArtifactHandler.class), sameInstance(registry.get(ArtifactHandler.class)));
+    }
+
+    @Test
+    public void providesADependencyHandler() {
+        expectConfigurationHandlerCreated();
+
+        assertThat(registry.get(DependencyHandler.class), instanceOf(DefaultDependencyHandler.class));
+        assertThat(registry.get(DependencyHandler.class), sameInstance(registry.get(DependencyHandler.class)));
+    }
+
+    @Test
+    public void providesAnAntBuilderFactory() {
+        assertThat(registry.get(AntBuilderFactory.class), instanceOf(DefaultAntBuilderFactory.class));
+        assertThat(registry.get(AntBuilderFactory.class), sameInstance(registry.get(AntBuilderFactory.class)));
+    }
+
+    @Test
+    public void providesAScriptHandlerAndScriptClassLoaderProvider() {
+        expectScriptClassLoaderProviderCreated();
+
+        assertThat(registry.get(ScriptHandler.class), instanceOf(DefaultScriptHandler.class));
+        assertThat(registry.get(ScriptHandler.class), sameInstance(registry.get(ScriptHandler.class)));
+        assertThat(registry.get(ScriptClassLoaderProvider.class), sameInstance((Object) registry.get(
+                ScriptHandler.class)));
+    }
+
+    @Test
+    public void providesAFileResolver() {
+        assertThat(registry.get(FileResolver.class), instanceOf(BaseDirConverter.class));
+        assertThat(registry.get(FileResolver.class), sameInstance(registry.get(FileResolver.class)));
+    }
+
+    @Test
+    public void providesAFileOperationsInstance() {
+        assertThat(registry.get(FileOperations.class), instanceOf(DefaultFileOperations.class));
+        assertThat(registry.get(FileOperations.class), sameInstance(registry.get(FileOperations.class)));
+    }
+    
+    @Test
+    public void providesATemporaryFileProvider() {
+        assertThat(registry.get(TemporaryFileProvider.class), instanceOf(DefaultTemporaryFileProvider.class));
+        assertThat(registry.get(TemporaryFileProvider.class), sameInstance(registry.get(TemporaryFileProvider.class)));
+    }
+    
+    @Test
+    public void providesALoggingManager() {
+        final LoggingManagerFactory loggingManagerFactory = context.mock(LoggingManagerFactory.class);
+        final LoggingManager loggingManager = context.mock(LoggingManagerInternal.class);
+
+        context.checking(new Expectations(){{
+            allowing(parent).get(LoggingManagerFactory.class);
+            will(returnValue(loggingManagerFactory));
+            one(loggingManagerFactory).create();
+            will(returnValue(loggingManager));
+        }});
+
+        assertThat(registry.get(LoggingManager.class), sameInstance(loggingManager));
+        assertThat(registry.get(LoggingManager.class), sameInstance(registry.get(LoggingManager.class)));
+    }
+
+    private void expectScriptClassLoaderProviderCreated() {
+        expectConfigurationHandlerCreated();
+        
+        context.checking(new Expectations() {{
+            allowing(project).getParent();
+            will(returnValue(null));
+
+            allowing(gradle).getScriptClassLoader();
+            will(returnValue(null));
+
+            ignoring(configurationContainer);
+        }});
+    }
+
+    private void expectConfigurationHandlerCreated() {
+        context.checking(new Expectations() {{
+            RepositoryHandler repositoryHandler = context.mock(RepositoryHandler.class);
+
+            one(repositoryHandlerFactory).createRepositoryHandler(with(notNullValue(Convention.class)));
+            will(returnValue(repositoryHandler));
+
+            one(configurationContainerFactory).createConfigurationContainer(with(sameInstance(repositoryHandler)), with(
+                    notNullValue(DependencyMetaDataProvider.class)), with(sameInstance(project)));
+            will(returnValue(configurationContainer));
+        }});
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistryTest.java
new file mode 100644
index 0000000..65e3a0c
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/TaskInternalServiceRegistryTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.project;
+
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.TaskOutputsInternal;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.tasks.DefaultTaskInputs;
+import org.gradle.api.internal.tasks.DefaultTaskOutputs;
+import org.gradle.api.logging.LoggingManager;
+import org.gradle.api.tasks.TaskInputs;
+import org.gradle.logging.LoggingManagerFactory;
+import org.gradle.logging.LoggingManagerInternal;
+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 static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class TaskInternalServiceRegistryTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final ServiceRegistry parent = context.mock(ServiceRegistry.class);
+    private final ProjectInternal project = context.mock(ProjectInternal.class);
+    private final TaskInternal task = context.mock(TaskInternal.class);
+    private final TaskInternalServiceRegistry registry = new TaskInternalServiceRegistry(parent, project, task);
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations() {{
+            allowing(project).getFileResolver();
+            will(returnValue(context.mock(FileResolver.class)));
+        }});
+    }
+
+    @Test
+    public void createsATaskInputsInstance() {
+        TaskInputs inputs = registry.get(TaskInputs.class);
+        assertThat(inputs, instanceOf(DefaultTaskInputs.class));
+    }
+
+    @Test
+    public void createsATaskOutputsInternalInstance() {
+        TaskOutputsInternal outputs = registry.get(TaskOutputsInternal.class);
+        assertThat(outputs, instanceOf(DefaultTaskOutputs.class));
+    }
+    
+    @Test
+    public void createsALoggingManagerAndStdOutputCapture() {
+        final LoggingManagerFactory loggingManagerFactory = context.mock(LoggingManagerFactory.class);
+        final LoggingManager loggingManager = context.mock(LoggingManagerInternal.class);
+
+        context.checking(new Expectations(){{
+            allowing(parent).get(LoggingManagerFactory.class);
+            will(returnValue(loggingManagerFactory));
+            one(loggingManagerFactory).create();
+            will(returnValue(loggingManager));
+        }});
+
+        assertThat(registry.get(LoggingManager.class), sameInstance(loggingManager));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/TestPlugin1.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/TestPlugin1.groovy
new file mode 100644
index 0000000..3dc70b5
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/TestPlugin1.groovy
@@ -0,0 +1,32 @@
+/*
+ * 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.project
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+/**
+* @author Hans Dockter
+*/
+class TestPlugin1 implements Plugin<Project> {
+
+    int applyCounter = 0
+
+    void apply(Project project) {
+        applyCounter++
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/TestPlugin2.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/TestPlugin2.groovy
new file mode 100644
index 0000000..b68f716
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/TestPlugin2.groovy
@@ -0,0 +1,28 @@
+/*
+ * 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.project
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+/**
+* @author Hans Dockter
+*/
+class TestPlugin2 implements Plugin<Project> {
+    void apply(Project project) {
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistryTest.java
new file mode 100644
index 0000000..c710aaf
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/TopLevelBuildServiceRegistryTest.java
@@ -0,0 +1,235 @@
+/*
+ * 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.project;
+
+import org.gradle.StartParameter;
+import org.gradle.api.artifacts.dsl.RepositoryHandlerFactory;
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.ExceptionAnalyser;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.artifacts.dsl.DefaultPublishArtifactFactory;
+import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandlerFactory;
+import org.gradle.api.internal.artifacts.dsl.PublishArtifactFactory;
+import org.gradle.api.internal.tasks.ExecuteAtMostOnceTaskExecuter;
+import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.cache.CacheFactory;
+import org.gradle.cache.CacheRepository;
+import org.gradle.cache.DefaultCacheRepository;
+import org.gradle.configuration.DefaultScriptPluginFactory;
+import org.gradle.configuration.ScriptPluginFactory;
+import org.gradle.groovy.scripts.DefaultScriptCompilerFactory;
+import org.gradle.groovy.scripts.ScriptCompilerFactory;
+import org.gradle.initialization.*;
+import org.gradle.listener.DefaultListenerManager;
+import org.gradle.listener.ListenerManager;
+import org.gradle.logging.LoggingManagerFactory;
+import org.gradle.logging.ProgressLoggerFactory;
+import org.gradle.messaging.concurrent.DefaultExecutorFactory;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.gradle.process.internal.DefaultWorkerProcessFactory;
+import org.gradle.process.internal.WorkerProcessFactory;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.gradle.util.MultiParentClassLoader;
+import org.gradle.util.TemporaryFolder;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.Collections;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class TopLevelBuildServiceRegistryTest {
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private final ServiceRegistry parent = context.mock(ServiceRegistry.class);
+    private final StartParameter startParameter = new StartParameter();
+    private final CacheFactory cacheFactory = context.mock(CacheFactory.class);
+    private final ClassPathRegistry classPathRegistry = context.mock(ClassPathRegistry.class);
+    private final TopLevelBuildServiceRegistry factory = new TopLevelBuildServiceRegistry(parent, startParameter);
+    private final ClassLoaderFactory classLoaderFactory = context.mock(ClassLoaderFactory.class);
+    private final LoggingManagerFactory loggingManagerFactory = context.mock(LoggingManagerFactory.class);
+    private final ProgressLoggerFactory progressLoggerFactory = context.mock(ProgressLoggerFactory.class);
+
+    @Before
+    public void setUp() {
+        startParameter.setGradleUserHomeDir(tmpDir.getDir());
+        context.checking(new Expectations(){{
+            allowing(parent).get(CacheFactory.class);
+            will(returnValue(cacheFactory));
+            allowing(parent).get(ClassPathRegistry.class);
+            will(returnValue(classPathRegistry));
+            allowing(parent).get(ClassLoaderFactory.class);
+            will(returnValue(classLoaderFactory));
+            allowing(parent).get(LoggingManagerFactory.class);
+            will(returnValue(loggingManagerFactory));
+            allowing(parent).get(ProgressLoggerFactory.class);
+            will(returnValue(progressLoggerFactory));
+        }});
+    }
+    
+    @Test
+    public void delegatesToParentForUnknownService() {
+        context.checking(new Expectations(){{
+            allowing(parent).get(String.class);
+            will(returnValue("value"));
+        }});
+
+        assertThat(factory.get(String.class), equalTo("value"));
+    }
+
+    @Test
+    public void throwsExceptionForUnknownDomainObject() {
+        try {
+            factory.createFor("string");
+            fail();
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage(), equalTo("Cannot create services for unknown domain object of type String."));
+        }
+    }
+
+    @Test
+    public void canCreateServicesForAGradleInstance() {
+        GradleInternal gradle = context.mock(GradleInternal.class);
+        ServiceRegistryFactory registry = factory.createFor(gradle);
+        assertThat(registry, instanceOf(GradleInternalServiceRegistry.class));
+    }
+
+    @Test
+    public void providesAListenerManager() {
+        ListenerManager listenerManager = expectListenerManagerCreated();
+        assertThat(factory.get(ListenerManager.class), sameInstance(listenerManager));
+    }
+
+    @Test
+    public void providesAPublishArtifactFactory() {
+        assertThat(factory.get(PublishArtifactFactory.class), instanceOf(DefaultPublishArtifactFactory.class));
+        assertThat(factory.get(PublishArtifactFactory.class), sameInstance(factory.get(PublishArtifactFactory.class)));
+    }
+
+    @Test
+    public void providesATaskExecuter() {
+        expectListenerManagerCreated();
+        context.checking(new Expectations(){{
+            allowing(cacheFactory).open(with(notNullValue(File.class)), with(equalTo(startParameter.getCacheUsage())), with(equalTo(Collections.EMPTY_MAP)));
+        }});
+        assertThat(factory.get(TaskExecuter.class), instanceOf(ExecuteAtMostOnceTaskExecuter.class));
+        assertThat(factory.get(TaskExecuter.class), sameInstance(factory.get(TaskExecuter.class)));
+    }
+
+    @Test
+    public void providesARepositoryHandlerFactory() {
+        assertThat(factory.get(RepositoryHandlerFactory.class), instanceOf(DefaultRepositoryHandlerFactory.class));
+        assertThat(factory.get(RepositoryHandlerFactory.class), sameInstance(factory.get(
+                RepositoryHandlerFactory.class)));
+    }
+
+    @Test
+    public void providesAScriptCompilerFactory() {
+        expectListenerManagerCreated();
+        assertThat(factory.get(ScriptCompilerFactory.class), instanceOf(DefaultScriptCompilerFactory.class));
+        assertThat(factory.get(ScriptCompilerFactory.class), sameInstance(factory.get(ScriptCompilerFactory.class)));
+    }
+
+    @Test
+    public void providesACacheRepository() {
+        assertThat(factory.get(CacheRepository.class), instanceOf(DefaultCacheRepository.class));
+        assertThat(factory.get(CacheRepository.class), sameInstance(factory.get(CacheRepository.class)));
+    }
+
+    @Test
+    public void providesAnInitScriptHandler() {
+        expectScriptClassLoaderCreated();
+        expectListenerManagerCreated();
+        assertThat(factory.get(InitScriptHandler.class), instanceOf(InitScriptHandler.class));
+        assertThat(factory.get(InitScriptHandler.class), sameInstance(factory.get(InitScriptHandler.class)));
+    }
+
+    @Test
+    public void providesAScriptObjectConfigurerFactory() {
+        expectListenerManagerCreated();
+        expectScriptClassLoaderCreated();
+        assertThat(factory.get(ScriptPluginFactory.class), instanceOf(DefaultScriptPluginFactory.class));
+        assertThat(factory.get(ScriptPluginFactory.class), sameInstance(factory.get(ScriptPluginFactory.class)));
+    }
+
+    @Test
+    public void providesASettingsProcessor() {
+        expectListenerManagerCreated();
+        expectScriptClassLoaderCreated();
+        assertThat(factory.get(SettingsProcessor.class), instanceOf(PropertiesLoadingSettingsProcessor.class));
+        assertThat(factory.get(SettingsProcessor.class), sameInstance(factory.get(SettingsProcessor.class)));
+    }
+
+    @Test
+    public void providesAnExceptionAnalyser() {
+        expectListenerManagerCreated();
+        assertThat(factory.get(ExceptionAnalyser.class), instanceOf(DefaultExceptionAnalyser.class));
+        assertThat(factory.get(ExceptionAnalyser.class), sameInstance(factory.get(ExceptionAnalyser.class)));
+    }
+
+    @Test
+    public void providesAWorkerProcessFactory() {
+        context.checking(new Expectations() {{
+            one(classLoaderFactory).getRootClassLoader();
+            will(returnValue(new ClassLoader() {
+            }));
+        }});
+
+        assertThat(factory.get(WorkerProcessFactory.class), instanceOf(DefaultWorkerProcessFactory.class));
+        assertThat(factory.get(WorkerProcessFactory.class), sameInstance(factory.get(WorkerProcessFactory.class)));
+    }
+
+    @Test
+    public void providesAProjectFactory() {
+        assertThat(factory.get(IProjectFactory.class), instanceOf(ProjectFactory.class));
+        assertThat(factory.get(IProjectFactory.class), sameInstance(factory.get(IProjectFactory.class)));
+    }
+
+    @Test
+    public void providesAnExecutorFactory() {
+        assertThat(factory.get(ExecutorFactory.class), instanceOf(DefaultExecutorFactory.class));
+        assertThat(factory.get(ExecutorFactory.class), sameInstance(factory.get(ExecutorFactory.class)));
+    }
+
+    private ListenerManager expectListenerManagerCreated() {
+        final ListenerManager listenerManager = new DefaultListenerManager();
+        context.checking(new Expectations(){{
+            allowing(parent).get(ListenerManager.class);
+            ListenerManager parent = context.mock(ListenerManager.class);
+            will(returnValue(parent));
+            one(parent).createChild();
+            will(returnValue(listenerManager));
+        }});
+        return listenerManager;
+    }
+
+    private void expectScriptClassLoaderCreated() {
+        context.checking(new Expectations() {{
+            one(classLoaderFactory).createScriptClassLoader();
+            will(returnValue(new MultiParentClassLoader()));
+        }});
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactoryTest.java
new file mode 100644
index 0000000..ec49817
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactoryTest.java
@@ -0,0 +1,714 @@
+/*
+ * 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.project.taskfactory;
+
+import org.gradle.api.*;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.project.DefaultProject;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.tasks.*;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.TemporaryFolder;
+import org.gradle.util.TestFile;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+
+import static org.gradle.util.Matchers.*;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class AnnotationProcessingTaskFactoryTest {
+    private final JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+
+    private final ITaskFactory delegate = context.mock(ITaskFactory.class);
+    private final ProjectInternal project = context.mock(ProjectInternal.class);
+    private final Map args = new HashMap();
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+    private final TestFile testDir = tmpDir.getDir();
+    private final File existingFile = testDir.file("file.txt").touch();
+    private final File missingFile = testDir.file("missing.txt");
+    private final TestFile existingDir = testDir.file("dir").createDir();
+    private final File missingDir = testDir.file("missing-dir");
+    private final AnnotationProcessingTaskFactory factory = new AnnotationProcessingTaskFactory(delegate);
+
+    @Test
+    public void attachesAnActionToTaskForMethodMarkedWithTaskActionAnnotation() {
+        final Runnable action = context.mock(Runnable.class);
+        final TestTask task = expectTaskCreated(TestTask.class, action);
+
+        context.checking(new Expectations() {{
+            one(action).run();
+        }});
+        task.execute();
+    }
+
+    private <T extends Task> T expectTaskCreated(final Class<T> type, final Object... params) {
+        DefaultProject project = HelperUtil.createRootProject();
+        T task = AbstractTask.injectIntoNewInstance(project, "task", new Callable<T>() {
+            public T call() throws Exception {
+                if (params.length > 0) {
+                    return type.cast(type.getConstructors()[0].newInstance(params));
+                } else {
+                    return type.newInstance();
+                }
+            }
+        });
+        return expectTaskCreated(task);
+    }
+
+    private <T extends Task> T expectTaskCreated(final T task) {
+        context.checking(new Expectations() {{
+            one(delegate).createTask(project, args);
+            will(returnValue(task));
+        }});
+
+        assertThat(factory.createTask(project, args), sameInstance((Object) task));
+        return task;
+    }
+
+    @Test
+    public void doesNothingToTaskWithNoTaskActionAnnotations() {
+        TaskInternal task = expectTaskCreated(DefaultTask.class);
+
+        assertThat(task.getActions(), isEmpty());
+    }
+
+    @Test
+    public void propagatesExceptionThrownByTaskActionMethod() {
+        final Runnable action = context.mock(Runnable.class);
+        TestTask task = expectTaskCreated(TestTask.class, action);
+
+        final RuntimeException failure = new RuntimeException();
+        context.checking(new Expectations() {{
+            one(action).run();
+            will(throwException(failure));
+        }});
+
+        try {
+            task.getActions().get(0).execute(task);
+            fail();
+        } catch (RuntimeException e) {
+            assertThat(e, sameInstance(failure));
+        }
+    }
+
+    @Test
+    public void canHaveMultipleMethodsWithTaskActionAnnotation() {
+        final Runnable action = context.mock(Runnable.class);
+        TaskWithMultipleMethods task = expectTaskCreated(TaskWithMultipleMethods.class, action);
+
+        context.checking(new Expectations() {{
+            exactly(3).of(action).run();
+        }});
+
+        task.execute();
+    }
+
+    @Test
+    public void cachesClassMetaInfo() {
+        TaskWithInputFile task = expectTaskCreated(TaskWithInputFile.class, existingFile);
+        TaskWithInputFile task2 = expectTaskCreated(TaskWithInputFile.class, missingFile);
+
+        assertThat(task.getActions().get(0), sameInstance((Action) task2.getActions().get(0)));
+    }
+    
+    @Test
+    public void failsWhenStaticMethodHasTaskActionAnnotation() {
+        assertTaskCreationFails(TaskWithStaticMethod.class,
+                "Cannot use @TaskAction annotation on static method TaskWithStaticMethod.doStuff().");
+    }
+
+    @Test
+    public void failsWhenMethodWithParametersHasTaskActionAnnotation() {
+        assertTaskCreationFails(TaskWithParamMethod.class,
+                "Cannot use @TaskAction annotation on method TaskWithParamMethod.doStuff() as this method takes parameters.");
+    }
+
+    private void assertTaskCreationFails(Class<? extends Task> type, String message) {
+        try {
+            expectTaskCreated(type);
+            fail();
+        } catch (GradleException e) {
+            assertThat(e.getMessage(), equalTo(message));
+        }
+    }
+
+    @Test
+    public void taskActionWorksForInheritedMethods() {
+        final Runnable action = context.mock(Runnable.class);
+        TaskWithInheritedMethod task = expectTaskCreated(TaskWithInheritedMethod.class, action);
+
+        context.checking(new Expectations() {{
+            one(action).run();
+        }});
+        task.execute();
+    }
+    
+    @Test
+    public void taskActionWorksForOverriddenMethods() {
+        final Runnable action = context.mock(Runnable.class);
+        TaskWithOverriddenMethod task = expectTaskCreated(TaskWithOverriddenMethod.class, action);
+
+        context.checking(new Expectations() {{
+            one(action).run();
+        }});
+        task.execute();
+    }
+
+    @Test
+    public void taskActionWorksForProtectedMethods() {
+        final Runnable action = context.mock(Runnable.class);
+        TaskWithProtectedMethod task = expectTaskCreated(TaskWithProtectedMethod.class, action);
+
+        context.checking(new Expectations() {{
+            one(action).run();
+        }});
+        task.execute();
+    }
+
+    @Test
+    public void validationActionSucceedsWhenSpecifiedInputFileExists() {
+        TaskWithInputFile task = expectTaskCreated(TaskWithInputFile.class, existingFile);
+        task.execute();
+    }
+
+    @Test
+    public void validationActionFailsWhenInputFileNotSpecified() {
+        TaskWithInputFile task = expectTaskCreated(TaskWithInputFile.class, new Object[]{null});
+        assertValidationFails(task, "Error validating task ':task': No value has been specified for property 'inputFile'.");
+    }
+
+    @Test
+    public void validationActionFailsWhenInputFileDoesNotExist() {
+        TaskWithInputFile task = expectTaskCreated(TaskWithInputFile.class, missingFile);
+        assertValidationFails(task, String.format("Error validating task ':task': File '%s' specified for property 'inputFile' does not exist.",
+                task.inputFile));
+    }
+
+    @Test
+    public void validationActionFailsWhenInputFileIsADirectory() {
+        TaskWithInputFile task = expectTaskCreated(TaskWithInputFile.class, existingDir);
+        assertValidationFails(task, String.format("Error validating task ':task': File '%s' specified for property 'inputFile' is not a file.",
+                task.inputFile));
+    }
+
+    @Test
+    public void registersSpecifiedInputFile() {
+        TaskWithInputFile task = expectTaskCreated(TaskWithInputFile.class, existingFile);
+        assertThat(task.getInputs().getFiles().getFiles(), equalTo(toSet(existingFile)));
+    }
+
+    @Test
+    public void doesNotRegistersInputFileWhenNoneSpecified() {
+        TaskWithInputFile task = expectTaskCreated(TaskWithInputFile.class, new Object[]{null});
+        assertThat(task.getInputs().getFiles().getFiles(), isEmpty());
+    }
+    
+    @Test
+    public void validationActionSucceedsWhenSpecifiedOutputFileIsAFile() {
+        TaskWithOutputFile task = expectTaskCreated(TaskWithOutputFile.class, existingFile);
+        task.execute();
+    }
+
+    @Test
+    public void validationActionSucceedsWhenSpecifiedOutputFileIsNotAFile() {
+        TaskWithOutputFile task = expectTaskCreated(TaskWithOutputFile.class, new File(testDir, "subdir/output.txt"));
+
+        task.execute();
+
+        assertTrue(new File(testDir, "subdir").isDirectory());
+    }
+
+    @Test
+    public void validationActionFailsWhenOutputFileNotSpecified() {
+        TaskWithOutputFile task = expectTaskCreated(TaskWithOutputFile.class, new Object[]{null});
+        assertValidationFails(task, "Error validating task ':task': No value has been specified for property 'outputFile'.");
+    }
+
+    @Test
+    public void validationActionFailsWhenSpecifiedOutputFileIsADirectory() {
+        TaskWithOutputFile task = expectTaskCreated(TaskWithOutputFile.class, existingDir);
+        assertValidationFails(task, String.format(
+                "Error validating task ':task': Cannot write to file '%s' specified for property 'outputFile' as it is a directory.",
+                task.outputFile));
+    }
+
+    @Test
+    public void validationActionFailsWhenSpecifiedOutputFileParentIsAFile() {
+        TaskWithOutputFile task = expectTaskCreated(TaskWithOutputFile.class, new File(testDir, "subdir/output.txt"));
+        GFileUtils.touch(task.outputFile.getParentFile());
+
+        assertValidationFails(task, String.format(
+                "Error validating task ':task': Cannot create parent directory '%s' of file specified for property 'outputFile'.",
+                task.outputFile.getParentFile()));
+    }
+
+    @Test
+    public void registersSpecifiedOutputFile() {
+        TaskWithOutputFile task = expectTaskCreated(TaskWithOutputFile.class, existingFile);
+        assertThat(task.getOutputs().getFiles().getFiles(), equalTo(toSet(existingFile)));
+    }
+
+    @Test
+    public void doesNotRegisterOutputFileWhenNoneSpecified() {
+        TaskWithOutputFile task = expectTaskCreated(TaskWithOutputFile.class, new Object[]{null});
+        assertThat(task.getOutputs().getFiles().getFiles(), isEmpty());
+    }
+
+    @Test
+    public void validationActionSucceedsWhenInputFilesSpecified() {
+        TaskWithInputFiles task = expectTaskCreated(TaskWithInputFiles.class, toList(testDir));
+        task.execute();
+    }
+
+    @Test
+    public void validationActionFailsWhenInputFilesNotSpecified() {
+        TaskWithInputFiles task = expectTaskCreated(TaskWithInputFiles.class, new Object[]{null});
+        assertValidationFails(task, "Error validating task ':task': No value has been specified for property 'input'.");
+    }
+
+    @Test
+    public void registersSpecifiedInputFiles() {
+        TaskWithInputFiles task = expectTaskCreated(TaskWithInputFiles.class, toList(testDir, missingFile));
+        assertThat(task.getInputs().getFiles().getFiles(), equalTo(toSet(testDir, missingFile)));
+    }
+
+    @Test
+    public void doesNotRegisterInputFilesWhenNoneSpecified() {
+        TaskWithInputFiles task = expectTaskCreated(TaskWithInputFiles.class, new Object[]{null});
+        assertThat(task.getInputs().getFiles().getFiles(), isEmpty());
+    }
+
+    @Test
+    public void skipsTaskWhenInputFileCollectionIsEmpty() {
+        final FileCollection inputFiles = context.mock(FileCollection.class);
+        context.checking(new Expectations() {{
+            one(inputFiles).stopExecutionIfEmpty();
+            will(throwException(new StopExecutionException()));
+        }});
+
+        TaskWithInputFiles task = expectTaskCreated(BrokenTaskWithInputFiles.class, inputFiles);
+
+        task.execute();
+    }
+
+    @Test
+    public void validationActionSucceedsWhenSpecifiedOutputDirectoryDoesNotExist() {
+        TaskWithOutputDir task = expectTaskCreated(TaskWithOutputDir.class, missingDir);
+        task.execute();
+
+        assertTrue(task.outputDir.isDirectory());
+    }
+
+    @Test
+    public void validationActionSucceedsWhenSpecifiedOutputDirectoryIsDirectory() {
+        TaskWithOutputDir task = expectTaskCreated(TaskWithOutputDir.class, existingDir);
+        task.execute();
+    }
+
+    @Test
+    public void validationActionFailsWhenOutputDirectoryNotSpecified() {
+        TaskWithOutputDir task = expectTaskCreated(TaskWithOutputDir.class, new Object[]{null});
+        assertValidationFails(task, "Error validating task ':task': No value has been specified for property 'outputDir'.");
+    }
+
+    @Test
+    public void validationActionFailsWhenOutputDirectoryIsAFile() {
+        TaskWithOutputDir task = expectTaskCreated(TaskWithOutputDir.class, existingFile);
+        assertValidationFails(task, String.format("Error validating task ':task': Cannot create directory '%s' specified for property 'outputDir'.",
+                task.outputDir));
+    }
+
+    @Test
+    public void validationActionFailsWhenParentOfOutputDirectoryIsAFile() {
+        TaskWithOutputDir task = expectTaskCreated(TaskWithOutputDir.class, new File(testDir, "subdir/output"));
+        GFileUtils.touch(task.outputDir.getParentFile());
+
+        assertValidationFails(task, String.format("Error validating task ':task': Cannot create directory '%s' specified for property 'outputDir'.",
+                task.outputDir));
+    }
+
+    @Test
+    public void registersSpecifiedOutputDirectory() {
+        TaskWithOutputDir task = expectTaskCreated(TaskWithOutputDir.class, missingDir);
+        assertThat(task.getOutputs().getFiles().getFiles(), equalTo(toSet(missingDir)));
+    }
+
+    @Test
+    public void doesNotRegisterOutputDirectoryWhenNoneSpecified() {
+        TaskWithOutputDir task = expectTaskCreated(TaskWithOutputDir.class, new Object[]{null});
+        assertThat(task.getOutputs().getFiles().getFiles(), isEmpty());
+    }
+
+    @Test
+    public void validationActionSucceedsWhenSpecifiedInputDirectoryIsDirectory() {
+        TaskWithInputDir task = expectTaskCreated(TaskWithInputDir.class, existingDir);
+        task.execute();
+    }
+
+    @Test
+    public void validationActionFailsWhenInputDirectoryNotSpecified() {
+        TaskWithInputDir task = expectTaskCreated(TaskWithInputDir.class, new Object[]{null});
+        assertValidationFails(task, "Error validating task ':task': No value has been specified for property 'inputDir'.");
+    }
+    
+    @Test
+    public void validationActionFailsWhenInputDirectoryDoesNotExist() {
+        TaskWithInputDir task = expectTaskCreated(TaskWithInputDir.class, missingDir);
+        assertValidationFails(task, String.format("Error validating task ':task': Directory '%s' specified for property 'inputDir' does not exist.",
+                task.inputDir));
+    }
+
+    @Test
+    public void validationActionFailsWhenInputDirectoryIsAFile() {
+        TaskWithInputDir task = expectTaskCreated(TaskWithInputDir.class, existingFile);
+        GFileUtils.touch(task.inputDir);
+
+        assertValidationFails(task, String.format(
+                "Error validating task ':task': Directory '%s' specified for property 'inputDir' is not a directory.", task.inputDir));
+    }
+
+    @Test
+    public void registersSpecifiedInputDirectory() {
+        TaskWithInputDir task = expectTaskCreated(TaskWithInputDir.class, existingDir);
+        File file = existingDir.file("some-file").createFile();
+        assertThat(task.getInputs().getFiles().getFiles(), equalTo(toSet(file)));
+    }
+
+    @Test
+    public void doesNotRegisterInputDirectoryWhenNoneSpecified() {
+        TaskWithInputDir task = expectTaskCreated(TaskWithInputDir.class, new Object[]{null});
+        assertThat(task.getInputs().getFiles().getFiles(), isEmpty());
+    }
+
+    @Test
+    public void skipsTaskWhenInputDirectoryIsEmptyAndSkipWhenEmpty() {
+        TaskWithInputDir task = expectTaskCreated(BrokenTaskWithInputDir.class, existingDir);
+        task.execute();
+    }
+
+    @Test
+    public void skipsTaskWhenInputDirectoryIsDoesNotExistAndSkipWhenEmpty() {
+        TaskWithInputDir task = expectTaskCreated(BrokenTaskWithInputDir.class, missingDir);
+        task.execute();
+    }
+
+    @Test
+    public void validationActionSucceedsWhenInputValueSpecified() {
+        TaskWithInput task = expectTaskCreated(TaskWithInput.class, "value");
+        task.execute();
+    }
+
+    @Test
+    public void validationActionFailsWhenInputValueNotSpecified() {
+        TaskWithInput task = expectTaskCreated(TaskWithInput.class, new Object[]{null});
+        assertValidationFails(task, "Error validating task ':task': No value has been specified for property 'inputValue'.");
+    }
+
+    @Test
+    public void registersSpecifiedInputValue() {
+        TaskWithInput task = expectTaskCreated(TaskWithInput.class, "value");
+        assertThat(task.getInputs().getProperties().get("inputValue"), equalTo((Object) "value"));
+    }
+
+    @Test
+    public void validationActionSucceedsWhenPropertyMarkedWithOptionalAnnotationNotSpecified() {
+        TaskWithOptionalInputFile task = expectTaskCreated(TaskWithOptionalInputFile.class);
+        task.execute();
+    }
+
+    @Test
+    public void validatesNestedBeans() {
+        TaskWithNestedBean task = expectTaskCreated(TaskWithNestedBean.class, new Object[]{null});
+        assertValidationFails(task, "Error validating task ':task': No value has been specified for property 'bean.inputFile'.");
+    }
+
+    @Test
+    public void registersInputPropertyForNestedBeanClass() {
+        TaskWithNestedBean task = expectTaskCreated(TaskWithNestedBean.class, new Object[]{null});
+        assertThat(task.getInputs().getProperties().get("bean.class"), equalTo((Object) Bean.class.getName()));
+    }
+
+    @Test
+    public void doesNotRegisterInputPropertyWhenNestedBeanIsNull() {
+        TaskWithOptionalNestedBean task = expectTaskCreated(TaskWithOptionalNestedBean.class);
+        assertThat(task.getInputs().getProperties().get("bean.class"), nullValue());
+    }
+
+    @Test
+    public void validationFailsWhenNestedBeanIsNull() {
+        TaskWithNestedBean task = expectTaskCreated(TaskWithNestedBean.class, new Object[]{null});
+        task.bean = null;
+        assertValidationFails(task, "Error validating task ':task': No value has been specified for property 'bean'.");
+    }
+
+    @Test
+    public void validationSucceedsWhenNestedBeanIsNullAndMarkedOptional() {
+        TaskWithOptionalNestedBean task = expectTaskCreated(TaskWithOptionalNestedBean.class);
+        task.execute();
+    }
+
+    @Test
+    public void canAttachAnnotationToGroovyProperty() {
+        InputFileTask task = expectTaskCreated(InputFileTask.class);
+        assertValidationFails(task, "Error validating task ':task': No value has been specified for property 'srcFile'.");
+    }
+    
+    private void assertValidationFails(TaskInternal task, String expectedErrorMessage) {
+        try {
+            task.execute();
+            fail();
+        } catch (GradleException e) {
+            assertThat(e.getCause(), instanceOf(InvalidUserDataException.class));
+            assertThat(e.getCause().getMessage(), equalTo(expectedErrorMessage));
+        }
+    }
+
+    public static class TestTask extends DefaultTask {
+        final Runnable action;
+
+        public TestTask(Runnable action) {
+            this.action = action;
+        }
+
+        @TaskAction
+        public void doStuff() {
+            action.run();
+        }
+    }
+
+    public static class TaskWithInheritedMethod extends TestTask {
+        public TaskWithInheritedMethod(Runnable action) {
+            super(action);
+        }
+    }
+
+    public static class TaskWithOverriddenMethod extends TestTask {
+        private final Runnable action;
+
+        public TaskWithOverriddenMethod(Runnable action) {
+            super(null);
+            this.action = action;
+        }
+
+        @Override @TaskAction
+        public void doStuff() {
+            action.run();
+        }
+    }
+
+    public static class TaskWithProtectedMethod extends DefaultTask {
+        private final Runnable action;
+
+        public TaskWithProtectedMethod(Runnable action) {
+            this.action = action;
+        }
+
+        @TaskAction
+        protected void doStuff() {
+            action.run();
+        }
+    }
+
+    public static class TaskWithStaticMethod extends DefaultTask {
+        @TaskAction
+        public static void doStuff() {
+        }
+    }
+
+    public static class TaskWithMultipleMethods extends TestTask {
+        public TaskWithMultipleMethods(Runnable action) {
+            super(action);
+        }
+
+        @TaskAction
+        public void aMethod() {
+            action.run();
+        }
+
+        @TaskAction
+        public void anotherMethod() {
+            action.run();
+        }
+    }
+
+    public static class TaskWithParamMethod extends DefaultTask {
+        @TaskAction
+        public void doStuff(int value) {
+        }
+    }
+
+    public static class TaskWithInputFile extends DefaultTask {
+        File inputFile;
+
+        public TaskWithInputFile(File inputFile) {
+            this.inputFile = inputFile;
+        }
+
+        @InputFile
+        public File getInputFile() {
+            return inputFile;
+        }
+    }
+
+    public static class TaskWithInputDir extends DefaultTask {
+        File inputDir;
+
+        public TaskWithInputDir(File inputDir) {
+            this.inputDir = inputDir;
+        }
+
+        @InputDirectory
+        public File getInputDir() {
+            return inputDir;
+        }
+    }
+
+    public static class TaskWithInput extends DefaultTask {
+        String inputValue;
+
+        public TaskWithInput(String inputValue) {
+            this.inputValue = inputValue;
+        }
+
+        @Input
+        public String getInputValue() {
+            return inputValue;
+        }
+    }
+
+    public static class BrokenTaskWithInputDir extends TaskWithInputDir {
+        public BrokenTaskWithInputDir(File inputDir) {
+            super(inputDir);
+        }
+
+        @Override @InputDirectory @SkipWhenEmpty
+        public File getInputDir() {
+            return super.getInputDir();
+        }
+
+        @TaskAction
+        public void doStuff() {
+            fail();
+        }
+
+    }
+
+    public static class TaskWithOutputFile extends DefaultTask {
+        File outputFile;
+
+        public TaskWithOutputFile(File outputFile) {
+            this.outputFile = outputFile;
+        }
+
+        @OutputFile
+        public File getOutputFile() {
+            return outputFile;
+        }
+    }
+
+    public static class TaskWithOutputDir extends DefaultTask {
+        File outputDir;
+
+        public TaskWithOutputDir(File outputDir) {
+            this.outputDir = outputDir;
+        }
+
+        @OutputDirectory
+        public File getOutputDir() {
+            return outputDir;
+        }
+    }
+
+    public static class TaskWithInputFiles extends DefaultTask {
+        Iterable<? extends File> input;
+
+        public TaskWithInputFiles(Iterable<? extends File> input) {
+            this.input = input;
+        }
+
+        @InputFiles @SkipWhenEmpty
+        public Iterable<? extends File> getInput() {
+            return input;
+        }
+    }
+
+    public static class BrokenTaskWithInputFiles extends TaskWithInputFiles {
+        public BrokenTaskWithInputFiles(Iterable<? extends File> input) {
+            super(input);
+        }
+
+        @TaskAction
+        public void doStuff() {
+            fail();
+        }
+    }
+
+    public static class TaskWithOptionalInputFile extends DefaultTask {
+        @InputFile @Optional
+        public File getInputFile() {
+            return null;
+        }
+    }
+
+    public static class TaskWithNestedBean extends DefaultTask {
+        Bean bean = new Bean();
+
+        public TaskWithNestedBean(File inputFile) {
+            bean.inputFile = inputFile;
+        }
+
+        @Nested
+        public Bean getBean() {
+            return bean;
+        }
+    }
+
+    public static class TaskWithOptionalNestedBean extends DefaultTask {
+        @Nested @Optional
+        public Bean getBean() {
+            return null;
+        }
+    }
+
+    public static class Bean {
+        @InputFile
+        File inputFile;
+
+        public File getInputFile() {
+            return inputFile;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/DependencyAutoWireTaskFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/DependencyAutoWireTaskFactoryTest.java
new file mode 100644
index 0000000..c99bf87
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/DependencyAutoWireTaskFactoryTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.project.taskfactory;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.tasks.TaskInputs;
+import static org.gradle.util.GUtil.*;
+import static org.hamcrest.Matchers.*;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+ at RunWith(JMock.class)
+public class DependencyAutoWireTaskFactoryTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final ITaskFactory delegate = context.mock(ITaskFactory.class);
+    private final DependencyAutoWireTaskFactory factory = new DependencyAutoWireTaskFactory(delegate);
+
+    @Test
+    public void addsDependencyOnInputFiles() {
+        final TaskInternal task = context.mock(TaskInternal.class);
+        final ProjectInternal project = context.mock(ProjectInternal.class);
+        final TaskInputs taskInputs = context.mock(TaskInputs.class);
+        final FileCollection inputFiles = context.mock(FileCollection.class);
+
+        context.checking(new Expectations() {{
+            one(delegate).createTask(project, map());
+            will(returnValue(task));
+            allowing(task).getInputs();
+            will(returnValue(taskInputs));
+            allowing(taskInputs).getFiles();
+            will(returnValue(inputFiles));
+            one(task).dependsOn(inputFiles);
+        }});
+
+        assertThat(factory.createTask(project, map()), sameInstance(task));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/ExecutionShortCircuitTaskExecuterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/ExecutionShortCircuitTaskExecuterTest.java
new file mode 100644
index 0000000..641b348
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/ExecutionShortCircuitTaskExecuterTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.project.taskfactory;
+
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.TaskOutputsInternal;
+import org.gradle.api.internal.changedetection.TaskArtifactState;
+import org.gradle.api.internal.changedetection.TaskArtifactStateRepository;
+import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.api.internal.tasks.TaskStateInternal;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.jmock.Expectations;
+import org.jmock.Sequence;
+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;
+
+ at RunWith(JMock.class)
+public class ExecutionShortCircuitTaskExecuterTest {
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private final TaskExecuter delegate = context.mock(TaskExecuter.class);
+    private final TaskOutputsInternal outputs = context.mock(TaskOutputsInternal.class);
+    private final TaskInternal task = context.mock(TaskInternal.class);
+    private final TaskStateInternal taskState = context.mock(TaskStateInternal.class);
+    private final TaskArtifactStateRepository repository = context.mock(TaskArtifactStateRepository.class);
+    private final TaskArtifactState taskArtifactState = context.mock(TaskArtifactState.class);
+    private final ExecutionShortCircuitTaskExecuter executer = new ExecutionShortCircuitTaskExecuter(delegate, repository);
+
+    @Before
+    public void setup() {
+
+        context.checking(new Expectations(){{
+            allowing(task).getOutputs();
+            will(returnValue(outputs));
+        }});
+    }
+    @Test
+    public void skipsTaskWhenOutputsAreUpToDate() {
+        context.checking(new Expectations() {{
+            one(repository).getStateFor(task);
+            will(returnValue(taskArtifactState));
+
+            one(taskArtifactState).isUpToDate();
+            will(returnValue(true));
+
+            one(taskState).upToDate();
+        }});
+
+        executer.execute(task, taskState);
+    }
+    
+    @Test
+    public void executesTaskWhenOutputsAreNotUpToDate() {
+        context.checking(new Expectations() {{
+            Sequence sequence = context.sequence("seq");
+
+            one(repository).getStateFor(task);
+            will(returnValue(taskArtifactState));
+            inSequence(sequence);
+
+            one(taskArtifactState).isUpToDate();
+            will(returnValue(false));
+            inSequence(sequence);
+
+            one(outputs).setHistory(taskArtifactState);
+            inSequence(sequence);
+
+            one(delegate).execute(task, taskState);
+            inSequence(sequence);
+
+            allowing(taskState).getFailure();
+            will(returnValue(null));
+
+            one(taskArtifactState).update();
+            inSequence(sequence);
+
+            one(outputs).setHistory(null);
+            inSequence(sequence);
+        }});
+
+        executer.execute(task, taskState);
+    }
+
+    @Test
+    public void doesNotUpdateStateWhenTaskFails() {
+        context.checking(new Expectations() {{
+            one(repository).getStateFor(task);
+            will(returnValue(taskArtifactState));
+
+            one(taskArtifactState).isUpToDate();
+            will(returnValue(false));
+
+            one(outputs).setHistory(taskArtifactState);
+
+            one(delegate).execute(task, taskState);
+
+            allowing(taskState).getFailure();
+            will(returnValue(new RuntimeException()));
+
+            one(outputs).setHistory(null);
+        }});
+
+        executer.execute(task, taskState);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/InputFileTask.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/InputFileTask.groovy
new file mode 100644
index 0000000..fac7dca
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/InputFileTask.groovy
@@ -0,0 +1,24 @@
+/*
+ * 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.project.taskfactory
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.InputFile
+
+class InputFileTask extends DefaultTask {
+    @InputFile
+    File srcFile
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/PostExecutionAnalysisTaskExecuterTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/PostExecutionAnalysisTaskExecuterTest.groovy
new file mode 100644
index 0000000..b27bcee
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/PostExecutionAnalysisTaskExecuterTest.groovy
@@ -0,0 +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.project.taskfactory
+
+import org.gradle.api.Action
+import org.gradle.api.internal.TaskInternal
+import org.gradle.api.internal.tasks.TaskExecuter
+import org.gradle.api.internal.tasks.TaskStateInternal
+import org.gradle.api.tasks.TaskDependency
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+ at RunWith(JMock.class)
+class PostExecutionAnalysisTaskExecuterTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final TaskExecuter target = context.mock(TaskExecuter.class)
+    private final TaskInternal task = context.mock(TaskInternal.class)
+    private final TaskInternal dependency = context.mock(TaskInternal.class)
+    private final TaskDependency taskDependency = context.mock(TaskDependency.class)
+    private final TaskStateInternal state = context.mock(TaskStateInternal.class)
+    private final TaskStateInternal dependencyState = context.mock(TaskStateInternal.class)
+    private final PostExecutionAnalysisTaskExecuter executer = new PostExecutionAnalysisTaskExecuter(target)
+
+    @Before
+    public void setup() {
+        context.checking {
+            allowing(task).getTaskDependencies()
+            will(returnValue(taskDependency))
+            allowing(dependency).getState()
+            will(returnValue(dependencyState))
+        }
+    }
+    
+    @Test
+    public void marksTaskUpToDateWhenItHasNoActionsAndAllOfItsDependenciesWereSkipped() {
+        context.checking {
+            one(target).execute(task, state)
+            allowing(task).getActions();
+            will(returnValue([]))
+            allowing(taskDependency).getDependencies(task)
+            will(returnValue([dependency] as Set))
+            allowing(dependencyState).getSkipped()
+            will(returnValue(true))
+            one(state).upToDate()
+        }
+
+        executer.execute(task, state)
+    }
+
+    @Test
+    public void doesNotMarkTaskUpToDateWhenAnyDependencyWasNotSkipped() {
+        context.checking {
+            one(target).execute(task, state)
+            allowing(task).getActions();
+            will(returnValue([]))
+            allowing(taskDependency).getDependencies(task)
+            will(returnValue([dependency] as Set))
+            allowing(dependencyState).getSkipped()
+            will(returnValue(false))
+        }
+
+        executer.execute(task, state)
+    }
+
+    @Test
+    public void marksTaskUpToDateWhenItHasActionsAndItDidNotDoWork() {
+        context.checking {
+            one(target).execute(task, state)
+            allowing(task).getActions();
+            will(returnValue([{} as Action]))
+            allowing(state).getDidWork()
+            will(returnValue(false))
+            one(state).upToDate()
+        }
+
+        executer.execute(task, state)
+    }
+
+    @Test
+    public void doesNotMarkTaskUpToDateWhenItHasActionsAndDidWork() {
+        context.checking {
+            one(target).execute(task, state)
+            allowing(task).getActions();
+            will(returnValue([{} as Action]))
+            allowing(state).getDidWork()
+            will(returnValue(true))
+        }
+
+        executer.execute(task, state)
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/TaskFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/TaskFactoryTest.java
new file mode 100644
index 0000000..c7df0c9
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/project/taskfactory/TaskFactoryTest.java
@@ -0,0 +1,222 @@
+/*
+ * 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.project.taskfactory;
+
+import org.gradle.api.*;
+import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.internal.IConventionAware;
+import org.gradle.api.internal.GroovySourceGenerationBackedClassGenerator;
+import org.gradle.api.internal.project.DefaultProject;
+import org.gradle.api.plugins.Convention;
+import org.gradle.api.tasks.ConventionValue;
+import org.gradle.api.tasks.TaskInstantiationException;
+import org.gradle.util.GUtil;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.WrapUtil;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class TaskFactoryTest {
+    public static final String TEST_TASK_NAME = "task";
+
+    private Map empyArgMap;
+
+    private TaskFactory taskFactory;
+
+    private DefaultProject testProject;
+
+    @Before
+    public void setUp() {
+        taskFactory = new TaskFactory(new GroovySourceGenerationBackedClassGenerator());
+        testProject = HelperUtil.createRootProject();
+        empyArgMap = new HashMap();
+    }
+
+    @Test
+    public void testCreateTask() {
+        Task task = checkTask(taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task")));
+        assertThat(task, instanceOf(DefaultTask.class));
+        assertThat(task.getProject(), sameInstance((Project) testProject));
+        assertThat(task.getName(), equalTo("task"));
+        assertTrue(task.getActions().isEmpty());
+    }
+
+    @Test
+    public void testCannotCreateTaskWithNoName() {
+        try {
+            taskFactory.createTask(testProject, empyArgMap);
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), equalTo("The task name must be provided."));
+        }
+    }
+
+    @Test
+    public void testCreateTaskWithDependencies() {
+        List testDependsOn = WrapUtil.toList("/path1");
+        Set expected = WrapUtil.toSet((Object) testDependsOn);
+        Task task = checkTask(taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_DEPENDS_ON, testDependsOn)));
+        assertEquals(expected, task.getDependsOn());
+        assertTrue(task.getActions().isEmpty());
+    }
+
+    @Test
+    public void testCreateTaskWithSingleDependency() {
+        String testDependsOn = "/path1";
+        Task task = checkTask(taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_DEPENDS_ON, testDependsOn)));
+        assertEquals(WrapUtil.toSet(testDependsOn), task.getDependsOn());
+        assertTrue(task.getActions().isEmpty());
+    }
+
+    @Test
+    public void testCreateTaskOfTypeWithNoArgsConstructor() {
+        Task task = checkTask(taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_TYPE, TestDefaultTask.class)));
+        assertThat(task.getProject(), sameInstance((Project) testProject));
+        assertThat(task.getName(), equalTo("task"));
+        assertTrue(TestDefaultTask.class.isAssignableFrom(task.getClass()));
+    }
+
+    @Test
+    public void testAppliesConventionMappingToEachGetter() {
+        TestConventionTask task = (TestConventionTask) checkTask(taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_TYPE, TestConventionTask.class)));
+
+        assertThat(task.getProperty(), nullValue());
+
+        task.getConventionMapping().map("property", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return "conventionValue";
+            }
+        });
+
+        assertThat(task.getProperty(), equalTo("conventionValue"));
+
+        task.setProperty("value");
+        assertThat(task.getProperty(), equalTo("value"));
+    }
+
+    @Test
+    public void doesNotApplyConventionMappingToGettersDefinedByTaskInterface() {
+        TestConventionTask task = (TestConventionTask) checkTask(taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_TYPE, TestConventionTask.class)));
+        task.getConventionMapping().map("description", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                throw new UnsupportedOperationException();
+            }
+        });
+        assertThat(task.getDescription(), nullValue());
+    }
+
+    @Test
+    public void testCreateTaskWithAction() {
+        Action<Task> action = new Action<Task>() {
+            public void execute(Task task) {
+            }
+        };
+
+        Task task = checkTask(taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_ACTION, action)));
+        assertThat((List)task.getActions(), equalTo((List) WrapUtil.toList(action)));
+    }
+
+    @Test
+    public void testCreateTaskWithActionClosure() {
+        Task task = checkTask(taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_ACTION, HelperUtil.TEST_CLOSURE)));
+        assertFalse(task.getActions().isEmpty());
+    }
+
+    @Test
+    public void testCreateTaskForTypeWithMissingConstructor() {
+        try {
+            taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_TYPE, MissingConstructorTask.class));
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertEquals(
+                    "Cannot create task of type 'MissingConstructorTask' as it does not have a public no-args constructor.",
+                    e.getMessage());
+        }
+    }
+
+    @Test
+    public void testCreateTaskForTypeWhichDoesNotImplementTask() {
+        try {
+            taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_TYPE, NotATask.class));
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertEquals("Cannot create task of type 'NotATask' as it does not implement the Task interface.",
+                    e.getMessage());
+        }
+    }
+
+    @Test
+    public void testCreateTaskWhenConstructorThrowsException() {
+        try {
+            taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_TYPE, CannotConstructTask.class));
+            fail();
+        } catch (TaskInstantiationException e) {
+            assertEquals("Could not create task of type 'CannotConstructTask'.", e.getMessage());
+            assertTrue(RuntimeException.class.isInstance(e.getCause()));
+            assertEquals("fail", e.getCause().getMessage());
+        }
+    }
+
+    @Test
+    public void createTaskWithDescription() {
+        Object testDescription = 9;
+        Task task = checkTask(taskFactory.createTask(testProject, GUtil.map(Task.TASK_NAME, "task", Task.TASK_DESCRIPTION, testDescription)));
+        assertEquals("9", task.getDescription());
+    }
+
+    private Task checkTask(Task task) {
+        assertEquals(TEST_TASK_NAME, task.getName());
+        assertSame(testProject, task.getProject());
+        return task;
+    }
+
+    public static class TestDefaultTask extends DefaultTask {
+    }
+
+    public static class TestConventionTask extends ConventionTask {
+        private String property;
+
+        public String getProperty() {
+            return property;
+        }
+
+        public void setProperty(String property) {
+            this.property = property;
+        }
+    }
+
+    public static class MissingConstructorTask extends DefaultTask {
+        public MissingConstructorTask(Integer something) {
+        }
+    }
+
+    public static class NotATask {
+    }
+
+    public static class CannotConstructTask extends DefaultTask {
+        public CannotConstructTask() {
+            throw new RuntimeException("fail");
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/resource/CachingResourceTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/resource/CachingResourceTest.groovy
new file mode 100644
index 0000000..f8ae030
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/resource/CachingResourceTest.groovy
@@ -0,0 +1,65 @@
+/*
+ * 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.resource
+
+import org.gradle.util.JUnit4GroovyMockery
+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.*
+
+ at RunWith(JMock.class)
+class CachingResourceTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final Resource target = context.mock(Resource.class)
+    private final CachingResource resource = new CachingResource(target)
+
+    @Test
+    public void fetchesAndCachesContentWhenExistenceIsChecked() {
+        context.checking {
+            one(target).getText()
+            will(returnValue('content'))
+        }
+
+        assertTrue(resource.exists)
+        assertThat(resource.text, equalTo('content'))
+    }
+
+    @Test
+    public void fetchesAndCachesContentWhenContentIsRead() {
+        context.checking {
+            one(target).getText()
+            will(returnValue('content'))
+        }
+
+        assertThat(resource.text, equalTo('content'))
+        assertTrue(resource.exists)
+    }
+    
+    @Test
+    public void fetchesAndCachesContentForResourceThatDoesNotExist() {
+        context.checking {
+            one(target).getText()
+            will(returnValue(null))
+        }
+
+        assertThat(resource.text, nullValue())
+        assertFalse(resource.exists)
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/resource/StringResourceTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/resource/StringResourceTest.groovy
new file mode 100644
index 0000000..9cd4ba0
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/resource/StringResourceTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * 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.resource
+
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+import org.junit.Test
+
+class StringResourceTest {
+    private final StringResource resource = new StringResource('displayname', 'text')
+
+    @Test
+    public void hasTextContent() {
+         assertThat(resource.text, equalTo('text'))
+    }
+
+    @Test
+    public void exists() {
+         assertTrue(resource.exists)
+    }
+
+    @Test
+    public void hasNoFile() {
+         assertThat(resource.file, nullValue())
+    }
+
+    @Test
+    public void hasNoURI() {
+        assertThat(resource.URI, nullValue())
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/resource/UriResourceTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/resource/UriResourceTest.groovy
new file mode 100644
index 0000000..3510fb6
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/resource/UriResourceTest.groovy
@@ -0,0 +1,177 @@
+/*
+ * 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.resource
+
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+class UriResourceTest {
+    private TestFile testDir;
+    private File file;
+    private URI fileUri;
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    @Before
+    public void setUp() throws URISyntaxException {
+        testDir = tmpDir.createDir('dir');
+        file = new File(testDir, 'build.script');
+        fileUri = file.toURI();
+    }
+
+    private URI createJar() throws URISyntaxException {
+        TestFile jarFile = tmpDir.dir.file('test.jar');
+        testDir.file('ignoreme').write('content');
+        testDir.zipTo(jarFile);
+        return new URI("jar:${jarFile.toURI()}!/build.script")
+    }
+
+    @Test
+    public void canConstructResourceFromFile() {
+        UriResource resource = new UriResource('<display-name>', file);
+        assertThat(resource.file, equalTo(file));
+        assertThat(resource.URI, equalTo(fileUri));
+    }
+
+    @Test
+    public void canConstructResourceFromFileURI() {
+        UriResource resource = new UriResource('<display-name>', fileUri);
+        assertThat(resource.file, equalTo(file));
+        assertThat(resource.URI, equalTo(fileUri));
+    }
+
+    @Test
+    public void canConstructResourceFromJarURI() {
+        URI jarUri = createJar()
+        UriResource resource = new UriResource('<display-name>', jarUri);
+        assertThat(resource.file, nullValue());
+        assertThat(resource.URI, equalTo(jarUri));
+    }
+
+    @Test
+    public void readsFileContentWhenFileExists() throws IOException {
+        file.text = '<content>'
+
+        UriResource resource = new UriResource('<display-name>', file);
+        assertTrue(resource.exists)
+        assertThat(resource.text, equalTo('<content>'));
+    }
+
+    @Test
+    public void hasNoContentWhenFileDoesNotExist() {
+        UriResource resource = new UriResource('<display-name>', file);
+        assertFalse(resource.exists)
+        try {
+            resource.text
+            fail()
+        } catch (ResourceNotFoundException e) {
+            assertThat(e.message, equalTo("Could not read <display-name> '$file' as it does not exist." as String))
+        }
+    }
+
+    @Test
+    public void hasNoContentWhenFileIsADirectory() {
+        TestFile dir = testDir.file('somedir').createDir()
+        UriResource resource = new UriResource('<display-name>', dir);
+        assertTrue(resource.exists)
+        try {
+            resource.text
+            fail()
+        } catch (ResourceException e) {
+            assertThat(e.message, equalTo("Could not read <display-name> '$dir' as it is a directory." as String))
+        }
+    }
+    
+    @Test
+    public void readsFileContentUsingFileUriWhenFileExists() {
+        file.text = '<content>'
+
+        UriResource resource = new UriResource('<display-name>', fileUri);
+        assertTrue(resource.exists)
+        assertThat(resource.text, equalTo('<content>'));
+    }
+
+    @Test
+    public void hasNoContentWhenUsingFileUriAndFileDoesNotExist() {
+        UriResource resource = new UriResource('<display-name>', fileUri);
+        assertFalse(resource.exists)
+        try {
+            resource.text
+            fail()
+        } catch (ResourceNotFoundException e) {
+            assertThat(e.message, equalTo("Could not read <display-name> '$file' as it does not exist." as String))
+        }
+    }
+
+    @Test
+    public void readsFileContentUsingJarUriWhenFileExists() {
+        file.text = '<content>'
+
+        UriResource resource = new UriResource('<display-name>', createJar());
+        assertTrue(resource.exists)
+        assertThat(resource.text, equalTo('<content>'));
+    }
+
+    @Test
+    public void hasNoContentWhenUsingJarUriAndFileDoesNotExistInJar() {
+        URI jarUri = createJar()
+        UriResource resource = new UriResource('<display-name>', jarUri);
+        assertFalse(resource.exists)
+        try {
+            resource.text
+            fail()
+        } catch (ResourceNotFoundException e) {
+            assertThat(e.message, equalTo("Could not read <display-name> '$jarUri' as it does not exist." as String))
+        }
+    }
+
+    @Test
+    public void hasNoContentWhenUsingHttpUriAndFileDoesNotExist() {
+        UriResource resource = new UriResource('<display-name>', new URI("http://www.gradle.org/unknown.txt"));
+        assertFalse(resource.exists)
+        try {
+            resource.text
+            fail()
+        } catch (ResourceNotFoundException e) {
+            assertThat(e.message, equalTo("Could not read <display-name> 'http://www.gradle.org/unknown.txt' as it does not exist." as String))
+        }
+    }
+
+    @Test
+    public void usesFilePathToBuildDisplayNameWhenUsingFile() {
+        UriResource resource = new UriResource("<file-type>", file);
+        assertThat(resource.displayName, equalTo(String.format("<file-type> '%s'", file.absolutePath)));
+    }
+
+    @Test
+    public void usesFilePathToBuildDisplayNameWhenUsingFileUri() {
+        UriResource resource = new UriResource("<file-type>", fileUri);
+        assertThat(resource.displayName, equalTo(String.format("<file-type> '%s'", file.absolutePath)));
+    }
+
+    @Test
+    public void usesUriToBuildDisplayNameWhenUsingHttpUri() {
+        UriResource resource = new UriResource("<file-type>", new URI("http://www.gradle.org/unknown.txt"));
+        assertThat(resource.displayName, equalTo('<file-type> \'http://www.gradle.org/unknown.txt\''))
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/CachingTaskDependencyResolveContextTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/CachingTaskDependencyResolveContextTest.groovy
new file mode 100644
index 0000000..4dc5b73
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/CachingTaskDependencyResolveContextTest.groovy
@@ -0,0 +1,209 @@
+/*
+ * 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
+
+import spock.lang.Specification
+import org.gradle.api.Task
+import org.gradle.api.tasks.TaskDependency
+import org.gradle.api.Buildable
+import org.gradle.api.GradleException
+
+class CachingTaskDependencyResolveContextTest extends Specification {
+    private final CachingTaskDependencyResolveContext context = new CachingTaskDependencyResolveContext()
+    private final Task task = Mock()
+    private final Task target = Mock()
+    private final TaskDependencyInternal dependency = Mock()
+
+    def setup() {
+        _ * task.getTaskDependencies() >> dependency
+    }
+    
+    def determinesTaskDependenciesByResolvingDependencyObjectForTask() {
+        when:
+        def tasks = context.getDependencies(task)
+
+        then:
+        1 * dependency.resolve(_)
+        tasks.isEmpty()
+    }
+
+    def resolvesTaskObject() {
+        when:
+        def tasks = context.getDependencies(task)
+
+        then:
+        1 * dependency.resolve(_) >> { args -> args[0].add(target) }
+        tasks == [target] as LinkedHashSet
+    }
+
+    def resolvesTaskDependency() {
+        TaskDependency otherDependency = Mock()
+
+        when:
+        def tasks = context.getDependencies(task)
+
+        then:
+        1 * dependency.resolve(_) >> { args -> args[0].add(otherDependency) }
+        1 * otherDependency.getDependencies(task) >> { [target] as Set }
+        tasks == [target] as LinkedHashSet
+    }
+
+    def resolvesTaskDependencyInternal() {
+        TaskDependencyInternal otherDependency = Mock()
+
+        when:
+        def tasks = context.getDependencies(task)
+
+        then:
+        1 * dependency.resolve(_) >> { args -> args[0].add(otherDependency) }
+        1 * otherDependency.resolve(_) >> { args -> args[0].add(target) }
+        tasks == [target] as LinkedHashSet
+    }
+
+    def resolvesBuildable() {
+        Buildable buildable = Mock()
+        TaskDependency otherDependency = Mock()
+
+        when:
+        def tasks = context.getDependencies(task)
+
+        then:
+        1 * dependency.resolve(_) >> { args -> args[0].add(buildable) }
+        1 * buildable.getBuildDependencies() >> { otherDependency }
+        1 * otherDependency.getDependencies(task) >> { [target] as Set }
+        tasks == [target] as LinkedHashSet
+    }
+
+    def resolvesBuildableInternal() {
+        Buildable buildable = Mock()
+        TaskDependencyInternal otherDependency = Mock()
+
+        when:
+        def tasks = context.getDependencies(task)
+
+        then:
+        1 * dependency.resolve(_) >> { args -> args[0].add(buildable) }
+        1 * buildable.getBuildDependencies() >> { otherDependency }
+        1 * otherDependency.resolve(_) >> { args -> args[0].add(target) }
+        tasks == [target] as LinkedHashSet
+    }
+
+    def throwsExceptionForUnresolvableObject() {
+        when:
+        context.getDependencies(task)
+
+        then:
+        1 * dependency.resolve(_) >> { args -> args[0].add('unknown') }
+        def e = thrown(GradleException)
+        e.cause instanceof IllegalArgumentException
+        e.cause.message == "Cannot resolve object of unknown type String to a Task."
+    }
+
+    def cachesResultForTaskDependency() {
+        TaskDependencyInternal otherDependency = Mock()
+        TaskDependency otherDependency2 = Mock()
+
+        when:
+        def tasks = context.getDependencies(task)
+
+        then:
+        1 * dependency.resolve(_) >> { args ->
+            args[0].add(otherDependency)
+            args[0].add(otherDependency2)
+        }
+        1 * otherDependency.resolve(_) >> { args -> args[0].add(otherDependency2) }
+        1 * otherDependency2.getDependencies(task) >> { [target] as Set }
+        tasks == [target] as LinkedHashSet
+    }
+
+    def cachesResultForTaskDependencyInternal() {
+        TaskDependencyInternal otherDependency = Mock()
+        TaskDependencyInternal otherDependency2 = Mock()
+
+        when:
+        def tasks = context.getDependencies(task)
+
+        then:
+        1 * dependency.resolve(_) >> { args ->
+            args[0].add(otherDependency)
+            args[0].add(otherDependency2)
+        }
+        1 * otherDependency.resolve(_) >> { args -> args[0].add(otherDependency2) }
+        1 * otherDependency2.resolve(_) >> { args -> args[0].add(target) }
+        tasks == [target] as LinkedHashSet
+    }
+
+    def cachesResultForBuildable() {
+        TaskDependencyInternal otherDependency = Mock()
+        Buildable buildable = Mock()
+        TaskDependency otherDependency2 = Mock()
+
+        when:
+        def tasks = context.getDependencies(task)
+
+        then:
+        1 * dependency.resolve(_) >> { args ->
+            args[0].add(otherDependency)
+            args[0].add(buildable)
+        }
+        1 * otherDependency.resolve(_) >> { args -> args[0].add(buildable) }
+        1 * buildable.getBuildDependencies() >> otherDependency2
+        1 * otherDependency2.getDependencies(task) >> { [target] as Set }
+        tasks == [target] as LinkedHashSet
+    }
+
+    def cachesResultForBuildableInternal() {
+        TaskDependencyInternal otherDependency = Mock()
+        Buildable buildable = Mock()
+        TaskDependencyInternal otherDependency2 = Mock()
+
+        when:
+        def tasks = context.getDependencies(task)
+
+        then:
+        1 * dependency.resolve(_) >> { args ->
+            args[0].add(otherDependency)
+            args[0].add(buildable)
+        }
+        1 * otherDependency.resolve(_) >> { args -> args[0].add(buildable) }
+        1 * buildable.getBuildDependencies() >> otherDependency2
+        1 * otherDependency2.resolve(_) >> { args -> args[0].add(target) }
+        tasks == [target] as LinkedHashSet
+    }
+
+    def wrapsFailureToResolveTask() {
+        def failure = new RuntimeException()
+
+        when:
+        context.getDependencies(task)
+
+        then:
+        1 * dependency.resolve(_) >> { throw failure }
+        def e = thrown(GradleException)
+        e.message == "Could not determine the dependencies of $task."
+        e.cause == failure
+    }
+
+    def failsWhenThereIsACyclicDependency() {
+        throw new UnsupportedOperationException()
+    }
+
+    def providesSomeWayToIndicateThatResultIsSpecificToTheResolvedTask() {
+        throw new UnsupportedOperationException()
+    }
+}
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java
new file mode 100644
index 0000000..1242f31
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskContainerTest.java
@@ -0,0 +1,283 @@
+/*
+ * 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;
+
+import groovy.lang.Closure;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.UnknownTaskException;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.project.taskfactory.ITaskFactory;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.tasks.TaskContainer;
+import org.gradle.util.GUtil;
+import org.gradle.util.HelperUtil;
+import static org.hamcrest.Matchers.*;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Map;
+
+ at RunWith(JMock.class)
+public class DefaultTaskContainerTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    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(ClassGenerator.class), taskFactory);
+
+    @Test
+    public void addsTaskWithMap() {
+        final Map<String, ?> options = GUtil.map("option", "value");
+        final Task task = task("task");
+
+        context.checking(new Expectations(){{
+            one(taskFactory).createTask(project, options);
+            will(returnValue(task));
+        }});
+        assertThat(container.add(options), sameInstance(task));
+        assertThat(container.getByName("task"), sameInstance(task));
+    }
+
+    @Test
+    public void addsTaskWithName() {
+        final Map<String, ?> options = GUtil.map(Task.TASK_NAME, "task");
+        final Task task = task("task");
+
+        context.checking(new Expectations(){{
+            one(taskFactory).createTask(project, options);
+            will(returnValue(task));
+        }});
+        assertThat(container.add("task"), sameInstance(task));
+    }
+
+    @Test
+    public void addsTaskWithNameAndType() {
+        final Map<String, ?> options = GUtil.map(Task.TASK_NAME, "task", Task.TASK_TYPE, Task.class);
+        final Task task = task("task");
+
+        context.checking(new Expectations(){{
+            one(taskFactory).createTask(project, options);
+            will(returnValue(task));
+        }});
+        assertThat(container.add("task", Task.class), sameInstance(task));
+    }
+
+    @Test
+    public void addsTaskWithNameAndConfigureClosure() {
+        final Closure action = HelperUtil.toClosure("{ description = 'description' }");
+        final Map<String, ?> options = GUtil.map(Task.TASK_NAME, "task");
+        final Task task = task("task");
+
+        context.checking(new Expectations(){{
+            one(taskFactory).createTask(project, options);
+            will(returnValue(task));
+            one(task).configure(action);
+            will(returnValue(task));
+        }});
+        assertThat(container.add("task", action), sameInstance(task));
+    }
+
+    @Test
+    public void replacesTaskWithName() {
+        final Map<String, ?> options = GUtil.map(Task.TASK_NAME, "task");
+        final Task task = task("task");
+
+        context.checking(new Expectations(){{
+            one(taskFactory).createTask(project, options);
+            will(returnValue(task));
+        }});
+        assertThat(container.replace("task"), sameInstance(task));
+        assertThat(container.getByName("task"), sameInstance(task));
+    }
+
+    @Test
+    public void replacesTaskWithNameAndType() {
+        final Map<String, ?> options = GUtil.map(Task.TASK_NAME, "task", Task.TASK_TYPE, Task.class);
+        final Task task = task("task");
+
+        context.checking(new Expectations(){{
+            one(taskFactory).createTask(project, options);
+            will(returnValue(task));
+        }});
+        assertThat(container.replace("task", Task.class), sameInstance(task));
+    }
+
+    @Test
+    public void cannotAddDuplicateTask() {
+        final Task task = addTask("task");
+
+        context.checking(new Expectations() {{
+            one(taskFactory).createTask(project, GUtil.map(Task.TASK_NAME, "task"));
+            will(returnValue(task("task")));
+        }});
+
+        try {
+            container.add("task");
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), equalTo("Cannot add [task2] as a task with that name already exists."));
+        }
+
+        assertThat(container.getByName("task"), sameInstance(task));
+    }
+
+    @Test
+    public void canReplaceDuplicateTask() {
+        addTask("task");
+
+        final Task newTask = task("task");
+        context.checking(new Expectations() {{
+            one(taskFactory).createTask(project, GUtil.map(Task.TASK_NAME, "task"));
+            will(returnValue(newTask));
+        }});
+        
+        container.replace("task");
+        assertThat(container.getByName("task"), sameInstance(newTask));
+    }
+
+    @Test
+    public void getByNameFailsForUnknownTask() {
+        try {
+            container.getByName("unknown");
+            fail();
+        } catch (UnknownTaskException e) {
+            assertThat(e.getMessage(), equalTo("Task with name 'unknown' not found in <project>."));
+        }
+    }
+
+    @Test
+    public void canFindTaskByName() {
+        Task task = addTask("task");
+
+        assertThat(container.findByPath("task"), sameInstance(task));
+    }
+
+    @Test
+    public void canFindTaskByRelativePath() {
+        Task task = task("task");
+        expectTaskLookupInOtherProject("sub", "task", task);
+
+        assertThat(container.findByPath("sub:task"), sameInstance(task));
+    }
+
+    @Test
+    public void canFindTaskByAbsolutePath() {
+        Task task = task("task");
+        expectTaskLookupInOtherProject(":", "task", task);
+
+        assertThat(container.findByPath(":task"), sameInstance(task));
+    }
+
+    @Test
+    public void findByPathReturnsNullForUnknownProject() {
+        context.checking(new Expectations(){{
+            allowing(project).findProject(":unknown");
+            will(returnValue(null));
+        }});
+
+        assertThat(container.findByPath(":unknown:task"), nullValue());
+    }
+
+    @Test
+    public void findByPathReturnsNullForUnknownTask() {
+        expectTaskLookupInOtherProject(":other", "task", null);
+
+        assertThat(container.findByPath(":other:task"), nullValue());
+    }
+
+    @Test
+    public void canGetTaskByName() {
+        Task task = addTask("task");
+
+        assertThat(container.getByPath("task"), sameInstance(task));
+    }
+
+    @Test
+    public void canGetTaskByPath() {
+        Task task = addTask("task");
+        expectTaskLookupInOtherProject(":a:b:c", "task", task);
+
+        assertThat(container.getByPath(":a:b:c:task"), sameInstance(task));
+    }
+
+    @Test
+    public void getByPathFailsForUnknownTask() {
+        try {
+            container.getByPath("unknown");
+            fail();
+        } catch (UnknownTaskException e) {
+            assertThat(e.getMessage(), equalTo("Task with path 'unknown' not found in <project>."));
+        }
+    }
+
+    @Test
+    public void resolveLocatesTaskByName() {
+        Task task = addTask("1");
+
+        assertThat(container.resolveTask(1), sameInstance(task));
+    }
+
+    @Test
+    public void resolveLocatesTaskByPath() {
+        Task task = addTask("task");
+        expectTaskLookupInOtherProject(":", "task", task);
+        assertThat(container.resolveTask(new StringBuilder(":task")), sameInstance(task));
+    }
+    
+    private void expectTaskLookupInOtherProject(final String projectPath, final String taskName, final Task task) {
+        context.checking(new Expectations() {{
+            Project otherProject = context.mock(Project.class);
+            TaskContainer otherTaskContainer = context.mock(TaskContainer.class);
+
+            allowing(project).findProject(projectPath);
+            will(returnValue(otherProject));
+
+            allowing(otherProject).getTasks();
+            will(returnValue(otherTaskContainer));
+
+            allowing(otherTaskContainer).findByName(taskName);
+            will(returnValue(task));
+        }});
+    }
+
+    private TaskInternal task(final String name) {
+        final TaskInternal task = context.mock(TaskInternal.class, "[task" + ++taskCount + "]");
+        context.checking(new Expectations(){{
+            allowing(task).getName();
+            will(returnValue(name));
+        }});
+        return task;
+    }
+
+    private Task addTask(String name) {
+        final Task task = task(name);
+        final Map<String, ?> options = GUtil.map(Task.TASK_NAME, name);
+        context.checking(new Expectations() {{
+            one(taskFactory).createTask(project, options);
+            will(returnValue(task));
+        }});
+        container.add(name);
+        return task;
+    }
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskDependencyTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskDependencyTest.groovy
new file mode 100644
index 0000000..e739c6f
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskDependencyTest.groovy
@@ -0,0 +1,211 @@
+/*
+ * 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.tasks
+
+import java.util.concurrent.Callable
+import org.gradle.api.Buildable
+import org.gradle.api.Task
+import org.gradle.api.tasks.TaskDependency
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.WrapUtil
+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.isEmpty
+import static org.gradle.util.WrapUtil.toList
+import static org.gradle.util.WrapUtil.toSet
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.gradle.api.GradleException
+
+ at RunWith (JMock.class)
+public class DefaultTaskDependencyTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery();
+    private final TaskResolver resolver = context.mock(TaskResolver.class)
+    private final DefaultTaskDependency dependency = new DefaultTaskDependency(resolver);
+    private Task task;
+    private Task otherTask;
+
+    @Before
+    public void setUp() throws Exception {
+        task = context.mock(Task.class, "task");
+        otherTask = context.mock(Task.class, "otherTask");
+    }
+
+    @Test
+    public void hasNoDependenciesByDefault() {
+        assertThat(dependency.getDependencies(task), equalTo(WrapUtil.toSet()));
+    }
+
+    @Test
+    public void canDependOnATaskInstance() {
+        dependency.add(otherTask);
+
+        assertThat(dependency.getDependencies(task), equalTo(toSet(otherTask)));
+    }
+
+    @Test
+    public void canDependOnATaskDependency() {
+        final TaskDependency otherDependency = context.mock(TaskDependency.class);
+        dependency.add(otherDependency);
+
+        context.checking({
+            one(otherDependency).getDependencies(task);
+            will(returnValue(toSet(otherTask)));
+        });
+
+        assertThat(dependency.getDependencies(task), equalTo(toSet(otherTask)));
+    }
+
+    @Test
+    public void canDependOnAClosure() {
+        dependency.add({Task suppliedTask ->
+            assertThat(suppliedTask, sameInstance(task))
+            otherTask
+        })
+
+        assertThat(dependency.getDependencies(task), equalTo(toSet(otherTask)));
+    }
+
+    @Test
+    public void closureCanReturnNull() {
+        dependency.add({ null })
+
+        assertThat(dependency.getDependencies(task), isEmpty());
+    }
+
+    @Test
+    public void canDependOnABuildable() {
+        Buildable buildable = context.mock(Buildable)
+        TaskDependency otherDependency = context.mock(TaskDependency)
+
+        dependency.add(buildable)
+
+        context.checking {
+            one(buildable).getBuildDependencies()
+            will(returnValue(otherDependency))
+            one(otherDependency).getDependencies(task)
+            will(returnValue(toSet(otherTask)))
+        }
+
+        assertThat(dependency.getDependencies(task), equalTo(toSet(otherTask)));
+    }
+
+    @Test
+    public void canDependOnAnIterable() {
+        List tasks = [otherTask]
+        Iterable iterable = { tasks.iterator() } as Iterable
+
+        dependency.add(iterable)
+
+        assertThat(dependency.getDependencies(task), equalTo(toSet(otherTask)));
+    }
+
+    @org.junit.Test
+    public void canDependOnACallable() {
+        Callable callable = context.mock(Callable)
+
+        dependency.add(callable)
+
+        context.checking {
+            one(callable).call()
+            will(returnValue(otherTask))
+        }
+        
+        assertThat(dependency.getDependencies(task), equalTo(toSet(otherTask)));
+    }
+
+    @org.junit.Test
+    public void callableCanReturnNull() {
+        Callable callable = context.mock(Callable)
+
+        dependency.add(callable)
+
+        context.checking {
+            one(callable).call()
+            will(returnValue(null))
+        }
+
+        assertThat(dependency.getDependencies(task), isEmpty());
+    }
+
+    @Test
+    public void failsForOtherObjectsWhenNoResolverProvided() {
+        StringBuffer dep = new StringBuffer("task")
+
+        DefaultTaskDependency dependency = new DefaultTaskDependency()
+        dependency.add(dep)
+
+        try {
+            dependency.getDependencies(task)
+            fail()
+        } catch (GradleException e) {
+            assertThat(e.cause, instanceOf(IllegalArgumentException))
+            assertThat(e.cause.message, equalTo("Cannot convert $dep to a task." as String))
+        }
+    }
+    
+    @Test
+    public void resolvesOtherObjects() {
+
+        dependency.add(9);
+
+        context.checking({
+            one(resolver).resolveTask(9);
+            will(returnValue(otherTask));
+        });
+
+        assertThat(dependency.getDependencies(task), equalTo(toSet(otherTask)));
+    }
+
+    @Test
+    public void flattensCollections() {
+        dependency.add(toList(otherTask));
+
+        assertThat(dependency.getDependencies(task), equalTo(toSet(otherTask)));
+    }
+
+    @Test
+    public void flattensMaps() {
+        dependency.add([key: otherTask])
+
+        assertThat(dependency.getDependencies(task), equalTo(toSet(otherTask)));
+    }
+
+    @Test
+    public void flattensArrays() {
+        dependency.add([[otherTask] as Task[]])
+
+        assertThat(dependency.getDependencies(task), equalTo(toSet(otherTask)));
+    }
+
+    @Test
+    public void canNestIterablesAndMapsAndClosuresAndCallables() {
+        Map nestedMap = [task: otherTask]
+        Iterable nestedCollection = [nestedMap]
+        Callable nestedCallable = {nestedCollection} as Callable
+        Closure nestedClosure = {nestedCallable}
+        List collection = [nestedClosure]
+        Closure closure = {collection}
+        Object[] array = [closure] as Object[]
+        Map map = [key: array]
+        Callable callable = {map} as Callable
+        dependency.add(callable)
+
+        assertThat(dependency.getDependencies(task), equalTo(toSet(otherTask)));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskExecuterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskExecuterTest.java
new file mode 100644
index 0000000..ece3aee
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskExecuterTest.java
@@ -0,0 +1,281 @@
+/*
+ * 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;
+
+import org.gradle.api.Action;
+import org.gradle.api.Task;
+import org.gradle.api.execution.TaskActionListener;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.logging.StandardOutputCapture;
+import org.gradle.api.tasks.StopActionException;
+import org.gradle.api.tasks.StopExecutionException;
+import org.gradle.api.tasks.TaskExecutionException;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.jmock.Expectations;
+import org.jmock.Sequence;
+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 static org.gradle.util.Matchers.*;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class DefaultTaskExecuterTest {
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private final TaskInternal task = context.mock(TaskInternal.class, "<task>");
+    private final Action<Task> action1 = context.mock(Action.class, "action1");
+    private final Action<Task> action2 = context.mock(Action.class, "action2");
+    private final TaskStateInternal state = context.mock(TaskStateInternal.class);
+    private final ScriptSource scriptSource = context.mock(ScriptSource.class);
+    private final StandardOutputCapture standardOutputCapture = context.mock(StandardOutputCapture.class);
+    private final Sequence sequence = context.sequence("seq");
+    private final TaskActionListener listener = context.mock(TaskActionListener.class);
+    private final DefaultTaskExecuter executer = new DefaultTaskExecuter(listener);
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations(){{
+            ProjectInternal project = context.mock(ProjectInternal.class);
+
+            allowing(task).getProject();
+            will(returnValue(project));
+
+            allowing(project).getBuildScriptSource();
+            will(returnValue(scriptSource));
+
+            allowing(task).getStandardOutputCapture();
+            will(returnValue(standardOutputCapture));
+
+            ignoring(scriptSource);
+        }});
+    }
+
+    @Test
+    public void doesNothingWhenTaskHasNoActions() {
+        context.checking(new Expectations() {{
+            allowing(task).getActions();
+            will(returnValue(toList()));
+
+            one(listener).beforeActions(task);
+            inSequence(sequence);
+
+            one(state).setExecuting(true);
+            inSequence(sequence);
+
+            one(state).executed(null);
+            inSequence(sequence);
+
+            one(state).setExecuting(false);
+            inSequence(sequence);
+
+            one(listener).afterActions(task);
+            inSequence(sequence);
+        }});
+
+        executer.execute(task, state);
+    }
+
+    @Test
+    public void executesEachActionInOrder() {
+        context.checking(new Expectations() {{
+            allowing(task).getActions();
+            will(returnValue(toList(action1, action2)));
+
+            one(listener).beforeActions(task);
+            inSequence(sequence);
+
+            one(state).setExecuting(true);
+            inSequence(sequence);
+
+            one(state).setDidWork(true);
+            inSequence(sequence);
+
+            one(standardOutputCapture).start();
+            inSequence(sequence);
+
+            one(action1).execute(task);
+            inSequence(sequence);
+
+            one(standardOutputCapture).stop();
+            inSequence(sequence);
+
+            one(state).setDidWork(true);
+            inSequence(sequence);
+
+            one(standardOutputCapture).start();
+            inSequence(sequence);
+
+            one(action2).execute(task);
+            inSequence(sequence);
+
+            one(standardOutputCapture).stop();
+            inSequence(sequence);
+
+            one(state).executed(null);
+            inSequence(sequence);
+            
+            one(state).setExecuting(false);
+            inSequence(sequence);
+
+            one(listener).afterActions(task);
+            inSequence(sequence);
+        }});
+
+        executer.execute(task, state);
+    }
+
+    @Test
+    public void stopsAtFirstActionWhichThrowsException() {
+        final Throwable failure = new RuntimeException("failure");
+        final Collector<Throwable> wrappedFailure = collector();
+        context.checking(new Expectations() {{
+            allowing(task).getActions();
+            will(returnValue(toList(action1, action2)));
+
+            one(listener).beforeActions(task);
+            inSequence(sequence);
+
+            one(state).setExecuting(true);
+            inSequence(sequence);
+
+            one(state).setDidWork(true);
+            inSequence(sequence);
+
+            one(standardOutputCapture).start();
+            inSequence(sequence);
+
+            one(action1).execute(task);
+            will(throwException(failure));
+            inSequence(sequence);
+
+            one(standardOutputCapture).stop();
+            inSequence(sequence);
+
+            one(state).executed(with(notNullValue(Throwable.class)));
+            will(collectTo(wrappedFailure));
+            inSequence(sequence);
+
+            one(state).setExecuting(false);
+            inSequence(sequence);
+
+            one(listener).afterActions(task);
+            inSequence(sequence);
+        }});
+
+        executer.execute(task, state);
+
+        assertThat(wrappedFailure.get(), instanceOf(TaskExecutionException.class));
+        TaskExecutionException exception = (TaskExecutionException) wrappedFailure.get();
+        assertThat(exception.getTask(), equalTo((Task) task));
+        assertThat(exception.getMessage(), equalTo("Execution failed for <task>."));
+        assertThat(exception.getCause(), sameInstance(failure));
+    }
+
+    @Test
+    public void stopsAtFirstActionWhichThrowsStopExecutionException() {
+        context.checking(new Expectations() {{
+            allowing(task).getActions();
+            will(returnValue(toList(action1, action2)));
+
+            one(listener).beforeActions(task);
+            inSequence(sequence);
+
+            one(state).setExecuting(true);
+            inSequence(sequence);
+
+            one(state).setDidWork(true);
+            inSequence(sequence);
+
+            one(standardOutputCapture).start();
+            inSequence(sequence);
+
+            one(action1).execute(task);
+            will(throwException(new StopExecutionException("stop")));
+            inSequence(sequence);
+
+            one(standardOutputCapture).stop();
+            inSequence(sequence);
+
+            one(state).executed(null);
+            inSequence(sequence);
+
+            one(state).setExecuting(false);
+            inSequence(sequence);
+
+            one(listener).afterActions(task);
+            inSequence(sequence);
+        }});
+
+        executer.execute(task, state);
+    }
+
+    @Test
+    public void skipsActionWhichThrowsStopActionException() {
+        context.checking(new Expectations() {{
+            allowing(task).getActions();
+            will(returnValue(toList(action1, action2)));
+
+            one(listener).beforeActions(task);
+            inSequence(sequence);
+
+            one(state).setExecuting(true);
+            inSequence(sequence);
+
+            one(state).setDidWork(true);
+            inSequence(sequence);
+
+            one(standardOutputCapture).start();
+            inSequence(sequence);
+
+            one(action1).execute(task);
+            will(throwException(new StopActionException("stop")));
+            inSequence(sequence);
+
+            one(standardOutputCapture).stop();
+            inSequence(sequence);
+
+            one(state).setDidWork(true);
+            inSequence(sequence);
+
+            one(standardOutputCapture).start();
+            inSequence(sequence);
+
+            one(action2).execute(task);
+            inSequence(sequence);
+
+            one(standardOutputCapture).stop();
+            inSequence(sequence);
+
+            one(state).executed(null);
+            inSequence(sequence);
+
+            one(state).setExecuting(false);
+            inSequence(sequence);
+
+            one(listener).afterActions(task);
+            inSequence(sequence);
+        }});
+
+        executer.execute(task, state);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskInputsTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskInputsTest.groovy
new file mode 100644
index 0000000..801ddd8
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskInputsTest.groovy
@@ -0,0 +1,105 @@
+/*
+ * 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
+
+import org.junit.Test
+import static org.junit.Assert.*
+import static org.hamcrest.Matchers.*
+import static org.gradle.util.Matchers.*
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.file.FileTree
+import java.util.concurrent.Callable
+import org.gradle.api.file.FileCollection
+
+class DefaultTaskInputsTest {
+    private final File treeFile = new File('tree')
+    private final FileTree tree = [getFiles: { [treeFile] as Set}] as FileTree
+    private final FileResolver resolver = [
+            resolve: {new File(it)},
+            resolveFilesAsTree: {tree}
+    ] as FileResolver
+    private final DefaultTaskInputs inputs = new DefaultTaskInputs(resolver)
+
+    @Test
+    public void defaultValues() {
+        assertThat(inputs.files.files, isEmpty())
+        assertFalse(inputs.hasInputs)
+    }
+
+    @Test
+    public void canRegisterInputFiles() {
+        inputs.files('a')
+        assertThat(inputs.files.files, equalTo([new File('a')] as Set))
+    }
+
+    @Test
+    public void canRegisterInputDir() {
+        inputs.dir('a')
+        assertThat(inputs.files.files, equalTo([treeFile] as Set))
+    }
+    
+    @Test
+    public void canRegisterInputProperty() {
+        inputs.property('a', 'value')
+        assertThat(inputs.properties, equalTo([a: 'value']))
+    }
+    
+    @Test
+    public void canRegisterInputPropertyUsingAClosure() {
+        inputs.property('a', { 'value' })
+        assertThat(inputs.properties, equalTo([a: 'value']))
+    }
+
+    @Test
+    public void canRegisterInputPropertyUsingACallable() {
+        inputs.property('a', { 'value' } as Callable)
+        assertThat(inputs.properties, equalTo([a: 'value']))
+    }
+
+    @Test
+    public void canRegisterInputPropertyUsingAFileCollection() {
+        def files = [new File('file')] as Set
+        inputs.property('a', [getFiles: { files }] as FileCollection)
+        assertThat(inputs.properties, equalTo([a: files]))
+    }
+
+    @Test
+    public void inputPropertyCanBeNestedCallableAndClosure() {
+        def files = [new File('file')] as Set
+        def fileCollection = [getFiles: { files }] as FileCollection
+        def callable = {fileCollection} as Callable
+        inputs.property('a', { callable })
+        assertThat(inputs.properties, equalTo([a: files]))
+    }
+
+    @Test
+    public void hasInputsWhenEmptyInputFilesRegistered() {
+        inputs.files([])
+        assertTrue(inputs.hasInputs)
+    }
+
+    @Test
+    public void hasInputsWhenNonEmptyInputFilesRegistered() {
+        inputs.files('a')
+        assertTrue(inputs.hasInputs)
+    }
+
+    @Test
+    public void hasInputsWhenInputPropertyRegistered() {
+        inputs.property('a', 'value')
+        assertTrue(inputs.hasInputs)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskOutputsTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskOutputsTest.groovy
new file mode 100644
index 0000000..881ad4c
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskOutputsTest.groovy
@@ -0,0 +1,110 @@
+/*
+ * 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
+
+import org.gradle.api.internal.TaskInternal
+import org.gradle.api.internal.file.FileResolver
+import spock.lang.Specification
+import org.gradle.api.internal.TaskExecutionHistory
+import org.gradle.api.file.FileCollection
+
+class DefaultTaskOutputsTest extends Specification {
+    private final TaskInternal task = [toString: {'task'}] as TaskInternal
+    private final DefaultTaskOutputs outputs = new DefaultTaskOutputs({new File(it)} as FileResolver, task)
+
+    public void hasNoOutputsByDefault() {
+        setup:
+        assert outputs.files.files.isEmpty()
+        assert !outputs.hasOutput
+    }
+
+    public void outputFileCollectionIsBuiltByTask() {
+        setup:
+        assert outputs.files.buildDependencies.getDependencies(task) == [task] as Set
+    }
+
+    public void canRegisterOutputFiles() {
+        when:
+        outputs.files('a')
+
+        then:
+        outputs.files.files == [new File('a')] as Set
+    }
+
+    public void hasOutputsWhenEmptyOutputFilesRegistered() {
+        when:
+        outputs.files([])
+
+        then:
+        outputs.hasOutput
+    }
+    
+    public void hasOutputsWhenNonEmptyOutputFilesRegistered() {
+        when:
+        outputs.files('a')
+
+        then:
+        outputs.hasOutput
+    }
+
+    public void hasOutputsWhenUpToDatePredicateRegistered() {
+        when:
+        outputs.upToDateWhen { false }
+
+        then:
+        outputs.hasOutput
+    }
+    
+    public void canSpecifyUpToDatePredicateUsingClosure() {
+        boolean upToDate = false
+
+        when:
+        outputs.upToDateWhen { upToDate }
+
+        then:
+        !outputs.upToDateSpec.isSatisfiedBy(task)
+
+        when:
+        upToDate = true
+
+        then:
+        outputs.upToDateSpec.isSatisfiedBy(task)
+    }
+
+    public void getPreviousFilesDelegatesToTaskHistory() {
+        TaskExecutionHistory history = Mock()
+        FileCollection outputFiles = Mock()
+
+        setup:
+        outputs.history = history
+
+        when:
+        def f = outputs.previousFiles
+
+        then:
+        f == outputFiles
+        1 * history.outputFiles >> outputFiles
+    }
+    
+    public void getPreviousFilesFailsWhenNoTaskHistoryAvailable() {
+        when:
+        outputs.previousFiles
+
+        then:
+        def e = thrown(IllegalStateException)
+        e.message == 'Task history is currently not available for this task.'
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/ExecuteAtMostOnceTaskExecuterTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/ExecuteAtMostOnceTaskExecuterTest.groovy
new file mode 100644
index 0000000..ed22980
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/ExecuteAtMostOnceTaskExecuterTest.groovy
@@ -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.api.internal.tasks
+
+import org.gradle.api.internal.TaskInternal
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.Test
+import org.junit.runner.RunWith
+
+ at RunWith(JMock.class)
+class ExecuteAtMostOnceTaskExecuterTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final TaskExecuter target = context.mock(TaskExecuter.class)
+    private final TaskInternal task = context.mock(TaskInternal.class)
+    private final TaskStateInternal state = context.mock(TaskStateInternal.class)
+    private final ExecuteAtMostOnceTaskExecuter executer = new ExecuteAtMostOnceTaskExecuter(target)
+
+    @Test
+    public void doesNothingWhenTaskHasAlreadyBeenExecuted() {
+        context.checking {
+            allowing(state).getExecuted()
+            will(returnValue(true))
+        }
+
+        executer.execute(task, state)
+    }
+
+    @Test
+    public void delegatesToExecuterWhenTaskHasNotBeenExecuted() {
+        context.checking {
+            allowing(state).getExecuted()
+            will(returnValue(false))
+            one(target).execute(task, state)
+        }
+
+        executer.execute(task, state)
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/SkipTaskExecuterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/SkipTaskExecuterTest.java
new file mode 100644
index 0000000..b9ae075
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/SkipTaskExecuterTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.Task;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.specs.Spec;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.util.JUnit4GroovyMockery;
+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 static org.gradle.util.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class SkipTaskExecuterTest {
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private final TaskInternal task = context.mock(TaskInternal.class, "<task>");
+    private final Spec<Task> spec = context.mock(Spec.class);
+    private final TaskStateInternal state = context.mock(TaskStateInternal.class);
+    private final ScriptSource scriptSource = context.mock(ScriptSource.class);
+    private final TaskExecuter delegate = context.mock(TaskExecuter.class);
+    private final SkipTaskExecuter executer = new SkipTaskExecuter(delegate);
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations(){{
+            ProjectInternal project = context.mock(ProjectInternal.class);
+
+            allowing(task).getProject();
+            will(returnValue(project));
+
+            allowing(project).getBuildScriptSource();
+            will(returnValue(scriptSource));
+
+            allowing(task).getOnlyIf();
+            will(returnValue(spec));
+
+            ignoring(scriptSource);
+        }});
+    }
+
+    @Test
+    public void executesTask() {
+        context.checking(new Expectations() {{
+            allowing(task).getEnabled();
+            will(returnValue(true));
+
+            allowing(spec).isSatisfiedBy(task);
+            will(returnValue(true));
+
+            one(delegate).execute(task, state);
+
+            one(state).executed();
+        }});
+
+        executer.execute(task, state);
+    }
+
+    @Test
+    public void skipsTaskWhoseOnlyIfPredicateIsFalse() {
+        context.checking(new Expectations() {{
+            allowing(task).getEnabled();
+            will(returnValue(true));
+            one(spec).isSatisfiedBy(task);
+            will(returnValue(false));
+            one(state).skipped("SKIPPED");
+            one(state).executed();
+        }});
+
+        executer.execute(task, state);
+    }
+
+    @Test
+    public void wrapsOnlyIfPredicateFailure() {
+        final Throwable failure = new RuntimeException();
+        final Collector<Throwable> wrappedFailure = collector();
+        context.checking(new Expectations() {{
+            allowing(task).getEnabled();
+            will(returnValue(true));
+            one(spec).isSatisfiedBy(task);
+            will(throwException(failure));
+            one(state).executed(with(notNullValue(GradleException.class)));
+            will(collectTo(wrappedFailure));
+            one(state).executed();
+        }});
+
+        executer.execute(task, state);
+
+        GradleException exception = (GradleException) wrappedFailure.get();
+        assertThat(exception.getMessage(), equalTo("Could not evaluate onlyIf predicate for <task>."));
+        assertThat(exception.getCause(), sameInstance(failure));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/TaskStateInternalTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/TaskStateInternalTest.groovy
new file mode 100644
index 0000000..a22a3e2
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/TaskStateInternalTest.groovy
@@ -0,0 +1,120 @@
+/*
+ * 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
+
+import org.gradle.api.GradleException
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+class TaskStateInternalTest {
+    private final TaskStateInternal state = new TaskStateInternal('task-description')
+
+    @Test
+    public void defaultValues() {
+        assertFalse(state.getExecuted())
+        assertFalse(state.getExecuting())
+        assertThat(state.getFailure(), nullValue())
+        assertFalse(state.getDidWork())
+        assertFalse(state.getSkipped())
+        assertThat(state.getSkipMessage(), nullValue())
+    }
+
+    @Test
+    public void canMarkTaskAsExecuted() {
+        state.executed()
+        assertTrue(state.executed)
+        assertFalse(state.skipped)
+        assertThat(state.getFailure(), nullValue())
+    }
+    
+    @Test
+    public void canMarkTaskAsExecutedWithFailure() {
+        RuntimeException failure = new RuntimeException()
+        state.executed(failure)
+        assertTrue(state.executed)
+        assertFalse(state.skipped)
+        assertThat(state.failure, sameInstance(failure))
+    }
+
+    @Test
+    public void canMarkTaskAsSkipped() {
+        state.skipped('skip-message')
+        assertTrue(state.executed)
+        assertTrue(state.skipped)
+        assertThat(state.skipMessage, equalTo('skip-message'))
+    }
+
+    @Test
+    public void canMarkTaskAsUpToDate() {
+        state.upToDate()
+        assertTrue(state.executed)
+        assertTrue(state.skipped)
+        assertThat(state.skipMessage, equalTo('UP-TO-DATE'))
+    }
+
+    @Test
+    public void rethrowFailureDoesNothingWhenTaskHasNotExecuted() {
+        state.rethrowFailure()
+    }
+
+    @Test
+    public void rethrowFailureDoesNothingWhenTaskDidNotFail() {
+        state.executed()
+        state.rethrowFailure()
+    }
+
+    @Test
+    public void rethrowsFailureWhenFailureIsRuntimeException() {
+        RuntimeException failure = new RuntimeException()
+        state.executed(failure)
+        try {
+            state.rethrowFailure()
+            fail()
+        } catch (RuntimeException e) {
+            assertThat(e, sameInstance(failure))
+        }
+    }
+
+    @Test
+    public void rethrowsFailureWhenFailureIsError() {
+        Error failure = new Error()
+        state.executed(failure)
+        try {
+            state.rethrowFailure()
+            fail()
+        } catch (Error e) {
+            assertThat(e, sameInstance(failure))
+        }
+    }
+
+    @Test
+    public void rethrowsFailureWhenFailureIsException() {
+        Exception failure = new Exception()
+        state.executed(failure)
+        try {
+            state.rethrowFailure()
+            fail()
+        } catch (GradleException e) {
+            assertThat(e.message, equalTo('Task-description failed with an exception.'))
+            assertThat(e.cause, sameInstance(failure))
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/util/DefaultJavaForkOptionsTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/util/DefaultJavaForkOptionsTest.groovy
new file mode 100644
index 0000000..99858e4
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/util/DefaultJavaForkOptionsTest.groovy
@@ -0,0 +1,266 @@
+/*
+ * 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.hamcrest.Matchers.*
+
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.file.IdentityFileResolver
+import org.gradle.process.JavaForkOptions
+import org.gradle.process.internal.DefaultJavaForkOptions
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.Jvm
+import org.jmock.integration.junit4.JMock
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.gradle.util.Matchers.isEmpty
+import static org.gradle.util.Matchers.isEmptyMap
+import static org.junit.Assert.*
+import org.junit.Before
+
+ at RunWith(JMock.class)
+public class DefaultJavaForkOptionsTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final FileResolver resolver = context.mock(FileResolver.class)
+    private DefaultJavaForkOptions options
+
+    @Before
+    public void setup() {
+        context.checking {
+            allowing(resolver).resolveLater(new File('.').absoluteFile)
+        }
+        options = new DefaultJavaForkOptions(resolver, Jvm.current())
+    }
+
+    @Test
+    public void defaultValues() {
+        assertThat(options.executable, notNullValue())
+        assertThat(options.jvmArgs, isEmpty())
+        assertThat(options.systemProperties, isEmptyMap())
+        assertThat(options.maxHeapSize, nullValue())
+        assertThat(options.bootstrapClasspath.files, isEmpty())
+        assertFalse(options.enableAssertions)
+        assertFalse(options.debug)
+        assertThat(options.allJvmArgs, isEmpty())
+    }
+
+    @Test
+    public void convertsJvmArgsToStringOnGet() {
+        options.jvmArgs = [12, "${1 + 2}"]
+        assertThat(options.jvmArgs, equalTo(['12', '3']))
+    }
+
+    @Test
+    public void canAddJvmArgs() {
+        options.jvmArgs('arg1', 'arg2')
+        assertThat(options.jvmArgs, equalTo(['arg1', 'arg2']))
+    }
+
+    @Test
+    public void canSetSystemProperties() {
+        options.systemProperties = [key: 12, key2: "value", key3: null]
+        assertThat(options.systemProperties, equalTo(key: 12, key2: "value", key3: null))
+    }
+
+    @Test
+    public void canAddSystemProperties() {
+        options.systemProperties(key: 12)
+        options.systemProperty('key2', 'value2')
+        assertThat(options.systemProperties, equalTo(key: 12, key2: 'value2'))
+    }
+
+    @Test
+    public void allJvmArgsIncludeSystemPropertiesAsString() {
+        options.systemProperties(key: 12, key2: null, "key3": 'value')
+        options.jvmArgs('arg1')
+
+        assertThat(options.allJvmArgs, equalTo(['arg1', '-Dkey=12', '-Dkey2', '-Dkey3=value']))
+    }
+
+    @Test
+    public void systemPropertiesAreUpdatedWhenAddedUsingJvmArgs() {
+        options.systemProperties(key: 12)
+        options.jvmArgs('-Dkey=new value', '-Dkey2')
+
+        assertThat(options.systemProperties, equalTo(key: 'new value', key2: null))
+
+        options.allJvmArgs = []
+
+        assertThat(options.systemProperties, equalTo([:]))
+
+        options.allJvmArgs = ['-Dkey=value']
+
+        assertThat(options.systemProperties, equalTo([key: 'value']))
+    }
+
+    @Test
+    public void allJvmArgsIncludeMaxHeapSize() {
+        options.maxHeapSize = '1g'
+        options.jvmArgs('arg1')
+
+        assertThat(options.allJvmArgs, equalTo(['arg1', '-Xmx1g']))
+    }
+
+    @Test
+    public void maxHeapSizeIsUpdatedWhenSetUsingJvmArgs() {
+        options.maxHeapSize = '1g'
+        options.jvmArgs('-Xmx1024m')
+
+        assertThat(options.maxHeapSize, equalTo('1024m'))
+
+        options.allJvmArgs = []
+
+        assertThat(options.maxHeapSize, nullValue())
+
+        options.allJvmArgs = ['-Xmx1g']
+
+        assertThat(options.maxHeapSize, equalTo('1g'))
+    }
+
+    @Test
+    public void allJvmArgsIncludeAssertionsEnabled() {
+        assertThat(options.allJvmArgs, equalTo([]))
+
+        options.enableAssertions = true
+
+        assertThat(options.allJvmArgs, equalTo(['-ea']))
+    }
+
+    @Test
+    public void assertionsEnabledIsUpdatedWhenSetUsingJvmArgs() {
+        options.jvmArgs('-ea')
+        assertTrue(options.enableAssertions)
+        assertThat(options.jvmArgs, equalTo([]))
+
+        options.allJvmArgs = []
+        assertFalse(options.enableAssertions)
+
+        options.jvmArgs('-enableassertions')
+        assertTrue(options.enableAssertions)
+
+        options.allJvmArgs = ['-da']
+        assertFalse(options.enableAssertions)
+    }
+
+    @Test
+    public void allJvmArgsIncludeDebugArgs() {
+        assertThat(options.allJvmArgs, equalTo([]))
+
+        options.debug = true
+
+        assertThat(options.allJvmArgs, equalTo(['-Xdebug', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005']))
+    }
+
+    @Test
+    public void debugIsUpdatedWhenSetUsingJvmArgs() {
+        options.jvmArgs('-Xdebug', '-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005')
+        assertTrue(options.debug)
+        assertThat(options.jvmArgs, equalTo([]))
+
+        options.allJvmArgs = []
+        assertFalse(options.debug)
+
+        options.jvmArgs = ['-Xdebug']
+        assertFalse(options.debug)
+        assertThat(options.jvmArgs, equalTo(['-Xdebug']))
+
+        options.jvmArgs = ['-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005']
+        assertFalse(options.debug)
+        assertThat(options.jvmArgs, equalTo(['-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005']))
+
+        options.jvmArgs '-Xdebug'
+        assertTrue(options.debug)
+        assertThat(options.jvmArgs, equalTo([]))
+
+        options.jvmArgs = ['-Xdebug', '-Xrunjdwp:transport=other']
+        assertFalse(options.debug)
+        assertThat(options.jvmArgs, equalTo(['-Xdebug', '-Xrunjdwp:transport=other']))
+
+        options.allJvmArgs = ['-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005', '-Xdebug']
+        assertTrue(options.debug)
+        assertThat(options.jvmArgs, equalTo([]))
+    }
+    
+    @Test
+    public void canSetBootstrapClasspath() {
+        def bootstrapClasspath = [:] as FileCollection
+        options.bootstrapClasspath = bootstrapClasspath
+
+        assertThat(options.bootstrapClasspath, sameInstance(bootstrapClasspath))
+    }
+
+    @Test
+    public void canAddToBootstrapClasspath() {
+        def files = ['file1.jar', 'file2.jar'].collect { new File(it).canonicalFile }
+        options = new DefaultJavaForkOptions(new IdentityFileResolver());
+        options.bootstrapClasspath(files[0])
+        options.bootstrapClasspath(files[1])
+
+        assertThat(options.bootstrapClasspath.getFiles(), equalTo(files as Set))
+    }
+
+    @Test
+    public void allJvmArgsIncludeBootstrapClasspath() {
+        def files = ['file1.jar', 'file2.jar'].collect { new File(it).canonicalFile }
+        options = new DefaultJavaForkOptions(new IdentityFileResolver());
+        options.bootstrapClasspath(files)
+
+        context.checking {
+            allowing(resolver).resolveFiles(['file.jar'])
+            will(returnValue([isEmpty: {false}, getAsPath: {'<classpath>'}] as FileCollection))
+        }
+
+        assertThat(options.allJvmArgs, equalTo(['-Xbootclasspath:' + files.join(System.properties['path.separator'])]))
+    }
+
+    @Test
+    public void canSetBootstrapClasspathViaAllJvmArgs() {
+        def files = ['file1.jar', 'file2.jar'].collect { new File(it).canonicalFile }
+        options = new DefaultJavaForkOptions(new IdentityFileResolver());
+        options.bootstrapClasspath(files[0])
+
+        options.allJvmArgs = ['-Xbootclasspath:' + files[1]]
+
+        assertThat(options.bootstrapClasspath.files, equalTo([files[1]] as Set))
+    }
+
+    @Test
+    public void canCopyToTargetOptions() {
+        options.executable('executable')
+        options.jvmArgs('arg')
+        options.systemProperties(key: 12)
+        options.maxHeapSize = '1g'
+
+        JavaForkOptions target = context.mock(JavaForkOptions.class)
+        context.checking {
+            one(target).setExecutable('executable')
+            one(target).setJvmArgs(['arg'])
+            one(target).setSystemProperties(key: 12)
+            one(target).setMaxHeapSize('1g')
+            one(target).setBootstrapClasspath(options.bootstrapClasspath)
+            one(target).setEnableAssertions(false)
+            one(target).setDebug(false)
+            ignoring(target)
+        }
+
+        options.copyTo(target)
+    }
+}
+
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/util/DefaultProcessForkOptionsTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/util/DefaultProcessForkOptionsTest.groovy
new file mode 100644
index 0000000..f886f4d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/internal/tasks/util/DefaultProcessForkOptionsTest.groovy
@@ -0,0 +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(new File('.').absoluteFile)
+            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)
+    }
+}
+
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/logging/LogLevelTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/logging/LogLevelTest.groovy
new file mode 100644
index 0000000..ad6a42f
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/logging/LogLevelTest.groovy
@@ -0,0 +1,30 @@
+/*
+ * 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.logging
+
+import spock.lang.Specification
+
+class LogLevelTest extends Specification {
+    def logLevelsAreOrderedByIncreasingSeverity() {
+        expect:
+        LogLevel.values().sort() == [LogLevel.DEBUG, LogLevel.INFO, LogLevel.LIFECYCLE, LogLevel.WARN, LogLevel.QUIET, LogLevel.ERROR]
+        LogLevel.DEBUG < LogLevel.INFO
+        LogLevel.INFO < LogLevel.LIFECYCLE
+        LogLevel.LIFECYCLE < LogLevel.WARN
+        LogLevel.WARN < LogLevel.QUIET
+        LogLevel.QUIET < LogLevel.ERROR
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/logging/LoggingTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/logging/LoggingTest.java
new file mode 100644
index 0000000..f974e09
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/logging/LoggingTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.logging;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.LoggingEvent;
+import ch.qos.logback.core.Appender;
+import org.gradle.logging.LoggingTestHelper;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.slf4j.Marker;
+
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class LoggingTest {
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private final Appender<ILoggingEvent> appender = context.mock(Appender.class);
+    private final LoggingTestHelper helper = new LoggingTestHelper(appender);
+
+    @Before
+    public void attachAppender() {
+        helper.attachAppender();
+    }
+
+    @After
+    public void detachAppender() {
+        helper.detachAppender();
+    }
+
+    @Test
+    public void routesLogMessagesViaSlf4j() {
+        Logger logger = Logging.getLogger(LoggingTest.class);
+
+        expectLogMessage(Level.TRACE, null, "trace");
+        logger.trace("trace");
+
+        expectLogMessage(Level.DEBUG, null, "debug");
+        logger.debug("debug");
+
+        expectLogMessage(Level.INFO, null, "info");
+        logger.info("info");
+
+        expectLogMessage(Level.WARN, null, "warn");
+        logger.warn("warn");
+
+        expectLogMessage(Level.INFO, Logging.LIFECYCLE, "lifecycle");
+        logger.lifecycle("lifecycle");
+
+        expectLogMessage(Level.ERROR, null, "error");
+        logger.error("error");
+
+        expectLogMessage(Level.INFO, Logging.QUIET, "quiet");
+        logger.quiet("quiet");
+
+        expectLogMessage(Level.INFO, Logging.LIFECYCLE, "lifecycle via level");
+        logger.log(LogLevel.LIFECYCLE, "lifecycle via level");
+    }
+
+    @Test
+    public void delegatesLevelIsEnabledToSlf4j() {
+        helper.setLevel(Level.WARN);
+
+        Logger logger = Logging.getLogger(LoggingTest.class);
+        assertTrue(logger.isErrorEnabled());
+        assertTrue(logger.isWarnEnabled());
+        assertFalse(logger.isQuietEnabled());
+        assertFalse(logger.isLifecycleEnabled());
+        assertFalse(logger.isInfoEnabled());
+        assertFalse(logger.isDebugEnabled());
+        assertFalse(logger.isTraceEnabled());
+
+        assertTrue(logger.isEnabled(LogLevel.ERROR));
+        assertFalse(logger.isEnabled(LogLevel.INFO));
+    }
+
+    private void expectLogMessage(final Level level, final Marker marker, final String text) {
+        final Matcher<LoggingEvent> matcher = new BaseMatcher<LoggingEvent>() {
+
+            public void describeTo(Description description) {
+                description.appendText("level: ").appendValue(level).appendText(", marker: ").appendValue(marker)
+                        .appendText(", text:").appendValue(text);
+            }
+
+            public boolean matches(Object o) {
+                LoggingEvent event = (LoggingEvent) o;
+                return event.getLevel().equals(level) && event.getMessage().equals(text) && event.getMarker() == marker;
+            }
+        };
+
+        context.checking(new Expectations() {{
+            one(appender).doAppend(with(matcher));
+        }});
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/plugins/TestPluginConvention1.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/plugins/TestPluginConvention1.groovy
new file mode 100644
index 0000000..c38c20f
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/plugins/TestPluginConvention1.groovy
@@ -0,0 +1,34 @@
+/*
+ * 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.plugins
+
+/**
+ * @author Hans Dockter
+ */
+class TestPluginConvention1 {
+    String a = 'a1'
+    String b = 'b'
+    String c = 'c' 
+
+    String meth() {
+        'called1'
+    }
+
+    String meth(String arg) {
+        'called1' + arg
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/plugins/TestPluginConvention2.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/plugins/TestPluginConvention2.groovy
new file mode 100644
index 0000000..a7e1b4d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/plugins/TestPluginConvention2.groovy
@@ -0,0 +1,32 @@
+/*
+ * 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.plugins
+
+/**
+ * @author Hans Dockter
+ */
+class TestPluginConvention2 {
+    String a = 'a2'
+
+    String meth() {
+        'called2'
+    }
+
+    String meth(String arg) {
+        'called2' + arg
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/specs/AbstractCompositeSpecTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/specs/AbstractCompositeSpecTest.java
new file mode 100644
index 0000000..ad0e701
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/specs/AbstractCompositeSpecTest.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.api.specs;
+
+import org.gradle.util.WrapUtil;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.gradle.util.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+abstract public class AbstractCompositeSpecTest {
+    private Spec spec1;
+    private Spec spec2;
+
+    public abstract CompositeSpec<Object> createCompositeSpec(Spec<Object>... specs);
+
+    @Before
+    public void setUp() {
+        spec1 = new Spec<Object>() {
+            public boolean isSatisfiedBy(Object o) {
+                return false;
+            }
+        };
+        spec2 = new Spec<Object>() {
+            public boolean isSatisfiedBy(Object o) {
+                return false;
+            }
+        };
+    }
+
+    @Test
+    public void init() {
+        CompositeSpec<Object> compositeSpec = createCompositeSpec(spec1, spec2);
+        assertEquals(WrapUtil.toList(spec1, spec2), compositeSpec.getSpecs());
+    }
+
+    protected Spec<Object>[] createAtomicElements(boolean... satisfies) {
+        List<Spec<Object>> result = new ArrayList<Spec<Object>>();
+        for (final boolean satisfy : satisfies) {
+            result.add(new Spec<Object>() {
+                public boolean isSatisfiedBy(Object o) {
+                    return satisfy;
+                }
+            });
+        }
+        return result.toArray(new Spec[result.size()]);
+    }
+
+    @Test
+    public void equality() {
+        assertThat(createCompositeSpec(spec1), strictlyEqual(createCompositeSpec(spec1)));
+        assertFalse(createCompositeSpec(spec1).equals(createCompositeSpec(spec2)));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/specs/AndSpecTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/specs/AndSpecTest.java
new file mode 100644
index 0000000..59ba97d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/specs/AndSpecTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.specs;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.gradle.util.HelperUtil;
+import org.junit.Test;
+
+/**
+ * @author Hans Dockter
+ */
+public class AndSpecTest extends AbstractCompositeSpecTest {
+    public CompositeSpec<Object> createCompositeSpec(Spec<Object>... specs) {
+        return new AndSpec<Object>(specs);
+    }
+
+    @Test
+    public void isSatisfiedWhenNoSpecs() {
+        assertTrue(new AndSpec<Object>().isSatisfiedBy(new Object()));
+    }
+    
+    @Test
+    public void isSatisfiedByWithAllTrue() {
+        assertTrue(new AndSpec<Object>(createAtomicElements(true, true, true)).isSatisfiedBy(new Object()));
+    }
+
+    @Test
+    public void isSatisfiedByWithOneFalse() {
+        assertFalse(new AndSpec<Object>(createAtomicElements(true, false, true)).isSatisfiedBy(new Object()));
+    }
+    
+    @Test
+    public void canAddSpecs() {
+        AndSpec<Object> spec = new AndSpec<Object>(createAtomicElements(true));
+        spec = spec.and(createAtomicElements(false));
+        assertFalse(spec.isSatisfiedBy(new Object()));
+    }
+    
+    @Test
+    public void canAddClosureAsASpec() {
+        AndSpec<Object> spec = new AndSpec<Object>(createAtomicElements(true));
+        spec = spec.and(HelperUtil.toClosure("{ false }"));
+        assertFalse(spec.isSatisfiedBy(new Object()));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/specs/NotSpecTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/specs/NotSpecTest.java
new file mode 100644
index 0000000..2ff25c7
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/specs/NotSpecTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.specs;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+
+/**
+ * @author Hans Dockter
+ */
+public class NotSpecTest {
+    @Test
+    public void testIsSatisfiedBy() {
+        assertThat(new NotSpec(createFilterSpec()).isSatisfiedBy(true), equalTo(false));
+        assertThat(new NotSpec(createFilterSpec()).isSatisfiedBy(false), equalTo(true));
+    }
+
+    private Spec<Boolean> createFilterSpec() {
+        return new Spec<Boolean>() {
+            public boolean isSatisfiedBy(Boolean element) {
+                return element;
+            }
+        };
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/specs/OrSpecTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/specs/OrSpecTest.java
new file mode 100644
index 0000000..1b760cc
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/specs/OrSpecTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.specs;
+
+import org.gradle.api.artifacts.Dependency;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+/**
+ * @author Hans Dockter
+ */
+public class OrSpecTest extends AbstractCompositeSpecTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    public CompositeSpec createCompositeSpec(Spec... specs) {
+        return new OrSpec(specs);
+    }
+
+    @Test
+    public void isSatisfiedWhenNoSpecs() {
+        assertTrue(new OrSpec().isSatisfiedBy(new Object()));
+    }
+    
+    @Test
+    public void isSatisfiedByWithOneTrue() {
+        assertTrue(new OrSpec(createAtomicElements(false, true, false)).isSatisfiedBy(context.mock(Dependency.class)));
+    }
+
+    @Test
+    public void isSatisfiedByWithAllFalse() {
+        assertFalse(new AndSpec(createAtomicElements(false, false, false)).isSatisfiedBy(context.mock(Dependency.class)));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/specs/SpecsTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/specs/SpecsTest.groovy
new file mode 100644
index 0000000..750f7b9
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/specs/SpecsTest.groovy
@@ -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.api.specs
+
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+class SpecsTest extends Specification {
+    def filterIterable() {
+        List list = ['a', 'b', 'c']
+
+        expect:
+        ['a', 'c'] as Set == Specs.filterIterable(list, Specs.convertClosureToSpec{ item -> item != 'b' })
+    }
+
+    def filterIterableWithNullReturningSpec() {
+        expect:
+        [] as Set == Specs.filterIterable(['a'], Specs.convertClosureToSpec { item -> println item })
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/AbstractConventionTaskTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/AbstractConventionTaskTest.java
new file mode 100644
index 0000000..b2a9b68
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/AbstractConventionTaskTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.tasks;
+
+import org.gradle.api.internal.ConventionAwareHelper;
+import org.gradle.api.internal.ConventionMapping;
+import org.gradle.api.internal.ConventionTask;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractConventionTaskTest extends AbstractTaskTest {
+
+    public abstract ConventionTask getTask();
+    
+    @Test
+    public void testConventionAwareness() {
+        ConventionTask task = getTask();
+        assertThat(task.getConventionMapping(), instanceOf(ConventionAwareHelper.class));
+        assertThat(task.getConventionMapping().getConvention(), sameInstance(getProject().getConvention()));
+
+        ConventionMapping conventionMapping = context.mock(ConventionMapping.class);
+        task.setConventionMapping(conventionMapping);
+        assertThat(task.getConventionMapping(), sameInstance(conventionMapping));
+    }
+}
+
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/AbstractCopyTaskTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/AbstractCopyTaskTest.java
new file mode 100644
index 0000000..da07e7d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/AbstractCopyTaskTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.tasks;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.internal.file.copy.CopyActionImpl;
+import org.gradle.util.JUnit4GroovyMockery;
+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 static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertThat;
+
+ at RunWith(JMock.class)
+public class AbstractCopyTaskTest extends AbstractTaskTest {
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private TestCopyTask task;
+
+    @Before
+    public void setUp() {
+        super.setUp();
+        task = createTask(TestCopyTask.class);
+        task.action = context.mock(CopyActionImpl.class);
+        task.defaultSource = context.mock(FileCollection.class);
+    }
+
+    @Override
+    public AbstractTask getTask() {
+        return task;
+    }
+
+    @Test
+    public void usesDefaultSourceWhenNoSourceHasBeenSpecified() {
+        context.checking(new Expectations(){{
+            one(task.action).hasSource();
+            will(returnValue(false));
+
+        }});
+        assertThat(task.getSource(), sameInstance(task.defaultSource));
+    }
+
+    @Test
+    public void doesNotUseDefaultSourceWhenSourceHasBeenSpecifiedOnSpec() {
+        final FileTree source = context.mock(FileTree.class, "source");
+        context.checking(new Expectations(){{
+            one(task.action).hasSource();
+            will(returnValue(true));
+            one(task.action).getAllSource();
+            will(returnValue(source));
+        }});
+        assertThat(task.getSource(), sameInstance((FileCollection) source));
+    }
+
+    @Test
+    public void copySpecMethodsDelegateToMainSpecOfCopyAction() {
+        context.checking(new Expectations() {{
+            one(task.action).include("include");
+            one(task.action).from("source");
+        }});
+
+        assertThat(task.include("include"), sameInstance((AbstractCopyTask) task));
+        assertThat(task.from("source"), sameInstance((AbstractCopyTask) task));
+    }
+
+    public static class TestCopyTask extends AbstractCopyTask {
+        CopyActionImpl action;
+        FileCollection defaultSource;
+
+        @Override
+        protected CopyActionImpl getCopyAction() {
+            return action;
+        }
+
+        @Override
+        public FileCollection getDefaultSource() {
+            return defaultSource;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/AbstractSpockTaskTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/AbstractSpockTaskTest.groovy
new file mode 100644
index 0000000..f1bb7e3
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/AbstractSpockTaskTest.groovy
@@ -0,0 +1,375 @@
+/*
+ * 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.util.concurrent.atomic.AtomicBoolean
+import org.gradle.api.Action
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.internal.AbstractTask
+import org.gradle.api.internal.AsmBackedClassGenerator
+import org.gradle.api.internal.project.AbstractProject
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory
+import org.gradle.api.internal.project.taskfactory.ITaskFactory
+import org.gradle.api.internal.project.taskfactory.TaskFactory
+import org.gradle.api.internal.tasks.TaskExecuter
+import org.gradle.api.internal.tasks.TaskStateInternal
+import org.gradle.api.logging.LogLevel
+import org.gradle.api.specs.Spec
+import org.gradle.util.GUtil
+import org.gradle.util.HelperUtil
+import org.gradle.util.Matchers
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+import static org.junit.Assert.assertFalse
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractSpockTaskTest extends Specification {
+    public static final String TEST_TASK_NAME = "taskname"
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder()
+
+    private AbstractProject project = HelperUtil.createRootProject()
+
+    private static final ITaskFactory TASK_FACTORY = new AnnotationProcessingTaskFactory(new TaskFactory(new AsmBackedClassGenerator()))
+
+    public abstract AbstractTask getTask();
+
+    public <T extends AbstractTask> T createTask(Class<T> type) {
+        return createTask(type, project, TEST_TASK_NAME);
+    }
+
+    public Task createTask(Project project, String name) {
+        return createTask(getTask().getClass(), project, name);
+    }
+
+    public <T extends AbstractTask> T createTask(Class<T> type, Project project, String name) {
+        Task task = TASK_FACTORY.createTask((ProjectInternal) project,
+                GUtil.map(Task.TASK_TYPE, type,
+                        Task.TASK_NAME, name))
+        assert type.isAssignableFrom(task.getClass())
+        return type.cast(task);
+    }
+
+    def testTask() {
+        expect:
+        getTask().isEnabled()
+        TEST_TASK_NAME ==  getTask().getName()
+        getTask().getDescription() == null
+        project.is( getTask().getProject())
+        getTask().getStandardOutputCapture() != null
+        new HashMap() ==  getTask().getAdditionalProperties()
+        getTask().getInputs() != null
+        getTask().getOutputs() != null
+        getTask().getOnlyIf() != null
+        getTask().getOnlyIf().isSatisfiedBy(getTask())
+    }
+
+    def testPath() {
+        DefaultProject rootProject = HelperUtil.createRootProject();
+        DefaultProject childProject = HelperUtil.createChildProject(rootProject, "child");
+        childProject.getProjectDir().mkdirs();
+        DefaultProject childchildProject = HelperUtil.createChildProject(childProject, "childchild");
+        childchildProject.getProjectDir().mkdirs();
+
+        when:
+        Task task = createTask(rootProject, TEST_TASK_NAME);
+
+        then:
+        Project.PATH_SEPARATOR + TEST_TASK_NAME ==  task.getPath()
+
+        when:
+        task = createTask(childProject, TEST_TASK_NAME);
+
+        then:
+        Project.PATH_SEPARATOR + "child" + Project.PATH_SEPARATOR + TEST_TASK_NAME ==  task.getPath()
+
+        when:
+        task = createTask(childchildProject, TEST_TASK_NAME);
+
+        then:
+        Project.PATH_SEPARATOR + "child" + Project.PATH_SEPARATOR + "childchild" + Project.PATH_SEPARATOR + TEST_TASK_NAME ==  task.getPath()
+    }
+
+    def testDependsOn() {
+        Task dependsOnTask = createTask(project, "somename");
+        Task task = createTask(project, TEST_TASK_NAME);
+        project.getTasks().add("path1");
+        project.getTasks().add("path2");
+
+        when:
+        task.dependsOn(Project.PATH_SEPARATOR + "path1");
+
+        then:
+        Matchers.dependsOn("path1").matches(task)
+
+        when:
+        task.dependsOn("path2", dependsOnTask);
+
+        then:
+        Matchers.dependsOn("path1", "path2", "somename").matches(task)
+    }
+
+    def testToString() {
+        "task '" + getTask().getPath() + "'" ==  getTask().toString()
+    }
+
+    def testDoFirst() {
+        when:
+        Action<Task> action1 = createTaskAction();
+        Action<Task> action2 = createTaskAction();
+
+        then:
+        int actionSizeBefore = getTask().getActions().size();
+        getTask().is( getTask().doFirst(action2))
+        actionSizeBefore + 1 ==  getTask().getActions().size()
+        action2 ==  getTask().getActions().get(0)
+        getTask().is( getTask().doFirst(action1))
+        action1 ==  getTask().getActions().get(0)
+    }
+
+    def testDoLast() {
+        when:
+        Action<Task> action1 = createTaskAction();
+        Action<Task> action2 = createTaskAction();
+
+        then:
+        int actionSizeBefore = getTask().getActions().size();
+        getTask().is( getTask().doLast(action1))
+        actionSizeBefore + 1 ==  getTask().getActions().size()
+        action1 ==  getTask().getActions().get(getTask().getActions().size() - 1)
+        getTask().is( getTask().doLast(action2))
+        action2 ==  getTask().getActions().get(getTask().getActions().size() - 1)
+    }
+
+    def testDeleteAllActions() {
+        when:
+        Action<Task> action1 = createTaskAction();
+        Action<Task> action2 = createTaskAction();
+        getTask().doLast(action1);
+        getTask().doLast(action2);
+
+        then:
+        getTask().is( getTask().deleteAllActions())
+        new ArrayList() ==  getTask().getActions()
+    }
+
+    def testAddActionWithNull() {
+        when:
+        getTask().doLast((Closure) null)
+
+        then:
+        thrown(InvalidUserDataException)
+    }
+
+    def testAddActionsWithClosures() {
+        when:
+        GroovyTaskTestHelper.checkAddActionsWithClosures(getTask());
+
+        then:
+        true
+    }
+
+    def testExecuteDelegatesToTaskExecuter() {
+        final AbstractTask task = getTask()
+        TaskExecuter executer = Mock()
+        task.setExecuter(executer);
+
+        when:
+        task.execute()
+
+        then:
+        1 * executer.execute(task, _ as TaskStateInternal)
+
+    }
+
+    def testConfigure() {
+        when:
+        getTask().setActions(new ArrayList());
+
+        then:
+        GroovyTaskTestHelper.checkConfigure(getTask());
+    }
+
+    public AbstractProject getProject() {
+        return project;
+    }
+
+    public void setProject(AbstractProject project) {
+        this.project = project;
+    }
+
+    def disableStandardOutCapture() {
+        when:
+        getTask().disableStandardOutputCapture();
+
+        then:
+        assertFalse(getTask().getLogging().isStandardOutputCaptureEnabled());
+    }
+
+    def captureStandardOut() {
+        when:
+        getTask().captureStandardOutput(LogLevel.DEBUG);
+
+        then:
+        getTask().getLogging().isStandardOutputCaptureEnabled()
+        LogLevel.DEBUG ==  getTask().getLogging().getStandardOutputCaptureLevel()
+    }
+
+    def setGetDescription() {
+        when:
+        String testDescription = "testDescription";
+        getTask().setDescription(testDescription);
+
+        then:
+        testDescription ==  getTask().getDescription()
+    }
+
+    def canSpecifyOnlyIfPredicateUsingClosure() {
+        AbstractTask task = getTask();
+
+        expect:
+        task.getOnlyIf().isSatisfiedBy(task)
+
+        when:
+        task.onlyIf(HelperUtil.toClosure("{ task -> false }"));
+
+        then:
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+    }
+
+    def canSpecifyOnlyIfPredicateUsingSpec() {
+        final Spec<Task> spec = Mock()
+        final AbstractTask task = getTask();
+
+        expect:
+        task.getOnlyIf().isSatisfiedBy(task)
+
+        when:
+        spec.isSatisfiedBy(task) >> false
+        task.onlyIf(spec);
+
+        then:
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+    }
+
+    def onlyIfPredicateIsTrueWhenTaskIsEnabledAndAllPredicatesAreTrue() {
+        final AtomicBoolean condition1 = new AtomicBoolean(true);
+        final AtomicBoolean condition2 = new AtomicBoolean(true);
+
+        AbstractTask task = getTask();
+        task.onlyIf {
+            condition1.get()
+        }
+        task.onlyIf {
+            condition2.get()
+        }
+
+        expect:
+        task.getOnlyIf().isSatisfiedBy(task)
+
+        when:
+        task.setEnabled(false);
+
+        then:
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+
+        when:
+        task.setEnabled(true);
+        condition1.set(false);
+
+        then:
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+
+        when:
+        condition1.set(true);
+        condition2.set(false);
+
+        then:
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+
+        when:
+        condition2.set(true);
+
+        then:
+        task.getOnlyIf().isSatisfiedBy(task)
+    }
+
+    def canReplaceOnlyIfSpec() {
+        final AtomicBoolean condition1 = new AtomicBoolean(true);
+        AbstractTask task = getTask();
+        task.onlyIf(Mock(Spec))
+        task.setOnlyIf {
+            return condition1.get();
+        }
+
+        expect:
+        task.getOnlyIf().isSatisfiedBy(task)
+
+        when:
+        task.setEnabled(false);
+
+        then:
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+
+        when:
+        task.setEnabled(true);
+        condition1.set(false);
+
+        then:
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+
+        when:
+        condition1.set(true);
+
+        then:
+        task.getOnlyIf().isSatisfiedBy(task)
+    }
+
+    def testDependentTaskDidWork() {
+        Task task1 = Mock()
+        Task task2 = Mock()
+        TaskDependency dependencyMock = Mock()
+        getTask().dependsOn(dependencyMock)
+        dependencyMock.getDependencies(getTask()) >> [task1, task2] 
+
+        when:
+        task1.getDidWork() >> false
+        task2.getDidWork() >>> [false, true]
+
+
+        then:
+        !getTask().dependsOnTaskDidWork()
+        getTask().dependsOnTaskDidWork()
+    }
+
+    public static Action<Task> createTaskAction() {
+        return new Action<Task>() {
+            public void execute(Task task) {
+
+            }
+        };
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/AbstractTaskTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/AbstractTaskTest.java
new file mode 100644
index 0000000..3776cf5
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/AbstractTaskTest.java
@@ -0,0 +1,341 @@
+/*
+ * 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 groovy.lang.Closure;
+import org.gradle.api.Action;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.internal.AsmBackedClassGenerator;
+import org.gradle.api.internal.project.AbstractProject;
+import org.gradle.api.internal.project.DefaultProject;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory;
+import org.gradle.api.internal.project.taskfactory.ITaskFactory;
+import org.gradle.api.internal.project.taskfactory.TaskFactory;
+import org.gradle.api.internal.tasks.TaskExecuter;
+import org.gradle.api.internal.tasks.TaskStateInternal;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.specs.Spec;
+import org.gradle.util.*;
+import org.jmock.Expectations;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.gradle.util.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractTaskTest {
+    public static final String TEST_TASK_NAME = "taskname";
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    private AbstractProject project;
+
+    protected JUnit4GroovyMockery context = new JUnit4GroovyMockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+    private static final ITaskFactory TASK_FACTORY = new AnnotationProcessingTaskFactory(new TaskFactory(new AsmBackedClassGenerator()));
+
+    @Before
+    public void setUp() {
+        project = HelperUtil.createRootProject();
+    }
+
+    public abstract AbstractTask getTask();
+
+    public <T extends AbstractTask> T createTask(Class<T> type) {
+        return createTask(type, project, TEST_TASK_NAME);
+    }
+
+    public Task createTask(Project project, String name) {
+        return createTask(getTask().getClass(), project, name);
+    }
+
+    public <T extends AbstractTask> T createTask(Class<T> type, Project project, String name) {
+        Task task = TASK_FACTORY.createTask((ProjectInternal) project,
+                GUtil.map(Task.TASK_TYPE, type,
+                        Task.TASK_NAME, name));
+        assertTrue(type.isAssignableFrom(task.getClass()));
+        return type.cast(task);
+    }
+
+    @Test
+    public void testTask() {
+        assertTrue(getTask().isEnabled());
+        assertEquals(TEST_TASK_NAME, getTask().getName());
+        assertNull(getTask().getDescription());
+        assertSame(project, getTask().getProject());
+        assertNotNull(getTask().getStandardOutputCapture());
+        assertEquals(new HashMap(), getTask().getAdditionalProperties());
+        assertNotNull(getTask().getInputs());
+        assertNotNull(getTask().getOutputs());
+        assertNotNull(getTask().getOnlyIf());
+        assertTrue(getTask().getOnlyIf().isSatisfiedBy(getTask()));
+    }
+
+    @Test
+    public void testPath() {
+        DefaultProject rootProject = HelperUtil.createRootProject();
+        DefaultProject childProject = HelperUtil.createChildProject(rootProject, "child");
+        childProject.getProjectDir().mkdirs();
+        DefaultProject childchildProject = HelperUtil.createChildProject(childProject, "childchild");
+        childchildProject.getProjectDir().mkdirs();
+
+        Task task = createTask(rootProject, TEST_TASK_NAME);
+        assertEquals(Project.PATH_SEPARATOR + TEST_TASK_NAME, task.getPath());
+        task = createTask(childProject, TEST_TASK_NAME);
+        assertEquals(Project.PATH_SEPARATOR + "child" + Project.PATH_SEPARATOR + TEST_TASK_NAME, task.getPath());
+        task = createTask(childchildProject, TEST_TASK_NAME);
+        assertEquals(Project.PATH_SEPARATOR + "child" + Project.PATH_SEPARATOR + "childchild" + Project.PATH_SEPARATOR + TEST_TASK_NAME, task.getPath());
+    }
+
+    @Test
+    public void testDependsOn() {
+        Task dependsOnTask = createTask(project, "somename");
+        Task task = createTask(project, TEST_TASK_NAME);
+        project.getTasks().add("path1");
+        project.getTasks().add("path2");
+
+        task.dependsOn(Project.PATH_SEPARATOR + "path1");
+        assertThat(task, dependsOn("path1"));
+        task.dependsOn("path2", dependsOnTask);
+        assertThat(task, dependsOn("path1", "path2", "somename"));
+    }
+
+    @Test
+    public void testToString() {
+        assertEquals("task '" + getTask().getPath() + "'", getTask().toString());
+    }
+
+    @Test
+    public void testDoFirst() {
+        Action<Task> action1 = createTaskAction();
+        Action<Task> action2 = createTaskAction();
+        int actionSizeBefore = getTask().getActions().size();
+        assertSame(getTask(), getTask().doFirst(action2));
+        assertEquals(actionSizeBefore + 1, getTask().getActions().size());
+        assertEquals(action2, getTask().getActions().get(0));
+        assertSame(getTask(), getTask().doFirst(action1));
+        assertEquals(action1, getTask().getActions().get(0));
+    }
+
+    @Test
+    public void testDoLast() {
+        Action<Task> action1 = createTaskAction();
+        Action<Task> action2 = createTaskAction();
+        int actionSizeBefore = getTask().getActions().size();
+        assertSame(getTask(), getTask().doLast(action1));
+        assertEquals(actionSizeBefore + 1, getTask().getActions().size());
+        assertEquals(action1, getTask().getActions().get(getTask().getActions().size() - 1));
+        assertSame(getTask(), getTask().doLast(action2));
+        assertEquals(action2, getTask().getActions().get(getTask().getActions().size() - 1));
+    }
+
+    @Test
+    public void testDeleteAllActions() {
+        Action<Task> action1 = createTaskAction();
+        Action<Task> action2 = createTaskAction();
+        getTask().doLast(action1);
+        getTask().doLast(action2);
+        assertSame(getTask(), getTask().deleteAllActions());
+        assertEquals(new ArrayList(), getTask().getActions());
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void testAddActionWithNull() {
+        getTask().doLast((Closure) null);
+    }
+
+    @Test
+    public void testAddActionsWithClosures() {
+        GroovyTaskTestHelper.checkAddActionsWithClosures(getTask());
+    }
+
+    @Test
+    public void testExecuteDelegatesToTaskExecuter() {
+        final AbstractTask task = getTask();
+
+        final TaskExecuter executer = context.mock(TaskExecuter.class);
+        task.setExecuter(executer);
+
+        context.checking(new Expectations(){{
+            one(executer).execute(with(sameInstance(task)), with(notNullValue(TaskStateInternal.class)));
+        }});
+
+        task.execute();
+    }
+
+    @Test
+    public void testConfigure() {
+        getTask().setActions(new ArrayList());
+        GroovyTaskTestHelper.checkConfigure(getTask());
+    }
+
+    public AbstractProject getProject() {
+        return project;
+    }
+
+    public void setProject(AbstractProject project) {
+        this.project = project;
+    }
+
+    @Test
+    public void disableStandardOutCapture() {
+        getTask().disableStandardOutputCapture();
+        assertFalse(getTask().getLogging().isStandardOutputCaptureEnabled());
+    }
+
+    @Test
+    public void captureStandardOut() {
+        getTask().captureStandardOutput(LogLevel.DEBUG);
+        assertTrue(getTask().getLogging().isStandardOutputCaptureEnabled());
+        assertEquals(LogLevel.DEBUG, getTask().getLogging().getStandardOutputCaptureLevel());
+    }
+
+    @Test
+    public void setGetDescription() {
+        String testDescription = "testDescription";
+        getTask().setDescription(testDescription);
+        assertEquals(testDescription, getTask().getDescription());
+    }
+
+    @Test
+    public void canSpecifyOnlyIfPredicateUsingClosure() {
+        AbstractTask task = getTask();
+        assertTrue(task.getOnlyIf().isSatisfiedBy(task));
+
+        task.onlyIf(HelperUtil.toClosure("{ task -> false }"));
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+    }
+
+    @Test
+    public void canSpecifyOnlyIfPredicateUsingSpec() {
+        final Spec<Task> spec = context.mock(Spec.class);
+
+        final AbstractTask task = getTask();
+        assertTrue(task.getOnlyIf().isSatisfiedBy(task));
+
+        context.checking(new Expectations() {{
+            allowing(spec).isSatisfiedBy(task);
+            will(returnValue(false));
+        }});
+
+        task.onlyIf(spec);
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+    }
+
+    @Test
+    public void onlyIfPredicateIsTrueWhenTaskIsEnabledAndAllPredicatesAreTrue() {
+        final AtomicBoolean condition1 = new AtomicBoolean(true);
+        final AtomicBoolean condition2 = new AtomicBoolean(true);
+
+        AbstractTask task = getTask();
+        task.onlyIf(new Spec<Task>() {
+            public boolean isSatisfiedBy(Task element) {
+                return condition1.get();
+            }
+        });
+        task.onlyIf(new Spec<Task>() {
+            public boolean isSatisfiedBy(Task element) {
+                return condition2.get();
+            }
+        });
+
+        assertTrue(task.getOnlyIf().isSatisfiedBy(task));
+
+        task.setEnabled(false);
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+
+        task.setEnabled(true);
+        condition1.set(false);
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+
+        condition1.set(true);
+        condition2.set(false);
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+
+        condition2.set(true);
+        assertTrue(task.getOnlyIf().isSatisfiedBy(task));
+    }
+
+    @Test
+    public void canReplaceOnlyIfSpec() {
+        final AtomicBoolean condition1 = new AtomicBoolean(true);
+        AbstractTask task = getTask();
+        task.onlyIf(context.mock(Spec.class, "spec1"));
+        task.setOnlyIf(new Spec<Task>() {
+            public boolean isSatisfiedBy(Task element) {
+                return condition1.get();
+            }
+        });
+
+        assertTrue(task.getOnlyIf().isSatisfiedBy(task));
+
+        task.setEnabled(false);
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+
+        task.setEnabled(true);
+        condition1.set(false);
+        assertFalse(task.getOnlyIf().isSatisfiedBy(task));
+
+        condition1.set(true);
+        assertTrue(task.getOnlyIf().isSatisfiedBy(task));
+    }
+
+    @Test
+    public void testDependentTaskDidWork() {
+        final Task task1 = context.mock(Task.class, "task1");
+        final Task task2 = context.mock(Task.class, "task2");
+        final TaskDependency dependencyMock = context.mock(TaskDependency.class);
+        getTask().dependsOn(dependencyMock);
+        context.checking(new Expectations() {{
+            allowing(dependencyMock).getDependencies(getTask()); will(returnValue(WrapUtil.toSet(task1, task2)));
+
+            exactly(2).of(task1).getDidWork();
+            will(returnValue(false));
+
+            exactly(2).of(task2).getDidWork();
+            will(onConsecutiveCalls(returnValue(false), returnValue(true)));
+        }});
+
+        assertFalse(getTask().dependsOnTaskDidWork());
+
+        assertTrue(getTask().dependsOnTaskDidWork());
+    }
+
+    public static Action<Task> createTaskAction() {
+        return new Action<Task>() {
+            public void execute(Task task) {
+
+            }
+        };
+    }
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/AntBuilderAwareUtil.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/AntBuilderAwareUtil.groovy
new file mode 100644
index 0000000..fee9f81
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/AntBuilderAwareUtil.groovy
@@ -0,0 +1,117 @@
+/*
+ * 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.tasks
+
+import org.apache.tools.ant.taskdefs.MatchingTask
+import org.apache.tools.ant.types.FileSet
+import org.apache.tools.ant.types.Path
+import org.apache.tools.ant.types.Resource
+import org.apache.tools.ant.types.ResourceCollection
+import org.gradle.api.AntBuilder
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.project.DefaultAntBuilder
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+class AntBuilderAwareUtil {
+
+    static def assertSetContains(FileCollection set, Set<String> filenames) {
+        assertSetContains(set, filenames, [FileCollection.AntType.ResourceCollection])
+    }
+
+    static def assertSetContains(FileCollection set, Set<String> filenames, Iterable<FileCollection.AntType> types, boolean generic = true) {
+        AntBuilder ant = new DefaultAntBuilder(null)
+        ant.antProject.addTaskDefinition('test', FileListTask)
+
+        if (generic) {
+            FileListTask task = ant.test {
+                set.addToAntBuilder(ant, null)
+            }
+            assertThat(task.filenames, equalTo(filenames))
+        }
+
+        types.each {FileCollection.AntType type ->
+            FileListTask task = ant.test {
+                set.addToAntBuilder(ant, type == FileCollection.AntType.ResourceCollection ? null : type.toString().toLowerCase(), type)
+            }
+
+            assertThat("Unexpected FileCollection contents for type $type", task.filenames, equalTo(filenames))
+        }
+    }
+
+    static def assertSetContains(FileCollection set, String ... filenames) {
+        assertSetContains(set, filenames as Set)
+    }
+
+    static def assertSetContainsForAllTypes(FileCollection set, String ... filenames) {
+        assertSetContains(set, filenames as Set, FileCollection.AntType.values() as List)
+    }
+
+    static def assertSetContainsForAllTypes(FileCollection set, Iterable<String> filenames) {
+        assertSetContains(set, filenames as Set, FileCollection.AntType.values() as List)
+    }
+
+    static def assertSetContainsForFileSet(FileCollection set, String ... filenames) {
+        assertSetContains(set, filenames as Set, [FileCollection.AntType.FileSet], false)
+    }
+
+    static def assertSetContainsForFileSet(FileCollection set, Set<String> filenames) {
+        assertSetContains(set, filenames, [FileCollection.AntType.FileSet], false)
+    }
+
+    static def assertSetContainsForMatchingTask(FileCollection set, String ... filenames) {
+        assertSetContains(set, filenames as Set, [FileCollection.AntType.MatchingTask], false)
+    }
+
+    static def assertSetContainsForMatchingTask(FileCollection set, Set<String> filenames) {
+        assertSetContains(set, filenames, [FileCollection.AntType.MatchingTask], false)
+    }
+}
+
+public class FileListTask extends MatchingTask {
+    final Set<String> filenames = new HashSet<String>()
+    Path src
+
+    public void addConfigured(ResourceCollection fileset) {
+        Iterator<Resource> iterator = fileset.iterator()
+        while (iterator.hasNext()) {
+            Resource resource = iterator.next()
+            assertTrue("File $resource.name found multiple times", filenames.add(resource.name.replace(File.separator, '/')))
+        }
+    }
+
+    public void addConfiguredFileset(FileSet fileset) {
+        addConfigured(fileset)
+    }
+
+    public Path createMatchingtask() {
+        if (src == null) {
+            src = new Path(getProject());
+        }
+        return src.createPath();
+    }
+
+    def void execute() {
+        if (src) {
+            src.list().each {String dirName ->
+                File dir = getProject().resolveFile(dirName);
+                getDirectoryScanner(dir).includedFiles.each {String fileName ->
+                    assertTrue("File $fileName found multiple times", filenames.add(fileName.replace(File.separator, '/')))
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/CopyTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/CopyTest.groovy
new file mode 100644
index 0000000..75a7083
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/CopyTest.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.api.tasks
+
+import org.gradle.api.internal.AbstractTask
+import org.gradle.api.internal.file.DefaultDirectoryWalker
+
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.lib.legacy.ClassImposteriser
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.gradle.api.internal.file.copy.FileCopyActionImpl
+
+ at RunWith (org.jmock.integration.junit4.JMock)
+public class CopyTest extends AbstractTaskTest {
+    Copy copyTask;
+    DefaultDirectoryWalker walker;
+    FileCopyActionImpl action;
+
+    JUnit4GroovyMockery context = new JUnit4GroovyMockery();
+
+    @Before
+    public void setUp() {
+        super.setUp()
+        context.setImposteriser(ClassImposteriser.INSTANCE)
+        walker = context.mock(DefaultDirectoryWalker.class)
+        action = context.mock(FileCopyActionImpl.class)
+        copyTask = createTask(Copy.class)
+        copyTask.copyAction = action
+    }
+
+    public AbstractTask getTask() {
+        return copyTask;
+    }
+
+    @Test public void executesActionOnExecute() {
+        context.checking {
+            one(action).hasSource(); will(returnValue(true))
+            one(action).getDestinationDir(); will(returnValue(new File('dest')))
+            one(action).execute()
+            one(action).getDidWork()
+        }
+
+        copyTask.copy()
+    }
+    
+    @Test public void usesConventionValuesForDestDirWhenNotSpecified() {
+        copyTask.conventionMapping.destinationDir = { new File('convention') }
+
+        context.checking {
+            exactly(2).of(action).getDestinationDir()
+            will(returnValue(null))
+            one(action).into(new File('convention'))
+            one(action).hasSource(); will(returnValue(true))
+        }
+
+        copyTask.configureRootSpec()
+    }
+
+    @Test public void doesNotUseConventionValueForDestDirWhenSpecified() {
+        copyTask.conventionMapping.destinationDir = { new File('convention') }
+
+        context.checking {
+            one(action).getDestinationDir()
+            will(returnValue(new File('dest')))
+            one(action).hasSource(); will(returnValue(true))
+        }
+
+        copyTask.configureRootSpec()
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/DeleteTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/DeleteTest.java
new file mode 100644
index 0000000..9fe918e
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/DeleteTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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 org.gradle.api.file.DeleteAction;
+import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.internal.file.DefaultFileOperations;
+import org.gradle.api.internal.file.FileOperations;
+import org.gradle.api.internal.project.DefaultProject;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.gradle.util.WrapUtil;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(org.jmock.integration.junit4.JMock.class)
+public class DeleteTest extends AbstractConventionTaskTest {
+    private Mockery context = new JUnit4GroovyMockery();
+    private DeleteAction deleteAction = context.mock(DeleteAction.class);
+    private Delete delete;
+
+    @Before
+    public void setUp() {
+        super.setUp();
+        delete = createTask(Delete.class);
+        DefaultFileOperations fileOperations = (DefaultFileOperations) ((DefaultProject)
+                delete.getProject()).getServiceRegistryFactory().get(FileOperations.class);
+        fileOperations.setDeleteAction(deleteAction);
+    }
+
+    public ConventionTask getTask() {
+        return delete;
+    }
+
+    @Test
+    public void defaultValues() {
+        assertTrue(delete.getDelete().isEmpty());
+    }
+
+    @Test
+    public void didWorkIsTrueWhenSomethingGetsDeleted() throws IOException {
+        context.checking(new Expectations() {{
+            one(deleteAction).delete(WrapUtil.toSet("someFile"));
+            returnValue(true);
+        }});
+
+        delete.delete("someFile");
+        delete.execute();
+
+        assertFalse(delete.getDidWork());
+    }
+
+    @Test
+    public void didWorkIsFalseWhenNothingDeleted() throws IOException {
+        context.checking(new Expectations() {{
+            one(deleteAction).delete(WrapUtil.toSet("someFile"));
+            returnValue(false);
+        }});
+
+        delete.delete("someFile");
+        delete.execute();
+
+        assertFalse(delete.getDidWork());
+    }
+
+    @Test
+    public void getTargetFilesAndMultipleTargets() throws IOException {
+        delete.delete("someFile");
+        delete.delete(new File("someOtherFile"));
+        delete.getTargetFiles();
+        assertThat(delete.getDelete(), equalTo(WrapUtil.<Object>toSet("someFile", new File("someOtherFile"))));
+        assertThat(delete.getTargetFiles().getFiles(), equalTo(getProject().files(delete.getDelete()).getFiles()));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/DirectoryTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/DirectoryTest.groovy
new file mode 100644
index 0000000..1bcb1bb
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/DirectoryTest.groovy
@@ -0,0 +1,80 @@
+/*
+ * 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.api.tasks
+
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.internal.AbstractTask
+import org.junit.Before
+import org.junit.Test
+import static org.junit.Assert.*
+import static org.hamcrest.Matchers.*
+import org.gradle.api.GradleException
+
+/**
+ * @author Hans Dockter
+ */
+class DirectoryTest extends AbstractTaskTest {
+    static final String TASK_DIR_NAME = 'parent/child'
+    Directory directoryForAbstractTest
+    Directory directory
+
+    public AbstractTask getTask() {
+        return directoryForAbstractTest
+    }
+
+    @Before public void setUp() {
+        super.setUp()
+        directoryForAbstractTest = createTask(Directory.class)
+        directory = createTask(Directory.class, project, TASK_DIR_NAME)
+    }
+
+    @Test public void testInit() {
+        assertEquals(new File(project.projectDir, TASK_DIR_NAME).absoluteFile, directory.dir)
+    }
+
+    @Test public void testInitWithAbsolutePathName() {
+        try {
+            createTask(Directory.class, project, new File('nonRelative').absolutePath)
+            fail()
+        } catch (GradleException e) {
+            assertThat(e.cause, instanceOf(InvalidUserDataException.class))
+        }
+    }
+
+    @Test public void testExecute() {
+        directory.execute()
+        assert new File(project.projectDir, TASK_DIR_NAME).isDirectory()
+    }
+
+    @Test public void testWithExistingDir() {
+        File dir = new File(project.projectDir, TASK_DIR_NAME)
+        dir.mkdirs()
+        // create new file to check later that dir has not been recreated 
+        File file = new File(dir, 'somefile')
+        file.createNewFile()
+        directory.execute()
+        assert dir.isDirectory()
+        assert file.isFile()
+    }
+
+    @Test (expected = InvalidUserDataException) public void testWithExistingFile() {
+        File file = new File(project.projectDir, 'testname')
+        file.createNewFile()
+        directory = createTask(Directory.class, project, 'testname')
+        directory.mkdir()
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/ExecTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/ExecTest.groovy
new file mode 100644
index 0000000..cffb52f
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/ExecTest.groovy
@@ -0,0 +1,65 @@
+/*
+ * 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.tasks
+
+import org.gradle.api.internal.AbstractTask
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.process.internal.ExecAction
+import org.gradle.process.ExecResult
+import org.hamcrest.Matchers
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.junit.Assert.assertThat
+
+ at RunWith (org.jmock.integration.junit4.JMock)
+public class ExecTest extends AbstractTaskTest {
+    JUnit4GroovyMockery context = new JUnit4GroovyMockery();
+    Exec execTask;
+    ExecAction execAction = context.mock(ExecAction);
+
+    @Before
+    public void setUp() {
+        super.setUp()
+        execTask = createTask(Exec.class)
+        execTask.setExecAction(execAction)
+    }
+
+    public AbstractTask getTask() {
+        return execTask;
+    }
+
+    @Test void executesActionOnExecute() {
+        context.checking {
+            one(execAction).setExecutable("ls")
+            one(execAction).execute(); will(returnValue({ 0 } as ExecResult))
+        }
+        execTask.setExecutable("ls")
+        execTask.execute()
+        assertThat(execTask.execResult.exitValue, Matchers.equalTo(0))
+    }
+
+    @Test
+    void executeWithNonZeroExitValueAndIgnoreExitValueShouldNotThrowException() {
+        context.checking {
+            one(execAction).execute(); will(returnValue({ 1 } as ExecResult))
+        }
+        execTask.execute()
+        assertThat(execTask.execResult.exitValue, Matchers.equalTo(1))
+    }
+
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/GradleBuildTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/GradleBuildTest.groovy
new file mode 100644
index 0000000..2825156
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/GradleBuildTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * 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.tasks
+
+import org.gradle.BuildResult
+import org.gradle.GradleLauncher
+import org.gradle.GradleLauncherFactory
+import org.gradle.api.internal.AbstractTask
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+public class GradleBuildTest extends AbstractTaskTest {
+    GradleLauncherFactory launcherFactoryMock = context.mock(GradleLauncherFactory.class)
+    GradleBuild task
+
+    AbstractTask getTask() {
+        return task
+    }
+
+    @Before
+    void setUp() {
+        super.setUp()
+        task = createTask(GradleBuild.class)
+        GradleLauncher.injectCustomFactory(launcherFactoryMock)
+    }
+
+    @After
+    void tearDown() {
+        GradleLauncher.injectCustomFactory(null)
+    }
+
+    @Test
+    void usesCopyOfCurrentBuildsStartParams() {
+        assertThat(task.startParameter, equalTo(project.gradle.startParameter.newBuild()))
+        task.tasks = ['a', 'b']
+        assertThat(task.tasks, equalTo(['a', 'b']))
+        assertThat(task.startParameter.taskNames, equalTo(['a', 'b']))
+    }
+
+    @Test
+    void executesBuild() {
+        GradleLauncher launcherMock = context.mock(GradleLauncher.class)
+        BuildResult resultMock = context.mock(BuildResult.class)
+
+        context.checking {
+            one(launcherFactoryMock).newInstance(task.startParameter)
+            will(returnValue(launcherMock))
+            one(launcherMock).run()
+            will(returnValue(resultMock))
+            one(resultMock).rethrowFailure()
+        }
+        task.build()
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/GroovyTaskTestHelper.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/GroovyTaskTestHelper.groovy
new file mode 100644
index 0000000..62fb0c5
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/GroovyTaskTestHelper.groovy
@@ -0,0 +1,45 @@
+/*
+ * 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.tasks
+
+import org.gradle.api.Task
+import org.gradle.api.internal.AbstractTask
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+class GroovyTaskTestHelper {
+    public static void checkAddActionsWithClosures(AbstractTask task) {
+        task.deleteAllActions();
+        boolean action1Called = false
+        Closure action1 = {action1Called = true}
+        boolean action2Called = false
+        Closure action2 = {Task t -> action2Called = true}
+        task.doFirst(action1)
+        task.doLast(action2)
+        assertEquals([action1, action2], task.actions.collect { it.closure })
+    }
+
+    public static void checkConfigure(AbstractTask task) {
+        Closure action1 = { Task t -> }
+        assertSame(task, task.configure {
+            doFirst(action1)
+        });
+        assertEquals(1, task.actions.size())
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/SourceTaskTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/SourceTaskTest.groovy
new file mode 100644
index 0000000..6922747
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/SourceTaskTest.groovy
@@ -0,0 +1,49 @@
+/*
+ * 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 org.gradle.api.internal.AbstractTask
+import org.junit.Before
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+class SourceTaskTest extends AbstractTaskTest {
+    private SourceTask task
+
+    AbstractTask getTask() {
+        return task
+    }
+
+    @Before
+    public void setUp() {
+        super.setUp()
+        task = createTask(SourceTask.class)
+    }
+    
+    @Test
+    public void canAppendToSource() {
+        File file1 = tmpDir.file('file1.txt').createFile()
+        File file2 = tmpDir.file('file2.txt').createFile()
+        file2.createNewFile()
+
+        task.source = file1
+        task.source = task.source + project.files(file2)
+
+        assertThat(task.source as List, equalTo([file1, file2]))
+    }
+}
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/UploadTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/UploadTest.java
new file mode 100644
index 0000000..21386cc
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/UploadTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.api.tasks;
+
+import org.apache.ivy.plugins.resolver.DependencyResolver;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.util.HelperUtil;
+import static org.gradle.util.WrapUtil.toList;
+import static org.hamcrest.Matchers.*;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class UploadTest extends AbstractTaskTest {
+    private Upload upload;
+
+    private JUnit4Mockery context = new JUnit4Mockery();
+    private RepositoryHandler repositoriesMock;
+    private DependencyResolver repositoryDummy;
+    private Configuration configurationMock;
+
+    @Before public void setUp() {
+        super.setUp();
+        upload = createTask(Upload.class);
+        repositoriesMock = context.mock(RepositoryHandler.class);
+        repositoryDummy = context.mock(DependencyResolver.class);
+
+        context.checking(new Expectations(){{
+            allowing(repositoriesMock).getResolvers();
+            will(returnValue(toList(repositoryDummy)));
+        }});
+        configurationMock = context.mock(Configuration.class);
+    }
+
+    public AbstractTask getTask() {
+        return upload;
+    }
+
+    @Test public void testUpload() {
+        assertThat(upload.isUploadDescriptor(), equalTo(false));
+        assertNull(upload.getDescriptorDestination());
+        assertNotNull(upload.getRepositories());
+    }
+
+    @Test public void testUploading() {
+        final File descriptorDestination = new File("somePath");
+        upload.setUploadDescriptor(true);
+        upload.setDescriptorDestination(descriptorDestination);
+        upload.setConfiguration(configurationMock);
+        upload.setRepositories(repositoriesMock);
+        context.checking(new Expectations() {{
+            one(configurationMock).publish(toList(repositoryDummy), descriptorDestination);
+        }});
+        upload.upload();
+    }
+
+    @Test public void testUploadingWithUploadDescriptorFalseAndDestinationSet() {
+        upload.setUploadDescriptor(false);
+        upload.setDescriptorDestination(new File("somePath"));
+        upload.setConfiguration(configurationMock);
+        upload.setRepositories(repositoriesMock);
+        context.checking(new Expectations() {{
+            one(configurationMock).publish(toList(repositoryDummy), null);
+        }});
+        upload.upload();
+    }
+
+    @Test public void testRepositories() {
+        upload.setRepositories(repositoriesMock);
+
+        context.checking(new Expectations(){{
+            one(repositoriesMock).mavenCentral();
+        }});
+
+        upload.repositories(HelperUtil.toClosure("{ mavenCentral() }"));
+    }
+
+    @Test public void testDeclaresConfigurationArtifactsAsInputFiles() {
+        assertThat(upload.getArtifacts(), nullValue());
+
+        upload.setConfiguration(configurationMock);
+
+        final FileCollection files = context.mock(FileCollection.class);
+        context.checking(new Expectations(){{
+            one(configurationMock).getAllArtifactFiles();
+            will(returnValue(files));
+        }});
+
+        assertThat(upload.getArtifacts(), sameInstance(files));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/ant/AntTargetTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/ant/AntTargetTest.java
new file mode 100644
index 0000000..0c263b9
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/ant/AntTargetTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.tasks.ant;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.Target;
+import org.gradle.api.Task;
+import org.gradle.api.internal.project.DefaultProject;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.TemporaryFolder;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.Rule;
+
+import java.io.File;
+import java.util.Set;
+
+public class AntTargetTest {
+    private final Target antTarget = new Target();
+    private final DefaultProject project = HelperUtil.createRootProject();
+    private final AntTarget task = HelperUtil.createTask(AntTarget.class, project);
+    @Rule
+    public TemporaryFolder testDir = new TemporaryFolder();
+    private final File baseDir = testDir.getDir();
+
+    @Before
+    public void setUp() {
+        antTarget.setProject(new Project());
+    }
+
+    @Test
+    public void executesTargetOnExecute() {
+        TestTask testTask = new TestTask();
+        testTask.setProject(antTarget.getProject());
+        antTarget.addTask(testTask);
+
+        task.setTarget(antTarget);
+        task.setBaseDir(baseDir);
+        task.executeAntTarget();
+
+        assertTrue(testTask.executed);
+    }
+
+    @Test
+    public void dependsOnTargetDependencies() {
+        Task a = project.getTasks().add("a");
+        Task b = project.getTasks().add("b");
+        antTarget.setDepends("a, b");
+
+        task.setTarget(antTarget);
+        Set dependencies = task.getTaskDependencies().getDependencies(task);
+        assertThat(dependencies, equalTo((Set) toSet(a, b)));
+    }
+
+    @Test
+    public void delegatesDescriptionToTarget() {
+        antTarget.setDescription("description");
+
+        task.setTarget(antTarget);
+        assertThat(task.getDescription(), equalTo("description"));
+
+        antTarget.setDescription("new description");
+        assertThat(task.getDescription(), equalTo("new description"));
+    }
+
+    public class TestTask extends org.apache.tools.ant.Task {
+        boolean executed;
+
+        @Override
+        public void execute() throws BuildException {
+            assertThat(antTarget.getProject().getBaseDir(), equalTo(baseDir));
+            executed = true;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTaskTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTaskTest.groovy
new file mode 100644
index 0000000..c802f3b
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/bundling/AbstractArchiveTaskTest.groovy
@@ -0,0 +1,111 @@
+/*
+ * 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.api.tasks.bundling
+
+import org.gradle.api.internal.ConventionTask
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.tasks.AbstractConventionTaskTest
+import org.junit.Test
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+abstract class AbstractArchiveTaskTest extends AbstractConventionTaskTest {
+
+    FileResolver resolver = [resolve: {it as File}] as FileResolver
+
+    abstract AbstractArchiveTask getArchiveTask()
+
+    ConventionTask getTask() {
+        archiveTask
+    }
+
+    void checkConstructor() {
+        assertEquals('', archiveTask.classifier)
+    }
+
+    protected void configure(AbstractArchiveTask archiveTask) {
+        archiveTask.baseName = 'testbasename'
+        archiveTask.appendix = 'testappendix'
+        archiveTask.version = '1.0'
+        archiveTask.classifier = 'src'
+        archiveTask.destinationDir = new File(tmpDir.dir, 'destinationDir')
+    }
+
+    @Test public void testExecute() {
+        archiveTask.execute()
+        assertTrue(archiveTask.destinationDir.isDirectory())
+        assertTrue(archiveTask.archivePath.isFile())
+    }
+
+    @Test public void testArchiveNameWithEmptyExtension() {
+        archiveTask.extension = null
+        assertEquals("testbasename-testappendix-1.0-src".toString(), archiveTask.archiveName)
+    }
+
+    @Test public void testArchiveNameWithEmptyBasename() {
+        archiveTask.baseName = null
+        assertEquals("testappendix-1.0-src.${archiveTask.extension}".toString(), archiveTask.archiveName)
+    }
+
+    @Test public void testArchiveNameWithEmptyBasenameAndAppendix() {
+        archiveTask.baseName = null
+        archiveTask.appendix = null
+        assertEquals("1.0-src.${archiveTask.extension}".toString(), archiveTask.archiveName)
+    }
+
+    @Test public void testArchiveNameWithEmptyBasenameAndAppendixAndVersion() {
+        archiveTask.baseName = null
+        archiveTask.appendix = null
+        archiveTask.version = null
+        assertEquals("src.${archiveTask.extension}".toString(), archiveTask.archiveName)
+    }
+
+    @Test public void testArchiveNameWithEmptyBasenameAndAppendixAndVersionAndClassifier() {
+        archiveTask.baseName = null
+        archiveTask.appendix = null
+        archiveTask.version = null
+        archiveTask.classifier = null
+        assertEquals(".${archiveTask.extension}".toString(), archiveTask.archiveName)
+    }
+
+
+    @Test public void testArchiveNameWithEmptyClassifier() {
+        archiveTask.classifier = null
+        assertEquals("testbasename-testappendix-1.0.${archiveTask.extension}".toString(), archiveTask.archiveName)
+    }
+
+    @Test public void testArchiveNameWithEmptyAppendix() {
+        archiveTask.appendix = null
+        assertEquals("testbasename-1.0-src.${archiveTask.extension}".toString(), archiveTask.archiveName)
+    }
+
+    @Test public void testArchiveNameWithEmptyVersion() {
+        archiveTask.version = null
+        assertEquals("testbasename-testappendix-src.${archiveTask.extension}".toString(), archiveTask.archiveName)
+    }
+
+    @Test public void testUsesCustomArchiveNameWhenSet() {
+        archiveTask.archiveName = 'somefile.out'
+        assertEquals('somefile.out', archiveTask.archiveName)
+    }
+
+    @Test public void testArchivePath() {
+        assertEquals(new File(archiveTask.destinationDir, archiveTask.archiveName), archiveTask.archivePath)
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/bundling/TarTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/bundling/TarTest.groovy
new file mode 100644
index 0000000..60f71d7
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/bundling/TarTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * 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.api.tasks.bundling
+
+import static org.junit.Assert.*
+import static org.hamcrest.Matchers.*
+import org.junit.Before
+import org.junit.Test;
+
+/**
+ * @author Hans Dockter
+ */
+class TarTest extends AbstractArchiveTaskTest {
+    Tar tar
+
+    @Before public void setUp()  {
+        super.setUp()
+        tar = createTask(Tar)
+        configure(tar)
+    }
+
+    AbstractArchiveTask getArchiveTask() {
+        tar
+    }
+
+    @Test public void testDefaultValues() {
+        assertThat(tar.compression, equalTo(Compression.NONE))
+        assertThat(tar.extension, equalTo('tar'))
+    }
+    
+    @Test public void testCompressionDeterminesDefaultExtension() {
+        tar.compression = Compression.GZIP
+        assertThat(tar.extension, equalTo('tgz'))
+
+        tar.compression = Compression.BZIP2
+        assertThat(tar.extension, equalTo('tbz2'))
+
+        tar.compression = Compression.NONE
+        assertThat(tar.extension, equalTo('tar'))
+
+        tar.extension = 'bin'
+
+        tar.compression = Compression.GZIP
+        assertThat(tar.extension, equalTo('bin'))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/bundling/ZipTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/bundling/ZipTest.groovy
new file mode 100644
index 0000000..3864059
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/bundling/ZipTest.groovy
@@ -0,0 +1,44 @@
+/*
+ * 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.api.tasks.bundling
+
+import org.junit.Before
+import org.junit.Test
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+class ZipTest extends AbstractArchiveTaskTest {
+    Zip zip
+
+    @Before public void setUp()  {
+        super.setUp()
+        zip = createTask(Zip)
+        configure(zip)
+    }
+
+    AbstractArchiveTask getArchiveTask() {
+        zip
+    }
+
+    @Test public void testZip() {
+        zip = createTask(Zip)
+        assertEquals(Zip.ZIP_EXTENSION, zip.extension)
+        checkConstructor()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTaskTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTaskTest.java
new file mode 100644
index 0000000..88d11c6
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/AbstractReportTaskTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.tasks.diagnostics;
+
+import org.gradle.api.Project;
+import org.gradle.api.internal.project.DefaultProject;
+import static org.gradle.util.HelperUtil.*;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.TemporaryFolder;
+import org.gradle.util.WrapUtil;
+import org.jmock.Expectations;
+import org.jmock.Sequence;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+
+ at RunWith(JMock.class)
+public class AbstractReportTaskTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final DefaultProject project = createRootProject();
+    private Runnable generator;
+    private TestReportTask task;
+    private ProjectReportRenderer renderer;
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    @Before
+    public void setUp() throws Exception {
+        generator = context.mock(Runnable.class);
+        renderer = context.mock(ProjectReportRenderer.class);
+        task = HelperUtil.createTask(TestReportTask.class, project);
+        task.setGenerator(generator);
+        task.setRenderer(renderer);
+        task.setProjects(WrapUtil.<Project>toSet(project));
+    }
+
+    @Test
+    public void completesRendererAtEndOfGeneration() throws IOException {
+        context.checking(new Expectations() {{
+            Sequence sequence = context.sequence("sequence");
+            one(renderer).startProject(project);
+            inSequence(sequence);
+            one(generator).run();
+            inSequence(sequence);
+            one(renderer).completeProject(project);
+            inSequence(sequence);
+            one(renderer).complete();
+            inSequence(sequence);
+        }});
+
+        task.generate();
+    }
+
+    @Test
+    public void setsOutputFileNameOnRendererBeforeGeneration() throws IOException {
+        final File file = tmpDir.getDir().file("report.txt");
+
+        context.checking(new Expectations() {{
+            Sequence sequence = context.sequence("sequence");
+            one(renderer).setOutputFile(file);
+            inSequence(sequence);
+            one(renderer).startProject(project);
+            inSequence(sequence);
+            one(generator).run();
+            inSequence(sequence);
+            one(renderer).completeProject(project);
+            inSequence(sequence);
+            one(renderer).complete();
+            inSequence(sequence);
+        }});
+
+        task.setOutputFile(file);
+        task.generate();
+    }
+
+    @Test
+    public void passesEachProjectToRenderer() throws IOException {
+        final Project child1 = createChildProject(project, "child1");
+        final Project child2 = createChildProject(project, "child2");
+        task.setProjects(project.getAllprojects());
+        context.checking(new Expectations() {{
+            Sequence sequence = context.sequence("seq");
+
+            one(renderer).startProject(project);
+            inSequence(sequence);
+            one(generator).run();
+            inSequence(sequence);
+            one(renderer).completeProject(project);
+            inSequence(sequence);
+            one(renderer).startProject(child1);
+            inSequence(sequence);
+            one(generator).run();
+            inSequence(sequence);
+            one(renderer).completeProject(child1);
+            inSequence(sequence);
+            one(renderer).startProject(child2);
+            inSequence(sequence);
+            one(generator).run();
+            inSequence(sequence);
+            one(renderer).completeProject(child2);
+            inSequence(sequence);
+            one(renderer).complete();
+            inSequence(sequence);
+        }});
+
+        task.generate();
+    }
+
+    public static class TestReportTask extends AbstractReportTask {
+        private Runnable generator;
+        private ProjectReportRenderer renderer;
+
+        public void setGenerator(Runnable generator) {
+            this.generator = generator;
+        }
+
+        public ProjectReportRenderer getRenderer() {
+            return renderer;
+        }
+
+        public void setRenderer(ProjectReportRenderer renderer) {
+            this.renderer = renderer;
+        }
+
+        public void generate(Project project) throws IOException {
+            generator.run();
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/AsciiReportRendererTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/AsciiReportRendererTest.java
new file mode 100644
index 0000000..9a0a901
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/AsciiReportRendererTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.tasks.diagnostics;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import static org.junit.Assert.*;
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.util.HelperUtil;
+import static org.gradle.util.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.Expectations;
+
+import java.io.StringWriter;
+
+ at RunWith(JMock.class)
+public class AsciiReportRendererTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final StringWriter writer = new StringWriter();
+    private final AsciiReportRenderer renderer = new AsciiReportRenderer(writer);
+    private final Project project = HelperUtil.createRootProject();
+
+    @Test
+    public void writesMessageWhenProjectHasNoConfigurations() {
+        renderer.startProject(project);
+        renderer.completeProject(project);
+
+        assertThat(writer.toString(), containsLine("No configurations"));
+    }
+
+    @Test
+    public void writesConfigurationHeader() {
+        final Configuration configuration = context.mock(Configuration.class);
+        context.checking(new Expectations(){{
+            allowing(configuration).getName();
+            will(returnValue("configName"));
+            allowing(configuration).getDescription();
+            will(returnValue("description"));
+        }});
+
+        renderer.startProject(project);
+        renderer.startConfiguration(configuration);
+        renderer.completeConfiguration(configuration);
+        renderer.completeProject(project);
+
+        assertThat(writer.toString(), containsLine("configName - description"));
+        assertThat(writer.toString(), not(containsLine("No configurations")));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskTest.java
new file mode 100644
index 0000000..b5fe240
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/DependencyReportTaskTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.tasks.diagnostics;
+
+import org.gradle.api.Project;
+import org.gradle.api.artifacts.Configuration;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.ResolvedConfiguration;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.util.WrapUtil;
+import org.gradle.util.HelperUtil;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.nullValue;
+import org.jmock.Expectations;
+import org.jmock.Sequence;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import static org.junit.Assert.assertThat;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+ at RunWith(JMock.class)
+public class DependencyReportTaskTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private Project project;
+    private DependencyReportTask task;
+
+    @Before
+    public void setup() {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        project = context.mock(ProjectInternal.class);
+
+        context.checking(new Expectations() {{
+            allowing(project).absolutePath("list");
+            will(returnValue(":path"));
+            allowing(project).getConvention();
+            will(returnValue(null));
+        }});
+
+        task = HelperUtil.createTask(DependencyReportTask.class);
+    }
+
+    @Test
+    public void init() {
+        assertThat(task.getRenderer(), instanceOf(AsciiReportRenderer.class));
+        assertThat(task.getConfigurations(), nullValue());
+    }
+
+    @Test
+    public void passesEachProjectConfigurationToRenderer() throws IOException {
+        final ConfigurationContainer configurationContainer = context.mock(ConfigurationContainer.class);
+        final Configuration configuration1 = context.mock(Configuration.class, "Configuration1");
+        final Configuration configuration2 = context.mock(Configuration.class, "Configuration2");
+        context.checking(new Expectations() {{
+            allowing(project).getConfigurations();
+            will(returnValue(configurationContainer));
+
+            allowing(configurationContainer).getAll();
+            will(returnValue(WrapUtil.toSet(configuration1, configuration2)));
+        }});
+        assertConfigurationsIsPassedToRenderer(configuration1, configuration2);
+    }
+
+    @Test
+    public void passesSpecifiedConfigurationToRenderer() throws IOException {
+        final Configuration configuration1 = context.mock(Configuration.class, "Configuration1");
+        final Configuration configuration2 = context.mock(Configuration.class, "Configuration2");
+        task.setConfigurations(WrapUtil.toSet(configuration1, configuration2));
+        assertConfigurationsIsPassedToRenderer(configuration1, configuration2);
+    }
+
+    private void assertConfigurationsIsPassedToRenderer(final Configuration configuration1, final Configuration configuration2) throws IOException {
+        final DependencyReportRenderer renderer = context.mock(DependencyReportRenderer.class);
+        final ResolvedConfiguration resolvedConfiguration1 = context.mock(ResolvedConfiguration.class, "ResolvedConf1");
+        final ResolvedConfiguration resolvedConfiguration2 = context.mock(ResolvedConfiguration.class, "ResolvedConf2");
+
+        task.setRenderer(renderer);
+        task.setConfigurations(WrapUtil.toSet(configuration1, configuration2));
+
+        context.checking(new Expectations() {{
+            allowing(configuration1).getName();
+            will(returnValue("config1"));
+
+            allowing(configuration2).getName();
+            will(returnValue("config2"));
+
+            Sequence resolve = context.sequence("resolve");
+            Sequence render = context.sequence("render");
+
+            one(configuration1).getResolvedConfiguration();
+            inSequence(resolve);
+            will(returnValue(resolvedConfiguration1));
+
+            one(renderer).startConfiguration(configuration1);
+            inSequence(render);
+
+            one(renderer).render(resolvedConfiguration1);
+            inSequence(render);
+
+            one(renderer).completeConfiguration(configuration1);
+            inSequence(render);
+
+            one(configuration2).getResolvedConfiguration();
+            inSequence(resolve);
+            will(returnValue(resolvedConfiguration2));
+
+            one(renderer).startConfiguration(configuration2);
+            inSequence(render);
+
+            one(renderer).render(resolvedConfiguration2);
+            inSequence(render);
+
+            one(renderer).completeConfiguration(configuration2);
+            inSequence(render);
+        }});
+        task.generate(project);
+    }
+
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/PropertyReportRendererTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/PropertyReportRendererTest.java
new file mode 100644
index 0000000..164848d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/PropertyReportRendererTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 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.tasks.diagnostics;
+
+import static org.gradle.util.Matchers.*;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.StringWriter;
+
+public class PropertyReportRendererTest {
+    private StringWriter out;
+    private PropertyReportRenderer renderer;
+
+    @Before
+    public void setUp() {
+        out = new StringWriter();
+        renderer = new PropertyReportRenderer(out);
+    }
+
+    @Test
+    public void writesProperty() {
+        renderer.addProperty("prop", "value");
+
+        assertThat(out.toString(), containsLine("prop: value"));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/PropertyReportTaskTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/PropertyReportTaskTest.java
new file mode 100644
index 0000000..9372a36
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/PropertyReportTaskTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 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.tasks.diagnostics;
+
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.util.GUtil;
+import org.gradle.util.HelperUtil;
+import org.jmock.Expectations;
+import org.jmock.Sequence;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+ at RunWith(JMock.class)
+public class PropertyReportTaskTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private ProjectInternal project;
+    private PropertyReportTask task;
+    private PropertyReportRenderer renderer;
+
+    @Before
+    public void setup() {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        project = context.mock(ProjectInternal.class);
+        renderer = context.mock(PropertyReportRenderer.class);
+
+        context.checking(new Expectations() {{
+            allowing(project).absolutePath("list");
+            will(returnValue(":path"));
+            allowing(project).getConvention();
+            will(returnValue(null));
+        }});
+
+        task = HelperUtil.createTask(PropertyReportTask.class);
+        task.setRenderer(renderer);
+    }
+
+    @Test
+    public void passesEachProjectPropertyToRenderer() throws IOException {
+        context.checking(new Expectations() {{
+            one(project).getProperties();
+            will(returnValue(GUtil.map("b", "value2", "a", "value1")));
+
+            Sequence sequence = context.sequence("seq");
+
+            one(renderer).addProperty("a", "value1");
+            inSequence(sequence);
+
+            one(renderer).addProperty("b", "value2");
+            inSequence(sequence);
+        }});
+
+        task.generate(project);
+    }
+
+    @Test
+    public void doesNotShowContentsOfThePropertiesProperty() throws IOException {
+        context.checking(new Expectations() {{
+            one(project).getProperties();
+            will(returnValue(GUtil.map("prop", "value", "properties", "prop")));
+
+            Sequence sequence = context.sequence("seq");
+
+            one(renderer).addProperty("prop", "value");
+            inSequence(sequence);
+            one(renderer).addProperty("properties", "{...}");
+            inSequence(sequence);
+        }});
+
+        task.generate(project);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportModelTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportModelTest.groovy
new file mode 100644
index 0000000..0cca9aa
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportModelTest.groovy
@@ -0,0 +1,164 @@
+/*
+ * 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.diagnostics
+
+import spock.lang.Specification
+import org.gradle.api.Task
+import org.gradle.api.tasks.TaskDependency
+
+class TaskReportModelTest extends Specification {
+    private final TaskReportModel model = new TaskReportModel()
+
+    def groupsTasksBasedOnTheirGroup() {
+        def task1 = task('task1', 'group1')
+        def task2 = task('task2', 'group2')
+        def task3 = task('task3', 'group1')
+
+        when:
+        model.calculate([task1, task2, task3])
+
+        then:
+        model.groups as List == ['group1', 'group2']
+        model.getTasksForGroup('group1')*.task == [task1, task3]
+        model.getTasksForGroup('group2')*.task == [task2]
+    }
+
+    def groupsAreTreatedAsCaseInsensitive() {
+        def task1 = task('task1', 'a')
+        def task2 = task('task2', 'B')
+        def task3 = task('task3', 'b')
+        def task4 = task('task4', 'c')
+
+        when:
+        model.calculate([task2, task3, task4, task1])
+
+        then:
+        model.groups as List == ['a', 'B', 'c']
+        model.getTasksForGroup('a')*.task == [task1]
+        model.getTasksForGroup('B')*.task == [task2, task3]
+        model.getTasksForGroup('c')*.task == [task4]
+    }
+
+    def tasksWithNoGroupAreTreatedAsChildrenOfTheNearestTopLevelTaskTheyAreReachableFrom() {
+        def task1 = task('task1')
+        def task2 = task('task2')
+        def task3 = task('task3', 'group1', task1, task2)
+        def task4 = task('task4', task3, task1)
+        def task5 = task('task5', 'group2', task4)
+
+        when:
+        model.calculate([task1, task2, task3, task4, task5])
+
+        then:
+        TaskDetails t = (model.getTasksForGroup('group1') as List).first()
+        t.task == task3
+        t.children*.task as List == [task1, task2]
+        t = (model.getTasksForGroup('group2') as List).first()
+        t.task == task5
+        t.children*.task as List == [task4]
+    }
+
+    def theDependenciesOfATopLevelTaskAreTheUnionOfItsChildrensDependencies() {
+        def task1 = task('task1', 'group1')
+        def task2 = task('task2', 'group2', task1)
+        def task3 = task('task3', 'group3')
+        def task4 = task('task4', task2)
+        def task5 = task('task5', 'group4', task3, task4)
+
+        when:
+        model.calculate([task1, task2, task3, task4, task5])
+
+        then:
+        TaskDetails t = (model.getTasksForGroup('group4') as List).first()
+        t.dependencies as List == [':task2', ':task3']
+    }
+    
+    def dependenciesDoNotIncludeTheChildrenOfOtherTopLevelTasks() {
+        def task1 = task('task1')
+        def task2 = task('task2', 'group1', task1)
+        def task3 = task('task3', task1)
+        def task4 = task('task4', task2)
+        def task5 = task('task5', 'group2', task3, task4)
+
+        when:
+        model.calculate([task1, task2, task3, task4, task5])
+
+        then:
+        TaskDetails t = (model.getTasksForGroup('group2') as List).first()
+        t.dependencies as List == [':task2']
+    }
+
+    def addsAGroupContainingTheTasksWithNoGroup() {
+        def task1 = task('task1')
+        def task2 = task('task2', 'group1', task1)
+        def task3 = task('task3')
+        def task4 = task('task4', task2)
+        def task5 = task('task5', task3, task4)
+
+        when:
+        model.calculate([task1, task2, task3, task4, task5])
+
+        then:
+        model.groups as List == ['group1', '']
+        def tasks = model.getTasksForGroup('') as List
+        tasks*.task == [task5]
+        def t = tasks.first()
+        t.task == task5
+        t.children*.task as List == [task3, task4]
+    }
+    
+    def addsAGroupWhenThereAreNoTasksWithAGroup() {
+        def task1 = task('task1')
+        def task2 = task('task2', task1)
+        def task3 = task('task3')
+
+        when:
+        model.calculate([task1, task2, task3])
+
+        then:
+        model.groups as List == ['']
+        def tasks = model.getTasksForGroup('') as List
+        tasks*.task == [task2, task3]
+    }
+
+    def ignoresReachableTasksOutsideTheProject() {
+        def other1 = task('other1')
+        def other2 = task('other2', other1)
+        def task1 = task('task1', other2)
+        def task2 = task('task2', 'group1', task1)
+
+        when:
+        model.calculate([task1, task2])
+
+        then:
+        TaskDetails t = (model.getTasksForGroup('group1') as List).first()
+        t.children*.task as List == [task1]
+        t.dependencies as List == [':other2']
+    }
+
+    def task(String name, String group = null, Task... dependencies) {
+        Task task = Mock()
+        _ * task.toString() >> name
+        _ * task.name >> name
+        _ * task.path >> ":$name"
+        _ * task.group >> group
+        _ * task.compareTo(!null) >> { args -> name.compareTo(args[0].name) }
+        TaskDependency dep = Mock()
+        _ * dep.getDependencies(task) >> {dependencies as Set}
+        _ * task.taskDependencies >> dep
+        return task
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportRendererTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportRendererTest.groovy
new file mode 100644
index 0000000..5b8eca3
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportRendererTest.groovy
@@ -0,0 +1,175 @@
+/*
+ * 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.diagnostics
+
+import org.gradle.api.Rule
+import org.junit.Test
+import static org.junit.Assert.*
+import groovy.io.PlatformLineWriter
+
+/**
+ * @author Hans Dockter
+ */
+class TaskReportRendererTest {
+    private final StringWriter writer = new StringWriter()
+    private final TaskReportRenderer renderer = new TaskReportRenderer(writer)
+
+    @Test public void testWritesTaskAndDependenciesWithNoDetail() {
+        TaskDetails task1 = [getPath: {':task1'}, getDescription: {'task1Description'}, getDependencies: {[':task11', ':task12'] as LinkedHashSet}] as TaskDetails
+        TaskDetails task2 = [getPath: {':task2'}, getDescription: {null}, getDependencies: {[] as Set}] as TaskDetails
+        TaskDetails task3 = [getPath: {':task3'}, getDescription: {null}, getDependencies: {[':task1'] as Set}] as TaskDetails
+        Rule rule1 = [getDescription: {'rule1Description'}] as Rule
+        Rule rule2 = [getDescription: {'rule2Description'}] as Rule
+
+        List testDefaultTasks = ['task1', 'task2']
+        renderer.showDetail(false)
+        renderer.addDefaultTasks(testDefaultTasks)
+        renderer.startTaskGroup('group')
+        renderer.addTask(task1)
+        renderer.addChildTask(task2)
+        renderer.addTask(task3)
+        renderer.completeTasks()
+        renderer.addRule(rule1)
+        renderer.addRule(rule2)
+
+        def expected = '''Default tasks: task1, task2
+
+Group tasks
+-----------
+:task1 - task1Description
+:task3
+
+Rules
+-----
+rule1Description
+rule2Description
+'''
+        assertEquals(replaceWithPlatformNewLines(expected), writer.toString())
+    }
+
+    @Test public void testWritesTaskAndDependenciesWithDetail() {
+        TaskDetails task1 = [getPath: {':task1'}, getDescription: {'task1Description'}, getDependencies: {[':task11', ':task12'] as LinkedHashSet}] as TaskDetails
+        TaskDetails task2 = [getPath: {':task2'}, getDescription: {null}, getDependencies: {[] as Set}] as TaskDetails
+        TaskDetails task3 = [getPath: {':task3'}, getDescription: {null}, getDependencies: {[':task1'] as Set}] as TaskDetails
+        Rule rule1 = [getDescription: {'rule1Description'}] as Rule
+        Rule rule2 = [getDescription: {'rule2Description'}] as Rule
+
+        List testDefaultTasks = ['task1', 'task2']
+        renderer.showDetail(true)
+        renderer.addDefaultTasks(testDefaultTasks)
+        renderer.startTaskGroup('group')
+        renderer.addTask(task1)
+        renderer.addChildTask(task2)
+        renderer.addTask(task3)
+        renderer.completeTasks()
+        renderer.addRule(rule1)
+        renderer.addRule(rule2)
+
+        def expected = '''Default tasks: task1, task2
+
+Group tasks
+-----------
+:task1 - task1Description [:task11, :task12]
+    :task2
+:task3 [:task1]
+
+Rules
+-----
+rule1Description
+rule2Description
+'''
+        assertEquals(replaceWithPlatformNewLines(expected), writer.toString())
+    }
+
+    @Test public void testWritesTasksForSingleGroup() {
+        TaskDetails task = [getPath: {':task1'}, getDescription: {null}, getDependencies: {[] as Set}] as TaskDetails
+        renderer.addDefaultTasks([])
+        renderer.startTaskGroup('group')
+        renderer.addTask(task)
+        renderer.completeTasks()
+
+        def expected = '''Group tasks
+-----------
+:task1
+'''
+        assertEquals(replaceWithPlatformNewLines(expected), writer.toString())
+    }
+
+    @Test public void testWritesTasksForMultipleGroups() {
+        TaskDetails task = [getPath: {':task1'}, getDescription: {null}, getDependencies: {[] as Set}] as TaskDetails
+        TaskDetails task2 = [getPath: {':task2'}, getDescription: {null}, getDependencies: {[] as Set}] as TaskDetails
+        renderer.addDefaultTasks([])
+        renderer.startTaskGroup('group')
+        renderer.addTask(task)
+        renderer.startTaskGroup('')
+        renderer.addTask(task2)
+        renderer.completeTasks()
+
+        def expected = '''Group tasks
+-----------
+:task1
+
+Other tasks
+-----------
+:task2
+'''
+        assertEquals(replaceWithPlatformNewLines(expected), writer.toString())
+    }
+
+    @Test public void testWritesTasksForDefaultGroup() {
+        TaskDetails task = [getPath: {':task1'}, getDescription: {null}, getDependencies: {[] as Set}] as TaskDetails
+        renderer.addDefaultTasks([])
+        renderer.startTaskGroup('')
+        renderer.addTask(task)
+        renderer.completeTasks()
+
+        def expected = '''Tasks
+-----
+:task1
+'''
+        assertEquals(replaceWithPlatformNewLines(expected), writer.toString())
+    }
+
+    @Test public void testProjectWithNoTasksAndNoRules() {
+        renderer.completeTasks()
+
+        def expected = '''No tasks
+'''
+        assertEquals(replaceWithPlatformNewLines(expected), writer.toString())
+    }
+
+    @Test public void testProjectWithRulesAndNoTasks() {
+        String ruleDescription = "someDescription"
+
+        renderer.completeTasks()
+        renderer.addRule([getDescription: {ruleDescription}] as Rule)
+
+        def expected = '''No tasks
+
+Rules
+-----
+someDescription
+'''
+        assertEquals(replaceWithPlatformNewLines(expected), writer.toString())
+    }
+
+    String replaceWithPlatformNewLines(String text) {
+        StringWriter stringWriter = new StringWriter()
+        new PlatformLineWriter(stringWriter).withWriter { it.write(text) }
+        stringWriter.toString()
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskTest.java
new file mode 100644
index 0000000..3c20f0d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/TaskReportTaskTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright 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.tasks.diagnostics;
+
+import org.gradle.api.Project;
+import org.gradle.api.Rule;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.TaskContainer;
+import org.gradle.api.tasks.TaskDependency;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.Matchers;
+import org.jmock.Expectations;
+import org.jmock.Sequence;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+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.IOException;
+import java.util.List;
+
+import static org.gradle.util.WrapUtil.*;
+
+ at RunWith(JMock.class)
+public class TaskReportTaskTest {
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private TaskReportRenderer renderer;
+    private Project project;
+    private TaskReportTask task;
+    private TaskContainer taskContainer;
+
+    @Before
+    public void setup() {
+        renderer = context.mock(TaskReportRenderer.class);
+        project = context.mock(Project.class);
+        taskContainer = context.mock(TaskContainer.class);
+
+        context.checking(new Expectations(){{
+            allowing(project).absolutePath("list");
+            will(returnValue(":path"));
+            allowing(project).getTasks();
+            will(returnValue(taskContainer));
+            allowing(project).getConvention();
+            will(returnValue(null));
+        }});
+
+        task = HelperUtil.createTask(TaskReportTask.class);
+        task.setRenderer(renderer);
+    }
+
+    @Test
+    public void groupsTasksByTaskGroupAndPassesTasksToTheRenderer() throws IOException {
+        context.checking(new Expectations() {{
+            Task task1 = task("a", "group a");
+            Task task2 = task("b", "group b");
+            Task task3 = task("c");
+            Task task4 = task("d", "group b", task3);
+
+            List<String> testDefaultTasks = toList("defaultTask1", "defaultTask2");
+            allowing(project).getDefaultTasks();
+            will(returnValue(testDefaultTasks));
+
+            one(taskContainer).getAll();
+            will(returnValue(toLinkedSet(task2, task3, task4, task1)));
+
+            allowing(taskContainer).getRules();
+            will(returnValue(toList()));
+
+            Sequence sequence = context.sequence("seq");
+
+            one(renderer).showDetail(false);
+            inSequence(sequence);
+
+            one(renderer).addDefaultTasks(testDefaultTasks);
+            inSequence(sequence);
+
+            one(renderer).startTaskGroup("group a");
+            inSequence(sequence);
+
+            one(renderer).addTask(with(isTask(task1)));
+            inSequence(sequence);
+
+            one(renderer).startTaskGroup("group b");
+            inSequence(sequence);
+
+            one(renderer).addTask(with(isTask(task2)));
+            inSequence(sequence);
+
+            one(renderer).addTask(with(isTask(task4)));
+            inSequence(sequence);
+
+            one(renderer).addChildTask(with(isTask(task3)));
+            inSequence(sequence);
+
+            one(renderer).completeTasks();
+            inSequence(sequence);
+        }});
+
+        task.generate(project);
+    }
+
+    @Test
+    public void passesEachRuleToRenderer() throws IOException {
+        context.checking(new Expectations() {{
+            Rule rule1 = context.mock(Rule.class);
+            Rule rule2 = context.mock(Rule.class);
+
+            List<String> defaultTasks = toList();
+            allowing(project).getDefaultTasks();
+            will(returnValue(defaultTasks));
+
+            one(taskContainer).getAll();
+            will(returnValue(toSet()));
+
+            one(taskContainer).getRules();
+            will(returnValue(toList(rule1, rule2)));
+
+            Sequence sequence = context.sequence("seq");
+
+            one(renderer).showDetail(false);
+            inSequence(sequence);
+
+            one(renderer).addDefaultTasks(defaultTasks);
+            inSequence(sequence);
+
+            one(renderer).completeTasks();
+            inSequence(sequence);
+
+            one(renderer).addRule(rule1);
+            inSequence(sequence);
+
+            one(renderer).addRule(rule2);
+            inSequence(sequence);
+        }});
+
+        task.generate(project);
+    }
+
+    private Matcher<TaskDetails> isTask(final Task task) {
+        return new BaseMatcher<TaskDetails>() {
+            @Override
+            public boolean matches(Object o) {
+                TaskDetails other = (TaskDetails) o;
+                return other.getPath().equals(task.getPath());
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("is ").appendValue(task);
+            }
+        };
+    }
+
+    private Task task(String name) {
+        return task(name, null);
+    }
+    
+    private Task task(final String name, final String taskGroup, final Task... dependencies) {
+        final Task task = context.mock(Task.class);
+        context.checking(new Expectations() {{
+            allowing(task).getName();
+            will(returnValue(name));
+            allowing(task).getPath();
+            will(returnValue(':' + name));
+            allowing(task).getGroup();
+            will(returnValue(taskGroup));
+            allowing(task).compareTo(with(Matchers.notNullValue(Task.class)));
+            will(new Action() {
+                @Override
+                public Object invoke(Invocation invocation) throws Throwable {
+                    Task other = (Task) invocation.getParameter(0);
+                    return name.compareTo(other.getName());
+                }
+
+                @Override
+                public void describeTo(Description description) {
+                    description.appendText("compare to");
+                }
+            });
+            
+            TaskDependency dependency = context.mock(TaskDependency.class);
+            allowing(task).getTaskDependencies();
+            will(returnValue(dependency));
+
+            allowing(dependency).getDependencies(task);
+            will(returnValue(toSet(dependencies)));
+        }});
+
+        return task;
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/TextProjectReportRendererTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/TextProjectReportRendererTest.java
new file mode 100644
index 0000000..709f53e
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/diagnostics/TextProjectReportRendererTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.diagnostics;
+
+import org.gradle.api.Project;
+import org.gradle.util.TemporaryFolder;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.StringWriter;
+
+import static org.gradle.util.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class TextProjectReportRendererTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    @Rule
+    public TemporaryFolder testDir = new TemporaryFolder();
+
+    @Test
+    public void writesReportToStandardOutByDefault() throws IOException {
+        TextProjectReportRenderer renderer = new TextProjectReportRenderer();
+        assertThat(renderer.getWriter(), sameInstance((Appendable) System.out));
+
+        renderer.complete();
+
+        assertThat(renderer.getWriter(), sameInstance((Appendable) System.out));
+    }
+
+    @Test
+    public void writesReportToAFile() throws IOException {
+        File outFile = new File(testDir.getDir(), "report.txt");
+        TextProjectReportRenderer renderer = new TextProjectReportRenderer();
+        renderer.setOutputFile(outFile);
+        assertThat(renderer.getWriter(), instanceOf(FileWriter.class));
+
+        renderer.complete();
+
+        assertTrue(outFile.isFile());
+        assertThat(renderer.getWriter(), sameInstance((Appendable) System.out));
+    }
+
+    @Test
+    public void writeRootProjectHeader() throws IOException {
+        final Project project = context.mock(Project.class);
+        StringWriter writer = new StringWriter();
+
+        context.checking(new Expectations() {{
+            allowing(project).getRootProject();
+            will(returnValue(project));
+        }});
+
+        TextProjectReportRenderer renderer = new TextProjectReportRenderer(writer);
+        renderer.startProject(project);
+        renderer.completeProject(project);
+        renderer.complete();
+
+        assertThat(writer.toString(), containsLine("Root Project"));
+    }
+    
+    @Test
+    public void writeSubProjectHeader() throws IOException {
+        final Project project = context.mock(Project.class);
+        StringWriter writer = new StringWriter();
+
+        context.checking(new Expectations() {{
+            allowing(project).getRootProject();
+            will(returnValue(context.mock(Project.class, "root")));
+            allowing(project).getPath();
+            will(returnValue("<path>"));
+        }});
+
+        TextProjectReportRenderer renderer = new TextProjectReportRenderer(writer);
+        renderer.startProject(project);
+        renderer.completeProject(project);
+        renderer.complete();
+
+        assertThat(writer.toString(), containsLine("Project <path>"));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/util/AbstractTestForPatternSet.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/util/AbstractTestForPatternSet.groovy
new file mode 100644
index 0000000..e2480a6
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/util/AbstractTestForPatternSet.groovy
@@ -0,0 +1,77 @@
+/*
+ * 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.api.tasks.util
+
+import static org.junit.Assert.*
+import static org.hamcrest.Matchers.*
+import static org.gradle.util.Matchers.*
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * @author Hans Dockter
+ */
+abstract class AbstractTestForPatternSet {
+    static final String TEST_PATTERN_1 = 'pattern1'
+    static final String TEST_PATTERN_2 = 'pattern2'
+    static final String TEST_PATTERN_3 = 'pattern3'
+
+    abstract PatternFilterable getPatternSet()
+
+    def contextObject
+
+    @Before public void setUp()  {
+        contextObject = new Object()
+    }
+
+    @Test public void testDefaultValues() {
+        assertThat(patternSet.includes, isEmpty())
+        assertThat(patternSet.excludes, isEmpty())
+    }
+
+    @Test public void testInclude() {
+        checkIncludesExcludes(patternSet, 'include', 'includes')
+    }
+
+    @Test public void testExclude() {
+        checkIncludesExcludes(patternSet, 'exclude', 'excludes')
+    }
+
+    void checkIncludesExcludes(PatternFilterable patternSet, String methodName, String propertyName) {
+        assertThat(patternSet."$methodName"(TEST_PATTERN_1, TEST_PATTERN_2), sameInstance(patternSet))
+        assertThat(patternSet."$propertyName", equalTo([TEST_PATTERN_1, TEST_PATTERN_2] as Set))
+
+        assertThat(patternSet."$methodName"(TEST_PATTERN_3), sameInstance(patternSet))
+        assertThat(patternSet."$propertyName", equalTo([TEST_PATTERN_1, TEST_PATTERN_2, TEST_PATTERN_3] as Set))
+
+        patternSet."$propertyName" = {[TEST_PATTERN_2].iterator()} as Iterable
+        assertThat(patternSet."$propertyName", equalTo([TEST_PATTERN_2] as Set))
+
+        assertThat(patternSet."$methodName"([TEST_PATTERN_3]), sameInstance(patternSet))
+        assertThat(patternSet."$propertyName", equalTo([TEST_PATTERN_2, TEST_PATTERN_3] as Set))
+    }
+
+    void preparePatternSetForAntBuilderTest(PatternFilterable patternSet) {
+        patternSet.include('i')
+        patternSet.exclude('e')
+    }
+
+    void checkPatternSetForAntBuilderTest(antPatternSet, PatternFilterable patternSet) {
+        assertEquals(patternSet.includes as String[], antPatternSet.getIncludePatterns())
+        assertEquals(patternSet.excludes as String[], antPatternSet.getExcludePatterns())
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/util/PatternSetTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/util/PatternSetTest.groovy
new file mode 100644
index 0000000..73bd602
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/api/tasks/util/PatternSetTest.groovy
@@ -0,0 +1,234 @@
+/*
+ * 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.api.tasks.util
+
+import org.gradle.api.file.FileTreeElement
+import org.gradle.api.file.RelativePath
+import org.gradle.api.specs.Spec
+import org.junit.Test
+import static org.gradle.util.Matchers.*
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+/**
+* @author Hans Dockter
+*/
+class PatternSetTest extends AbstractTestForPatternSet {
+    PatternSet patternSet = new PatternSet()
+
+    PatternSet getPatternSet() {
+        patternSet
+    }
+
+    @Test public void testConstructionFromMap() {
+        Map map = [includes: [TEST_PATTERN_1], excludes: [TEST_PATTERN_2]]
+        PatternFilterable patternSet = new PatternSet(map)
+        assertThat(patternSet.includes, equalTo([TEST_PATTERN_1] as Set))
+        assertThat(patternSet.excludes, equalTo([TEST_PATTERN_2] as Set))
+    }
+
+    @Test public void patternSetsAreEqualWhenAllPropertiesAreEqual() {
+        assertThat(new PatternSet(), strictlyEqual(new PatternSet()))
+        assertThat(new PatternSet(caseSensitive: false), strictlyEqual(new PatternSet(caseSensitive: false)))
+        assertThat(new PatternSet(includes: ['i']), strictlyEqual(new PatternSet(includes: ['i'])))
+        assertThat(new PatternSet(excludes: ['e']), strictlyEqual(new PatternSet(excludes: ['e'])))
+        assertThat(new PatternSet(includes: ['i'], excludes: ['e']), strictlyEqual(new PatternSet(includes: ['i'], excludes: ['e'])))
+
+        assertThat(new PatternSet(), not(equalTo(new PatternSet(caseSensitive: false))))
+        assertThat(new PatternSet(), not(equalTo(new PatternSet(includes: ['i']))))
+        assertThat(new PatternSet(), not(equalTo(new PatternSet(excludes: ['e']))))
+        assertThat(new PatternSet(includes: ['i']), not(equalTo(new PatternSet(includes: ['other']))))
+        assertThat(new PatternSet(excludes: ['e']), not(equalTo(new PatternSet(excludes: ['other']))))
+    }
+    
+    @Test public void canCopyFromAnotherPatternSet() {
+        PatternSet other = new PatternSet()
+        other.include 'a', 'b'
+        other.exclude 'c'
+        other.include({true} as Spec)
+        other.exclude({false} as Spec)
+        patternSet.copyFrom(other)
+        assertThat(patternSet.includes, equalTo(['a', 'b'] as Set))
+        assertThat(patternSet.excludes, equalTo(['c'] as Set))
+        assertThat(patternSet.includes, not(sameInstance(other.includes)))
+        assertThat(patternSet.excludes, not(sameInstance(other.excludes)))
+        assertThat(patternSet.includeSpecs, equalTo(other.includeSpecs))
+        assertThat(patternSet.excludeSpecs, equalTo(other.excludeSpecs))
+    }
+
+    @Test public void createsSpecForEmptyPatternSet() {
+        Spec<FileTreeElement> spec = patternSet.asSpec
+
+        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
+        assertTrue(spec.isSatisfiedBy(element(true, 'b')))
+    }
+
+    private FileTreeElement element(boolean isFile, String... elements) {
+        [
+                getRelativePath: { return new RelativePath(isFile, elements) },
+                getFile: { return new File(elements.join('/')) }
+        ] as FileTreeElement
+    }
+
+    @Test public void createsSpecForIncludePatterns() {
+        patternSet.include '*a*'
+        patternSet.include '*b*'
+        Spec<FileTreeElement> spec = patternSet.asSpec
+
+        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
+        assertTrue(spec.isSatisfiedBy(element(true, 'b')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'c')))
+    }
+
+    @Test public void createsSpecForExcludePatterns() {
+        patternSet.exclude '*b*'
+        patternSet.exclude '*c*'
+        Spec<FileTreeElement> spec = patternSet.asSpec
+
+        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'b')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'c')))
+    }
+
+    @Test public void createsSpecForIncludeAndExcludePatterns() {
+        patternSet.include '*a*'
+        patternSet.exclude '*b*'
+        Spec<FileTreeElement> spec = patternSet.asSpec
+
+        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'ab')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'ba')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'c')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'b')))
+    }
+
+    @Test public void createsSpecForIncludeSpecs() {
+        patternSet.include({ FileTreeElement element -> element.file.name.contains('a') } as Spec)
+        Spec<FileTreeElement> spec = patternSet.asSpec
+
+        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'b')))
+    }
+
+    @Test public void createsSpecForExcludeSpecs() {
+        patternSet.exclude({ FileTreeElement element -> element.file.name.contains('b') } as Spec)
+        Spec<FileTreeElement> spec = patternSet.asSpec
+
+        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'b')))
+    }
+
+    @Test public void createsSpecForIncludeAndExcludeSpecs() {
+        patternSet.include({ FileTreeElement element -> element.file.name.contains('a') } as Spec)
+        patternSet.exclude({ FileTreeElement element -> element.file.name.contains('b') } as Spec)
+        Spec<FileTreeElement> spec = patternSet.asSpec
+
+        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'ab')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'b')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'c')))
+    }
+
+    @Test public void createsSpecForIncludeClosure() {
+        patternSet.include { FileTreeElement element -> element.file.name.contains('a') }
+        Spec<FileTreeElement> spec = patternSet.asSpec
+
+        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'b')))
+    }
+
+    @Test public void createsSpecForExcludeClosure() {
+        patternSet.exclude { FileTreeElement element -> element.file.name.contains('b') }
+        Spec<FileTreeElement> spec = patternSet.asSpec
+
+        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'b')))
+    }
+
+    @Test public void createsSpecForIncludeAndExcludeClosures() {
+        patternSet.include { FileTreeElement element -> element.file.name.contains('a') }
+        patternSet.exclude { FileTreeElement element -> element.file.name.contains('b') }
+        Spec<FileTreeElement> spec = patternSet.asSpec
+
+        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'ab')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'c')))
+    }
+
+    @Test public void isCaseSensitiveByDefault() {
+        patternSet.include '*a*'
+        patternSet.exclude '*b*'
+        Spec<FileTreeElement> spec = patternSet.asSpec
+
+        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'A')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'Ab')))
+        assertTrue(spec.isSatisfiedBy(element(true, 'aB')))
+    }
+    
+    @Test public void createsSpecForCaseInsensitivePatternSet() {
+        patternSet.include '*a*'
+        patternSet.exclude '*b*'
+        patternSet.caseSensitive = false
+        Spec<FileTreeElement> spec = patternSet.asSpec
+
+        assertTrue(spec.isSatisfiedBy(element(true, 'A')))
+        assertTrue(spec.isSatisfiedBy(element(true, 'a')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'AB')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'bA')))
+    }
+
+    @Test public void createIntersectPatternSet() {
+        patternSet.include '*a*'
+        patternSet.include { FileTreeElement element -> element.file.name.contains('1') }
+        patternSet.exclude '*b*'
+        patternSet.exclude { FileTreeElement element -> element.file.name.contains('2') }
+        PatternSet intersection = patternSet.intersect()
+        intersection.include '*c*'
+        intersection.include { FileTreeElement element -> element.file.name.contains('3') }
+        intersection.exclude '*d*'
+        intersection.exclude { FileTreeElement element -> element.file.name.contains('4') }
+        Spec<FileTreeElement> spec = intersection.asSpec
+
+        assertTrue(spec.isSatisfiedBy(element(true, 'ac')))
+        assertTrue(spec.isSatisfiedBy(element(true, '13')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'a')))
+        assertFalse(spec.isSatisfiedBy(element(true, '1')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'c')))
+        assertFalse(spec.isSatisfiedBy(element(true, '3')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'acb')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'acd')))
+        assertFalse(spec.isSatisfiedBy(element(true, '132')))
+        assertFalse(spec.isSatisfiedBy(element(true, '132')))
+    }
+
+    @Test public void addsGlobalExcludesToExcludePatterns() {
+        Spec<FileTreeElement> spec = patternSet.asSpec
+
+        assertFalse(spec.isSatisfiedBy(element(false, '.svn')))
+        assertFalse(spec.isSatisfiedBy(element(true, '.svn', 'abc')))
+        assertFalse(spec.isSatisfiedBy(element(false, 'a', 'b', '.svn')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'a', 'b', '.svn', 'c')))
+
+        PatternSet.globalExcludes = ['*a*']
+
+        spec = patternSet.asSpec
+
+        assertTrue(spec.isSatisfiedBy(element(false, '.svn')))
+        assertFalse(spec.isSatisfiedBy(element(true, 'abc')))
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/cache/AutoCloseCacheFactoryTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/cache/AutoCloseCacheFactoryTest.groovy
new file mode 100644
index 0000000..5b9c16e
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/cache/AutoCloseCacheFactoryTest.groovy
@@ -0,0 +1,102 @@
+/*
+ * 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.cache
+
+import org.gradle.CacheUsage
+import spock.lang.Specification
+
+public class AutoCloseCacheFactoryTest extends Specification {
+    private final CacheFactory backingFactory = Mock()
+    private final AutoCloseCacheFactory factory = new AutoCloseCacheFactory(backingFactory)
+
+    public void createsCachesUsingBackingFactory() {
+        PersistentCache cache = Mock()
+
+        when:
+        def retval = factory.open(new File('dir1'), CacheUsage.ON, [:])
+
+        then:
+        1 * backingFactory.open(new File('dir1'), CacheUsage.ON, [:]) >> cache
+        retval == cache
+    }
+
+    public void cachesCacheInstanceForAGivenDirectory() {
+        PersistentCache cache = Mock()
+
+        when:
+        def cache1 = factory.open(new File('dir1'), CacheUsage.ON, [:])
+        def cache2 = factory.open(new File('dir1').canonicalFile, CacheUsage.ON, [:])
+
+        then:
+        1 * backingFactory.open(new File('dir1'), CacheUsage.ON, [:]) >> cache
+        cache1 == cache2
+    }
+
+    public void closesCacheUsingBackingFactory() {
+        PersistentCache cache = Mock()
+
+        when:
+        factory.open(new File('dir1'), CacheUsage.ON, [:])
+
+        then:
+        1 * backingFactory.open(new File('dir1'), CacheUsage.ON, [:]) >> cache
+
+        when:
+        factory.close(cache)
+
+        then:
+        1 * backingFactory.close(cache)
+    }
+
+    public void closesCacheWhenLastReferenceClosed() {
+        PersistentCache cache = Mock()
+
+        when:
+        factory.open(new File('dir1'), CacheUsage.ON, [:])
+        factory.open(new File('dir1'), CacheUsage.ON, [:])
+
+        then:
+        1 * backingFactory.open(new File('dir1'), CacheUsage.ON, [:]) >> cache
+
+        when:
+        factory.close(cache)
+        factory.close(cache)
+
+        then:
+        1 * backingFactory.close(cache)
+    }
+    
+    public void closesEachOpenCacheOnClose() {
+        PersistentCache cache1 = Mock()
+        PersistentCache cache2 = Mock()
+
+        when:
+        factory.open(new File('dir1'), CacheUsage.ON, [:])
+        factory.open(new File('dir2'), CacheUsage.ON, [:])
+
+        then:
+        1 * backingFactory.open(new File('dir1'), CacheUsage.ON, [:]) >> cache1
+        1 * backingFactory.open(new File('dir2'), CacheUsage.ON, [:]) >> cache2
+
+        when:
+        factory.close()
+
+        then:
+        1 * backingFactory.close(cache1)
+        1 * backingFactory.close(cache2)
+    }
+}
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/cache/DefaultCacheFactoryTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/cache/DefaultCacheFactoryTest.groovy
new file mode 100644
index 0000000..9796005
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/cache/DefaultCacheFactoryTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * 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.cache
+
+import org.gradle.CacheUsage
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+class DefaultCacheFactoryTest extends Specification {
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder()
+    private final DefaultCacheFactory factory = new DefaultCacheFactory()
+
+    public void createsCache() {
+        when:
+        PersistentCache cache = factory.open(tmpDir.dir, CacheUsage.ON, [prop: 'value'])
+
+        then:
+        cache instanceof DefaultPersistentDirectoryCache
+        cache.baseDir == tmpDir.dir
+    }
+}
+
+
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/cache/DefaultCacheRepositoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/cache/DefaultCacheRepositoryTest.java
new file mode 100644
index 0000000..2f740ef
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/cache/DefaultCacheRepositoryTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.cache;
+
+import org.gradle.CacheUsage;
+import org.gradle.api.Project;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.util.GUtil;
+import org.gradle.util.GradleVersion;
+import org.gradle.util.TemporaryFolder;
+import org.gradle.util.TestFile;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.Map;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class DefaultCacheRepositoryTest {
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final TestFile homeDir = tmpDir.createDir("home");
+    private final TestFile buildRootDir = tmpDir.createDir("build");
+    private final TestFile sharedCacheDir = homeDir.file("caches");
+    private final String version = new GradleVersion().getVersion();
+    private final Map<String, ?> properties = GUtil.map("a", "value", "b", "value2");
+    private final CacheFactory cacheFactory = context.mock(CacheFactory.class);
+    private final PersistentCache cache = context.mock(PersistentCache.class);
+    private final Gradle gradle = context.mock(Gradle.class);
+    private final DefaultCacheRepository repository = new DefaultCacheRepository(homeDir, CacheUsage.ON, cacheFactory);
+
+    @Before
+    public void setup() {
+        context.checking(new Expectations() {{
+            Project project = context.mock(Project.class);
+
+            allowing(cache).getBaseDir();
+            will(returnValue(tmpDir.getDir()));
+            allowing(gradle).getRootProject();
+            will(returnValue(project));
+            allowing(project).getProjectDir();
+            will(returnValue(buildRootDir));
+        }});
+    }
+
+    @Test
+    public void createsGlobalCache() {
+        context.checking(new Expectations() {{
+            one(cacheFactory).open(sharedCacheDir.file(version, "a/b/c"), CacheUsage.ON, Collections.EMPTY_MAP);
+            will(returnValue(cache));
+        }});
+
+        assertThat(repository.cache("a/b/c").open(), sameInstance(cache));
+    }
+
+    @Test
+    public void createsGlobalCacheWithProperties() {
+        context.checking(new Expectations() {{
+            one(cacheFactory).open(sharedCacheDir.file(version, "a/b/c"), CacheUsage.ON, properties);
+            will(returnValue(cache));
+        }});
+
+        assertThat(repository.cache("a/b/c").withProperties(properties).open(), sameInstance(cache));
+    }
+
+    @Test
+    public void createsCacheForAGradleInstance() {
+
+        context.checking(new Expectations() {{
+            one(cacheFactory).open(buildRootDir.file(".gradle", version, "a/b/c"), CacheUsage.ON,
+                    Collections.EMPTY_MAP);
+            will(returnValue(cache));
+        }});
+
+        assertThat(repository.cache("a/b/c").forObject(gradle).open(), sameInstance(cache));
+    }
+
+    @Test
+    public void createsCacheForAFile() {
+        final TestFile dir = tmpDir.createDir("otherDir");
+
+        context.checking(new Expectations() {{
+            one(cacheFactory).open(dir.file(".gradle", version, "a/b/c"), CacheUsage.ON, Collections.EMPTY_MAP);
+            will(returnValue(cache));
+        }});
+
+        assertThat(repository.cache("a/b/c").forObject(dir).open(), sameInstance(cache));
+    }
+
+    @Test
+    public void createsCrossVersionCache() {
+        context.checking(new Expectations() {{
+            one(cacheFactory).open(sharedCacheDir.file("noVersion", "a/b/c"), CacheUsage.ON, Collections.singletonMap(
+                    "gradle.version", version));
+            will(returnValue(cache));
+        }});
+
+        assertThat(repository.cache("a/b/c").invalidateOnVersionChange().open(), sameInstance(cache));
+    }
+
+    @Test
+    public void createsCrossVersionCacheForAGradleInstance() {
+        context.checking(new Expectations() {{
+            one(cacheFactory).open(buildRootDir.file(".gradle", "noVersion", "a/b/c"), CacheUsage.ON,
+                    Collections.singletonMap("gradle.version", version));
+            will(returnValue(cache));
+        }});
+
+        assertThat(repository.cache("a/b/c").invalidateOnVersionChange().forObject(gradle).open(), sameInstance(cache));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/cache/DefaultPersistentDirectoryCacheTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/cache/DefaultPersistentDirectoryCacheTest.java
new file mode 100644
index 0000000..0f1e4a8
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/cache/DefaultPersistentDirectoryCacheTest.java
@@ -0,0 +1,171 @@
+/*
+ * 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 org.gradle.CacheUsage;
+import org.gradle.cache.btree.BTreePersistentIndexedCache;
+import org.gradle.util.TestFile;
+import org.gradle.util.GUtil;
+import org.gradle.util.TemporaryFolder;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+public class DefaultPersistentDirectoryCacheTest {
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    private final Map<String, String> properties = GUtil.map("prop", "value", "prop2", "other-value");
+
+    @Test
+    public void cacheIsInvalidWhenCacheDirDoesNotExist() {
+        TestFile emptyDir = tmpDir.getDir().file("dir");
+        emptyDir.assertDoesNotExist();
+
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(emptyDir, CacheUsage.ON, properties);
+        assertFalse(cache.isValid());
+
+        emptyDir.assertIsDir();
+    }
+
+    @Test
+    public void cacheIsInvalidWhenPropertiesFileDoesNotExist() {
+        TestFile dir = tmpDir.getDir().file("dir").createDir();
+
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.ON, properties);
+        assertFalse(cache.isValid());
+
+        dir.assertIsDir();
+    }
+
+    @Test
+    public void rebuildsCacheWhenPropertiesHaveChanged() {
+        TestFile dir = createCacheDir("prop", "other-value");
+
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.ON, properties);
+        assertFalse(cache.isValid());
+
+        dir.assertHasDescendants();
+    }
+
+    @Test
+    public void rebuildsCacheWhenCacheRebuildRequested() {
+        TestFile dir = createCacheDir();
+
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.REBUILD, properties);
+        assertFalse(cache.isValid());
+
+        dir.assertHasDescendants();
+    }
+
+    @Test
+    public void usesExistingCacheDirWhenItIsNotInvalid() {
+        TestFile dir = createCacheDir();
+
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.ON, properties);
+        assertTrue(cache.isValid());
+
+        dir.file("cache.properties").assertIsFile();
+        dir.file("some-file").assertIsFile();
+    }
+
+    @Test
+    public void updateCreatesPropertiesFileWhenItDoesNotExist() {
+        TestFile dir = tmpDir.getDir().file("dir");
+
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.ON, properties);
+        cache.markValid();
+
+        assertTrue(cache.isValid());
+        assertThat(loadProperties(dir.file("cache.properties")), equalTo(properties));
+    }
+
+    @Test
+    public void updatesPropertiesWhenMarkedValid() {
+        TestFile dir = createCacheDir("prop", "some-other-value");
+
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.ON, properties);
+        cache.markValid();
+
+        assertTrue(cache.isValid());
+        assertThat(loadProperties(dir.file("cache.properties")), equalTo(properties));
+    }
+
+    @Test
+    public void createsAnIndexedCache() {
+        TestFile dir = tmpDir.getDir().file("dir");
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.ON, properties);
+        assertThat(cache.openIndexedCache(), instanceOf(BTreePersistentIndexedCache.class));
+    }
+
+    @Test
+    public void reusesTheIndexedCache() {
+        TestFile dir = tmpDir.getDir().file("dir");
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.ON, properties);
+        assertThat(cache.openIndexedCache(), sameInstance(cache.openIndexedCache()));
+    }
+
+    @Test
+    public void closesIndexedCacheOnClose() {
+        TestFile dir = tmpDir.getDir().file("dir");
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.ON, properties);
+        
+        BTreePersistentIndexedCache indexedCache = cache.openIndexedCache();
+        assertTrue(indexedCache.isOpen());
+
+        cache.close();
+        assertFalse(indexedCache.isOpen());
+    }
+    
+    @Test
+    public void createsAnStateCache() {
+        TestFile dir = tmpDir.getDir().file("dir");
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.ON, properties);
+        assertThat(cache.openStateCache(), instanceOf(SimpleStateCache.class));
+    }
+
+    @Test
+    public void reusesTheStateCache() {
+        TestFile dir = tmpDir.getDir().file("dir");
+        DefaultPersistentDirectoryCache cache = new DefaultPersistentDirectoryCache(dir, CacheUsage.ON, properties);
+        assertThat(cache.openStateCache(), sameInstance(cache.openStateCache()));
+    }
+
+    private Map<String, String> loadProperties(TestFile file) {
+        Properties properties = GUtil.loadProperties(file);
+        Map<String, String> result = new HashMap<String, String>();
+        for (Map.Entry<Object, Object> entry : properties.entrySet()) {
+            result.put(entry.getKey().toString(), entry.getValue().toString());
+        }
+        return result;
+    }
+
+    private TestFile createCacheDir(String... extraProps) {
+        TestFile dir = tmpDir.getDir();
+        Properties properties = new Properties();
+        properties.putAll(this.properties);
+        properties.putAll(GUtil.map((Object[])extraProps));
+        GUtil.saveProperties(properties, dir.file("cache.properties"));
+        dir.file("some-file").touch();
+
+        return dir;
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/cache/SimpleStateCacheTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/cache/SimpleStateCacheTest.groovy
new file mode 100644
index 0000000..8252d4e
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/cache/SimpleStateCacheTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * 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.cache
+
+
+import org.gradle.util.JUnit4GroovyMockery;
+import org.jmock.integration.junit4.JMock
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+
+ at RunWith(JMock.class)
+class SimpleStateCacheTest {
+    @Rule public TemporaryFolder tmpDir = new TemporaryFolder()
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final PersistentCache backingCache = context.mock(PersistentCache.class)
+
+    @Before
+    public void setup() {
+        context.checking {
+            allowing(backingCache).getBaseDir()
+            will(returnValue(tmpDir.dir))
+        }
+    }
+
+    @Test
+    public void getReturnsNullWhenFileDoesNotExist() {
+        SimpleStateCache<String> cache = new SimpleStateCache<String>(backingCache, new DefaultSerializer<String>())
+        assertThat(cache.get(), nullValue())
+    }
+    
+    @Test
+    public void getReturnsLastWrittenValue() {
+        SimpleStateCache<String> cache = new SimpleStateCache<String>(backingCache, new DefaultSerializer<String>())
+
+        context.checking {
+            one(backingCache).markValid()
+        }
+
+        cache.set('some value')
+        tmpDir.file('state.bin').assertIsFile()
+        assertThat(cache.get(), equalTo('some value'))
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/cache/btree/BTreePersistentIndexedCacheTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/cache/btree/BTreePersistentIndexedCacheTest.java
new file mode 100644
index 0000000..a9b3e73
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/cache/btree/BTreePersistentIndexedCacheTest.java
@@ -0,0 +1,321 @@
+/*
+ * 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.cache.btree;
+
+import org.gradle.cache.DefaultSerializer;
+import org.gradle.cache.PersistentCache;
+import org.gradle.cache.Serializer;
+import org.gradle.util.TestFile;
+import org.gradle.util.TemporaryFolder;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.*;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class BTreePersistentIndexedCacheTest {
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final PersistentCache backingCache = context.mock(PersistentCache.class);
+    private final Serializer<Integer> serializer = new DefaultSerializer<Integer>();
+    private BTreePersistentIndexedCache<String, Integer> cache;
+
+    @Before
+    public void setup() {
+        context.checking(new Expectations(){{
+            allowing(backingCache).getBaseDir();
+            will(returnValue(tmpDir.getDir()));
+            allowing(backingCache).markValid();
+        }});
+
+        cache = new BTreePersistentIndexedCache<String, Integer>(backingCache, serializer, (short) 4, 100);
+    }
+
+    @Test
+    public void getReturnsNullWhenEntryDoesNotExist() {
+        assertNull(cache.get("unknown"));
+        cache.verify();
+    }
+
+    @Test
+    public void persistsAddedEntries() {
+        checkAdds(1, 2, 3, 4, 5);
+        cache.verify();
+    }
+
+    @Test
+    public void persistsAddedEntriesInReverseOrder() {
+        checkAdds(5, 4, 3, 2, 1);
+        cache.verify();
+    }
+
+    @Test
+    public void persistsAddedEntriesOverMultipleIndexBlocks() {
+        checkAdds(3, 2, 11, 5, 7, 1, 10, 8, 9, 4, 6, 0);
+        cache.verify();
+    }
+
+    @Test
+    public void persistsAddedEntriesAfterReopen() {
+        checkAdds(1, 2, 3, 4);
+
+        cache.reset();
+
+        checkAdds(5, 6, 7, 8);
+        cache.verify();
+    }
+    
+    @Test
+    public void persistsReplacedEntries() {
+
+        cache.put("key_1", 1);
+        cache.put("key_2", 2);
+        cache.put("key_3", 3);
+        cache.put("key_4", 4);
+        cache.put("key_5", 5);
+
+        cache.put("key_1", 1);
+        cache.put("key_4", 12);
+
+        assertThat(cache.get("key_1"), equalTo(1));
+        assertThat(cache.get("key_2"), equalTo(2));
+        assertThat(cache.get("key_3"), equalTo(3));
+        assertThat(cache.get("key_4"), equalTo(12));
+        assertThat(cache.get("key_5"), equalTo(5));
+
+        cache.reset();
+
+        assertThat(cache.get("key_1"), equalTo(1));
+        assertThat(cache.get("key_2"), equalTo(2));
+        assertThat(cache.get("key_3"), equalTo(3));
+        assertThat(cache.get("key_4"), equalTo(12));
+        assertThat(cache.get("key_5"), equalTo(5));
+
+        cache.verify();
+    }
+
+    @Test
+    public void reusesEmptySpaceWhenPuttingEntries() {
+        BTreePersistentIndexedCache<String, String> cache = new BTreePersistentIndexedCache<String, String>(
+                backingCache, new DefaultSerializer<String>(), (short) 4, 100);
+        TestFile cacheFile = tmpDir.getDir().file("cache.bin");
+
+        cache.put("key_1", "abcd");
+        cache.put("key_2", "abcd");
+        cache.put("key_3", "abcd");
+        cache.put("key_4", "abcd");
+        cache.put("key_5", "abcd");
+
+        long len = cacheFile.length();
+        assertThat(len, greaterThan(0L));
+
+        cache.put("key_1", "1234");
+        assertThat(cacheFile.length(), equalTo(len));
+
+        cache.remove("key_1");
+        cache.put("key_new", "a1b2");
+        assertThat(cacheFile.length(), equalTo(len));
+
+        cache.put("key_new", "longer value");
+        assertThat(cacheFile.length(), greaterThan(len));
+        len = cacheFile.length();
+
+        cache.put("key_1", "1234");
+        assertThat(cacheFile.length(), equalTo(len));
+    }
+    
+    @Test
+    public void canHandleLargeNumberOfEntries() {
+
+        int count = 2000;
+        List<Integer> values = new ArrayList<Integer>();
+        for (int i = 0; i < count; i++) {
+            values.add(i);
+        }
+
+        checkAddsAndRemoves(null, values);
+
+        TestFile testFile = tmpDir.getDir().file("cache.bin");
+        long len = testFile.length();
+
+        checkAddsAndRemoves(Collections.<Integer>reverseOrder(), values);
+
+        // need to make this better
+        assertThat(testFile.length(), lessThan((long)(1.4 * len)));
+
+        checkAdds(values);
+        
+        // need to make this better
+        assertThat(testFile.length(), lessThan((long)(1.4 * 1.4 * len)));
+    }
+
+    @Test
+    public void persistsRemovalOfEntries() {
+        checkAddsAndRemoves(1, 2, 3, 4, 5);
+        cache.verify();
+    }
+
+    @Test
+    public void persistsRemovalOfEntriesInReverse() {
+        checkAddsAndRemoves(Collections.<Integer>reverseOrder(), 1, 2, 3, 4, 5);
+        cache.verify();
+    }
+
+    @Test
+    public void persistsRemovalOfEntriesOverMultipleIndexBlocks() {
+        checkAddsAndRemoves(4, 12, 9, 1, 3, 10, 11, 7, 8, 2, 5, 6);
+        cache.verify();
+    }
+
+    @Test
+    public void removalRedistributesRemainingEntriesWithLeftSibling() {
+        // Ends up with: 1 2 3 -> 4 <- 5 6
+        checkAdds(1, 2, 5, 6, 4, 3);
+        cache.verify();
+        cache.remove("key_5");
+        cache.verify();
+    }
+
+    @Test
+    public void removalMergesRemainingEntriesIntoLeftSibling() {
+        // Ends up with: 1 2 -> 3 <- 4 5
+        checkAdds(1, 2, 4, 5, 3);
+        cache.verify();
+        cache.remove("key_4");
+        cache.verify();
+    }
+
+    @Test
+    public void removalRedistributesRemainingEntriesWithRightSibling() {
+        // Ends up with: 1 2 -> 3 <- 4 5 6
+        checkAdds(1, 2, 4, 5, 3, 6);
+        cache.verify();
+        cache.remove("key_2");
+        cache.verify();
+    }
+
+    @Test
+    public void removalMergesRemainingEntriesIntoRightSibling() {
+        // Ends up with: 1 2 -> 3 <- 4 5
+        checkAdds(1, 2, 4, 5, 3);
+        cache.verify();
+        cache.remove("key_2");
+        cache.verify();
+    }
+
+    @Test
+    public void handlesBadlyFormedCacheFile() throws IOException {
+
+        TestFile testFile = tmpDir.getDir().file("cache.bin");
+        testFile.assertIsFile();
+        testFile.write("some junk");
+
+        BTreePersistentIndexedCache<String, Integer> cache = new BTreePersistentIndexedCache<String, Integer>(backingCache, serializer);
+
+        assertNull(cache.get("key_1"));
+        cache.put("key_1", 99);
+
+        RandomAccessFile file = new RandomAccessFile(testFile, "rw");
+        file.setLength(file.length() - 10);
+
+        cache.reset();
+
+        assertNull(cache.get("key_1"));
+        cache.verify();
+    }
+
+    @Test
+    public void canUseFileAsKey() {
+
+        BTreePersistentIndexedCache<File, Integer> cache = new BTreePersistentIndexedCache<File, Integer>(backingCache, serializer);
+
+        cache.put(new File("file"), 1);
+        cache.put(new File("dir/file"), 2);
+        cache.put(new File("File"), 3);
+
+        assertThat(cache.get(new File("file")), equalTo(1));
+        assertThat(cache.get(new File("dir/file")), equalTo(2));
+        assertThat(cache.get(new File("File")), equalTo(3));
+    }
+
+    private void checkAdds(Integer... values) {
+        checkAdds(Arrays.asList(values));
+    }
+
+    private Map<String, Integer> checkAdds(Iterable<Integer> values) {
+        Map<String, Integer> added = new LinkedHashMap<String, Integer>();
+
+        for (Integer value : values) {
+            String key = String.format("key_%d", value);
+            cache.put(key, value);
+            added.put(String.format("key_%d", value), value);
+        }
+
+        for (Map.Entry<String, Integer> entry : added.entrySet()) {
+            assertThat(cache.get(entry.getKey()), equalTo(entry.getValue()));
+        }
+
+        cache.reset();
+
+        for (Map.Entry<String, Integer> entry : added.entrySet()) {
+            assertThat(cache.get(entry.getKey()), equalTo(entry.getValue()));
+        }
+
+        return added;
+    }
+
+    private void checkAddsAndRemoves(Integer... values) {
+        checkAddsAndRemoves(null, values);
+    }
+
+    private void checkAddsAndRemoves(Comparator<Integer> comparator, Integer... values) {
+        checkAddsAndRemoves(comparator, Arrays.asList(values));
+    }
+
+    private void checkAddsAndRemoves(Comparator<Integer> comparator, Collection<Integer> values) {
+        checkAdds(values);
+
+        List<Integer> deleteValues = new ArrayList<Integer>(values);
+        Collections.sort(deleteValues, comparator);
+        for (Integer value : deleteValues) {
+            String key = String.format("key_%d", value);
+            assertThat(cache.get(key), notNullValue());
+            cache.remove(key);
+            assertThat(cache.get(key), nullValue());
+        }
+
+        cache.reset();
+        cache.verify();
+
+        for (Integer value : deleteValues) {
+            String key = String.format("key_%d", value);
+            assertThat(cache.get(key), nullValue());
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/BuildConfigurerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/BuildConfigurerTest.java
new file mode 100644
index 0000000..2e17901
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/BuildConfigurerTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.configuration;
+
+import org.gradle.api.Action;
+import org.gradle.api.Project;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.util.HelperUtil;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(org.jmock.integration.junit4.JMock.class)
+public class BuildConfigurerTest {
+    BuildConfigurer buildConfigurer;
+    ProjectDependencies2TaskResolver projectDependencies2TasksResolver;
+    ProjectInternal rootProject;
+    boolean evaluatedCalled;
+    boolean resolveCalled;
+    SortedMap expectedTasksMap;
+    boolean expectedRecursive;
+
+    JUnit4Mockery context = new JUnit4Mockery();
+
+    @Before
+    public void setUp()  {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        projectDependencies2TasksResolver = context.mock(ProjectDependencies2TaskResolver.class);
+        buildConfigurer = new BuildConfigurer(projectDependencies2TasksResolver);
+        resolveCalled = false;
+        expectedTasksMap = new TreeMap();
+        rootProject = HelperUtil.createRootProject();
+        rootProject = context.mock(ProjectInternal.class);
+    }
+
+    private void createExpectations() {
+        final Action<Project> testEvaluateAction = new Action<Project>() {
+            public void execute(Project project) {
+            }
+        };
+        buildConfigurer.setProjectEvaluateAction(testEvaluateAction);
+        context.checking(new Expectations() {{
+            allowing(rootProject).evaluate(); will(returnValue(rootProject));
+            allowing(rootProject).getAllTasks(expectedRecursive); will(returnValue(expectedTasksMap));
+            one(rootProject).allprojects(testEvaluateAction);
+            one(projectDependencies2TasksResolver).resolve(with(same(rootProject)));
+        }});
+    }
+
+    @Test
+    public void testBuildConfigurer() {
+        assert buildConfigurer.getProjectDependencies2TasksResolver() == projectDependencies2TasksResolver;
+    }
+
+    @Test
+    public void testProcess() {
+        createExpectations();
+        buildConfigurer.process(rootProject);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/BuildScriptProcessorTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/BuildScriptProcessorTest.java
new file mode 100644
index 0000000..60ae726
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/BuildScriptProcessorTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.configuration;
+
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ProjectStateInternal;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.util.JUnit4GroovyMockery;
+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;
+
+ at RunWith(JMock.class)
+public class BuildScriptProcessorTest {
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private final ProjectInternal project = context.mock(ProjectInternal.class);
+    private final ScriptSource scriptSource = context.mock(ScriptSource.class);
+    private final ScriptPluginFactory configurerFactory = context.mock(ScriptPluginFactory.class);
+    private final ScriptPlugin scriptPlugin = context.mock(ScriptPlugin.class);
+    private final ProjectStateInternal state = context.mock(ProjectStateInternal.class);
+    private final BuildScriptProcessor evaluator = new BuildScriptProcessor(configurerFactory);
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations() {{
+            allowing(project).getBuildScriptSource();
+            will(returnValue(scriptSource));
+            ignoring(scriptSource);
+        }});
+    }
+
+    @Test
+    public void configuresProjectUsingBuildScript() {
+        context.checking(new Expectations() {{
+            one(configurerFactory).create(scriptSource);
+            will(returnValue(scriptPlugin));
+
+            one(scriptPlugin).apply(project);
+        }});
+
+        evaluator.evaluate(project, state);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/DefaultInitScriptProcessorTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/DefaultInitScriptProcessorTest.java
new file mode 100644
index 0000000..da6730f
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/DefaultInitScriptProcessorTest.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.configuration;
+
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.initialization.InitScript;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+ at RunWith(JMock.class)
+public class DefaultInitScriptProcessorTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+
+    @Test
+    public void testProcess() {
+        final ScriptPluginFactory scriptPluginFactory = context.mock(ScriptPluginFactory.class);
+        final ScriptPlugin configurer = context.mock(ScriptPlugin.class);
+        final ScriptSource initScriptMock = context.mock(ScriptSource.class);
+        final GradleInternal gradleMock = context.mock(GradleInternal.class);
+
+        context.checking(new Expectations() {{
+            one(scriptPluginFactory).create(initScriptMock);
+            will(returnValue(configurer));
+
+            one(configurer).setClasspathClosureName("initscript");
+            one(configurer).setScriptBaseClass(InitScript.class);
+
+            one(configurer).apply(gradleMock);
+        }});
+
+        DefaultInitScriptProcessor processor = new DefaultInitScriptProcessor(scriptPluginFactory);
+        processor.process(initScriptMock, gradleMock);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/DefaultProjectEvaluatorTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/DefaultProjectEvaluatorTest.java
new file mode 100644
index 0000000..0b85c76
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/DefaultProjectEvaluatorTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.configuration;
+
+import org.gradle.api.ProjectEvaluationListener;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ProjectStateInternal;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.hamcrest.Matchers;
+import org.jmock.Expectations;
+import org.jmock.Sequence;
+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 static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class DefaultProjectEvaluatorTest {
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private final ProjectInternal project = context.mock(ProjectInternal.class);
+    private final ProjectEvaluationListener listener = context.mock(ProjectEvaluationListener.class);
+    private final ProjectEvaluator delegate = context.mock(ProjectEvaluator.class, "delegate");
+    private final ProjectStateInternal state = context.mock(ProjectStateInternal.class);
+    private final DefaultProjectEvaluator evaluator = new DefaultProjectEvaluator(delegate);
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations() {{
+            allowing(project).getProjectEvaluationBroadcaster();
+            will(returnValue(listener));
+        }});
+    }
+
+    @Test
+    public void doesNothingWhenProjectHasAlreadyBeenExecuted() {
+        context.checking(new Expectations() {{
+            allowing(state).getExecuted();
+            will(returnValue(true));
+        }});
+
+        evaluator.evaluate(project, state);
+    }
+    
+    @Test
+    public void createsAndExecutesScriptAndNotifiesListener() {
+        context.checking(new Expectations() {{
+            allowing(state).getExecuted();
+            will(returnValue(false));
+
+            Sequence sequence = context.sequence("seq");
+
+            one(listener).beforeEvaluate(project);
+            inSequence(sequence);
+
+            one(state).setExecuting(true);
+            inSequence(sequence);
+
+            one(delegate).evaluate(project, state);
+            inSequence(sequence);
+
+            one(state).setExecuting(false);
+            inSequence(sequence);
+
+            one(state).executed();
+            inSequence(sequence);
+
+            one(listener).afterEvaluate(project, state);
+            inSequence(sequence);
+        }});
+
+        evaluator.evaluate(project, state);
+    }
+
+    @Test
+    public void notifiesListenerOnFailure() {
+        final RuntimeException failure = new RuntimeException();
+
+        context.checking(new Expectations() {{
+            allowing(state).getExecuted();
+            will(returnValue(false));
+
+            Sequence sequence = context.sequence("seq");
+
+            one(listener).beforeEvaluate(project);
+            inSequence(sequence);
+
+            one(state).setExecuting(true);
+            inSequence(sequence);
+
+            one(delegate).evaluate(project, state);
+            will(throwException(failure));
+            inSequence(sequence);
+
+            one(state).setExecuting(false);
+            inSequence(sequence);
+
+            one(state).executed();
+            inSequence(sequence);
+            
+            one(listener).afterEvaluate(project, state);
+            inSequence(sequence);
+        }});
+
+        try {
+            evaluator.evaluate(project, state);
+            fail();
+        } catch (RuntimeException e) {
+            assertThat(e, Matchers.sameInstance(failure));
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.java
new file mode 100644
index 0000000..4cb4870
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/DefaultScriptPluginFactoryTest.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.configuration;
+
+import org.gradle.api.internal.artifacts.dsl.ClasspathScriptTransformer;
+import org.gradle.api.internal.initialization.ScriptHandlerFactory;
+import org.gradle.api.internal.initialization.ScriptHandlerInternal;
+import org.gradle.api.internal.project.ServiceRegistry;
+import org.gradle.groovy.scripts.*;
+import org.gradle.logging.LoggingManagerFactory;
+import org.gradle.logging.LoggingManagerInternal;
+import org.jmock.Expectations;
+import org.jmock.Sequence;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import static org.hamcrest.Matchers.*;
+
+ at RunWith(JMock.class)
+public class DefaultScriptPluginFactoryTest {
+    private final JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+    private final ScriptCompilerFactory scriptCompilerFactoryMock = context.mock(ScriptCompilerFactory.class);
+    private final ImportsReader importsReaderMock = context.mock(ImportsReader.class);
+    private final ScriptCompiler scriptCompilerMock = context.mock(ScriptCompiler.class);
+    private final ScriptSource scriptSourceMock = context.mock(ScriptSource.class);
+    private final ScriptRunner scriptRunnerMock = context.mock(ScriptRunner.class, "scriptRunner");
+    private final BasicScript scriptMock = context.mock(BasicScript.class);
+    private final URLClassLoader parentClassLoader = new URLClassLoader(new URL[0]);
+    private final URLClassLoader scriptClassLoader = new URLClassLoader(new URL[0]);
+    private final ScriptHandlerFactory scriptHandlerFactoryMock = context.mock(ScriptHandlerFactory.class);
+    private final ScriptHandlerInternal scriptHandlerMock = context.mock(ScriptHandlerInternal.class);
+    private final ScriptRunner classPathScriptRunnerMock = context.mock(ScriptRunner.class, "classpathScriptRunner");
+    private final BasicScript classPathScriptMock = context.mock(BasicScript.class, "classpathScript");
+    private final LoggingManagerFactory loggingManagerFactoryMock = context.mock(LoggingManagerFactory.class);
+    private final DefaultScriptPluginFactory factory = new DefaultScriptPluginFactory(scriptCompilerFactoryMock, importsReaderMock, scriptHandlerFactoryMock, parentClassLoader, loggingManagerFactoryMock);
+
+    @Test
+    public void configuresATargetObjectUsingScript() {
+        final Object target = new Object();
+
+        context.checking(new Expectations() {{
+            Sequence sequence = context.sequence("seq");
+            ScriptSource sourceWithImportsMock = context.mock(ScriptSource.class, "imports");
+            LoggingManagerInternal loggingManagerMock = context.mock(LoggingManagerInternal.class);
+
+            one(loggingManagerFactoryMock).create();
+            will(returnValue(loggingManagerMock));
+
+            one(importsReaderMock).withImports(scriptSourceMock);
+            will(returnValue(sourceWithImportsMock));
+
+            one(scriptCompilerFactoryMock).createCompiler(sourceWithImportsMock);
+            will(returnValue(scriptCompilerMock));
+
+            one(scriptHandlerFactoryMock).create(sourceWithImportsMock, parentClassLoader);
+            will(returnValue(scriptHandlerMock));
+
+            allowing(scriptHandlerMock).getClassLoader();
+            will(returnValue(scriptClassLoader));
+
+            one(scriptCompilerMock).setClassloader(scriptClassLoader);
+            inSequence(sequence);
+
+            one(scriptCompilerMock).setTransformer(with(any(ClasspathScriptTransformer.class)));
+            inSequence(sequence);
+
+            one(scriptCompilerMock).compile(DefaultScript.class);
+            will(returnValue(classPathScriptRunnerMock));
+
+            allowing(classPathScriptRunnerMock).getScript();
+            will(returnValue(classPathScriptMock));
+
+            one(classPathScriptMock).init(with(sameInstance(target)), with(notNullValue(ServiceRegistry.class)));
+            inSequence(sequence);
+
+            one(classPathScriptRunnerMock).run();
+            inSequence(sequence);
+
+            one(scriptHandlerMock).updateClassPath();
+            inSequence(sequence);
+            
+            one(scriptCompilerMock).setTransformer(with(notNullValue(Transformer.class)));
+            inSequence(sequence);
+            
+            one(scriptCompilerMock).compile(DefaultScript.class);
+            will(returnValue(scriptRunnerMock));
+            inSequence(sequence);
+
+            allowing(scriptRunnerMock).getScript();
+            will(returnValue(scriptMock));
+
+            one(scriptMock).init(with(sameInstance(target)), with(notNullValue(ServiceRegistry.class)));
+            inSequence(sequence);
+
+            one(scriptRunnerMock).run();
+            inSequence(sequence);
+        }});
+
+        ScriptPlugin configurer = factory.create(scriptSourceMock);
+        configurer.apply(target);
+    }
+
+    @Test
+    public void configuresAScriptAwareObjectUsingScript() {
+        final ScriptAware target = context.mock(ScriptAware.class);
+
+        context.checking(new Expectations() {{
+            Sequence sequence = context.sequence("seq");
+            ScriptSource sourceWithImportsMock = context.mock(ScriptSource.class, "imports");
+            LoggingManagerInternal loggingManagerMock = context.mock(LoggingManagerInternal.class);
+
+            one(loggingManagerFactoryMock).create();
+            will(returnValue(loggingManagerMock));
+
+            one(importsReaderMock).withImports(scriptSourceMock);
+            will(returnValue(sourceWithImportsMock));
+
+            one(scriptCompilerFactoryMock).createCompiler(sourceWithImportsMock);
+            will(returnValue(scriptCompilerMock));
+
+            allowing(target).beforeCompile(with(notNullValue(ScriptPlugin.class)));
+
+            one(scriptHandlerFactoryMock).create(sourceWithImportsMock, parentClassLoader);
+            will(returnValue(scriptHandlerMock));
+            
+            allowing(scriptHandlerMock).getClassLoader();
+            will(returnValue(scriptClassLoader));
+
+            one(scriptCompilerMock).setClassloader(scriptClassLoader);
+            inSequence(sequence);
+
+            one(scriptCompilerMock).setTransformer(with(any(ClasspathScriptTransformer.class)));
+            inSequence(sequence);
+
+            one(scriptCompilerMock).compile(DefaultScript.class);
+            will(returnValue(classPathScriptRunnerMock));
+
+            allowing(classPathScriptRunnerMock).getScript();
+            will(returnValue(classPathScriptMock));
+
+            one(classPathScriptMock).init(with(sameInstance(target)), with(notNullValue(ServiceRegistry.class)));
+            inSequence(sequence);
+
+            one(classPathScriptRunnerMock).run();
+            inSequence(sequence);
+
+            one(scriptHandlerMock).updateClassPath();
+            inSequence(sequence);
+
+            one(scriptCompilerMock).setTransformer(with(notNullValue(Transformer.class)));
+            inSequence(sequence);
+
+            one(scriptCompilerMock).compile(DefaultScript.class);
+            will(returnValue(scriptRunnerMock));
+            inSequence(sequence);
+
+            allowing(scriptRunnerMock).getScript();
+            will(returnValue(scriptMock));
+
+            one(scriptMock).init(with(sameInstance(target)), with(notNullValue(ServiceRegistry.class)));
+            inSequence(sequence);
+
+            one(target).afterCompile(with(notNullValue(ScriptPlugin.class)), with(sameInstance(scriptMock)));
+            inSequence(sequence);
+
+            one(scriptRunnerMock).run();
+            inSequence(sequence);
+        }});
+
+        ScriptPlugin configurer = factory.create(scriptSourceMock);
+        configurer.apply(target);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/ImportsReaderTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/ImportsReaderTest.groovy
new file mode 100644
index 0000000..7ca7732
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/ImportsReaderTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * 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.configuration
+
+import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.util.Resources
+import org.junit.Rule
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+class ImportsReaderTest {
+    @Rule public Resources resources = new Resources()
+    ImportsReader testObj = new ImportsReader()
+
+    @Test public void testReadImportsFromResource() {
+        String result = testObj.getImports()
+        assertEquals(resources.getResource('default-imports.txt').text, result)
+    }
+
+    @Test public void testCreatesScriptSource() {
+        ScriptSource source = [:] as ScriptSource
+        ScriptSource importsSource = testObj.withImports(source)
+        assertThat(importsSource, instanceOf(ImportsScriptSource.class))
+        assertThat(importsSource.source, sameInstance(source))
+        assertThat(importsSource.importsReader, sameInstance(testObj))
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/ImportsScriptSourceTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/ImportsScriptSourceTest.java
new file mode 100644
index 0000000..1fe9fac
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/ImportsScriptSourceTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.configuration;
+
+import org.gradle.api.internal.resource.Resource;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(org.jmock.integration.junit4.JMock.class)
+public class ImportsScriptSourceTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private ScriptSource backingSource;
+    private ImportsReader importsReader;
+    private ImportsScriptSource source;
+    private Resource resource;
+
+    @Before
+    public void setUp() {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        backingSource = context.mock(ScriptSource.class);
+        importsReader = context.mock(ImportsReader.class);
+        resource = context.mock(Resource.class);
+        source = new ImportsScriptSource(backingSource, importsReader);
+    }
+    
+    @Test
+    public void prependsImportsToScriptText() {
+        context.checking(new Expectations() {{
+            one(backingSource).getResource();
+            will(returnValue(resource));
+
+            one(resource).getText();
+            will(returnValue("<content>"));
+
+            one(importsReader).getImports();
+            will(returnValue("<imports>"));
+        }});
+
+        assertThat(source.getResource().getText(), equalTo("<content>\n<imports>"));
+    }
+
+    @Test
+    public void doesNotPrependImportsWhenScriptHasNoText() {
+        context.checking(new Expectations(){{
+            one(backingSource).getResource();
+            will(returnValue(resource));
+
+            one(resource).getText();
+            will(returnValue(""));
+        }});
+
+        assertThat(source.getResource().getText(), equalTo(""));
+    }
+
+    @Test
+    public void delegatesAllOtherMethodsToBackingScriptSource() {
+        context.checking(new Expectations(){{
+            one(backingSource).getClassName();
+            will(returnValue("classname"));
+
+            one(backingSource).getDisplayName();
+            will(returnValue("description"));
+        }});
+
+        assertThat(source.getClassName(), equalTo("classname"));
+        assertThat(source.getDisplayName(), equalTo("description"));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/ProjectDependencies2TaskResolverTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/ProjectDependencies2TaskResolverTest.groovy
new file mode 100644
index 0000000..da2caef
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/configuration/ProjectDependencies2TaskResolverTest.groovy
@@ -0,0 +1,50 @@
+/*
+ * 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.configuration
+
+import org.gradle.util.HelperUtil
+import static org.junit.Assert.*
+import static org.hamcrest.Matchers.*
+import org.junit.Before
+import org.junit.Test
+import org.gradle.api.Task
+import org.gradle.api.Project
+
+/**
+ * @author Hans Dockter
+ */
+class ProjectDependencies2TaskResolverTest {
+    Project root
+    Project child
+    Task rootTask
+    Task childTask
+    ProjectDependencies2TaskResolver resolver
+
+    @Before public void setUp()  {
+        resolver = new ProjectDependencies2TaskResolver()
+        root = HelperUtil.createRootProject()
+        child = HelperUtil.createChildProject(root, "child")
+        rootTask = root.tasks.add('compile')
+        childTask = child.tasks.add('compile')
+    }
+
+    @Test public void testResolve() {
+        child.dependsOn(root.path, false)
+        resolver.resolve(root)
+        assertThat(childTask.taskDependencies.getDependencies(childTask), equalTo([rootTask] as Set))
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/execution/BuiltInTaskBuildExecuterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/execution/BuiltInTaskBuildExecuterTest.java
new file mode 100644
index 0000000..e718ee8
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/execution/BuiltInTaskBuildExecuterTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.execution;
+
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.project.ServiceRegistryFactory;
+import org.gradle.api.internal.project.taskfactory.ITaskFactory;
+import org.gradle.api.tasks.diagnostics.AbstractReportTask;
+import org.gradle.api.tasks.diagnostics.TaskReportTask;
+import org.gradle.util.GUtil;
+import org.gradle.util.HelperUtil;
+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.Collections;
+import java.util.Set;
+
+import static org.gradle.util.WrapUtil.toSet;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertThat;
+
+ at RunWith(JMock.class)
+public class BuiltInTaskBuildExecuterTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final GradleInternal gradle = context.mock(GradleInternal.class);
+    private final ProjectInternal project = HelperUtil.createRootProject();
+    private final TaskGraphExecuter taskExecuter = context.mock(TaskGraphExecuter.class);
+    private final ITaskFactory taskFactory = context.mock(ITaskFactory.class);
+    private final BuiltInTaskBuildExecuter executer = new TestTaskBuildExecuter(null);
+    private final ServiceRegistryFactory serviceRegistryFactory = context.mock(ServiceRegistryFactory.class);
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations() {{
+            allowing(gradle).getDefaultProject();
+            will(returnValue(project));
+            allowing(gradle).getTaskGraph();
+            will(returnValue(taskExecuter));
+        }});
+    }
+
+    @Test
+    public void executesReportTask() {
+        expectTaskCreated();
+
+        executer.select(gradle);
+        assertThat(executer.getTask(), instanceOf(TaskReportTask.class));
+        assertThat(executer.getDisplayName(), equalTo("task list"));
+
+        expectTaskExecuted();
+
+        executer.execute();
+    }
+
+    @Test
+    public void executesAgainstDefaultProjectIfPathEmpty() {
+        expectTaskCreated();
+
+        executer.select(gradle);
+
+        assertThat(executer.getTask().getProjects(), equalTo(toSet((Project) gradle.getDefaultProject())));
+    }
+
+    @Test
+    public void executesAgainstSingleProjectSpecifiedByPath() {
+        String somePath = ":SomePath";
+        final ProjectInternal rootProject = context.mock(ProjectInternal.class, "rootProject");
+        final ProjectInternal someProject = context.mock(ProjectInternal.class, "someProject");
+
+        context.checking(new Expectations() {{
+            allowing(gradle).getRootProject();
+            will(returnValue(rootProject));
+            allowing(rootProject).project(":SomePath");
+            will(returnValue(someProject));
+        }});
+
+        BuiltInTaskBuildExecuter executer = new TestTaskBuildExecuter(somePath);
+
+        expectTaskCreated();
+        executer.select(gradle);
+
+        assertThat(executer.getTask().getProjects(), equalTo(toSet((Project) someProject)));
+    }
+
+    @Test
+    public void executesAgainstAllProjectWhenWildcardIsUsed() {
+        final ProjectInternal rootProject = context.mock(ProjectInternal.class, "rootProject");
+        final Set<Project> allProjects = toSet(context.mock(Project.class, "someProject"));
+        context.checking(new Expectations() {{
+            allowing(gradle).getRootProject();
+            will(returnValue(rootProject));
+            allowing(rootProject).getAllprojects();
+            will(returnValue(allProjects));
+        }});
+
+        BuiltInTaskBuildExecuter executer = new TestTaskBuildExecuter(BuiltInTaskBuildExecuter.ALL_PROJECTS_WILDCARD);
+
+        expectTaskCreated();
+
+        executer.select(gradle);
+        assertThat(executer.getTask().getProjects(), equalTo(allProjects));
+    }
+
+    private void expectTaskCreated() {
+        context.checking(new Expectations(){{
+            allowing(gradle).getServiceRegistryFactory();
+            will(returnValue(serviceRegistryFactory));
+
+            allowing(serviceRegistryFactory).get(ITaskFactory.class);
+            will(returnValue(taskFactory));
+
+            one(taskFactory).createTask(project, GUtil.map(Task.TASK_NAME, "report", Task.TASK_TYPE, TaskReportTask.class));
+            will(returnValue(HelperUtil.createTask(TaskReportTask.class)));
+        }});
+    }
+
+    private void expectTaskExecuted() {
+        context.checking(new Expectations() {{
+            one(taskExecuter).execute(Collections.singleton(executer.getTask()));
+        }});
+    }
+    
+    private class TestTaskBuildExecuter extends BuiltInTaskBuildExecuter {
+        private TestTaskBuildExecuter(String path) {
+            super(path);
+        }
+
+        @Override
+        protected Class<? extends AbstractReportTask> getTaskType() {
+            return TaskReportTask.class;
+        }
+
+        public String getDisplayName() {
+            return "task list";
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/execution/DefaultBuildExecuterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/execution/DefaultBuildExecuterTest.java
new file mode 100644
index 0000000..434e61e
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/execution/DefaultBuildExecuterTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.execution;
+
+import org.gradle.api.Task;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.specs.Spec;
+import static org.gradle.util.Matchers.*;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.List;
+
+ at RunWith(JMock.class)
+public class DefaultBuildExecuterTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final GradleInternal gradle = context.mock(GradleInternal.class);
+    private final TaskGraphExecuter taskExecuter = context.mock(TaskGraphExecuter.class);
+    private final ProjectInternal project = context.mock(ProjectInternal.class);
+
+    @Before
+    public void setup() {
+        context.checking(new Expectations(){{
+            allowing(gradle).getTaskGraph();
+            will(returnValue(taskExecuter));
+            allowing(gradle).getDefaultProject();
+            will(returnValue(project));
+        }});
+    }
+    
+    @Test
+    public void usesProjectDefaultExecuterWhenNoTaskNamesProvided() {
+        DefaultBuildExecuter executer = new DefaultBuildExecuter(Collections.EMPTY_LIST, Collections.EMPTY_LIST);
+        assertThat(executer.getDelegate(), instanceOf(ProjectDefaultsBuildExecuter.class));
+    }
+
+    @Test
+    public void usesNameResolvingExecuterWhenTaskNamesProvided() {
+        List<String> taskNames = toList("a", "b");
+        DefaultBuildExecuter executer = new DefaultBuildExecuter(taskNames, Collections.EMPTY_LIST);
+        assertThat(executer.getDelegate(), reflectionEquals((Object) new TaskNameResolvingBuildExecuter(taskNames)));
+    }
+
+    @Test
+    public void usesProjectDefaultExecuterAndNameFilterWhenExcludePatternsProvided() {
+        DefaultBuildExecuter executer = new DefaultBuildExecuter(Collections.EMPTY_LIST, toList("b"));
+        assertThat(executer.getDelegate(), instanceOf(ProjectDefaultsBuildExecuter.class));
+
+        checkNameFilterApplied(executer);
+    }
+
+    @Test
+    public void usesNameResolvingExecuterAndNameFilterWhenTaskNamesAndExcludePatternsProvided() {
+        DefaultBuildExecuter executer = new DefaultBuildExecuter(toList("a"), toList("b"));
+        assertThat(executer.getDelegate(), reflectionEquals((Object) new TaskNameResolvingBuildExecuter(toList("a"))));
+
+        checkNameFilterApplied(executer);
+    }
+
+    private void checkNameFilterApplied(DefaultBuildExecuter executer) {
+        final BuildExecuter delegate = context.mock(BuildExecuter.class);
+        executer.setDelegate(delegate);
+
+        context.checking(new Expectations(){{
+            one(project).getTasksByName("b", true);
+            will(returnValue(toSet(context.mock(Task.class))));
+            one(taskExecuter).useFilter(with(notNullValue(Spec.class)));
+            one(delegate).select(gradle);
+        }});
+
+        executer.select(gradle);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/execution/DefaultTaskGraphExecuterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/execution/DefaultTaskGraphExecuterTest.java
new file mode 100644
index 0000000..b4c8df4
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/execution/DefaultTaskGraphExecuterTest.java
@@ -0,0 +1,402 @@
+/*
+ * 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.execution;
+
+import org.gradle.api.Action;
+import org.gradle.api.CircularReferenceException;
+import org.gradle.api.DefaultTask;
+import org.gradle.api.Task;
+import org.gradle.api.execution.TaskExecutionGraphListener;
+import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.internal.TaskInternal;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.TaskExecutionException;
+import org.gradle.api.tasks.TaskState;
+import org.gradle.listener.ListenerBroadcast;
+import org.gradle.listener.ListenerManager;
+import org.gradle.util.TestClosure;
+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.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+import static org.gradle.util.HelperUtil.*;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultTaskGraphExecuterTest {
+
+    JUnit4Mockery context = new JUnit4Mockery();
+    private final ListenerManager listenerManager = context.mock(ListenerManager.class);
+    TaskGraphExecuter taskExecuter;
+    ProjectInternal root;
+    List<Task> executedTasks = new ArrayList<Task>();
+
+    @Before
+    public void setUp() {
+        root = createRootProject();
+        context.checking(new Expectations(){{
+            one(listenerManager).createAnonymousBroadcaster(TaskExecutionGraphListener.class);
+            will(returnValue(new ListenerBroadcast<TaskExecutionGraphListener>(TaskExecutionGraphListener.class)));
+            one(listenerManager).createAnonymousBroadcaster(TaskExecutionListener.class);
+            will(returnValue(new ListenerBroadcast<TaskExecutionListener>(TaskExecutionListener.class)));
+        }});
+        taskExecuter = new DefaultTaskGraphExecuter(listenerManager);
+    }
+
+    @Test
+    public void testExecutesTasksInDependencyOrder() {
+        Task a = createTask("a");
+        Task b = createTask("b", a);
+        Task c = createTask("c", b, a);
+        Task d = createTask("d", c);
+
+        taskExecuter.execute(toList(d));
+
+        assertThat(executedTasks, equalTo(toList(a, b, c, d)));
+    }
+
+    @Test
+    public void testExecutesDependenciesInNameOrder() {
+        Task a = createTask("a");
+        Task b = createTask("b");
+        Task c = createTask("c");
+        Task d = createTask("d", b, a, c);
+
+        taskExecuter.execute(toList(d));
+
+        assertThat(executedTasks, equalTo(toList(a, b, c, d)));
+    }
+
+    @Test
+    public void testExecutesTasksInASingleBatchInNameOrder() {
+        Task a = createTask("a");
+        Task b = createTask("b");
+        Task c = createTask("c");
+
+        taskExecuter.execute(toList(b, c, a));
+
+        assertThat(executedTasks, equalTo(toList(a, b, c)));
+    }
+
+    @Test
+    public void testExecutesBatchesInOrderAdded() {
+        Task a = createTask("a");
+        Task b = createTask("b");
+        Task c = createTask("c");
+        Task d = createTask("d");
+
+        taskExecuter.addTasks(toList(c, b));
+        taskExecuter.addTasks(toList(d, a));
+        taskExecuter.execute();
+
+        assertThat(executedTasks, equalTo(toList(b, c, a, d)));
+    }
+
+    @Test
+    public void testExecutesSharedDependenciesOfBatchesOnceOnly() {
+        Task a = createTask("a");
+        Task b = createTask("b");
+        Task c = createTask("c", a, b);
+        Task d = createTask("d");
+        Task e = createTask("e", b, d);
+
+        taskExecuter.addTasks(toList(c));
+        taskExecuter.addTasks(toList(e));
+        taskExecuter.execute();
+
+        assertThat(executedTasks, equalTo(toList(a, b, c, d, e)));
+    }
+
+    @Test
+    public void testAddTasksAddsDependencies() {
+        Task a = createTask("a");
+        Task b = createTask("b", a);
+        Task c = createTask("c", b, a);
+        Task d = createTask("d", c);
+        taskExecuter.addTasks(toList(d));
+
+        assertTrue(taskExecuter.hasTask(":a"));
+        assertTrue(taskExecuter.hasTask(a));
+        assertTrue(taskExecuter.hasTask(":b"));
+        assertTrue(taskExecuter.hasTask(b));
+        assertTrue(taskExecuter.hasTask(":c"));
+        assertTrue(taskExecuter.hasTask(c));
+        assertTrue(taskExecuter.hasTask(":d"));
+        assertTrue(taskExecuter.hasTask(d));
+        assertThat(taskExecuter.getAllTasks(), equalTo(toList(a, b, c, d)));
+    }
+
+    @Test
+    public void testGetAllTasksReturnsTasksInExecutionOrder() {
+        Task d = createTask("d");
+        Task c = createTask("c");
+        Task b = createTask("b", d, c);
+        Task a = createTask("a", b);
+        taskExecuter.addTasks(toList(a));
+
+        assertThat(taskExecuter.getAllTasks(), equalTo(toList(c, d, b, a)));
+    }
+
+    @Test
+    public void testCannotUseGetterMethodsWhenGraphHasNotBeenCalculated() {
+        try {
+            taskExecuter.hasTask(":a");
+            fail();
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(), equalTo(
+                    "Task information is not available, as this task execution graph has not been populated."));
+        }
+
+        try {
+            taskExecuter.hasTask(createTask("a"));
+            fail();
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(), equalTo(
+                    "Task information is not available, as this task execution graph has not been populated."));
+        }
+
+        try {
+            taskExecuter.getAllTasks();
+            fail();
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(), equalTo(
+                    "Task information is not available, as this task execution graph has not been populated."));
+        }
+    }
+
+    @Test
+    public void testDiscardsTasksAfterExecute() {
+        Task a = createTask("a");
+        Task b = createTask("b", a);
+
+        taskExecuter.addTasks(toList(b));
+        taskExecuter.execute();
+
+        assertFalse(taskExecuter.hasTask(":a"));
+        assertFalse(taskExecuter.hasTask(a));
+        assertTrue(taskExecuter.getAllTasks().isEmpty());
+    }
+
+    @Test
+    public void testCanExecuteMultipleTimes() {
+        Task a = createTask("a");
+        Task b = createTask("b", a);
+        Task c = createTask("c");
+
+        taskExecuter.addTasks(toList(b));
+        taskExecuter.execute();
+        assertThat(executedTasks, equalTo(toList(a, b)));
+
+        executedTasks.clear();
+
+        taskExecuter.addTasks(toList(c));
+
+        assertThat(taskExecuter.getAllTasks(), equalTo(toList(c)));
+
+        taskExecuter.execute();
+
+        assertThat(executedTasks, equalTo(toList(c)));
+    }
+
+    @Test
+    public void testCannotAddTaskWithCircularReference() {
+        Task a = createTask("a");
+        Task b = createTask("b", a);
+        Task c = createTask("c", b);
+        a.dependsOn(c);
+
+        try {
+            taskExecuter.addTasks(toList(c));
+            fail();
+        } catch (CircularReferenceException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void testNotifiesGraphListenerBeforeExecute() {
+        final TaskExecutionGraphListener listener = context.mock(TaskExecutionGraphListener.class);
+        Task a = createTask("a");
+
+        taskExecuter.addTaskExecutionGraphListener(listener);
+        taskExecuter.addTasks(toList(a));
+
+        context.checking(new Expectations() {{
+            one(listener).graphPopulated(taskExecuter);
+        }});
+
+        taskExecuter.execute();
+    }
+
+    @Test
+    public void testExecutesWhenReadyClosureBeforeExecute() {
+        final TestClosure runnable = context.mock(TestClosure.class);
+        Task a = createTask("a");
+
+        taskExecuter.whenReady(toClosure(runnable));
+
+        taskExecuter.addTasks(toList(a));
+
+        context.checking(new Expectations() {{
+            one(runnable).call(taskExecuter);
+        }});
+
+        taskExecuter.execute();
+    }
+
+    @Test
+    public void testNotifiesTaskListenerAsTasksAreExecuted() {
+        final TaskExecutionListener listener = context.mock(TaskExecutionListener.class);
+        final Task a = createTask("a");
+        final Task b = createTask("b");
+
+        taskExecuter.addTaskExecutionListener(listener);
+        taskExecuter.addTasks(toList(a, b));
+
+        context.checking(new Expectations() {{
+            one(listener).beforeExecute(a);
+            one(listener).afterExecute(with(equalTo(a)), with(notNullValue(TaskState.class)));
+            one(listener).beforeExecute(b);
+            one(listener).afterExecute(with(equalTo(b)), with(notNullValue(TaskState.class)));
+        }});
+
+        taskExecuter.execute();
+    }
+
+    @Test
+    public void testNotifiesTaskListenerWhenTaskFails() {
+        final TaskExecutionListener listener = context.mock(TaskExecutionListener.class);
+        final RuntimeException failure = new RuntimeException();
+        final Task a = createTask("a");
+        a.doLast(new Action<Task>() {
+            public void execute(Task task) {
+                throw failure;
+            }
+        });
+
+        taskExecuter.addTaskExecutionListener(listener);
+        taskExecuter.addTasks(toList(a));
+
+        context.checking(new Expectations() {{
+            one(listener).beforeExecute(a);
+            one(listener).afterExecute(with(sameInstance(a)), with(notNullValue(TaskState.class)));
+        }});
+
+        try {
+            taskExecuter.execute();
+            fail();
+        } catch (TaskExecutionException e) {
+            assertThat(e.getCause(), sameInstance((Throwable) failure));
+        }
+    }
+
+    @Test
+    public void testNotifiesBeforeTaskClosureAsTasksAreExecuted() {
+        final TestClosure runnable = context.mock(TestClosure.class);
+        final Task a = createTask("a");
+        final Task b = createTask("b");
+
+        taskExecuter.beforeTask(toClosure(runnable));
+
+        taskExecuter.addTasks(toList(a, b));
+
+        context.checking(new Expectations() {{
+            one(runnable).call(a);
+            one(runnable).call(b);
+        }});
+
+        taskExecuter.execute();
+    }
+
+    @Test
+    public void testNotifiesAfterTaskClosureAsTasksAreExecuted() {
+        final TestClosure runnable = context.mock(TestClosure.class);
+        final Task a = createTask("a");
+        final Task b = createTask("b");
+
+        taskExecuter.afterTask(toClosure(runnable));
+
+        taskExecuter.addTasks(toList(a, b));
+
+        context.checking(new Expectations() {{
+            one(runnable).call(a);
+            one(runnable).call(b);
+        }});
+
+        taskExecuter.execute();
+    }
+
+    @Test
+    public void doesNotAddFilteredTasks() {
+        final Task a = createTask("a", createTask("a-dep"));
+        Task b = createTask("b");
+        Spec<Task> spec = new Spec<Task>() {
+            public boolean isSatisfiedBy(Task element) {
+                return element != a;
+            }
+        };
+
+        taskExecuter.useFilter(spec);
+        taskExecuter.addTasks(toList(a, b));
+        assertThat(taskExecuter.getAllTasks(), equalTo(toList(b)));
+    }
+
+    @Test
+    public void doesNotAddFilteredDependencies() {
+        final Task a = createTask("a", createTask("a-dep"));
+        Task b = createTask("b");
+        Task c = createTask("c", a, b);
+        Spec<Task> spec = new Spec<Task>() {
+            public boolean isSatisfiedBy(Task element) {
+                return element != a;
+            }
+        };
+
+        taskExecuter.useFilter(spec);
+        taskExecuter.addTasks(toList(c));
+        assertThat(taskExecuter.getAllTasks(), equalTo(toList(b, c)));
+    }
+
+    private Task createTask(String name, final Task... dependsOn) {
+        final TaskInternal task = AbstractTask.injectIntoNewInstance(root, name, new Callable<TaskInternal>() {
+            public TaskInternal call() throws Exception {
+                return new DefaultTask();
+            }
+        });
+        task.dependsOn((Object[]) dependsOn);
+        task.doFirst(new Action<Task>() {
+            public void execute(Task task) {
+                executedTasks.add(task);
+            }
+        });
+        return task;
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/execution/DependencyReportBuildExecuterTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/execution/DependencyReportBuildExecuterTest.groovy
new file mode 100644
index 0000000..6f05853
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/execution/DependencyReportBuildExecuterTest.groovy
@@ -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.execution
+
+import org.gradle.api.tasks.diagnostics.DependencyReportTask
+import spock.lang.Specification
+
+class DependencyReportBuildExecuterTest extends Specification {
+    private final DependencyReportBuildExecuter executer = new DependencyReportBuildExecuter("path")
+    
+    def constructsADependencyReportTask() {
+        expect:
+        executer.taskType == DependencyReportTask
+        executer.displayName == 'dependency list'
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/execution/DryRunBuildExecuterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/execution/DryRunBuildExecuterTest.java
new file mode 100644
index 0000000..0dca12b
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/execution/DryRunBuildExecuterTest.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.execution;
+
+import org.gradle.api.Task;
+import org.gradle.api.internal.GradleInternal;
+import static org.gradle.util.WrapUtil.*;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+ at RunWith(JMock.class)
+public class DryRunBuildExecuterTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final BuildExecuter delegate = context.mock(BuildExecuter.class);
+    private final GradleInternal gradle = context.mock(GradleInternal.class);
+    private final TaskGraphExecuter taskExecuter = context.mock(TaskGraphExecuter.class);
+    private final DryRunBuildExecuter executer = new DryRunBuildExecuter(delegate);
+
+    @Test
+    public void disablesAllSelectedTasksBeforeExecution() {
+        final Task task1 = context.mock(Task.class, "task1");
+        final Task task2 = context.mock(Task.class, "task2");
+
+        context.checking(new Expectations() {{
+            allowing(gradle).getTaskGraph();
+            will(returnValue(taskExecuter));
+
+            one(delegate).select(gradle);
+        }});
+
+        executer.select(gradle);
+
+        context.checking(new Expectations() {{
+            one(taskExecuter).getAllTasks();
+            will(returnValue(toList(task1, task2)));
+
+            one(task1).setEnabled(false);
+            one(task2).setEnabled(false);
+
+            one(delegate).execute();
+        }});
+
+        executer.execute();
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/execution/ProjectDefaultsBuildExecuterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/execution/ProjectDefaultsBuildExecuterTest.java
new file mode 100644
index 0000000..1bf0acc
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/execution/ProjectDefaultsBuildExecuterTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 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.execution;
+
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.util.Matchers;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+ at RunWith (org.jmock.integration.junit4.JMock.class)
+public class ProjectDefaultsBuildExecuterTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final ProjectInternal project = context.mock(ProjectInternal.class, "[project]");
+    private final GradleInternal gradle = context.mock(GradleInternal.class);
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations(){{
+            allowing(gradle).getDefaultProject();
+            will(returnValue(project));
+        }});
+    }
+    
+    @Test public void usesProjectDefaultTasksFromProject() {
+        context.checking(new Expectations() {{
+            one(project).getDefaultTasks();
+            will(returnValue(toList("a", "b")));
+        }});
+
+        TestProjectDefaultsBuildExecuter executer = new TestProjectDefaultsBuildExecuter();
+        executer.select(gradle);
+        assertThat(executer.actualDelegate, Matchers.reflectionEquals((Object) new TaskNameResolvingBuildExecuter(toList("a", "b"))));
+    }
+
+    @Test public void createsDescription() {
+        context.checking(new Expectations() {{
+            one(project).getDefaultTasks();
+            will(returnValue(toList("a", "b")));
+        }});
+
+        TestProjectDefaultsBuildExecuter executer = new TestProjectDefaultsBuildExecuter();
+        executer.select(gradle);
+        assertThat(executer.getDisplayName(), equalTo("project default tasks 'a', 'b'"));
+    }
+
+    @Test public void failsWhenNoProjectDefaultTasksSpecified() {
+        context.checking(new Expectations() {{
+            one(project).getDefaultTasks();
+            will(returnValue(toList()));
+        }});
+
+        BuildExecuter executer = new ProjectDefaultsBuildExecuter();
+        try {
+            executer.select(gradle);
+            fail();
+        } catch (TaskSelectionException e) {
+            assertThat(e.getMessage(), equalTo("No tasks have been specified and [project] has not defined any default tasks."));
+        }
+    }
+
+    private static class TestProjectDefaultsBuildExecuter extends ProjectDefaultsBuildExecuter {
+        private BuildExecuter actualDelegate;
+
+        @Override
+        protected void setDelegate(BuildExecuter delegate) {
+            actualDelegate = delegate;
+            super.setDelegate(new BuildExecuter() {
+                public void select(GradleInternal gradle) {
+                }
+
+                public String getDisplayName() {
+                    return null;
+                }
+
+                public void execute() {
+                }
+            });
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/execution/PropertyReportBuildExecuterTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/execution/PropertyReportBuildExecuterTest.groovy
new file mode 100644
index 0000000..74196cd
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/execution/PropertyReportBuildExecuterTest.groovy
@@ -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.execution
+
+import org.gradle.api.tasks.diagnostics.PropertyReportTask
+import spock.lang.Specification
+
+class PropertyReportBuildExecuterTest extends Specification {
+    private final PropertyReportBuildExecuter executer = new PropertyReportBuildExecuter("path")
+    
+    def constructsAPropertyReportTask() {
+        expect:
+        executer.taskType == PropertyReportTask
+        executer.displayName == 'property list'
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/execution/TaskNameResolvingBuildExecuterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/execution/TaskNameResolvingBuildExecuterTest.java
new file mode 100644
index 0000000..defc675
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/execution/TaskNameResolvingBuildExecuterTest.java
@@ -0,0 +1,359 @@
+/*
+ * 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.execution;
+
+import org.gradle.api.Task;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.TaskContainerInternal;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import org.gradle.util.GUtil;
+import org.jmock.Expectations;
+import org.jmock.Sequence;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashSet;
+import java.util.Set;
+
+ at RunWith (org.jmock.integration.junit4.JMock.class)
+public class TaskNameResolvingBuildExecuterTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final ProjectInternal project = context.mock(ProjectInternal.class, "[project]");
+    private final ProjectInternal otherProject = context.mock(ProjectInternal.class, "[otherProject]");
+    private final GradleInternal gradle = context.mock(GradleInternal.class);
+    private final TaskContainerInternal taskContainer = context.mock(TaskContainerInternal.class, "[projectTasks]");
+    private final TaskContainerInternal otherProjectTaskContainer = context.mock(TaskContainerInternal.class, "[otherProjectTasks]");
+    private final TaskGraphExecuter taskExecuter = context.mock(TaskGraphExecuter.class);
+    private int counter;
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations(){{
+            allowing(gradle).getDefaultProject();
+            will(returnValue(project));
+            allowing(gradle).getTaskGraph();
+            will(returnValue(taskExecuter));
+            allowing(project).getTasks();
+            will(returnValue(taskContainer));
+            allowing(project).getAllprojects();
+            will(returnValue(toSet(project, otherProject)));
+            allowing(otherProject).getTasks();
+            will(returnValue(otherProjectTaskContainer));
+        }});
+    }
+
+    @Test
+    public void selectsAllTasksWithTheProvidedNameInCurrentProjectAndSubprojects() {
+        final Task task1 = task("name");
+        final Task task2 = task("name");
+
+        context.checking(new Expectations() {{
+            one(project).getTasksByName("name", true);
+            will(returnValue(toSet(task1, task2)));
+            one(taskExecuter).addTasks(toSet(task1, task2));
+        }});
+
+        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("name"));
+        executer.select(gradle);
+        assertThat(executer.getDisplayName(), equalTo("primary task 'name'"));
+    }
+
+    @Test
+    public void usesCamelCaseAbbreviationToSelectTasksWhenNoExactMatch() {
+        assertMatches("soTaWN", "someTaskWithName", "saTaWN");
+        assertMatches("t1", "task1", "Task1", "T1", "t2");
+        assertMatches("t1", "t1extra");
+        assertMatches("t1", "t12");
+        assertMatches("t1", "task1extra", "task2extra");
+        assertMatches("ABC", "AbcBbcCdc", "abc");
+        assertMatches("s-t", "some-task");
+        assertMatches("s t", "some task");
+        assertMatches("s.t", "some.task");
+        assertMatches("a\\De(", "abc\\Def(", "a\\Df(");
+    }
+
+    private void assertMatches(final String pattern, String matches, String... otherNames) {
+        final Task task1 = task(matches);
+        final Task task2 = task(matches);
+        final Set<Task> tasks = new HashSet<Task>();
+        tasks.add(task2);
+        for (String name : otherNames) {
+            tasks.add(task(name));
+        }
+        tasks.add(task("."));
+        tasks.add(task("other"));
+
+        context.checking(new Expectations() {{
+            one(project).getTasksByName(pattern, true);
+            will(returnValue(toSet()));
+            one(taskContainer).getAll();
+            will(returnValue(toSet(task1)));
+            one(otherProjectTaskContainer).getAll();
+            will(returnValue(tasks));
+            one(taskExecuter).addTasks(toSet(task1, task2));
+        }});
+
+        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList(pattern));
+        executer.select(gradle);
+        assertThat(executer.getDisplayName(), equalTo(String.format("primary task '%s'", matches)));
+    }
+    
+    @Test
+    public void selectsTaskWithMatchingRelativePath() {
+        final Task task1 = task("b");
+
+        context.checking(new Expectations(){{
+            one(project).getChildProjects();
+            will(returnValue(toMap("a", otherProject)));
+            one(otherProjectTaskContainer).findByName("b");
+            will(returnValue(task1));
+            one(taskExecuter).addTasks(toSet(task1));
+        }});
+
+        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("a:b"));
+        executer.select(gradle);
+        assertThat(executer.getDisplayName(), equalTo("primary task 'a:b'"));
+    }
+
+    @Test
+    public void selectsTaskWithMatchingTaskInRootProject() {
+        final Task task1 = task("b");
+
+        context.checking(new Expectations(){{
+            one(project).getRootProject();
+            will(returnValue(otherProject));
+            one(otherProjectTaskContainer).findByName("b");
+            will(returnValue(task1));
+            one(taskExecuter).addTasks(toSet(task1));
+        }});
+
+        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList(":b"));
+        executer.select(gradle);
+        assertThat(executer.getDisplayName(), equalTo("primary task ':b'"));
+    }
+
+    @Test
+    public void selectsTaskWithMatchingAbsolutePath() {
+        final Task task1 = task("b");
+
+        context.checking(new Expectations(){{
+            one(project).getRootProject();
+            will(returnValue(otherProject));
+            one(otherProject).getChildProjects();
+            will(returnValue(toMap("a", otherProject)));
+            one(otherProjectTaskContainer).findByName("b");
+            will(returnValue(task1));
+            one(taskExecuter).addTasks(toSet(task1));
+        }});
+
+        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList(":a:b"));
+        executer.select(gradle);
+        assertThat(executer.getDisplayName(), equalTo("primary task ':a:b'"));
+    }
+
+    @Test
+    public void usesCamelCaseAbbreviationToSelectTasksWhenNoExactMatchAndPathProvided() {
+        final Task task1 = task("someTask");
+        final Task task2 = task("other");
+
+        context.checking(new Expectations(){{
+            one(project).getChildProjects();
+            will(returnValue(toMap("a", otherProject)));
+            one(otherProjectTaskContainer).findByName("soTa");
+            will(returnValue(null));
+            one(otherProjectTaskContainer).getAll();
+            will(returnValue(toSet(task1, task2)));
+            one(taskExecuter).addTasks(toSet(task1));
+            allowing(otherProject).getPath();
+            will(returnValue(":a"));
+        }});
+
+        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("a:soTa"));
+        executer.select(gradle);
+        assertThat(executer.getDisplayName(), equalTo("primary task ':a:someTask'"));
+    }
+
+    @Test
+    public void usesCamelCaseAbbreviationToSelectProjectWhenPathProvided() {
+        final Task task1 = task("someTask");
+        final Task task2 = task("other");
+
+        context.checking(new Expectations(){{
+            one(project).getChildProjects();
+            will(returnValue(toMap("someProject", otherProject)));
+            one(otherProjectTaskContainer).findByName("soTa");
+            will(returnValue(null));
+            one(otherProjectTaskContainer).getAll();
+            will(returnValue(toSet(task1, task2)));
+            one(taskExecuter).addTasks(toSet(task1));
+            allowing(otherProject).getPath();
+            will(returnValue(":someProject"));
+        }});
+
+        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("soPr:soTa"));
+        executer.select(gradle);
+        assertThat(executer.getDisplayName(), equalTo("primary task ':someProject:someTask'"));
+    }
+
+    @Test
+    public void failsWhenProvidedTaskNameIsAmbiguous() {
+        final Task task1 = task("someTask");
+        final Task task2 = task("someTasks");
+
+        context.checking(new Expectations() {{
+            one(project).getTasksByName("soTa", true);
+            will(returnValue(toSet()));
+            one(taskContainer).getAll();
+            will(returnValue(toSet(task1)));
+            one(otherProjectTaskContainer).getAll();
+            will(returnValue(toSet(task2)));
+        }});
+
+        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("soTa"));
+        try {
+            executer.select(gradle);
+            fail();
+        } catch (TaskSelectionException e) {
+            assertThat(e.getMessage(), equalTo("Task 'soTa' is ambiguous in [project]. Candidates are: 'someTask', 'someTasks'."));
+        }
+    }
+
+    @Test
+    public void reportsTyposInTaskName() {
+        final Task task1 = task("someTask");
+        final Task task2 = task("someTasks");
+        final Task task3 = task("sometask");
+        final Task task4 = task("other");
+
+        context.checking(new Expectations() {{
+            one(project).getTasksByName("ssomeTask", true);
+            will(returnValue(toSet()));
+            one(taskContainer).getAll();
+            will(returnValue(toSet(task1, task2)));
+            one(otherProjectTaskContainer).getAll();
+            will(returnValue(toSet(task3, task4)));
+        }});
+
+        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("ssomeTask"));
+        try {
+            executer.select(gradle);
+            fail();
+        } catch (TaskSelectionException e) {
+            assertThat(e.getMessage(), equalTo("Task 'ssomeTask' not found in [project]. Some candidates are: 'someTask', 'someTasks', 'sometask'."));
+        }
+    }
+
+    @Test
+    public void executesAllSelectedTasks() {
+        final Task task1 = task("name");
+        final Task task2 = task("name");
+
+        context.checking(new Expectations() {{
+            one(project).getTasksByName("name", true);
+            will(returnValue(toSet(task1, task2)));
+            one(taskExecuter).addTasks(toSet(task1, task2));
+            one(taskExecuter).execute();
+        }});
+
+        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("name"));
+        executer.select(gradle);
+        executer.execute();
+    }
+    
+    @Test
+    public void treatsEachProvidedNameAsASeparateGroup() {
+        final Task task1 = task("name1");
+        final Task task2 = task("name2");
+
+        context.checking(new Expectations() {{
+            one(project).getChildProjects();
+            will(returnValue(toMap("child", otherProject)));
+            one(otherProjectTaskContainer).findByName("name1");
+            will(returnValue(task1));
+            one(project).getTasksByName("name2", true);
+            will(returnValue(toSet(task2)));
+
+            Sequence sequence = context.sequence("tasks");
+
+            one(taskExecuter).addTasks(toSet(task1));
+            inSequence(sequence);
+
+            one(taskExecuter).addTasks(toSet(task2));
+            inSequence(sequence);
+
+            one(taskExecuter).execute();
+            inSequence(sequence);
+        }});
+
+        TaskNameResolvingBuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("child:name1", "name2"));
+        executer.select(gradle);
+        assertThat(executer.getDisplayName(), equalTo("primary tasks 'child:name1', 'name2'"));
+        executer.execute();
+    }
+
+    @Test
+    public void failsWhenUnknownTaskNameIsProvided() {
+        final Task task1 = task("t1");
+        final Task task2 = task("t2");
+
+        context.checking(new Expectations() {{
+            one(project).getTasksByName("b3", true);
+            will(returnValue(toSet()));
+            one(taskContainer).getAll();
+            will(returnValue(toSet(task1, task2)));
+            one(otherProjectTaskContainer).getAll();
+            will(returnValue(toSet()));
+        }});
+
+        BuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("b3"));
+        try {
+            executer.select(gradle);
+            fail();
+        } catch (TaskSelectionException e) {
+            assertThat(e.getMessage(), equalTo("Task 'b3' not found in [project]."));
+        }
+    }
+
+    @Test
+    public void failsWhenCannotFindProjectInPath() {
+        context.checking(new Expectations() {{
+            one(project).getChildProjects();
+            will(returnValue(GUtil.map("aa", otherProject, "ab", otherProject)));
+        }});
+
+        BuildExecuter executer = new TaskNameResolvingBuildExecuter(toList("a:b", "name2"));
+        try {
+            executer.select(gradle);
+            fail();
+        } catch (TaskSelectionException e) {
+            assertThat(e.getMessage(), equalTo("Project 'a' is ambiguous in [project]. Candidates are: 'aa', 'ab'."));
+        }
+    }
+
+    private Task task(final String name) {
+        final Task task = context.mock(Task.class, "task" + counter++ + "_" + name);
+        context.checking(new Expectations(){{
+            allowing(task).getName();
+            will(returnValue(name));
+        }});
+        return task;
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/execution/TaskReportBuildExecuterTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/execution/TaskReportBuildExecuterTest.groovy
new file mode 100644
index 0000000..e2de13d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/execution/TaskReportBuildExecuterTest.groovy
@@ -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.execution
+
+import spock.lang.Specification
+import org.gradle.api.tasks.diagnostics.TaskReportTask
+
+class TaskReportBuildExecuterTest extends Specification {
+    private final TaskReportBuildExecuter executer = new TaskReportBuildExecuter("path", false)
+    
+    def constructsATaskReportTask() {
+        expect:
+        executer.taskType == TaskReportTask
+        executer.displayName == 'task list'
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/AsmBackedEmptyScriptGeneratorTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/AsmBackedEmptyScriptGeneratorTest.groovy
new file mode 100644
index 0000000..a91b183
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/AsmBackedEmptyScriptGeneratorTest.groovy
@@ -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.groovy.scripts
+
+import spock.lang.Specification
+
+class AsmBackedEmptyScriptGeneratorTest extends Specification {
+    private final AsmBackedEmptyScriptGenerator generator = new AsmBackedEmptyScriptGenerator()
+
+    def generatesEmptyScriptClass() {
+        expect:
+        def cl = generator.generate(groovy.lang.Script.class)
+        def script = cl.newInstance()
+        script instanceof groovy.lang.Script
+        script.run() == null
+    }
+    
+    def cachesScriptClass() {
+        expect:
+        def cl1 = generator.generate(groovy.lang.Script.class)
+        def cl2 = generator.generate(groovy.lang.Script.class)
+        cl1 == cl2
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/CachingScriptCompilationHandlerTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/CachingScriptCompilationHandlerTest.groovy
new file mode 100644
index 0000000..ce25fba
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/CachingScriptCompilationHandlerTest.groovy
@@ -0,0 +1,91 @@
+/*
+ * 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 spock.lang.Specification
+
+class CachingScriptCompilationHandlerTest extends Specification {
+    private final ScriptCompilationHandler target = Mock()
+    private final CachingScriptCompilationHandler handler = new CachingScriptCompilationHandler(target)
+
+    def cachesScriptClassForGivenClassDirAndParentClassLoader() {
+        ScriptSource script1 = scriptSource('script')
+        ScriptSource script2 = scriptSource('script')
+        ClassLoader parentClassLoader = Mock()
+        File cacheDir = new File('cacheDir')
+
+        when:
+        def c1 = handler.loadFromDir(script1, parentClassLoader, cacheDir, Script.class)
+        def c2 = handler.loadFromDir(script2, parentClassLoader, cacheDir, Script.class)
+
+        then:
+        1 * target.loadFromDir(script1, parentClassLoader, cacheDir, Script.class) >> Script.class
+        0 * target._
+    }
+
+    def doesNotCacheForDifferentScriptClass() {
+        ScriptSource script1 = scriptSource('script')
+        ScriptSource script2 = scriptSource('other')
+        ClassLoader parentClassLoader = Mock()
+        File cacheDir = new File('cacheDir')
+
+        when:
+        def c1 = handler.loadFromDir(script1, parentClassLoader, cacheDir, Script.class)
+        def c2 = handler.loadFromDir(script2, parentClassLoader, cacheDir, Script.class)
+
+        then:
+        1 * target.loadFromDir(script1, parentClassLoader, cacheDir, Script.class) >> Script.class
+        1 * target.loadFromDir(script2, parentClassLoader, cacheDir, Script.class) >> Script.class
+    }
+
+    def doesNotCacheForDifferentClassDir() {
+        ScriptSource script1 = scriptSource('script')
+        ScriptSource script2 = scriptSource('script')
+        ClassLoader parentClassLoader = Mock()
+        File cacheDir1 = new File('cacheDir')
+        File cacheDir2 = new File('cacheDir2')
+
+        when:
+        def c1 = handler.loadFromDir(script1, parentClassLoader, cacheDir1, Script.class)
+        def c2 = handler.loadFromDir(script2, parentClassLoader, cacheDir2, Script.class)
+
+        then:
+        1 * target.loadFromDir(script1, parentClassLoader, cacheDir1, Script.class) >> Script.class
+        1 * target.loadFromDir(script2, parentClassLoader, cacheDir2, Script.class) >> Script.class
+    }
+
+    def doesNotCacheForDifferentParentClassLoader() {
+        ScriptSource script1 = scriptSource('script')
+        ScriptSource script2 = scriptSource('script')
+        ClassLoader parentClassLoader1 = Mock()
+        ClassLoader parentClassLoader2 = Mock()
+        File cacheDir = new File('cacheDir')
+
+        when:
+        def c1 = handler.loadFromDir(script1, parentClassLoader1, cacheDir, Script.class)
+        def c2 = handler.loadFromDir(script2, parentClassLoader2, cacheDir, Script.class)
+
+        then:
+        1 * target.loadFromDir(script1, parentClassLoader1, cacheDir, Script.class) >> Script.class
+        1 * target.loadFromDir(script2, parentClassLoader2, cacheDir, Script.class) >> Script.class
+    }
+    
+    def scriptSource(String className = 'script') {
+        ScriptSource script = Mock()
+        _ * script.className >> className
+        script
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/CachingScriptSourceTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/CachingScriptSourceTest.java
new file mode 100644
index 0000000..d6c6f4d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/CachingScriptSourceTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.internal.resource.CachingResource;
+import org.gradle.api.internal.resource.Resource;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class CachingScriptSourceTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final ScriptSource delegate = context.mock(ScriptSource.class);
+
+    @Test
+    public void cachesContentOfSource() {
+        context.checking(new Expectations() {{
+            one(delegate).getResource();
+            will(returnValue(context.mock(Resource.class)));
+        }});
+
+        CachingScriptSource source = new CachingScriptSource(delegate);
+        assertThat(source.getResource(), instanceOf(CachingResource.class));
+        assertThat(source.getResource(), sameInstance(source.getResource()));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptCompilationHandlerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptCompilationHandlerTest.java
new file mode 100644
index 0000000..9cfeef3
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptCompilationHandlerTest.java
@@ -0,0 +1,286 @@
+/*
+ * 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.Script;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.CodeVisitorSupport;
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+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.ScriptCompilationException;
+import org.gradle.api.internal.artifacts.dsl.AbstractScriptTransformer;
+import org.gradle.api.internal.resource.Resource;
+import org.gradle.util.TemporaryFolder;
+import static org.hamcrest.Matchers.*;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.After;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultScriptCompilationHandlerTest {
+
+    static final String TEST_EXPECTED_SYSTEMPROP_VALUE = "somevalue";
+    static final String TEST_EXPECTED_SYSTEMPROP_KEY = "somekey";
+
+    private DefaultScriptCompilationHandler scriptCompilationHandler;
+
+    private File scriptCacheDir;
+    private File cachedFile;
+
+    private ScriptSource scriptSource;
+    private String scriptText;
+    private String scriptClassName;
+    private String scriptFileName;
+
+    private ClassLoader classLoader;
+
+    private Class<? extends Script> expectedScriptClass;
+
+    private JUnit4Mockery context = new JUnit4Mockery();
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    @Before
+    public void setUp() throws IOException, ClassNotFoundException {
+        File testProjectDir = tmpDir.createDir("projectDir");
+        classLoader = getClass().getClassLoader();
+        scriptCompilationHandler = new DefaultScriptCompilationHandler();
+        scriptCacheDir = new File(testProjectDir, "cache");
+        scriptText = "System.setProperty('" + TEST_EXPECTED_SYSTEMPROP_KEY + "', '" + TEST_EXPECTED_SYSTEMPROP_VALUE
+                + "')";
+
+        scriptClassName = "ScriptClassName";
+        scriptFileName = "script-file-name";
+        scriptSource = scriptSource();
+        cachedFile = new File(scriptCacheDir, scriptClassName + ".class");
+        expectedScriptClass = TestBaseScript.class;
+    }
+
+    private ScriptSource scriptSource() {
+        return scriptSource(scriptText);
+    }
+
+    private ScriptSource scriptSource(final String scriptText) {
+        final ScriptSource source = context.mock(ScriptSource.class, scriptText);
+        context.checking(new Expectations(){{
+            Resource resource = context.mock(Resource.class, scriptText + "resource");
+
+            allowing(source).getClassName();
+            will(returnValue(scriptClassName));
+            allowing(source).getFileName();
+            will(returnValue(scriptFileName));
+            allowing(source).getDisplayName();
+            will(returnValue("script-display-name"));
+            allowing(source).getResource();
+            will(returnValue(resource));
+            allowing(resource).getText();
+            will(returnValue(scriptText));
+        }});
+        return source;
+    }
+
+    @After
+    public void tearDown() {
+        System.getProperties().remove(TEST_EXPECTED_SYSTEMPROP_KEY);
+    }
+
+    @Test
+    public void testCompileScriptToDir() throws Exception {
+        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
+
+        checkScriptClassesInCache();
+
+        Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
+                expectedScriptClass).newInstance();
+        evaluateScript(script);
+    }
+
+    @Test
+    public void testCompileScriptToDirWithPackageDeclaration() throws Exception {
+        final ScriptSource scriptSource = scriptSource("package org.gradle.test\n" + scriptText);
+
+        try {
+            scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
+            fail();
+        } catch (UnsupportedOperationException e) {
+            assertThat(e.getMessage(), equalTo("Script-display-name should not contain a package statement."));
+        }
+    }
+
+    @Test
+    public void testCompileScriptToDirWithWhitespaceOnly() throws Exception {
+        final ScriptSource scriptSource = scriptSource("// ignore me\n");
+        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
+
+        checkEmptyScriptInCache();
+
+        Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
+                expectedScriptClass).newInstance();
+        assertThat(script, is(expectedScriptClass));
+    }
+
+    @Test
+    public void testCompileScriptToDirWithEmptyScript() throws Exception {
+        final ScriptSource scriptSource = scriptSource("");
+        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
+
+        checkEmptyScriptInCache();
+
+        Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
+                expectedScriptClass).newInstance();
+        assertThat(script, is(expectedScriptClass));
+    }
+
+    @Test
+    public void testCompileScriptToDirWithClassDefinitionOnlyScript() throws Exception {
+        final ScriptSource scriptSource = scriptSource("class SomeClass {}");
+        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
+
+        checkEmptyScriptInCache();
+
+        Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
+                expectedScriptClass).newInstance();
+        assertThat(script, is(expectedScriptClass));
+    }
+
+    @Test
+    public void testCompileScriptToDirWithMethodOnlyScript() throws Exception {
+        final ScriptSource scriptSource = scriptSource("def method() { println 'hi' }");
+        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
+
+        checkScriptClassesInCache();
+
+        Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
+                expectedScriptClass).newInstance();
+        assertThat(script, is(expectedScriptClass));
+    }
+
+    @Test
+    public void testCompileScriptToDirWithPropertiesOnlyScript() throws Exception {
+        final ScriptSource scriptSource = scriptSource("String a");
+        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, expectedScriptClass);
+
+        checkScriptClassesInCache();
+
+        Script script = scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
+                expectedScriptClass).newInstance();
+        assertThat(script, is(expectedScriptClass));
+    }
+
+    @Test
+    public void testLoadFromDirWhenNotAssignableToBaseClass() {
+        scriptCompilationHandler.compileToDir(scriptSource, classLoader, scriptCacheDir, null, Script.class);
+        try {
+            scriptCompilationHandler.loadFromDir(scriptSource, classLoader, scriptCacheDir,
+                    expectedScriptClass);
+            fail();
+        } catch (GradleException e) {
+            assertThat(e.getMessage(), equalTo("Could not load compiled classes for script-display-name from cache."));
+            assertThat(e.getCause(), instanceOf(ClassCastException.class));
+        }
+    }
+
+    @Test
+    public void testCompileToDirWithException() {
+        ScriptSource source = new StringScriptSource("script", "\n\nnew HHHHJSJSJ jsj");
+        try {
+            scriptCompilationHandler.compileToDir(source, classLoader, scriptCacheDir, null, expectedScriptClass);
+            fail();
+        } catch (ScriptCompilationException e) {
+            assertThat(e.getScriptSource(), sameInstance(source));
+            assertThat(e.getLineNumber(), equalTo(3));
+        }
+
+        checkScriptCacheEmpty();
+    }
+
+    @Test
+    public void testCanVisitAndTransformScriptClass() throws Exception {
+        Transformer visitor = new AbstractScriptTransformer() {
+            public String getId() {
+                return "id";
+            }
+
+            protected int getPhase() {
+                return Phases.CANONICALIZATION;
+            }
+
+            @Override
+            public void call(SourceUnit source) throws CompilationFailedException {
+                source.getAST().getStatementBlock().visit(new CodeVisitorSupport() {
+                    @Override
+                    public void visitMethodCallExpression(MethodCallExpression call) {
+                        call.setObjectExpression(new ClassExpression(ClassHelper.make(System.class)));
+                        call.setMethod(new ConstantExpression("setProperty"));
+                        ArgumentListExpression arguments = (ArgumentListExpression) call.getArguments();
+                        arguments.addExpression(new ConstantExpression(TEST_EXPECTED_SYSTEMPROP_KEY));
+                        arguments.addExpression(new ConstantExpression(TEST_EXPECTED_SYSTEMPROP_VALUE));
+                    }
+                });
+            }
+        };
+
+        ScriptSource source = scriptSource("transformMe()");
+        scriptCompilationHandler.compileToDir(source, classLoader, scriptCacheDir, visitor, expectedScriptClass);
+        Script script = scriptCompilationHandler.loadFromDir(source, classLoader, scriptCacheDir, expectedScriptClass).newInstance();
+        evaluateScript(script);
+    }
+
+    private void checkScriptClassesInCache() {
+        assertTrue(scriptCacheDir.isDirectory());
+        assertTrue(cachedFile.isFile());
+        assertFalse(new File(scriptCacheDir, "emptyScript.txt").exists());
+    }
+
+    private void checkEmptyScriptInCache() {
+        assertTrue(scriptCacheDir.isDirectory());
+        assertTrue(new File(scriptCacheDir, "emptyScript.txt").isFile());
+    }
+
+    private void checkScriptCacheEmpty() {
+        assertFalse(scriptCacheDir.exists());
+    }
+
+    private void evaluateScript(Script script) {
+        assertThat(script, instanceOf(expectedScriptClass));
+        assertEquals(script.getClass().getSimpleName(), scriptClassName);
+        System.setProperty(TEST_EXPECTED_SYSTEMPROP_KEY, "not the expected value");
+        script.run();
+        assertEquals(TEST_EXPECTED_SYSTEMPROP_VALUE, System.getProperty(TEST_EXPECTED_SYSTEMPROP_KEY));
+    }
+
+    public abstract static class TestBaseScript extends Script {
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactoryTest.java
new file mode 100644
index 0000000..6845ad7
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptCompilerFactoryTest.java
@@ -0,0 +1,272 @@
+/*
+ * 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.internal.project.ServiceRegistry;
+import org.gradle.api.internal.resource.Resource;
+import org.gradle.logging.StandardOutputCapture;
+import org.gradle.cache.CacheBuilder;
+import org.gradle.cache.CacheRepository;
+import org.gradle.cache.PersistentCache;
+import org.gradle.util.GUtil;
+import org.gradle.util.HashUtil;
+import org.gradle.util.TemporaryFolder;
+import org.gradle.util.TestFile;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Map;
+
+import static org.gradle.util.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultScriptCompilerFactoryTest {
+    static final String TEST_SCRIPT_TEXT = "sometext";
+
+    DefaultScriptCompilerFactory scriptProcessor;
+
+    File cacheDir;
+    File expectedScriptCacheDir;
+    File testScriptFile;
+
+    ClassLoader testClassLoader;
+    ClassLoader originalClassLoader;
+
+    ScriptCompilationHandler scriptCompilationHandlerMock;
+    ScriptRunnerFactory scriptRunnerFactoryMock;
+    CacheRepository cacheRepositoryMock;
+    PersistentCache cacheMock;
+
+    Mockery context = new JUnit4Mockery();
+
+    Class expectedScriptBaseClass = groovy.lang.Script.class;
+    Map<String, Object> expectedCacheProperties;
+
+    ScriptSource source;
+    ScriptSource expectedSource;
+    ScriptRunner expectedScriptRunner;
+    CacheBuilder cacheBuilder;
+
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    @Before
+    public void setUp() {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        scriptCompilationHandlerMock = context.mock(ScriptCompilationHandler.class);
+        scriptRunnerFactoryMock = context.mock(ScriptRunnerFactory.class);
+        cacheRepositoryMock = context.mock(CacheRepository.class);
+        cacheMock = context.mock(PersistentCache.class);
+        testClassLoader = new URLClassLoader(new URL[0]);
+        testScriptFile = new File(tmpDir.getDir(), "script/mybuild.craidle");
+        cacheDir = new File(tmpDir.getDir(), "cache");
+        expectedScriptCacheDir = new TestFile(cacheDir, "Script").createDir();
+        expectedScriptRunner = context.mock(ScriptRunner.class);
+        scriptProcessor = new DefaultScriptCompilerFactory(scriptCompilationHandlerMock, scriptRunnerFactoryMock, cacheRepositoryMock);
+        source = context.mock(ScriptSource.class);
+        cacheBuilder = context.mock(CacheBuilder.class);
+
+        context.checking(new Expectations() {{
+            Resource resource = context.mock(Resource.class);
+
+            allowing(source).getDisplayName();
+            will(returnValue("[script source]"));
+            allowing(source).getClassName();
+            will(returnValue("class-name"));
+            allowing(source).getFileName();
+            will(returnValue("file-name"));
+            allowing(source).getResource();
+            will(returnValue(resource));
+            allowing(resource).getText();
+            will(returnValue(TEST_SCRIPT_TEXT));
+
+            allowing(cacheMock).getBaseDir();
+            will(returnValue(cacheDir));
+        }});
+
+        expectedSource = new CachingScriptSource(source);
+        String expectedHash = HashUtil.createHash(TEST_SCRIPT_TEXT);
+        expectedCacheProperties = GUtil.map("source.filename", "file-name", "source.hash", expectedHash);
+
+        originalClassLoader = Thread.currentThread().getContextClassLoader();
+        Thread.currentThread().setContextClassLoader(testClassLoader);
+    }
+
+    @After
+    public void tearDown() {
+        Thread.currentThread().setContextClassLoader(originalClassLoader);
+    }
+
+    @Test
+    public void testWithSourceFileNotCached() {
+        final Collector<TestScript> collector = collector();
+
+        context.checking(new Expectations() {{
+            one(cacheRepositoryMock).cache("scripts/class-name");
+            will(returnValue(cacheBuilder));
+
+            one(cacheBuilder).withProperties(expectedCacheProperties);
+            will(returnValue(cacheBuilder));
+
+            one(cacheBuilder).open();
+            will(returnValue(cacheMock));
+
+            allowing(cacheMock).isValid();
+            will(returnValue(false));
+
+            one(scriptCompilationHandlerMock).compileToDir(expectedSource, testClassLoader, expectedScriptCacheDir, null,
+                    expectedScriptBaseClass);
+
+            one(cacheMock).markValid();
+
+            one(scriptCompilationHandlerMock).loadFromDir(expectedSource, testClassLoader, expectedScriptCacheDir,
+                    expectedScriptBaseClass);
+            will(returnValue(TestScript.class));
+
+            one(scriptRunnerFactoryMock).create(with(notNullValue(TestScript.class)));
+            will(collectTo(collector).then(returnValue(expectedScriptRunner)));
+        }});
+
+        assertSame(expectedScriptRunner, scriptProcessor.createCompiler(source).compile(expectedScriptBaseClass));
+        assertSame(testClassLoader, collector.get().getContextClassloader());
+        assertEquals(expectedSource, collector.get().getScriptSource());
+    }
+
+    @Test
+    public void testWithCachedSourceFile() {
+        final Collector<TestScript> collector = collector();
+
+        context.checking(new Expectations() {{
+            one(cacheRepositoryMock).cache("scripts/class-name");
+            will(returnValue(cacheBuilder));
+
+            one(cacheBuilder).withProperties(expectedCacheProperties);
+            will(returnValue(cacheBuilder));
+
+            one(cacheBuilder).open();
+            will(returnValue(cacheMock));
+
+            allowing(cacheMock).isValid();
+            will(returnValue(true));
+
+            one(scriptCompilationHandlerMock).loadFromDir(expectedSource, testClassLoader, expectedScriptCacheDir, expectedScriptBaseClass);
+            will(returnValue(TestScript.class));
+
+            one(scriptRunnerFactoryMock).create(with(notNullValue(TestScript.class)));
+            will(collectTo(collector).then(returnValue(expectedScriptRunner)));
+        }});
+
+        assertSame(expectedScriptRunner, scriptProcessor.createCompiler(source).compile(expectedScriptBaseClass));
+        assertSame(testClassLoader, collector.get().getContextClassloader());
+        assertEquals(expectedSource, collector.get().getScriptSource());
+    }
+
+    @Test
+    public void testUsesSuppliedClassLoaderToLoadScript() {
+        final Collector<TestScript> collector = collector();
+        final ClassLoader classLoader = new ClassLoader() {
+        };
+
+        context.checking(new Expectations(){{
+            one(cacheRepositoryMock).cache("scripts/class-name");
+            will(returnValue(cacheBuilder));
+
+            one(cacheBuilder).withProperties(expectedCacheProperties);
+            will(returnValue(cacheBuilder));
+
+            one(cacheBuilder).open();
+            will(returnValue(cacheMock));
+
+            allowing(cacheMock).isValid();
+            will(returnValue(true));
+
+            one(scriptCompilationHandlerMock).loadFromDir(expectedSource, classLoader, expectedScriptCacheDir,
+                    expectedScriptBaseClass);
+            will(returnValue(TestScript.class));
+
+            one(scriptRunnerFactoryMock).create(with(notNullValue(TestScript.class)));
+            will(collectTo(collector).then(returnValue(expectedScriptRunner)));
+        }});
+
+        assertSame(expectedScriptRunner, scriptProcessor.createCompiler(source).setClassloader(classLoader).compile(expectedScriptBaseClass));
+        assertSame(classLoader, collector.get().getContextClassloader());
+    }
+
+    @Test
+    public void testUsesSuppliedTransformerToDetermineCacheDirName() {
+        final Transformer transformer = context.mock(Transformer.class);
+        final File expectedCacheDir = new TestFile(expectedScriptCacheDir.getParentFile(), "transformer_Script").createDir();
+
+        context.checking(new Expectations(){{
+            allowing(transformer).getId();
+            will(returnValue("transformer"));
+
+            one(cacheRepositoryMock).cache("scripts/class-name");
+            will(returnValue(cacheBuilder));
+
+            one(cacheBuilder).withProperties(expectedCacheProperties);
+            will(returnValue(cacheBuilder));
+
+            one(cacheBuilder).open();
+            will(returnValue(cacheMock));
+
+            allowing(cacheMock).isValid();
+            will(returnValue(true));
+
+            one(scriptCompilationHandlerMock).loadFromDir(expectedSource, testClassLoader, expectedCacheDir,
+                    expectedScriptBaseClass);
+            will(returnValue(TestScript.class));
+
+            one(scriptRunnerFactoryMock).create(with(notNullValue(TestScript.class)));
+            will(returnValue(expectedScriptRunner));
+        }});
+
+        assertSame(expectedScriptRunner, scriptProcessor.createCompiler(source).setTransformer(transformer).compile(expectedScriptBaseClass));
+    }
+
+    public static class TestScript extends Script {
+        @Override
+        public StandardOutputCapture getStandardOutputCapture() {
+            return null;
+        }
+
+        @Override
+        public void init(Object target, ServiceRegistry services) {
+        }
+
+        @Override
+        public Object run() {
+            return null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptRunnerFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptRunnerFactoryTest.java
new file mode 100644
index 0000000..92786a0
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptRunnerFactoryTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.GradleScriptException;
+import org.gradle.logging.StandardOutputCapture;
+import org.hamcrest.Description;
+import org.jmock.Expectations;
+import org.jmock.Sequence;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class DefaultScriptRunnerFactoryTest {
+    private final JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+    private final Script scriptMock = context.mock(Script.class, "<script-to-string>");
+    private final StandardOutputCapture standardOutputCaptureMock = context.mock(StandardOutputCapture.class);
+    private final ClassLoader classLoaderDummy = context.mock(ClassLoader.class);
+    private final ScriptSource scriptSourceDummy = context.mock(ScriptSource.class);
+    private final ScriptExecutionListener scriptExecutionListenerMock = context.mock(ScriptExecutionListener.class);
+    private final DefaultScriptRunnerFactory factory = new DefaultScriptRunnerFactory(scriptExecutionListenerMock);
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations() {{
+            allowing(scriptMock).getStandardOutputCapture();
+            will(returnValue(standardOutputCaptureMock));
+            allowing(scriptMock).getScriptSource();
+            will(returnValue(scriptSourceDummy));
+            allowing(scriptMock).getContextClassloader();
+            will(returnValue(classLoaderDummy));
+            ignoring(scriptSourceDummy);
+        }});
+    }
+
+    @Test
+    public void createsScriptRunner() {
+        ScriptRunner<Script> scriptRunner = factory.create(scriptMock);
+        assertThat(scriptRunner.getScript(), sameInstance(scriptMock));
+    }
+
+    @Test
+    public void redirectsStandardOutputAndSetsContextClassLoaderWhenScriptIsRun() {
+        ScriptRunner<Script> scriptRunner = factory.create(scriptMock);
+
+        context.checking(new Expectations() {{
+            Sequence sequence = context.sequence("seq");
+
+            one(scriptExecutionListenerMock).beforeScript(scriptMock);
+            inSequence(sequence);
+
+            one(standardOutputCaptureMock).start();
+            inSequence(sequence);
+
+            one(scriptMock).run();
+            inSequence(sequence);
+            will(doAll(new Action() {
+                public void describeTo(Description description) {
+                    description.appendValue("check context classloader");
+                }
+
+                public Object invoke(Invocation invocation) throws Throwable {
+                    assertThat(Thread.currentThread().getContextClassLoader(), sameInstance(classLoaderDummy));
+                    return null;
+                }
+            }));
+
+            one(standardOutputCaptureMock).stop();
+            inSequence(sequence);
+
+            one(scriptExecutionListenerMock).afterScript(scriptMock, null);
+            inSequence(sequence);
+        }});
+
+        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+        assertThat(originalClassLoader, not(sameInstance(classLoaderDummy)));
+
+        scriptRunner.run();
+
+        assertThat(Thread.currentThread().getContextClassLoader(), sameInstance(originalClassLoader));
+    }
+
+    @Test
+    public void wrapsExecutionExceptionAndRestoresStateWhenScriptFails() {
+        final RuntimeException failure = new RuntimeException();
+
+        ScriptRunner<Script> scriptRunner = factory.create(scriptMock);
+
+        context.checking(new Expectations() {{
+            Sequence sequence = context.sequence("seq");
+
+            one(scriptExecutionListenerMock).beforeScript(scriptMock);
+            inSequence(sequence);
+
+            one(standardOutputCaptureMock).start();
+            inSequence(sequence);
+
+            one(scriptMock).run();
+            inSequence(sequence);
+            will(throwException(failure));
+
+            one(standardOutputCaptureMock).stop();
+            inSequence(sequence);
+
+            one(scriptExecutionListenerMock).afterScript(with(sameInstance(scriptMock)), with(notNullValue(Throwable.class)));
+            inSequence(sequence);
+        }});
+
+        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+        assertThat(originalClassLoader, not(sameInstance(classLoaderDummy)));
+
+        try {
+            scriptRunner.run();
+            fail();
+        } catch (GradleScriptException e) {
+            assertThat(e.getMessage(), equalTo("A problem occurred evaluating <script-to-string>."));
+            assertThat(e.getCause(), sameInstance((Throwable) failure));
+        }
+
+        assertThat(Thread.currentThread().getContextClassLoader(), sameInstance(originalClassLoader));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptTest.groovy
new file mode 100644
index 0000000..c00d33b
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/DefaultScriptTest.groovy
@@ -0,0 +1,84 @@
+/*
+ * 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.codehaus.groovy.control.CompilerConfiguration
+import org.gradle.api.initialization.dsl.ScriptHandler
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.api.internal.project.ServiceRegistry
+import org.gradle.api.logging.LoggingManager
+import org.gradle.logging.StandardOutputCapture
+import org.gradle.util.HelperUtil
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock)
+class DefaultScriptTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+
+    @Test public void testApplyMetaData() {
+        ServiceRegistry serviceRegistryMock = context.mock(ServiceRegistry.class)
+        context.checking {
+            allowing(serviceRegistryMock).get(ScriptHandler.class)
+            will(returnValue(context.mock(ScriptHandler.class)))
+            allowing(serviceRegistryMock).get(StandardOutputCapture.class)
+            will(returnValue(context.mock(StandardOutputCapture.class)))
+            allowing(serviceRegistryMock).get(LoggingManager.class)
+            will(returnValue(context.mock(LoggingManager.class)))
+        }
+
+        DefaultScript script = new GroovyShell(createBaseCompilerConfiguration()).parse(testScriptText)
+        DefaultProject testProject = HelperUtil.createRootProject()
+        testProject.custom = 'true'
+        script.setScriptSource(new StringScriptSource('script', '//'))
+        script.init(testProject, serviceRegistryMock)
+        script.run();
+        assertEquals("scriptMethod", script.scriptMethod())
+        assertEquals(testProject.path + "mySuffix", script.scriptProperty)
+        assertEquals(testProject.path + "mySuffix", testProject.additionalProperties["scriptProperty"])
+    }
+
+    private CompilerConfiguration createBaseCompilerConfiguration() {
+        CompilerConfiguration configuration = new CompilerConfiguration()
+        configuration.scriptBaseClass = DefaultScript.class.name
+        configuration
+    }
+
+    private String getTestScriptText() {
+        '''
+// We leave out the path to check import adding
+getName() // call a project method
+assert hasProperty('custom')
+repositories { } 
+def scriptMethod() { 'scriptMethod' }
+scriptProperty = project.path + 'mySuffix'
+String internalProp = 'a'
+assert internalProp == 'a'
+newProperty = 'a'
+assert newProperty == 'a'
+assert newProperty == project.newProperty
+'''
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/EmptyScript.java b/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/EmptyScript.java
new file mode 100644
index 0000000..17a7b8e
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/EmptyScript.java
@@ -0,0 +1,25 @@
+/*
+ * 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.groovy.scripts;
+
+/**
+ * @author Hans Dockter
+ */
+public class EmptyScript extends groovy.lang.Script {
+    public Object run() {
+        return null;
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/StringScriptSourceTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/StringScriptSourceTest.java
new file mode 100644
index 0000000..65dce34
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/StringScriptSourceTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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 static org.gradle.util.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import org.gradle.api.internal.resource.StringResource;
+import org.junit.Test;
+
+public class StringScriptSourceTest {
+    private final StringScriptSource source = new StringScriptSource("<description>", "<content>");
+
+    @Test
+    public void usesProvidedContent() {
+        assertThat(source.getResource(), instanceOf(StringResource.class));
+        assertThat(source.getResource().getText(), equalTo("<content>"));
+    }
+
+    @Test
+    public void generatesClassNameAndSourceFileNameUsingHashOfText() {
+        assertThat(source.getClassName(), matchesRegexp("script_[a-z0-9]+"));
+        assertThat(source.getFileName(), equalTo(source.getClassName()));
+    }
+
+    @Test
+    public void sourcesWithDifferentTextHaveDifferentClassNames() {
+        assertThat(source.getClassName(), equalTo(new StringScriptSource("?", "<content>").getClassName()));
+        assertThat(source.getClassName(), not(equalTo(new StringScriptSource("?", "<other>").getClassName())));
+    }
+
+    @Test
+    public void usesProvidedDescription() {
+        assertThat(source.getDisplayName(), equalTo("<description>"));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/UriScriptSourceTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/UriScriptSourceTest.java
new file mode 100644
index 0000000..29b200e
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/groovy/scripts/UriScriptSourceTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.internal.resource.UriResource;
+import org.gradle.util.TemporaryFolder;
+import org.gradle.util.TestFile;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import static org.gradle.util.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+public class UriScriptSourceTest {
+    private TestFile testDir;
+    private File scriptFile;
+    private URI scriptFileUri;
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    @Before
+    public void setUp() throws URISyntaxException {
+        testDir = tmpDir.createDir("scripts");
+        scriptFile = new File(testDir, "build.script");
+        scriptFileUri = scriptFile.toURI();
+        createJar();
+    }
+
+    private URI createJar() throws URISyntaxException {
+        TestFile jarFile = tmpDir.getDir().file("test.jar");
+        testDir.file("ignoreme").write("content");
+        testDir.zipTo(jarFile);
+        return new URI(String.format("jar:%s!/build.script", jarFile.toURI()));
+    }
+
+    @Test
+    public void canConstructSourceFromFile() {
+        UriScriptSource source = new UriScriptSource("<file-type>", scriptFile);
+        assertThat(source.getResource(), instanceOf(UriResource.class));
+        assertThat(source.getResource().getFile(), equalTo(scriptFile));
+    }
+
+    @Test
+    public void canConstructSourceFromFileURI() {
+        UriScriptSource source = new UriScriptSource("<file-type>", scriptFileUri);
+        assertThat(source.getResource(), instanceOf(UriResource.class));
+        assertThat(source.getResource().getFile(), equalTo(scriptFile));
+    }
+
+    @Test
+    public void canConstructSourceFromJarURI() throws URISyntaxException {
+        URI uri = createJar();
+        UriScriptSource source = new UriScriptSource("<file-type>", uri);
+        assertThat(source.getResource(), instanceOf(UriResource.class));
+        assertThat(source.getResource().getURI(), equalTo(uri));
+    }
+
+    @Test
+    public void usesScriptFileNameToBuildDescription() {
+        UriScriptSource source = new UriScriptSource("<file-type>", scriptFile);
+        assertThat(source.getDisplayName(), equalTo(String.format("<file-type> '%s'", scriptFile.getAbsolutePath())));
+    }
+
+    @Test
+    public void usesScriptFileNameToBuildDescriptionWhenUsingFileUri() {
+        UriScriptSource source = new UriScriptSource("<file-type>", scriptFileUri);
+        assertThat(source.getDisplayName(), equalTo(String.format("<file-type> '%s'", scriptFile.getAbsolutePath())));
+    }
+
+    @Test
+    public void usesScriptFileNameToBuildDescriptionWhenUsingHttpUri() throws URISyntaxException {
+        UriScriptSource source = new UriScriptSource("<file-type>", new URI("http://www.gradle.org/unknown.txt"));
+        assertThat(source.getDisplayName(), equalTo(String.format("<file-type> 'http://www.gradle.org/unknown.txt'")));
+    }
+
+    @Test
+    public void usesScriptFilePathForFileNameUsingFile() {
+        UriScriptSource source = new UriScriptSource("<file-type>", scriptFile);
+        assertThat(source.getFileName(), equalTo(scriptFile.getAbsolutePath()));
+    }
+
+    @Test
+    public void usesScriptFilePathForFileNameUsingFileUri() {
+        UriScriptSource source = new UriScriptSource("<file-type>", scriptFileUri);
+        assertThat(source.getFileName(), equalTo(scriptFile.getAbsolutePath()));
+    }
+
+    @Test
+    public void usesScriptUriForFileNameUsingHttpUri() throws URISyntaxException {
+        UriScriptSource source = new UriScriptSource("<file-type>", new URI("http://www.gradle.org/unknown.txt"));
+        assertThat(source.getFileName(), equalTo("http://www.gradle.org/unknown.txt"));
+    }
+    
+    @Test
+    public void encodesScriptFileBaseNameToClassName() {
+        UriScriptSource source = new UriScriptSource("<file-type>", scriptFile);
+        assertThat(source.getClassName(), matchesRegexp("build_script_[0-9a-z]+"));
+
+        source = new UriScriptSource("<file-type>", new File(testDir, "name with-some + invalid.chars"));
+        assertThat(source.getClassName(), matchesRegexp("name_with_some___invalid_chars_[0-9a-z]+"));
+
+        source = new UriScriptSource("<file-type>", new File(testDir, "123"));
+        assertThat(source.getClassName(), matchesRegexp("_123_[0-9a-z]+"));
+
+        source = new UriScriptSource("<file-type>", new File(testDir, "-"));
+        assertThat(source.getClassName(), matchesRegexp("__[0-9a-z]+"));
+    }
+
+    @Test
+    public void filesWithSameNameAndDifferentPathHaveDifferentClassName() {
+        ScriptSource source1 = new UriScriptSource("<file-type>", new File(testDir, "build.gradle"));
+        ScriptSource source2 = new UriScriptSource("<file-type>", new File(testDir, "subdir/build.gradle"));
+        assertThat(source1.getClassName(), not(equalTo(source2.getClassName())));
+
+        ScriptSource source3 = new UriScriptSource("<file-type>", new File(testDir, "build.gradle"));
+        assertThat(source1.getClassName(), equalTo(source3.getClassName()));
+    }
+    
+    @Test
+    public void filesWithSameNameAndUriHaveDifferentClassName() throws URISyntaxException {
+        ScriptSource source1 = new UriScriptSource("<file-type>", new File(testDir, "build.gradle"));
+        ScriptSource source2 = new UriScriptSource("<file-type>", new URI("http://localhost/build.gradle"));
+        assertThat(source1.getClassName(), not(equalTo(source2.getClassName())));
+
+        ScriptSource source3 = new UriScriptSource("<file-type>", new File(testDir, "build.gradle"));
+        assertThat(source1.getClassName(), equalTo(source3.getClassName()));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/AbstractSettingsFinderStrategyTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/AbstractSettingsFinderStrategyTest.java
new file mode 100644
index 0000000..817fb46
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/AbstractSettingsFinderStrategyTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+import org.gradle.api.initialization.Settings;
+import org.gradle.util.TemporaryFolder;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractSettingsFinderStrategyTest {
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+    protected File testDir = tmpDir.getDir();
+    protected File currentDir;
+
+    protected abstract ISettingsFileSearchStrategy getStrategy();
+
+    protected File createSettingsFile(File dir) {
+        File file = new File(dir, Settings.DEFAULT_SETTINGS_FILE);
+        try {
+            file.createNewFile();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        file.deleteOnExit();
+        return file;
+    }
+
+    protected StartParameter createStartParams(boolean searchUpwards) {
+        StartParameter startParameter = new StartParameter();
+        startParameter.setCurrentDir(currentDir);
+        startParameter.setSearchUpwards(searchUpwards);
+        return startParameter;
+    }
+
+    @Test
+    public void findExistingSettingsInCurrentDirWithSearchUpwardsTrue() {
+        org.junit.Assert.assertEquals(createSettingsFile(currentDir), getStrategy().find(createStartParams(true)));
+    }
+
+    @Test
+    public void findExistingSettingsInCurrentDirWithSearchUpwardsFalse() {
+        org.junit.Assert.assertEquals(createSettingsFile(currentDir), getStrategy().find(createStartParams(false)));
+    }
+
+    @Test
+    public void findNonExistingSettingsInCurrentDirWithSearchUpwardsFalse() {
+        org.junit.Assert.assertEquals(null, getStrategy().find(createStartParams(false)));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/BuildFileProjectSpecTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/BuildFileProjectSpecTest.java
new file mode 100644
index 0000000..00b495d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/BuildFileProjectSpecTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.api.internal.project.ProjectIdentifier;
+import org.gradle.util.TemporaryFolder;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+ at RunWith(JMock.class)
+public class BuildFileProjectSpecTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+    private final File file = tmpDir.createFile("build");
+    private final BuildFileProjectSpec spec = new BuildFileProjectSpec(file);
+    private int counter;
+
+    @Test
+    public void containsMatchWhenAtLeastOneProjectHasSpecifiedBuildFile() {
+        assertFalse(spec.containsProject(registry()));
+        assertFalse(spec.containsProject(registry(project(new File("other")))));
+
+        assertTrue(spec.containsProject(registry(project(file))));
+        assertTrue(spec.containsProject(registry(project(file), project(new File("other")))));
+        assertTrue(spec.containsProject(registry(project(file), project(file))));
+    }
+
+    @Test
+    public void selectsSingleProjectWhichHasSpecifiedBuildFile() {
+        ProjectIdentifier project = project(file);
+        assertThat(spec.selectProject(registry(project, project(new File("other")))), sameInstance(project));
+    }
+
+    @Test
+    public void selectProjectFailsWhenNoProjectHasSpecifiedBuildFile() {
+        try {
+            spec.selectProject(registry());
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), equalTo("No projects in this build have build file '" + file + "'."));
+        }
+    }
+
+    @Test
+    public void selectProjectFailsWhenMultipleProjectsHaveSpecifiedBuildFile() {
+        ProjectIdentifier project1 = project(file);
+        ProjectIdentifier project2 = project(file);
+        try {
+            spec.selectProject(registry(project1, project2));
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), startsWith("Multiple projects in this build have build file '" + file + "':"));
+        }
+    }
+
+    @Test
+    public void cannotSelectProjectWhenBuildFileIsNotAFile() {
+        file.delete();
+        
+        try {
+            spec.containsProject(registry());
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), equalTo("Build file '" + file + "' does not exist."));
+        }
+
+        file.mkdirs();
+
+        try {
+            spec.containsProject(registry());
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), equalTo("Build file '" + file + "' is not a file."));
+        }
+    }
+
+    private IProjectRegistry<ProjectIdentifier> registry(final ProjectIdentifier... projects) {
+        final IProjectRegistry<ProjectIdentifier> registry = context.mock(IProjectRegistry.class, String.valueOf(counter++));
+        context.checking(new Expectations(){{
+            allowing(registry).getAllProjects();
+            will(returnValue(toSet(projects)));
+        }});
+        return registry;
+    }
+
+    private ProjectIdentifier project(final File buildFile) {
+        final ProjectIdentifier projectIdentifier = context.mock(ProjectIdentifier.class, String.valueOf(counter++));
+        context.checking(new Expectations(){{
+            allowing(projectIdentifier).getBuildFile();
+            will(returnValue(buildFile));
+        }});
+        return projectIdentifier;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/BuildLoaderTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/BuildLoaderTest.groovy
new file mode 100644
index 0000000..e393dea
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/BuildLoaderTest.groovy
@@ -0,0 +1,209 @@
+/*
+ * 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.initialization
+
+import org.gradle.StartParameter
+import org.gradle.api.GradleException
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.Project
+import org.gradle.api.artifacts.repositories.InternalRepository
+import org.gradle.api.initialization.ProjectDescriptor
+import org.gradle.api.internal.GradleInternal
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.api.internal.project.IProjectFactory
+import org.gradle.api.internal.project.IProjectRegistry
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.util.GUtil
+import org.gradle.util.HelperUtil
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.TemporaryFolder
+import org.jmock.integration.junit4.JMock
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.gradle.initialization.*
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+class BuildLoaderTest {
+
+    BuildLoader buildLoader
+    IProjectFactory projectFactory
+    File testDir
+    File rootProjectDir
+    File childProjectDir
+    IProjectDescriptorRegistry projectDescriptorRegistry = new DefaultProjectDescriptorRegistry()
+    StartParameter startParameter = new StartParameter()
+    ProjectDescriptor rootDescriptor
+    ProjectInternal rootProject
+    ProjectDescriptor childDescriptor
+    ProjectInternal childProject
+    InternalRepository internalRepository
+    GradleInternal build
+    JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    @Rule public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    @Before public void setUp()  {
+        projectFactory = context.mock(IProjectFactory)
+        internalRepository = context.mock(InternalRepository)
+        buildLoader = new BuildLoader(projectFactory)
+        testDir = tmpDir.dir
+        (rootProjectDir = new File(testDir, 'root')).mkdirs()
+        (childProjectDir = new File(rootProjectDir, 'child')).mkdirs()
+        startParameter.currentDir = rootProjectDir
+        rootDescriptor = descriptor('root', null, rootProjectDir)
+        rootProject = project(rootDescriptor, null)
+        childDescriptor = descriptor('child', rootDescriptor, childProjectDir)
+        childProject = project(childDescriptor, rootProject)
+        build = context.mock(GradleInternal)
+        context.checking {
+            allowing(build).getStartParameter()
+            will(returnValue(startParameter))
+        }
+    }
+
+    @Test public void createsBuildWithRootProject() {
+        ProjectDescriptor rootDescriptor = descriptor('root', null, rootProjectDir)
+        ProjectInternal rootProject = project(rootDescriptor, null)
+
+        context.checking {
+            one(projectFactory).createProject(withParam(equalTo(rootDescriptor)),
+                    withParam(nullValue()),
+                    withParam(notNullValue()))
+            will(returnValue(rootProject))
+            one(build).setRootProject(rootProject)
+            allowing(build).getRootProject()
+            will(returnValue(rootProject))
+            one(build).setDefaultProject(rootProject)
+        }
+
+        buildLoader.load(rootDescriptor, build, [:])
+    }
+
+    @Test public void createsBuildWithMultipleProjects() {
+        expectProjectsCreated()
+
+        buildLoader.load(rootDescriptor, build, [:])
+
+        assertThat(rootProject.childProjects['child'], sameInstance(childProject))
+    }
+
+    @Test public void setsExternalPropertiesOnEachProject() {
+        expectProjectsCreated()
+
+        buildLoader.load(rootDescriptor, build, [buildDirName: 'target', prop: 'value'])
+
+        assertThat(rootProject.buildDirName, equalTo('target'))
+        assertThat(rootProject.prop, equalTo('value'))
+
+        assertThat(rootProject.project('child').buildDirName, equalTo('target'))
+        assertThat(rootProject.project('child').prop, equalTo('value'))
+    }
+
+    @Test public void setsProjectSpecificProperties() {
+        GUtil.saveProperties(new Properties([buildDirName: 'target/root', prop: 'rootValue']), new File(rootProjectDir, Project.GRADLE_PROPERTIES))
+        GUtil.saveProperties(new Properties([buildDirName: 'target/child', prop: 'childValue']), new File(childProjectDir, Project.GRADLE_PROPERTIES))
+
+        expectProjectsCreated()
+
+        buildLoader.load(rootDescriptor, build, [:])
+
+        assertThat(rootProject.buildDirName, equalTo('target/root'))
+        assertThat(rootProject.prop, equalTo('rootValue'))
+
+        assertThat(rootProject.project('child').buildDirName, equalTo('target/child'))
+        assertThat(rootProject.project('child').prop, equalTo('childValue'))
+    }
+
+    @Test public void selectsDefaultProject() {
+        expectProjectsCreatedNoDefaultProject()
+
+        ProjectSpec selector = context.mock(ProjectSpec)
+        startParameter.defaultProjectSelector = selector
+        context.checking {
+            one(selector).selectProject(withParam(instanceOf(IProjectRegistry)))
+            will(returnValue(childProject))
+
+            one(build).setDefaultProject(childProject)
+        }
+
+        buildLoader.load(rootDescriptor, build, [:])
+    }
+
+    @Test public void wrapsDefaultProjectSelectionException() {
+        expectProjectsCreatedNoDefaultProject()
+
+        ProjectSpec selector = context.mock(ProjectSpec)
+        startParameter.defaultProjectSelector = selector
+        context.checking {
+            one(selector).selectProject(withParam(instanceOf(IProjectRegistry)))
+            will(throwException(new InvalidUserDataException("<error>")))
+        }
+
+        try {
+            buildLoader.load(rootDescriptor, build, [:])
+            fail()
+        } catch (GradleException e) {
+            assertThat(e.message, equalTo('Could not select the default project for this build. <error>'))
+        }
+    }
+
+    private def expectProjectsCreatedNoDefaultProject() {
+        context.checking {
+            one(projectFactory).createProject(withParam(equalTo(rootDescriptor)),
+                    withParam(nullValue()),
+                    withParam(notNullValue()))
+            will(returnValue(rootProject))
+
+            one(projectFactory).createProject(withParam(equalTo(childDescriptor)),
+                    withParam(equalTo(rootProject)),
+                    withParam(notNullValue()))
+            will(returnValue(childProject))
+
+            one(build).setRootProject(rootProject)
+            allowing(build).getRootProject()
+            will(returnValue(rootProject))
+        }
+    }
+
+    private def expectProjectsCreated() {
+        expectProjectsCreatedNoDefaultProject()
+
+        context.checking {
+            one(build).setDefaultProject(rootProject)
+        }
+    }
+
+    private ProjectDescriptor descriptor(String name, ProjectDescriptor parent, File projectDir) {
+        new DefaultProjectDescriptor(parent, name, projectDir, projectDescriptorRegistry)
+    }
+
+    private ProjectInternal project(ProjectDescriptor descriptor, ProjectInternal parent) {
+        DefaultProject project
+        if (parent) {
+            project = HelperUtil.createChildProject(parent, descriptor.name, descriptor.projectDir)
+        } else {
+            project = HelperUtil.createRootProject(descriptor.projectDir)
+        }
+        project
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/BuildProgressLoggerTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/BuildProgressLoggerTest.groovy
new file mode 100644
index 0000000..934f034
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/BuildProgressLoggerTest.groovy
@@ -0,0 +1,69 @@
+/*
+ * 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.initialization
+
+import spock.lang.Specification
+import org.gradle.logging.ProgressLoggerFactory
+import org.gradle.logging.ProgressLogger
+import org.gradle.api.invocation.Gradle
+import org.gradle.api.execution.TaskExecutionGraph
+import org.gradle.BuildResult
+
+class BuildProgressLoggerTest extends Specification {
+    private final ProgressLoggerFactory progressLoggerFactory = Mock()
+    private final ProgressLogger progressLogger = Mock()
+    private final Gradle gradle = Mock()
+    private final TaskExecutionGraph graph = Mock()
+    private final BuildResult result = Mock()
+    private final BuildProgressLogger logger = new BuildProgressLogger(progressLoggerFactory)
+
+    def logsBuildStages() {
+        _ * gradle.getTaskGraph() >> graph
+        _ * result.getGradle() >> gradle
+
+        when:
+        logger.buildStarted(gradle)
+
+        then:
+        1 * progressLoggerFactory.start() >> progressLogger
+        1 * progressLogger.progress('Loading')
+
+        when:
+        logger.graphPopulated(graph)
+
+        then:
+        1 * progressLogger.progress('Building')
+
+        when:
+        logger.buildFinished(result)
+
+        then:
+        1 * progressLogger.completed()
+    }
+
+    def ignoresNestedBuilds() {
+        _ * gradle.getParent() >> Mock(Gradle)
+        
+        when:
+        logger.buildStarted(gradle)
+
+        then:
+        0 * progressLoggerFactory._
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy
new file mode 100644
index 0000000..94593cd
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/BuildSourceBuilderTest.groovy
@@ -0,0 +1,186 @@
+/*
+ * 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.initialization
+
+import static org.junit.Assert.*
+
+import org.gradle.BuildResult
+import org.gradle.GradleLauncher
+import org.gradle.GradleLauncherFactory
+import org.gradle.StartParameter
+import org.gradle.api.Project
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.plugins.EmbeddableJavaProject
+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.PersistentCache
+import org.gradle.cache.PersistentStateCache
+import org.gradle.groovy.scripts.StringScriptSource
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.jmock.api.Action
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.hamcrest.Matchers.*
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(org.jmock.integration.junit4.JMock)
+class BuildSourceBuilderTest {
+    @Rule public TemporaryFolder tmpDir = new TemporaryFolder();
+    JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    BuildSourceBuilder buildSourceBuilder
+    GradleLauncherFactory gradleFactoryMock = context.mock(GradleLauncherFactory.class)
+    GradleLauncher gradleMock = context.mock(GradleLauncher.class)
+    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
+    StartParameter expectedStartParameter
+    CacheRepository cacheRepository = context.mock(CacheRepository.class)
+    PersistentStateCache stateCache = context.mock(PersistentStateCache.class)
+    BuildResult expectedBuildResult
+    Gradle build = context.mock(Gradle.class)
+    EmbeddableJavaProject projectMetaInfo = context.mock(EmbeddableJavaProject.class)
+
+    @Before public void setUp() {
+        buildSourceBuilder = new BuildSourceBuilder(gradleFactoryMock, context.mock(ClassLoaderFactory.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))
+            allowing(build).getStartParameter(); will(returnValue(expectedStartParameter))
+            allowing(rootProjectMock).getConvention(); will(returnValue(convention))
+            allowing(convention).getPlugin(EmbeddableJavaProject);
+            will(returnValue(projectMetaInfo))
+            allowing(projectMetaInfo).getRuntimeClasspath(); will(returnValue(configurationMock))
+            allowing(configurationMock).getFiles(); will(returnValue(testDependencies))
+        }
+        expectedBuildResult = new BuildResult(build, null)
+    }
+
+    @Test public void testCreateClasspathWhenBuildSrcDirExistsAndContainsBuildScript() {
+        expectValueFetchedFromCache(null)
+        context.checking {
+            one(projectMetaInfo).getRebuildTasks(); will(returnValue(['clean', 'build']))
+            one(gradleFactoryMock).newInstance((StartParameter) withParam(notNullValue()));
+            will(returnValue(gradleMock))
+            one(gradleMock).addListener(withParam(not(nullValue()))); will(notifyProjectsEvaluated())
+            one(gradleMock).run(); will(returnValue(expectedBuildResult))
+        }
+        expectValueWrittenToCache()
+        
+        createBuildFile()
+        Set<File> actualClasspath = buildSourceBuilder.createBuildSourceClasspath(expectedStartParameter)
+        assertEquals(testDependencies, actualClasspath)
+    }
+
+    @Test public void testCreateClasspathWhenBuildSrcDirExistsAndDoesNotContainBuildScript() {
+        expectValueFetchedFromCache(null)
+        context.checking {
+            one(projectMetaInfo).getRebuildTasks(); will(returnValue(['clean', 'build']))
+            one(gradleFactoryMock).newInstance((StartParameter) withParam(notNullValue()))
+            will { StartParameter param ->
+                assertThat(param.buildScriptSource, instanceOf(StringScriptSource.class))
+                assertThat(param.buildScriptSource.displayName, equalTo('default buildSrc build script'))
+                assertThat(param.buildScriptSource.resource.text, equalTo(BuildSourceBuilder.defaultScript))
+                return gradleMock
+            }
+            one(gradleMock).addListener(withParam(not(nullValue()))); will(notifyProjectsEvaluated())
+            one(gradleMock).run(); will(returnValue(expectedBuildResult))
+        }
+        expectValueWrittenToCache()
+
+        Set actualClasspath = buildSourceBuilder.createBuildSourceClasspath(expectedStartParameter)
+        assertEquals(testDependencies, actualClasspath)
+    }
+
+    @Test public void testCreateClasspathWhenBuildSrcDirExistsAndHasBeenBuiltBefore() {
+        expectValueFetchedFromCache(true)
+        context.checking {
+            one(projectMetaInfo).getBuildTasks(); will(returnValue(['build']))
+            one(gradleFactoryMock).newInstance((StartParameter) withParam(notNullValue()))
+            will { StartParameter param ->
+                assertThat(param.buildScriptSource, instanceOf(StringScriptSource.class))
+                assertThat(param.buildScriptSource.displayName, equalTo('default buildSrc build script'))
+                assertThat(param.buildScriptSource.resource.text, equalTo(BuildSourceBuilder.defaultScript))
+                return gradleMock
+            }
+            one(gradleMock).addListener(withParam(not(nullValue()))); will(notifyProjectsEvaluated())
+            one(gradleMock).run(); will(returnValue(expectedBuildResult))
+        }
+        expectValueWrittenToCache()
+
+        Set actualClasspath = buildSourceBuilder.createBuildSourceClasspath(expectedStartParameter)
+        assertEquals(testDependencies, actualClasspath)
+    }
+
+    @Test public void testCreateClasspathWhenBuildSrcDirDoesNotExist() {
+        expectedStartParameter = expectedStartParameter.newInstance()
+        expectedStartParameter.setCurrentDir(new File('nonexisting'));
+        assertEquals([] as Set, buildSourceBuilder.createBuildSourceClasspath(expectedStartParameter))
+    }
+
+    private expectValueFetchedFromCache(def value) {
+        context.checking {
+            CacheBuilder builder = context.mock(CacheBuilder.class)
+            PersistentCache cache = context.mock(PersistentCache.class)
+            one(cacheRepository).cache('buildSrc')
+            will(returnValue(builder))
+
+            one(builder).forObject(testBuildSrcDir)
+            will(returnValue(builder))
+
+            one(builder).invalidateOnVersionChange()
+            will(returnValue(builder))
+
+            one(builder).open()
+            will(returnValue(cache))
+
+            one(cache).openStateCache()
+            will(returnValue(stateCache))
+
+            one(stateCache).get()
+            will(returnValue(value))
+        }
+    }
+
+    private expectValueWrittenToCache() {
+        context.checking {
+            one(stateCache).set(true)
+        }
+    }
+
+    private createBuildFile() {
+        new File(testBuildSrcDir, Project.DEFAULT_BUILD_FILE).createNewFile()
+    }
+
+    private Action notifyProjectsEvaluated() {
+        return [invoke: {invocation -> invocation.getParameter(0).projectsEvaluated(build)},
+                describeTo: {description -> }] as Action
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultCommandLine2StartParameterConverterTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultCommandLine2StartParameterConverterTest.java
new file mode 100644
index 0000000..5477f54
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultCommandLine2StartParameterConverterTest.java
@@ -0,0 +1,397 @@
+/*
+ * 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.initialization;
+
+import org.gradle.CacheUsage;
+import org.gradle.CommandLineArgumentException;
+import org.gradle.GradleLauncher;
+import org.gradle.StartParameter;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.artifacts.ProjectDependenciesBuildInstruction;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.execution.*;
+import org.gradle.groovy.scripts.UriScriptSource;
+import org.gradle.util.GUtil;
+import org.gradle.util.Matchers;
+import org.gradle.util.TemporaryFolder;
+import org.gradle.util.WrapUtil;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultCommandLine2StartParameterConverterTest {
+    private File expectedBuildFile;
+    private File expectedGradleUserHome = StartParameter.DEFAULT_GRADLE_USER_HOME;
+    private File expectedProjectDir;
+    private List<String> expectedTaskNames = toList();
+    private Set<String> expectedExcludedTasks = toSet();
+    private ProjectDependenciesBuildInstruction expectedProjectDependenciesBuildInstruction
+            = new ProjectDependenciesBuildInstruction(WrapUtil.<String>toList());
+    private Map<String, String> expectedSystemProperties = new HashMap<String, String>();
+    private Map<String, String> expectedProjectProperties = new HashMap<String, String>();
+    private List<File> expectedInitScripts = new ArrayList<File>();
+    private CacheUsage expectedCacheUsage = CacheUsage.ON;
+    private boolean expectedSearchUpwards = true;
+    private boolean expectedDryRun;
+    private boolean expectedShowHelp;
+    private boolean expectedShowVersion;
+    private StartParameter.ShowStacktrace expectedShowStackTrace = StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS;
+    private String expectedEmbeddedScript = "somescript";
+    private LogLevel expectedLogLevel = LogLevel.LIFECYCLE;
+    private StartParameter actualStartParameter;
+    @Rule
+    public TemporaryFolder testDir = new TemporaryFolder();
+
+    @Before
+    public void setUp() throws IOException {
+        expectedProjectDir = new File("").getCanonicalFile();
+    }
+
+    @After
+    public void tearDown() {
+        GradleLauncher.injectCustomFactory(null);
+    }
+
+    @Test
+    public void withoutAnyOptions() {
+        checkConversion();
+    }
+
+    private void checkConversion(String... args) {
+        checkConversion(false, false, args);
+    }
+
+    private void checkStartParameter(StartParameter startParameter, boolean emptyTasks) {
+        assertEquals(expectedBuildFile, startParameter.getBuildFile());
+        assertEquals(emptyTasks ? new ArrayList() : expectedTaskNames, startParameter.getTaskNames());
+        assertEquals(expectedProjectDependenciesBuildInstruction,
+                startParameter.getProjectDependenciesBuildInstruction());
+        assertEquals(expectedProjectDir.getAbsoluteFile(), startParameter.getCurrentDir().getAbsoluteFile());
+        assertEquals(expectedCacheUsage, startParameter.getCacheUsage());
+        assertEquals(expectedSearchUpwards, startParameter.isSearchUpwards());
+        assertEquals(expectedProjectProperties, startParameter.getProjectProperties());
+        assertEquals(expectedSystemProperties, startParameter.getSystemPropertiesArgs());
+        assertEquals(expectedGradleUserHome.getAbsoluteFile(), startParameter.getGradleUserHomeDir().getAbsoluteFile());
+        assertEquals(expectedGradleUserHome.getAbsoluteFile(), startParameter.getGradleUserHomeDir().getAbsoluteFile());
+        assertEquals(expectedLogLevel, startParameter.getLogLevel());
+        assertEquals(expectedDryRun, startParameter.isDryRun());
+        assertEquals(expectedShowHelp, startParameter.isShowHelp());
+        assertEquals(expectedShowVersion, startParameter.isShowVersion());
+        assertEquals(expectedShowStackTrace, startParameter.getShowStacktrace());
+        assertEquals(expectedExcludedTasks, startParameter.getExcludedTaskNames());
+        assertEquals(expectedInitScripts, startParameter.getInitScripts());
+    }
+
+    private void checkConversion(final boolean embedded, final boolean noTasks, String... args) {
+        actualStartParameter = new DefaultCommandLine2StartParameterConverter().convert(args);
+        // We check the params passed to the build factory
+        checkStartParameter(actualStartParameter, noTasks);
+        if (embedded) {
+            assertThat(actualStartParameter.getBuildScriptSource().getResource().getText(), equalTo(expectedEmbeddedScript));
+        } else {
+            assert !GUtil.isTrue(actualStartParameter.getBuildScriptSource());
+        }
+    }
+
+    @Test
+    public void withSpecifiedGradleUserHomeDirectory() {
+        expectedGradleUserHome = testDir.getDir();
+        checkConversion("-g", expectedGradleUserHome.getAbsoluteFile().toString());
+    }
+
+    @Test
+    public void withSpecifiedProjectDirectory() {
+        expectedProjectDir = testDir.getDir();
+        checkConversion("-p", expectedProjectDir.getAbsoluteFile().toString());
+    }
+
+    @Test
+    public void withSpecifiedBuildFileName() throws IOException {
+        expectedBuildFile = new File("somename").getCanonicalFile();
+        checkConversion("-b", "somename");
+    }
+
+    @Test
+    public void withSpecifiedSettingsFileName() throws IOException {
+        checkConversion("-c", "somesettings");
+
+        assertThat(actualStartParameter.getSettingsScriptSource(), instanceOf(UriScriptSource.class));
+        assertThat(actualStartParameter.getSettingsScriptSource().getResource().getFile(), equalTo(new File(
+                "somesettings").getCanonicalFile()));
+    }
+
+    @Test
+    public void withSystemProperties() {
+        final String prop1 = "gradle.prop1";
+        final String valueProp1 = "value1";
+        final String prop2 = "gradle.prop2";
+        final String valueProp2 = "value2";
+        expectedSystemProperties = toMap(prop1, valueProp1);
+        expectedSystemProperties.put(prop2, valueProp2);
+        checkConversion("-D", prop1 + "=" + valueProp1, "-D", prop2 + "=" + valueProp2);
+    }
+
+    @Test
+    public void withStartProperties() {
+        final String prop1 = "prop1";
+        final String valueProp1 = "value1";
+        final String prop2 = "prop2";
+        final String valueProp2 = "value2";
+        expectedProjectProperties = toMap(prop1, valueProp1);
+        expectedProjectProperties.put(prop2, valueProp2);
+        checkConversion("-P", prop1 + "=" + valueProp1, "-P", prop2 + "=" + valueProp2);
+    }
+
+    @Test
+    public void withTaskNames() {
+        expectedTaskNames = toList("a", "b");
+        checkConversion("a", "b");
+    }
+
+    @Test
+    public void withRebuildCacheFlagSet() {
+        expectedCacheUsage = CacheUsage.REBUILD;
+        checkConversion("-C", "rebuild");
+    }
+
+    @Test
+    public void withCacheOnFlagSet() {
+        checkConversion("-C", "on");
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withUnknownCacheFlags() {
+        checkConversion("-C", "unknown");
+    }
+
+    @Test
+    public void withSearchUpwardsFlagSet() {
+        expectedSearchUpwards = false;
+        checkConversion("-u");
+    }
+
+    @Test
+    public void withShowFullStacktrace() {
+        expectedShowStackTrace = StartParameter.ShowStacktrace.ALWAYS_FULL;
+        checkConversion("-S");
+    }
+
+    @Test
+    public void withShowStacktrace() {
+        expectedShowStackTrace = StartParameter.ShowStacktrace.ALWAYS;
+        checkConversion("-s");
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withShowStacktraceAndShowFullStacktraceShouldThrowCommandLineArgumentEx() {
+        checkConversion("-sf");
+    }
+
+    @Test
+    public void withDryRunFlagSet() {
+        expectedDryRun = true;
+        checkConversion("-m");
+    }
+
+    @Test
+    public void withExcludeTask() {
+        expectedExcludedTasks.add("excluded");
+        checkConversion("-x", "excluded");
+        expectedExcludedTasks.add("excluded2");
+        checkConversion("-x", "excluded", "-x", "excluded2");
+    }
+
+    @Test
+    public void withShowHelp() {
+        expectedShowHelp = true;
+        checkConversion("-h");
+    }
+
+    @Test
+    public void withShowVersion() {
+        expectedShowVersion = true;
+        checkConversion("-v");
+    }
+
+    @Test
+    public void withEmbeddedScript() {
+        expectedSearchUpwards = false;
+        checkConversion(true, false, "-e", expectedEmbeddedScript);
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withEmbeddedScriptAndConflictingNoSearchUpwardsOption() {
+        checkConversion("-e", "someScript", "-u", "clean");
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withEmbeddedScriptAndConflictingSpecifyBuildFileOption() {
+        checkConversion("-e", "someScript", "-bsomeFile", "clean");
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withEmbeddedScriptAndConflictingSpecifySettingsFileOption() {
+        checkConversion("-e", "someScript", "-csomeFile", "clean");
+    }
+
+    @Test
+    public void withConflictingLoggingOptionsDQ() {
+        List<String> illegalOptions = toList("dq", "di", "qd", "qi", "iq", "id");
+        for (String illegalOption : illegalOptions) {
+            try {
+                checkConversion("-" + illegalOption, "clean");
+            } catch (InvalidUserDataException e) {
+                continue;
+            }
+            fail("Expected InvalidUserDataException");
+        }
+    }
+
+    @Test
+    public void withQuietLoggingOptions() {
+        expectedLogLevel = LogLevel.QUIET;
+        checkConversion("-q");
+    }
+
+    @Test
+    public void withNoProjectDependencyRebuild() {
+        expectedProjectDependenciesBuildInstruction = new ProjectDependenciesBuildInstruction(null);
+        checkConversion("-a");
+    }
+
+    @Test
+    public void withProjectDependencyTaskNames() {
+        expectedProjectDependenciesBuildInstruction = new ProjectDependenciesBuildInstruction(WrapUtil.toList("task1",
+                "task2"));
+        checkConversion("-Atask1", "-A task2");
+    }
+
+    @Test
+    public void withInfoLoggingOptions() {
+        expectedLogLevel = LogLevel.INFO;
+        checkConversion("-i");
+    }
+
+    @Test
+    public void withDebugLoggingOptions() {
+        expectedLogLevel = LogLevel.DEBUG;
+        checkConversion("-d");
+    }
+
+    @Test
+    public void withShowTasks() {
+        checkConversion(false, true, "-t");
+        BuildExecuter expectedExecuter = new TaskReportBuildExecuter(null, false);
+        assertThat(actualStartParameter.getBuildExecuter(), Matchers.reflectionEquals(expectedExecuter));
+    }
+
+    @Test
+    public void withShowAllTasks() {
+        checkConversion(false, true, "-t", "--all");
+        BuildExecuter expectedExecuter = new TaskReportBuildExecuter(null, true);
+        assertThat(actualStartParameter.getBuildExecuter(), Matchers.reflectionEquals(expectedExecuter));
+    }
+
+    @Test
+    public void withShowTasksAndEmbeddedScript() {
+        expectedSearchUpwards = false;
+        checkConversion(true, true, "-e", expectedEmbeddedScript, "-t");
+    }
+
+    @Test
+    public void withShowTasksAndPath() {
+        String somePath = ":SomeProject";
+        checkConversion(false, true, "-t" + somePath);
+        BuildExecuter expectedExecuter = new TaskReportBuildExecuter(somePath, false);
+        assertThat(actualStartParameter.getBuildExecuter(), Matchers.reflectionEquals(expectedExecuter));
+    }
+
+    @Test
+    public void withShowProperties() {
+        checkConversion(false, true, "-r");
+        BuildExecuter expectedExecuter = new PropertyReportBuildExecuter(null);
+        assertThat(actualStartParameter.getBuildExecuter(), Matchers.reflectionEquals(expectedExecuter));
+    }
+
+    @Test
+    public void withShowPropertiesAndPath() {
+        String somePath = ":SomeProject";
+        checkConversion(false, true, "-r" + somePath);
+        BuildExecuter expectedExecuter = new PropertyReportBuildExecuter(somePath);
+        assertThat(actualStartParameter.getBuildExecuter(), Matchers.reflectionEquals(expectedExecuter));
+    }
+
+    @Test
+    public void withShowDependencies() {
+        checkConversion(false, true, "-n");
+        BuildExecuter expectedExecuter = new DependencyReportBuildExecuter(null);
+        assertThat(actualStartParameter.getBuildExecuter(), Matchers.reflectionEquals(expectedExecuter));
+    }
+
+    @Test
+    public void withShowDependenciesAndPath() {
+        String somePath = ":SomeProject";
+        checkConversion(false, true, "-n" + somePath);
+        BuildExecuter expectedExecuter = new DependencyReportBuildExecuter(somePath);
+        assertThat(actualStartParameter.getBuildExecuter(), Matchers.reflectionEquals(expectedExecuter));
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withShowTasksPropertiesAndDependencies() {
+        checkConversion("-r", "-t");
+        checkConversion("-r", "-n");
+        checkConversion("-r", "-n", "-t");
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withLowerPParameterWithoutArgument() {
+        checkConversion("-p");
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withAParameterWithoutArgument() {
+        checkConversion("-A");
+    }
+
+    @Test(expected = CommandLineArgumentException.class)
+    public void withUpperAAndLowerAParameter() {
+        checkConversion("-a -Atask1");
+    }
+
+    @Test
+    public void withInitScripts() {
+        File script1 = new File("init1.gradle");
+        expectedInitScripts.add(script1);
+        checkConversion("-Iinit1.gradle");
+
+        File script2 = new File("init2.gradle");
+        expectedInitScripts.add(script2);
+        checkConversion("-Iinit1.gradle", "-Iinit2.gradle");
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultExceptionAnalyserTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultExceptionAnalyserTest.java
new file mode 100644
index 0000000..4c40dbf
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultExceptionAnalyserTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.LocationAwareException;
+import org.gradle.api.internal.Contextual;
+import org.gradle.groovy.scripts.Script;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.listener.ListenerManager;
+import org.gradle.util.WrapUtil;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class DefaultExceptionAnalyserTest {
+    private final JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+    private final ListenerManager listenerManager = context.mock(ListenerManager.class);
+    private final StackTraceElement element = new StackTraceElement("class", "method", "filename", 7);
+    private final StackTraceElement callerElement = new StackTraceElement("class", "method", "filename", 11);
+    private final StackTraceElement otherElement = new StackTraceElement("class", "method", "otherfile", 11);
+    private final ScriptSource source = context.mock(ScriptSource.class);
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations() {{
+            allowing(source).getFileName();
+            will(returnValue("filename"));
+        }});
+    }
+
+    @Test
+    public void usesOriginalExceptionWhenItIsNotAContextualException() {
+        Throwable failure = new RuntimeException();
+
+        DefaultExceptionAnalyser analyser = analyser();
+        assertThat(analyser.transform(failure), sameInstance(failure));
+    }
+
+    @Test
+    public void wrapsContextualExceptionWithLocationInfoFromDeepestStackFrame() {
+        ContextualException failure = new ContextualException();
+        failure.setStackTrace(WrapUtil.toArray(element, otherElement, callerElement));
+
+        DefaultExceptionAnalyser analyser = analyser();
+        notifyAnalyser(analyser, source);
+
+        Throwable transformedFailure = analyser.transform(failure);
+        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
+
+        LocationAwareException gse = (LocationAwareException) transformedFailure;
+        assertThat(gse.getScriptSource(), sameInstance(source));
+        assertThat(gse.getLineNumber(), equalTo(7));
+    }
+
+    @Test
+    public void wrapsContextualExceptionWithLocationInfoFromDeepestCause() {
+        RuntimeException cause = new RuntimeException();
+        ContextualException failure = new ContextualException(new RuntimeException(cause));
+        failure.setStackTrace(WrapUtil.toArray(otherElement, callerElement));
+        cause.setStackTrace(WrapUtil.toArray(element, otherElement, callerElement));
+
+        DefaultExceptionAnalyser analyser = analyser();
+        notifyAnalyser(analyser, source);
+
+        Throwable transformedFailure = analyser.transform(failure);
+        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
+
+        LocationAwareException gse = (LocationAwareException) transformedFailure;
+        assertThat(gse.getScriptSource(), sameInstance(source));
+        assertThat(gse.getLineNumber(), equalTo(7));
+    }
+
+    @Test
+    public void wrapsDeepestContextualExceptionWithLocationInfo() {
+        ContextualException cause = new ContextualException();
+        ContextualException failure = new ContextualException(new RuntimeException(cause));
+        failure.setStackTrace(WrapUtil.toArray(otherElement, callerElement));
+        cause.setStackTrace(WrapUtil.toArray(element, otherElement, callerElement));
+
+        DefaultExceptionAnalyser analyser = analyser();
+        notifyAnalyser(analyser, source);
+
+        Throwable transformedFailure = analyser.transform(failure);
+        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
+
+        LocationAwareException gse = (LocationAwareException) transformedFailure;
+        assertThat(gse.getScriptSource(), sameInstance(source));
+        assertThat(gse.getLineNumber(), equalTo(7));
+    }
+
+    @Test
+    public void wrapsOriginalExceptionWhenLocationCannotBeDetermined() {
+        Throwable failure = new ContextualException();
+        Throwable transformedFailure = analyser().transform(failure);
+        assertThat(transformedFailure, instanceOf(LocationAwareException.class));
+
+        LocationAwareException gse = (LocationAwareException) transformedFailure;
+        assertThat(gse.getScriptSource(), nullValue());
+        assertThat(gse.getLineNumber(), nullValue());
+    }
+
+    @Test
+    public void usesOriginalExceptionWhenItIsAlreadyLocationAware() {
+        final Throwable failure = context.mock(TestException.class);
+        context.checking(new Expectations() {{
+            allowing(failure).getCause();
+            will(returnValue(null));
+            allowing(failure).getStackTrace();
+            will(returnValue(WrapUtil.toArray(element)));
+        }});
+
+        DefaultExceptionAnalyser analyser = analyser();
+        notifyAnalyser(analyser, source);
+        
+        assertThat(analyser.transform(failure), sameInstance(failure));
+    }
+
+    private void notifyAnalyser(DefaultExceptionAnalyser analyser, final ScriptSource source) {
+        final Script script = context.mock(Script.class);
+        context.checking(new Expectations() {{
+            allowing(script).getScriptSource();
+            will(returnValue(source));
+        }});
+        analyser.beforeScript(script);
+    }
+
+    private DefaultExceptionAnalyser analyser() {
+        context.checking(new Expectations() {{
+            one(listenerManager).addListener(with(notNullValue(DefaultExceptionAnalyser.class)));
+        }});
+        return new DefaultExceptionAnalyser(listenerManager);
+    }
+
+    @Contextual
+    public static class ContextualException extends RuntimeException {
+        public ContextualException() {
+            super("failed");
+        }
+
+        public ContextualException(Throwable throwable) {
+            super(throwable);
+        }
+    }
+
+    @Contextual
+    public abstract static class TestException extends RuntimeException implements LocationAwareException {
+
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherFactoryTest.java
new file mode 100644
index 0000000..ea4c7c5
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherFactoryTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+import org.gradle.logging.LoggingConfigurer;
+import org.gradle.util.WrapUtil;
+import org.hamcrest.Matchers;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultGradleLauncherFactoryTest {
+    private JUnit4Mockery context = new JUnit4Mockery();
+    private final LoggingConfigurer loggingConfigurer = context.mock(LoggingConfigurer.class);
+    private final CommandLine2StartParameterConverter parameterConverter = context.mock(CommandLine2StartParameterConverter.class);
+    private final DefaultGradleLauncherFactory factory = new DefaultGradleLauncherFactory();
+
+    @Before
+    public void setUp() {
+        factory.setCommandLine2StartParameterConverter(parameterConverter);
+    }
+
+    @Test
+    public void newInstanceWithStartParameter() {
+        final StartParameter startParameter = new StartParameter();
+        context.checking(new Expectations() {{
+            one(loggingConfigurer).configure(startParameter.getLogLevel());
+        }});
+        assertNotNull(factory.newInstance(startParameter));
+    }
+
+    @Test
+    public void newInstanceWithCommandLineArgs() {
+        final StartParameter startParameter = new StartParameter();
+        final String[] commandLineArgs = WrapUtil.toArray("A", "B");
+        context.checking(new Expectations() {{
+            one(loggingConfigurer).configure(startParameter.getLogLevel());
+            allowing(parameterConverter).convert(commandLineArgs); will(returnValue(startParameter));
+        }});
+        assertNotNull(factory.newInstance(commandLineArgs));
+    }
+
+    @Test
+    public void createStartParameter() {
+        final StartParameter startParameter = new StartParameter();
+        final String[] commandLineArgs = WrapUtil.toArray("A", "B");
+        context.checking(new Expectations() {{
+            one(loggingConfigurer).configure(startParameter.getLogLevel());
+            allowing(parameterConverter).convert(commandLineArgs); will(returnValue(startParameter));
+        }});
+
+        assertThat(factory.createStartParameter(commandLineArgs),
+            Matchers.sameInstance(startParameter));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherTest.java
new file mode 100644
index 0000000..1569468
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultGradleLauncherTest.java
@@ -0,0 +1,378 @@
+/*
+ * 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.initialization;
+
+import org.gradle.BuildListener;
+import org.gradle.BuildResult;
+import org.gradle.GradleLauncher;
+import org.gradle.StartParameter;
+import org.gradle.api.Task;
+import org.gradle.api.initialization.ProjectDescriptor;
+import org.gradle.api.internal.ExceptionAnalyser;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.SettingsInternal;
+import org.gradle.api.internal.project.DefaultProject;
+import org.gradle.configuration.BuildConfigurer;
+import org.gradle.execution.TaskGraphExecuter;
+import org.gradle.logging.LoggingConfigurer;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.TemporaryFolder;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(org.jmock.integration.junit4.JMock.class)
+public class DefaultGradleLauncherTest {
+    private BuildLoader buildLoaderMock;
+    private InitScriptHandler initscriptHandlerMock;
+    private SettingsHandler settingsHandlerMock;
+    private IGradlePropertiesLoader gradlePropertiesLoaderMock;
+    private BuildConfigurer buildConfigurerMock;
+    private DefaultProject expectedRootProject;
+    private DefaultProject expectedCurrentProject;
+    private SettingsInternal settingsMock;
+    private List<String> expectedTaskNames;
+    private List<Iterable<Task>> expectedTasks;
+    private StartParameter expectedStartParams;
+    private GradleInternal gradleMock;
+    private BuildListener buildBroadcaster;
+
+    private Map testGradleProperties = new HashMap();
+
+    private GradleLauncher gradleLauncher;
+
+    private TaskGraphExecuter taskExecuterMock;
+
+    private ProjectDescriptor expectedRootProjectDescriptor;
+
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    private LoggingConfigurer loggingConfigurerMock = context.mock(LoggingConfigurer.class);
+
+    private ExceptionAnalyser exceptionAnalyserMock = context.mock(ExceptionAnalyser.class);
+
+    private LoggingManagerInternal loggingManagerMock = context.mock(LoggingManagerInternal.class);
+
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    @Before
+    public void setUp() {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        initscriptHandlerMock = context.mock(InitScriptHandler.class);
+        settingsHandlerMock = context.mock(SettingsHandler.class);
+        gradlePropertiesLoaderMock = context.mock(IGradlePropertiesLoader.class);
+        settingsMock = context.mock(SettingsInternal.class);
+        taskExecuterMock = context.mock(TaskGraphExecuter.class);
+        buildLoaderMock = context.mock(BuildLoader.class);
+        buildConfigurerMock = context.mock(BuildConfigurer.class);
+        gradleMock = context.mock(GradleInternal.class);
+        buildBroadcaster = context.mock(BuildListener.class);
+        testGradleProperties = toMap("prop1", "value1");
+        boolean expectedSearchUpwards = false;
+
+        File expectedRootDir = tmpDir.file("rootDir");
+        File expectedCurrentDir = new File(expectedRootDir, "currentDir");
+
+        expectedRootProjectDescriptor = new DefaultProjectDescriptor(null, "someName", new File("somedir"), new DefaultProjectDescriptorRegistry());
+        expectedRootProject = HelperUtil.createRootProject(expectedRootDir);
+        expectedCurrentProject = HelperUtil.createRootProject(expectedCurrentDir);
+
+        expectTasks("a", "b");
+
+        expectedStartParams = new StartParameter();
+        expectedStartParams.setTaskNames(expectedTaskNames);
+        expectedStartParams.setCurrentDir(expectedCurrentDir);
+        expectedStartParams.setSearchUpwards(expectedSearchUpwards);
+        expectedStartParams.setGradleUserHomeDir(tmpDir.createDir("gradleUserHome"));
+
+        gradleLauncher = new DefaultGradleLauncher(gradleMock, initscriptHandlerMock, settingsHandlerMock,
+                gradlePropertiesLoaderMock, buildLoaderMock, buildConfigurerMock, buildBroadcaster, exceptionAnalyserMock, loggingManagerMock);
+
+        context.checking(new Expectations() {
+            {
+                allowing(gradlePropertiesLoaderMock).getGradleProperties();
+                will(returnValue(testGradleProperties));
+                allowing(settingsMock).getRootProject();
+                will(returnValue(expectedRootProjectDescriptor));
+                allowing(gradleMock).getRootProject();
+                will(returnValue(expectedRootProject));
+                allowing(gradleMock).getDefaultProject();
+                will(returnValue(expectedCurrentProject));
+                allowing(gradleMock).getTaskGraph();
+                will(returnValue(taskExecuterMock));
+                allowing(gradleMock).getStartParameter();
+                will(returnValue(expectedStartParams));
+            }
+        });
+    }
+
+    private void expectTasks(String... tasks) {
+        expectedTaskNames = toList(tasks);
+        expectedTasks = new ArrayList<Iterable<Task>>();
+        for (String task : tasks) {
+            expectedTasks.add(toSortedSet(expectedCurrentProject.createTask(task)));
+        }
+    }
+
+    @Test
+    public void testRun() {
+        expectLoggingStartedAndStoped();
+        expectInitScriptsExecuted();
+        expectSettingsBuilt();
+        expectDagBuilt();
+        expectTasksRun();
+        expectBuildListenerCallbacks();
+        BuildResult buildResult = gradleLauncher.run();
+        assertThat(buildResult.getGradle(), sameInstance((Object) gradleMock));
+        assertThat(buildResult.getFailure(), nullValue());
+    }
+
+    @Test
+    public void testDryRun() {
+        expectLoggingStartedAndStoped();
+        expectInitScriptsExecuted();
+        expectSettingsBuilt();
+        expectDagBuilt();
+        expectTasksRun();
+        expectBuildListenerCallbacks();
+        context.checking(new Expectations() {{
+            one(taskExecuterMock).getAllTasks();
+            will(returnValue(toList()));
+        }});
+        expectedStartParams.setDryRun(true);
+        BuildResult buildResult = gradleLauncher.run();
+        assertThat(buildResult.getGradle(), sameInstance((Object) gradleMock));
+        assertThat(buildResult.getFailure(), nullValue());
+    }
+
+    @Test
+    public void testGetBuildAndRunAnalysis() {
+        expectLoggingStartedAndStoped();
+        expectInitScriptsExecuted();
+        expectSettingsBuilt();
+        expectDagBuilt();
+        expectBuildListenerCallbacks();
+        BuildResult buildResult = gradleLauncher.getBuildAndRunAnalysis();
+        assertThat(buildResult.getGradle(), sameInstance((Object) gradleMock));
+        assertThat(buildResult.getFailure(), nullValue());
+    }
+
+    @Test
+    public void testGetBuildAnalysis() {
+        expectLoggingStartedAndStoped();
+        expectInitScriptsExecuted();
+        expectSettingsBuilt();
+        expectBuildListenerCallbacks();
+        context.checking(new Expectations() {{
+            one(buildLoaderMock).load(expectedRootProjectDescriptor, gradleMock, testGradleProperties);
+            one(buildConfigurerMock).process(expectedRootProject);
+        }});
+        BuildResult buildResult = gradleLauncher.getBuildAnalysis();
+        assertThat(buildResult.getFailure(), nullValue());
+        assertThat(buildResult.getGradle(), sameInstance((Object) gradleMock));
+    }
+
+    @Test
+    public void testGetBuildAnalysisWithFailure() {
+        final RuntimeException exception = new RuntimeException();
+        final RuntimeException transformedException = new RuntimeException();
+        expectLoggingStartedAndStoped();
+        expectInitScriptsExecuted();
+        expectSettingsBuilt();
+        context.checking(new Expectations() {{
+            one(buildBroadcaster).buildStarted(gradleMock);
+            one(buildLoaderMock).load(expectedRootProjectDescriptor, gradleMock, testGradleProperties);
+            will(throwException(exception));
+            one(exceptionAnalyserMock).transform(exception);
+            will(returnValue(transformedException));
+            one(buildBroadcaster).buildFinished(with(any(BuildResult.class)));
+        }});
+        BuildResult buildResult = gradleLauncher.getBuildAnalysis();
+        assertThat(buildResult.getGradle(), sameInstance((Object) gradleMock));
+        assertThat((RuntimeException) buildResult.getFailure(), sameInstance(transformedException));
+    }
+
+    @Test
+    public void testNotifiesListenerOfBuildAnalysisStages() {
+        expectLoggingStartedAndStoped();
+        expectInitScriptsExecuted();
+        expectSettingsBuilt();
+        expectBuildListenerCallbacks();
+        context.checking(new Expectations() {{
+            one(buildLoaderMock).load(expectedRootProjectDescriptor, gradleMock, testGradleProperties);
+            one(buildConfigurerMock).process(expectedRootProject);
+        }});
+
+        gradleLauncher.getBuildAnalysis();
+    }
+
+    @Test
+    public void testNotifiesListenerOfBuildStages() {
+        expectLoggingStartedAndStoped();
+        expectInitScriptsExecuted();
+        expectSettingsBuilt();
+        expectDagBuilt();
+        expectTasksRun();
+        expectBuildListenerCallbacks();
+
+        gradleLauncher.run();
+    }
+
+    @Test
+    public void testNotifiesListenerOnSettingsInitWithFailure() {
+        final RuntimeException failure = new RuntimeException();
+        final RuntimeException transformedException = new RuntimeException();
+        expectLoggingStartedAndStoped();
+        expectInitScriptsExecuted();
+        context.checking(new Expectations() {{
+            one(buildBroadcaster).buildStarted(gradleMock);
+            one(settingsHandlerMock).findAndLoadSettings(gradleMock, gradlePropertiesLoaderMock);
+            will(throwException(failure));
+            one(exceptionAnalyserMock).transform(failure);
+            will(returnValue(transformedException));
+            one(buildBroadcaster).buildFinished(with(result(sameInstance(transformedException))));
+        }});
+
+        BuildResult buildResult = gradleLauncher.run();
+        assertThat(buildResult.getFailure(), sameInstance((Throwable) transformedException));
+    }
+
+    @Test
+    public void testNotifiesListenerOnBuildCompleteWithFailure() {
+        final RuntimeException failure = new RuntimeException();
+        final RuntimeException transformedException = new RuntimeException();
+        expectLoggingStartedAndStoped();
+        expectInitScriptsExecuted();
+        expectSettingsBuilt();
+        expectDagBuilt();
+        expectTasksRunWithFailure(failure);
+        context.checking(new Expectations() {{
+            one(buildBroadcaster).buildStarted(gradleMock);
+            one(buildBroadcaster).projectsLoaded(gradleMock);
+            one(buildBroadcaster).projectsEvaluated(gradleMock);
+            one(exceptionAnalyserMock).transform(failure);
+            will(returnValue(transformedException));
+            one(buildBroadcaster).buildFinished(with(result(sameInstance(transformedException))));
+        }});
+
+        BuildResult buildResult = gradleLauncher.run();
+        assertThat(buildResult.getFailure(), sameInstance((Throwable) transformedException));
+    }
+
+    private void expectLoggingStartedAndStoped() {
+        context.checking(new Expectations(){{
+            one(loggingManagerMock).start();
+            one(loggingManagerMock).stop();
+        }});
+    }
+
+    private void expectInitScriptsExecuted() {
+        context.checking(new Expectations() {{
+            one(initscriptHandlerMock).executeScripts(gradleMock);
+        }});
+    }
+
+    private void expectSettingsBuilt() {
+        context.checking(new Expectations() {
+            {
+                one(settingsHandlerMock).findAndLoadSettings(gradleMock, gradlePropertiesLoaderMock);
+                will(returnValue(settingsMock));
+                one(buildBroadcaster).settingsEvaluated(settingsMock);
+            }
+        });
+    }
+
+    private void expectBuildListenerCallbacks() {
+        context.checking(new Expectations() {{
+            one(buildBroadcaster).buildStarted(gradleMock);
+            one(buildBroadcaster).projectsLoaded(gradleMock);
+            one(buildBroadcaster).projectsEvaluated(gradleMock);
+            one(buildBroadcaster).buildFinished(with(result(nullValue(Throwable.class))));
+        }});
+    }
+
+    private void expectDagBuilt() {
+        context.checking(new Expectations() {
+            {
+                one(buildLoaderMock).load(expectedRootProjectDescriptor, gradleMock, testGradleProperties);
+                one(buildConfigurerMock).process(expectedRootProject);
+                one(taskExecuterMock).addTasks(expectedTasks.get(0));
+                one(taskExecuterMock).addTasks(expectedTasks.get(1));
+            }
+        });
+    }
+
+    private void expectTasksRun() {
+        context.checking(new Expectations() {
+            {
+                one(taskExecuterMock).execute();
+            }
+        });
+    }
+
+    private void expectTasksRunWithFailure(final Throwable failure) {
+        context.checking(new Expectations() {
+            {
+                one(taskExecuterMock).execute();
+                will(throwException(failure));
+            }
+        });
+    }
+
+    // todo: This test is rather weak. Make it stronger.
+    @Test
+    public void testNewInstanceFactory() {
+        StartParameter startParameter = new StartParameter();
+        GradleLauncher gradleLauncher = GradleLauncher.newInstance(startParameter);
+        assertThat(gradleLauncher, notNullValue());
+    }
+
+    private Matcher<BuildResult> result(final Matcher<? extends Throwable> exceptionMatcher) {
+        return new BaseMatcher<BuildResult>() {
+            public void describeTo(Description description) {
+                description.appendText("matching build result");
+            }
+
+            public boolean matches(Object actual) {
+                BuildResult result = (BuildResult) actual;
+                return (result.getGradle() == gradleMock) && exceptionMatcher.matches(result.getFailure());
+            }
+        };
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultGradlePropertiesLoaderTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultGradlePropertiesLoaderTest.java
new file mode 100644
index 0000000..a9eafc9
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultGradlePropertiesLoaderTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+import org.gradle.api.Project;
+import org.gradle.util.GUtil;
+import org.gradle.util.TemporaryFolder;
+import org.gradle.util.WrapUtil;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultGradlePropertiesLoaderTest {
+    private DefaultGradlePropertiesLoader gradlePropertiesLoader;
+    private File gradleUserHomeDir;
+    private File settingsDir;
+    private Map<String, String> systemProperties;
+    private Map<String, String> envProperties;
+    private Map<String, String> userHomeProperties;
+    private Map<String, String> settingsDirProperties;
+    private StartParameter startParameter;
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    @Before
+    public void setUp() {
+        gradleUserHomeDir = tmpDir.createDir("gradleUserHome");
+        settingsDir = tmpDir.createDir("settingsDir");
+        gradlePropertiesLoader = new DefaultGradlePropertiesLoader();
+        startParameter = new StartParameter();
+        startParameter.setGradleUserHomeDir(gradleUserHomeDir);
+        startParameter.setSystemPropertiesArgs(WrapUtil.toMap("systemPropArgKey", "systemPropArgValue"));
+        systemProperties = GUtil.map(
+                IGradlePropertiesLoader.SYSTEM_PROJECT_PROPERTIES_PREFIX + "systemProp1", "systemValue1");
+        envProperties = GUtil.map(
+                IGradlePropertiesLoader.ENV_PROJECT_PROPERTIES_PREFIX + "systemProp1", "envValue1",
+                IGradlePropertiesLoader.ENV_PROJECT_PROPERTIES_PREFIX + "envProp2", "envValue2");
+        writePropertyFile(gradleUserHomeDir, userHomeProperties = GUtil.map(
+                "envProp2", "userValue1",
+                "userProp2", "userValue2",
+                Project.SYSTEM_PROP_PREFIX + ".userSystemProp", "userSystemValue"));
+        writePropertyFile(settingsDir, settingsDirProperties = GUtil.map(
+                "userProp2", "settingsValue1",
+                "settingsProp2", "settingsValue2",
+                Project.SYSTEM_PROP_PREFIX + ".userSystemProp", "settingsSystemValue",
+                Project.SYSTEM_PROP_PREFIX + ".settingsSystemProp2", "settingsSystemValue2"));
+    }
+
+    private void writePropertyFile(File location, Map<String, String> propertiesMap) {
+        Properties properties = new Properties();
+        properties.putAll(propertiesMap);
+        GUtil.saveProperties(properties, new File(location, Project.GRADLE_PROPERTIES));
+    }
+
+    @Test
+    public void loadProperties() {
+        gradlePropertiesLoader.loadProperties(settingsDir, startParameter, systemProperties, envProperties);
+        assertEquals("systemValue1", gradlePropertiesLoader.getGradleProperties().get("systemProp1"));
+        assertEquals("envValue2", gradlePropertiesLoader.getGradleProperties().get("envProp2"));
+        assertEquals("userValue2", gradlePropertiesLoader.getGradleProperties().get("userProp2"));
+        assertEquals("settingsValue2", gradlePropertiesLoader.getGradleProperties().get("settingsProp2"));
+        assertEquals("userSystemValue", System.getProperty("userSystemProp"));
+        assertEquals("settingsSystemValue2", System.getProperty("settingsSystemProp2"));
+        assertEquals("systemPropArgValue", System.getProperty("systemPropArgKey"));
+    }
+
+    @Test
+    public void loadPropertiesWithNoExceptionForNonExistingUserHomeAndSettingsDir() {
+        tmpDir.getDir().deleteDir();
+        gradlePropertiesLoader.loadProperties(settingsDir, startParameter, systemProperties, envProperties);
+    }
+
+    @Test
+    public void reloadsProperties() {
+        writePropertyFile(settingsDir, GUtil.map("prop1", "value", "prop2", "value"));
+
+        File otherSettingsDir = tmpDir.createDir("otherSettingsDir");
+        writePropertyFile(otherSettingsDir, GUtil.map("prop1", "otherValue"));
+
+        gradlePropertiesLoader.loadProperties(settingsDir, startParameter, systemProperties, envProperties);
+        assertEquals("value", gradlePropertiesLoader.getGradleProperties().get("prop1"));
+        assertEquals("value", gradlePropertiesLoader.getGradleProperties().get("prop2"));
+
+        gradlePropertiesLoader.loadProperties(otherSettingsDir, startParameter, systemProperties, envProperties);
+        assertEquals("otherValue", gradlePropertiesLoader.getGradleProperties().get("prop1"));
+        assertNull(gradlePropertiesLoader.getGradleProperties().get("prop2"));
+    }
+
+    @Test
+    public void buildSystemProperties() {
+        System.setProperty("gradle-loader-test", "value");
+        assertTrue(gradlePropertiesLoader.getAllSystemProperties().containsKey("gradle-loader-test"));
+        assertEquals("value", gradlePropertiesLoader.getAllSystemProperties().get("gradle-loader-test"));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultInitScriptFinderTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultInitScriptFinderTest.java
new file mode 100644
index 0000000..d1fb305
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultInitScriptFinderTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.initialization;
+
+import org.gradle.util.GFileUtils;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.Expectations;
+import org.gradle.StartParameter;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.*;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.io.File;
+
+public class DefaultInitScriptFinderTest {
+    @Test
+    public void testFindScripts() {
+        JUnit4Mockery context = new JUnit4Mockery();
+
+        final GradleInternal gradleMock = context.mock(GradleInternal.class);
+        final StartParameter testStartParameter = new StartParameter();
+        testStartParameter.addInitScript(new File("some init script"));
+        testStartParameter.addInitScript(new File("/path/to/another init script"));
+
+        context.checking(new Expectations() {{
+            allowing(gradleMock).getStartParameter();
+            will(returnValue(testStartParameter));
+        }});
+
+        List<ScriptSource> sourceList = new DefaultInitScriptFinder().findScripts(gradleMock);
+        assertThat(getSourceFiles(sourceList), equalTo(canonicalise(testStartParameter.getInitScripts())));
+    }
+
+    private List<File> canonicalise(List<File> files) {
+        List<File> results = new ArrayList<File>();
+        for (File file : files) {
+            results.add(GFileUtils.canonicalise(file));
+        }
+        return results;
+    }
+
+    private List<File> getSourceFiles(List<ScriptSource> sources)
+    {
+        List<File> results = new ArrayList<File>(sources.size());
+        for (ScriptSource source : sources) {
+            results.add(source.getResource().getFile());
+        }
+        return results;
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultProjectDescriptorRegistryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultProjectDescriptorRegistryTest.java
new file mode 100644
index 0000000..ae46103
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultProjectDescriptorRegistryTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.initialization;
+
+import org.junit.Test;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.*;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultProjectDescriptorRegistryTest {
+    private static final File TEST_DIR = new File("testDir");
+
+    private final DefaultProjectDescriptorRegistry registry = new DefaultProjectDescriptorRegistry();
+
+    @Test
+    public void addProjectDescriptor() {
+        DefaultProjectDescriptor rootProject = new DefaultProjectDescriptor(null, "testName", TEST_DIR, registry);
+
+        registry.addProject(rootProject);
+        assertSame(rootProject, registry.getProject(rootProject.getPath()));
+        assertSame(rootProject, registry.getProject(rootProject.getProjectDir()));
+    }
+
+    @Test
+    public void changeProjectDescriptorPath() {
+        DefaultProjectDescriptor project = new DefaultProjectDescriptor(null, "name", TEST_DIR, registry);
+        registry.addProject(project);
+
+        registry.changeDescriptorPath(":", ":newPath");
+        assertThat(registry.getProject(":"), nullValue());
+        assertThat(registry.getProject(":newPath"), sameInstance(project));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultProjectDescriptorTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultProjectDescriptorTest.java
new file mode 100644
index 0000000..890474d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultProjectDescriptorTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.initialization;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import org.gradle.api.Project;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.Expectations;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(org.jmock.integration.junit4.JMock.class)
+public class DefaultProjectDescriptorTest {
+    private DefaultProjectDescriptor projectDescriptor;
+    private DefaultProjectDescriptor parentProjectDescriptor;
+    private static final String TEST_NAME = "testName";
+    private static final File TEST_DIR = new File("testDir");
+    private DefaultProjectDescriptorRegistry testProjectDescriptorRegistry;
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    @Before
+    public void setUp() {
+        testProjectDescriptorRegistry = new DefaultProjectDescriptorRegistry();
+        parentProjectDescriptor = new DefaultProjectDescriptor(null, "somename", new File("somefile"),
+                testProjectDescriptorRegistry);
+        projectDescriptor = new DefaultProjectDescriptor(parentProjectDescriptor, TEST_NAME, TEST_DIR,
+                testProjectDescriptorRegistry);
+    }
+
+    @Test
+    public void init() throws IOException {
+        assertSame(parentProjectDescriptor, projectDescriptor.getParent());
+        assertEquals(1, parentProjectDescriptor.getChildren().size());
+        assertTrue(parentProjectDescriptor.getChildren().contains(projectDescriptor));
+        assertSame(testProjectDescriptorRegistry, projectDescriptor.getProjectDescriptorRegistry());
+        assertEquals(TEST_NAME, projectDescriptor.getName());
+        assertEquals(TEST_DIR.getCanonicalFile(), projectDescriptor.getProjectDir());
+        assertEquals(Project.DEFAULT_BUILD_FILE, projectDescriptor.getBuildFileName());
+        checkPath();
+    }
+
+    private void checkPath() {
+        assertEquals(Project.PATH_SEPARATOR, parentProjectDescriptor.getPath());
+        assertEquals(Project.PATH_SEPARATOR + projectDescriptor.getName(), projectDescriptor.getPath());
+    }
+
+    @Test
+    public void setName() {
+        final String newName = "newName";
+        final IProjectDescriptorRegistry projectDescriptorRegistryMock = context.mock(IProjectDescriptorRegistry.class);
+        projectDescriptor.setProjectDescriptorRegistry(projectDescriptorRegistryMock);
+        context.checking(new Expectations() {{
+            one(projectDescriptorRegistryMock).changeDescriptorPath(projectDescriptor.getPath(),
+                    Project.PATH_SEPARATOR + newName);
+        }});
+        projectDescriptor.setName(newName);
+        assertEquals(newName, projectDescriptor.getName());
+    }
+
+    @Test
+    public void buildFileIsBuiltFromBuildFileNameAndProjectDir() throws IOException {
+        projectDescriptor.setBuildFileName("project.gradle");
+        assertEquals(new File(TEST_DIR, "project.gradle").getCanonicalFile(), projectDescriptor.getBuildFile());
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultSettingsFinderTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultSettingsFinderTest.java
new file mode 100644
index 0000000..dcb3acb
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultSettingsFinderTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+import org.gradle.groovy.scripts.StringScriptSource;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.WrapUtil;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(org.jmock.integration.junit4.JMock.class)
+public class DefaultSettingsFinderTest {
+    private static final StartParameter TEST_START_PARAMETER = new StartParameter();
+    private static final File TEST_SETTINGSFILE = new File("parent", "testFile1");
+    private DefaultSettingsFinder defaultSettingsFinder;
+    private ISettingsFileSearchStrategy searchStrategyMock1;
+    private ISettingsFileSearchStrategy searchStrategyMock2;
+
+    private JUnit4Mockery context = new JUnit4Mockery();
+
+    @Before
+    public void setUp() {
+        searchStrategyMock1 = context.mock(ISettingsFileSearchStrategy.class, "strategy1");
+        searchStrategyMock2 = context.mock(ISettingsFileSearchStrategy.class, "strategy2");
+        defaultSettingsFinder = new DefaultSettingsFinder(WrapUtil.toList(searchStrategyMock1, searchStrategyMock2));
+    }
+
+    @Test
+    public void testFindWithStrategy1() {
+        context.checking(new Expectations() {{
+            allowing(searchStrategyMock1).find(TEST_START_PARAMETER);
+            will(returnValue(TEST_SETTINGSFILE));
+        }});
+        SettingsLocation settingsLocation = defaultSettingsFinder.find(TEST_START_PARAMETER);
+        assertEquals(TEST_SETTINGSFILE.getParentFile(), settingsLocation.getSettingsDir());
+        assertEquals(GFileUtils.canonicalise(TEST_SETTINGSFILE), settingsLocation.getSettingsScriptSource().getResource().getFile());
+    }
+
+    @Test
+    public void testFindWithStrategy2() {
+        context.checking(new Expectations() {{
+            allowing(searchStrategyMock1).find(TEST_START_PARAMETER);
+            will(returnValue(null));
+            allowing(searchStrategyMock2).find(TEST_START_PARAMETER);
+            will(returnValue(TEST_SETTINGSFILE));
+        }});
+        SettingsLocation settingsLocation = defaultSettingsFinder.find(TEST_START_PARAMETER);
+        assertEquals(TEST_SETTINGSFILE.getParentFile(), settingsLocation.getSettingsDir());
+        assertEquals(GFileUtils.canonicalise(TEST_SETTINGSFILE), settingsLocation.getSettingsScriptSource().getResource().getFile());
+    }
+
+    @Test
+    public void testNotFound() {
+        context.checking(new Expectations() {{
+            allowing(searchStrategyMock1).find(TEST_START_PARAMETER);
+            will(returnValue(null));
+            allowing(searchStrategyMock2).find(TEST_START_PARAMETER);
+            will(returnValue(null));
+        }});
+        SettingsLocation settingsLocation = defaultSettingsFinder.find(TEST_START_PARAMETER);
+        assertEquals(TEST_START_PARAMETER.getCurrentDir(), settingsLocation.getSettingsDir());
+        assertThat(settingsLocation.getSettingsScriptSource(), instanceOf(StringScriptSource.class));
+        assertThat(settingsLocation.getSettingsScriptSource().getResource().getText(), equalTo(""));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultSettingsTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultSettingsTest.groovy
new file mode 100644
index 0000000..2f762df
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/DefaultSettingsTest.groovy
@@ -0,0 +1,176 @@
+/*
+ * 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.initialization
+
+import org.gradle.StartParameter
+import org.gradle.api.Project
+import org.gradle.api.UnknownProjectException
+import org.gradle.api.initialization.ProjectDescriptor
+import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.jmock.lib.legacy.ClassImposteriser
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.junit.Assert.*
+import org.gradle.api.internal.GradleInternal
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith (JMock)
+class DefaultSettingsTest {
+    File settingsDir
+    StartParameter startParameter
+    URLClassLoader expectedClassLoader
+    Map gradleProperties
+    ScriptSource scriptSourceMock
+    GradleInternal gradleMock
+    DefaultSettings settings
+    JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    DefaultProjectDescriptorRegistry projectDescriptorRegistry
+
+    @Before public void setUp() {
+        context.setImposteriser(ClassImposteriser.INSTANCE)
+        settingsDir = new File('/somepath/root').absoluteFile
+        gradleProperties = [someGradleProp: 'someValue']
+        startParameter = new StartParameter(currentDir: new File(settingsDir, 'current'), gradleUserHomeDir: new File('gradleUserHomeDir'))
+        expectedClassLoader = new URLClassLoader(new URL[0])
+
+        scriptSourceMock = context.mock(ScriptSource)
+        gradleMock = context.mock(GradleInternal)
+
+        projectDescriptorRegistry = new DefaultProjectDescriptorRegistry()
+        settings = new DefaultSettings(gradleMock, projectDescriptorRegistry, expectedClassLoader, settingsDir, scriptSourceMock, startParameter)
+    }
+
+    @Test public void testSettings() {
+        assert settings.startParameter.is(startParameter)
+        assertSame(settings, settings.getSettings())
+        assertEquals(settingsDir, settings.getSettingsDir())
+
+        assertNull(settings.getRootProject().getParent())
+        assertEquals(settingsDir, settings.getRootProject().getProjectDir())
+        assertEquals(settings.getRootProject().getProjectDir().getName(), settings.getRootProject().getName())
+        assertEquals(settings.rootProject.buildFileName, Project.DEFAULT_BUILD_FILE);
+        assertSame(gradleMock, settings.gradle)
+    }
+
+    @Test public void testInclude() {
+        ProjectDescriptor rootProjectDescriptor = settings.getRootProject();
+        String projectA = "a"
+        String projectB = "b"
+        String projectC = "c"
+        String projectD = "d"
+        settings.include([projectA, "$projectB:$projectC"] as String[])
+
+        assertEquals(2, rootProjectDescriptor.getChildren().size())
+        testDescriptor(settings.project(":$projectA"), projectA, new File(settingsDir, projectA))
+        testDescriptor(settings.project(":$projectB"), projectB, new File(settingsDir, projectB))
+
+        assertEquals(1, settings.project(":$projectB").getChildren().size())
+        testDescriptor(settings.project(":$projectB:$projectC"), projectC, new File(settingsDir, "$projectB/$projectC"))
+    }
+
+    @Test public void testIncludeFlat() {
+        ProjectDescriptor rootProjectDescriptor = settings.getRootProject();
+        String projectA = "a"
+        String projectB = "b"
+        String[] paths = [projectA, projectB]
+        settings.includeFlat(paths)
+        assertEquals(2, rootProjectDescriptor.getChildren().size())
+        testDescriptor(settings.project(":" + projectA), projectA, new File(settingsDir.parentFile, projectA))
+        testDescriptor(settings.project(":" + projectB), projectB, new File(settingsDir.parentFile, projectB))
+    }
+
+    private void testDescriptor(DefaultProjectDescriptor descriptor, String name, File projectDir) {
+        assertEquals(name, descriptor.getName(), descriptor.getName())
+        assertEquals(projectDir, descriptor.getProjectDir())
+    }
+
+    @Test public void testCreateProjectDescriptor() {
+        String testName = "testname"
+        File testDir = new File("testDir")
+        DefaultProjectDescriptor projectDescriptor = settings.createProjectDescriptor(settings.getRootProject(), testName, testDir)
+        assertSame(settings.getRootProject(), projectDescriptor.getParent())
+        assertSame(settings.getProjectDescriptorRegistry(), projectDescriptor.getProjectDescriptorRegistry())
+        assertEquals(testName, projectDescriptor.getName())
+        assertEquals(testDir.canonicalFile, projectDescriptor.getProjectDir())
+    }
+
+    @Test public void testFindDescriptorByPath() {
+        DefaultProjectDescriptor projectDescriptor = createTestDescriptor();
+        DefaultProjectDescriptor foundProjectDescriptor = settings.project(projectDescriptor.getPath())
+        assertSame(foundProjectDescriptor, projectDescriptor)
+    }
+
+    @Test public void testFindDescriptorByProjectDir() {
+        DefaultProjectDescriptor projectDescriptor = createTestDescriptor()
+        DefaultProjectDescriptor foundProjectDescriptor = settings.project(projectDescriptor.getProjectDir())
+        assertSame(foundProjectDescriptor, projectDescriptor)
+    }
+
+    @Test (expected = UnknownProjectException) public void testDescriptorByPath() {
+        DefaultProjectDescriptor projectDescriptor = createTestDescriptor()
+        DefaultProjectDescriptor foundProjectDescriptor = settings.project(projectDescriptor.getPath())
+        assertSame(foundProjectDescriptor, projectDescriptor)
+        settings.project("unknownPath")
+    }
+
+
+    @Test (expected = UnknownProjectException) public void testDescriptorByProjectDir() {
+        DefaultProjectDescriptor projectDescriptor = createTestDescriptor()
+        DefaultProjectDescriptor foundProjectDescriptor = settings.project(projectDescriptor.getProjectDir())
+        assertSame(foundProjectDescriptor, projectDescriptor)
+        settings.project(new File("unknownPath"))
+    }
+
+    private DefaultProjectDescriptor createTestDescriptor() {
+        String testName = "testname"
+        File testDir = new File("testDir")
+        return settings.createProjectDescriptor(settings.getRootProject(), testName, testDir)
+    }
+
+    private Map createTestRepoArgs() {
+        return [name: 'someName']
+    }
+
+    @Test public void testCreateClassLoader() {
+        StartParameter expectedStartParameter = settings.startParameter.newInstance()
+        expectedStartParameter.setCurrentDir(new File(settingsDir, DefaultSettings.DEFAULT_BUILD_SRC_DIR))
+        URLClassLoader createdClassLoader = settings.getClassLoader()
+        assertSame(createdClassLoader, expectedClassLoader)
+    }
+
+    @Test public void testCanGetAndSetDynamicProperties() {
+        settings.dynamicProp = 'value'
+        assertEquals('value', settings.dynamicProp)
+    }
+
+    @Test (expected = MissingPropertyException) public void testPropertyMissing() {
+        settings.unknownProp
+    }
+
+    @Test public void testGetRootDir() {
+        assertEquals(settingsDir, settings.rootDir);
+    }
+
+    @Test public void testHasUsefulToString() {
+        assertEquals('settings \'root\'', settings.toString())
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/EmbeddedScriptSettingsFinderTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/EmbeddedScriptSettingsFinderTest.java
new file mode 100644
index 0000000..7f34671
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/EmbeddedScriptSettingsFinderTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+import org.gradle.groovy.scripts.ScriptSource;
+import static org.hamcrest.Matchers.*;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.Expectations;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+
+ at RunWith(org.jmock.integration.junit4.JMock.class)
+public class EmbeddedScriptSettingsFinderTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final ISettingsFinder delegate = context.mock(ISettingsFinder.class);
+    private final ScriptSource settingsScriptSource = context.mock(ScriptSource.class);
+    private final EmbeddedScriptSettingsFinder settingsFinder = new EmbeddedScriptSettingsFinder(delegate);
+
+    @Test
+    public void usesProvidedScriptAsSettingsFileWhenSettingsFileSpecifiedInStartParam() {
+        StartParameter parameter = new StartParameter();
+        parameter.setSettingsScriptSource(settingsScriptSource);
+
+        SettingsLocation settingsLocation = settingsFinder.find(parameter);
+
+        assertThat(settingsLocation.getSettingsScriptSource(), sameInstance(settingsScriptSource));
+    }
+
+    @Test
+    public void usesCurrentDirAsSettingsDirWhenSettingsFileSpecifiedInStartParam() throws IOException {
+        StartParameter parameter = new StartParameter();
+        File currentDir = new File("current dir");
+
+        parameter.setSettingsScriptSource(settingsScriptSource);
+        parameter.setCurrentDir(currentDir);
+
+        SettingsLocation settingsLocation = settingsFinder.find(parameter);
+
+        assertThat(settingsLocation.getSettingsDir(), equalTo(currentDir.getCanonicalFile()));
+    }
+
+    @Test
+    public void delegatesWhenSettingsFileNotSpecifiedInStartParam() {
+        final StartParameter parameter = new StartParameter();
+        final File settingsDir = new File("settings dir");
+
+        context.checking(new Expectations() {{
+            one(delegate).find(parameter);
+            will(returnValue(new SettingsLocation(settingsDir, settingsScriptSource)));
+        }});
+
+        SettingsLocation settingsLocation = settingsFinder.find(parameter);
+
+        assertThat(settingsLocation.getSettingsDir(), sameInstance(settingsDir));
+        assertThat(settingsLocation.getSettingsScriptSource(), sameInstance(settingsScriptSource));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/ExceptionDecoratingClassGeneratorTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/ExceptionDecoratingClassGeneratorTest.groovy
new file mode 100644
index 0000000..64209d7
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/ExceptionDecoratingClassGeneratorTest.groovy
@@ -0,0 +1,123 @@
+/*
+ * 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.initialization
+
+
+import static org.junit.Assert.*
+import static org.hamcrest.Matchers.*
+import org.junit.Test
+import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.api.GradleException
+import org.gradle.api.GradleScriptException
+import org.gradle.api.ScriptCompilationException
+import org.gradle.api.internal.Contextual
+import org.gradle.api.LocationAwareException
+
+public class ExceptionDecoratingClassGeneratorTest {
+    private final ScriptSource script = [getDisplayName: {"description"}] as ScriptSource
+    private final ExceptionDecoratingClassGenerator generator = new ExceptionDecoratingClassGenerator()
+
+    @Test
+    public void mixesLocationAwareIntoException() {
+        RuntimeException cause = new RuntimeException()
+        GradleException target = new GradleException("message", cause)
+
+        GradleException exception = generator.newInstance(GradleException.class, target, script, 12)
+        assertThat(exception.getClass().getPackage(), equalTo(GradleException.class.getPackage()))
+        assertThat(exception.getClass().simpleName, equalTo('LocationAwareGradleException'))
+        assertThat(exception, instanceOf(LocationAwareException))
+        assertThat(exception.originalMessage, equalTo("message"))
+        assertThat(exception.originalException, sameInstance(target))
+        assertThat(exception.cause, sameInstance(cause))
+        assertThat(exception.scriptSource, sameInstance(script))
+        assertThat(exception.lineNumber, equalTo(12))
+        assertThat(exception.stackTrace, equalTo(target.stackTrace))
+    }
+
+    @Test
+    public void cachesGeneratedType() {
+        assertSame(generator.generate(GradleException.class), generator.generate(GradleException.class))
+    }
+
+    @Test
+    public void messageIncludesSourceFileAndLineNumber() {
+        GradleException target = new GradleException("<message>")
+        GradleException exception = generator.newInstance(GradleException.class, target, script, 91)
+        assertThat(exception.location, equalTo("Description line: 91"));
+        assertThat(exception.message, equalTo(String.format("Description line: 91%n<message>")));
+    }
+
+    @Test
+    public void handlesExceptionWithNoLineNumber() {
+        GradleException target = new GradleException("<message>")
+        GradleException exception = generator.newInstance(GradleException.class, target, script, null)
+        assertThat(exception.location, equalTo("Description"));
+        assertThat(exception.message, equalTo(String.format("Description%n<message>")));
+    }
+
+    @Test
+    public void handlesExceptionWithNoLocation() {
+        GradleException target = new GradleException("<message>")
+        GradleException exception = generator.newInstance(GradleException.class, target, null, null)
+        assertThat(exception.location, nullValue());
+        assertThat(exception.message, equalTo("<message>"));
+    }
+
+    @Test
+    public void handlesExceptionWithNoMessage() {
+        GradleException target = new GradleException()
+        GradleException exception = generator.newInstance(GradleException.class, target, script, 91)
+        assertThat(exception.location, equalTo("Description line: 91"));
+        assertThat(exception.message, equalTo(String.format("Description line: 91")));
+    }
+
+    @Test
+    public void usesAllCauseExceptionsWhichAreContextualAsReportableCauses() {
+        RuntimeException actualCause = new RuntimeException(new Throwable());
+        ContextualException contextualCause = new ContextualException(actualCause);
+        ContextualException outerContextualCause = new ContextualException(contextualCause);
+        GradleException target = new GradleException('fail', outerContextualCause)
+        GradleException exception = generator.newInstance(GradleException.class, target, script, 91)
+        assertThat(exception.reportableCauses, equalTo([outerContextualCause, contextualCause, actualCause]));
+    }
+
+    @Test
+    public void usesDirectCauseAsReportableCauseWhenNoContextualCausesPresent() {
+        RuntimeException actualCause = new RuntimeException(new Throwable());
+        GradleException target = new GradleException('fail', actualCause)
+        GradleException exception = generator.newInstance(GradleException.class, target, script, 91)
+        assertThat(exception.reportableCauses, equalTo([actualCause]));
+    }
+
+    @Test
+    public void handlesTaskWithCopyConstructor() {
+        ScriptCompilationException target = new ScriptCompilationException("message", new RuntimeException(), script, 12)
+
+        GradleScriptException exception = generator.newInstance(ScriptCompilationException.class, target, null, null)
+        assertThat(exception.originalException, sameInstance(target))
+    }
+}
+
+ at Contextual
+class ContextualException extends RuntimeException {
+    private ContextualException() {
+    }
+
+    private ContextualException(Throwable throwable) {
+        super(throwable);
+    }
+}
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/InitScriptHandlerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/InitScriptHandlerTest.java
new file mode 100644
index 0000000..4cddead
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/InitScriptHandlerTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.initialization;
+
+import org.junit.Test;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.Expectations;
+import org.gradle.configuration.InitScriptProcessor;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.api.internal.GradleInternal;
+
+import java.util.List;
+import java.util.ArrayList;
+
+public class InitScriptHandlerTest {
+
+    @Test
+    public void testExecuteScripts() {
+        JUnit4Mockery context = new JUnit4Mockery();
+
+        final InitScriptFinder finderMock = context.mock(InitScriptFinder.class);
+        final InitScriptProcessor processorMock = context.mock(InitScriptProcessor.class);
+        final GradleInternal gradleMock = context.mock(GradleInternal.class);
+        final ScriptSource source1Mock = context.mock(ScriptSource.class, "source 1");
+        final ScriptSource source2Mock = context.mock(ScriptSource.class, "source 2");
+        final List<ScriptSource> testSources = new ArrayList<ScriptSource>();
+        testSources.add(source1Mock);
+        testSources.add(source2Mock);
+
+        context.checking(new Expectations() {{
+            one(finderMock).findScripts(gradleMock);
+            will(returnValue(testSources));
+            one(processorMock).process(source1Mock, gradleMock);
+            one(processorMock).process(source2Mock, gradleMock);
+        }});
+
+        InitScriptHandler handler = new InitScriptHandler(finderMock, processorMock);
+        handler.executeScripts(gradleMock);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/MasterDirSettingsFinderStrategyTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/MasterDirSettingsFinderStrategyTest.java
new file mode 100644
index 0000000..83a2452
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/MasterDirSettingsFinderStrategyTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.initialization;
+
+import static org.junit.Assert.assertEquals;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class MasterDirSettingsFinderStrategyTest extends AbstractSettingsFinderStrategyTest {
+
+    private MasterDirSettingsFinderStrategy masterDirSettingsFinderStrategy;
+    private File masterDir;
+
+    protected ISettingsFileSearchStrategy getStrategy() {
+        return masterDirSettingsFinderStrategy;
+    }
+
+    @Before
+    public void setUp() {
+        masterDirSettingsFinderStrategy = new MasterDirSettingsFinderStrategy();
+        masterDir = new File(testDir, MasterDirSettingsFinderStrategy.MASTER_DIR_NAME);
+        masterDir.mkdirs();
+        currentDir = new File(testDir, "current");
+        currentDir.mkdir();
+    }
+
+    @Test
+    public void findExistingSettingsInMasterDirWithSearchUpwardsFalse() {
+        createSettingsFile(masterDir);
+        assertEquals(null, masterDirSettingsFinderStrategy.find(createStartParams(false)));
+    }
+
+    @Test
+    public void findExistingSettingsInMasterDirWithSearchUpwardsTrue() {
+        assertEquals(createSettingsFile(masterDir), masterDirSettingsFinderStrategy.find(createStartParams(true)));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/NestedBuildTrackerTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/NestedBuildTrackerTest.groovy
new file mode 100644
index 0000000..af2b834
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/NestedBuildTrackerTest.groovy
@@ -0,0 +1,75 @@
+/*
+ * 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.initialization
+
+
+import org.gradle.util.JUnit4GroovyMockery;
+import org.jmock.integration.junit4.JMock
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.gradle.api.invocation.Gradle
+import org.gradle.BuildResult
+
+ at RunWith(JMock.class)
+class NestedBuildTrackerTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final NestedBuildTracker tracker = new NestedBuildTracker()
+
+    @Test
+    public void noCurrentBuildByDefault() {
+        assertThat(tracker.currentBuild, nullValue())
+    }
+
+    @Test
+    public void setsCurrentBuildWhenBuildStartsAndStops() {
+        def build = context.mock(Gradle.class, 'build1')
+        def build2 = context.mock(Gradle.class, 'build2')
+
+        tracker.buildStarted(build)
+        assertThat(tracker.currentBuild, sameInstance(build))
+
+        tracker.buildFinished(new BuildResult(build, null))
+        assertThat(tracker.currentBuild, nullValue())
+
+        tracker.buildStarted(build2)
+        assertThat(tracker.currentBuild, sameInstance(build2))
+
+        tracker.buildFinished(new BuildResult(build2, null))
+        assertThat(tracker.currentBuild, nullValue())
+    }
+
+    @Test
+    public void pushesBuildWhenBuildStartsWhileOneIsCurrentlyRunning() {
+        def build = context.mock(Gradle.class, 'build1')
+        def build2 = context.mock(Gradle.class, 'build2')
+
+        tracker.buildStarted(build)
+        assertThat(tracker.currentBuild, sameInstance(build))
+
+        tracker.buildStarted(build2)
+        assertThat(tracker.currentBuild, sameInstance(build2))
+
+        tracker.buildFinished(new BuildResult(build2, null))
+        assertThat(tracker.currentBuild, sameInstance(build))
+
+        tracker.buildFinished(new BuildResult(build, null))
+        assertThat(tracker.currentBuild, nullValue())
+    }
+}
+
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/ParentDirSettingsFinderStrategyTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/ParentDirSettingsFinderStrategyTest.java
new file mode 100644
index 0000000..5a3fb47
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/ParentDirSettingsFinderStrategyTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.initialization;
+
+import static org.junit.Assert.assertEquals;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class ParentDirSettingsFinderStrategyTest extends AbstractSettingsFinderStrategyTest {
+    private ParentDirSettingsFinderStrategy parentDirSettingsFinderStrategy;
+    private File rootDir;
+
+    protected ISettingsFileSearchStrategy getStrategy() {
+        return parentDirSettingsFinderStrategy;
+    }
+
+    @Before
+    public void setUp() {
+        parentDirSettingsFinderStrategy = new ParentDirSettingsFinderStrategy();
+        rootDir = new File(testDir, "root");
+        rootDir.mkdirs();
+        currentDir = new File(rootDir, "current");
+        currentDir.mkdir();
+    }
+
+    @Test
+    public void findExistingSettingsInMasterDirWithSearchUpwardsFalse() {
+        createSettingsFile(rootDir);
+        assertEquals(null, parentDirSettingsFinderStrategy.find(createStartParams(false)));
+    }
+
+    @Test
+    public void findExistingSettingsInMasterDirWithSearchUpwardsTrue() {
+        assertEquals(createSettingsFile(rootDir), parentDirSettingsFinderStrategy.find(createStartParams(true)));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/ProjectDirectoryProjectSpecTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/ProjectDirectoryProjectSpecTest.java
new file mode 100644
index 0000000..7070c4f
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/ProjectDirectoryProjectSpecTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.api.internal.project.ProjectIdentifier;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.TemporaryFolder;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+ at RunWith(JMock.class)
+public class ProjectDirectoryProjectSpecTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+    private final File dir = tmpDir.createDir("build");
+    private final ProjectDirectoryProjectSpec spec = new ProjectDirectoryProjectSpec(dir);
+    private int counter;
+
+    @Test
+    public void containsMatchWhenAtLeastOneProjectHasSpecifiedProjectDir() {
+        assertFalse(spec.containsProject(registry()));
+        assertFalse(spec.containsProject(registry(project(new File("other")))));
+
+        assertTrue(spec.containsProject(registry(project(dir))));
+        assertTrue(spec.containsProject(registry(project(dir), project(new File("other")))));
+        assertTrue(spec.containsProject(registry(project(dir), project(dir))));
+    }
+
+    @Test
+    public void selectsSingleProjectWhichHasSpecifiedProjectDir() {
+        ProjectIdentifier project = project(dir);
+        assertThat(spec.selectProject(registry(project, project(new File("other")))), sameInstance(project));
+    }
+
+    @Test
+    public void selectProjectFailsWhenNoProjectHasSpecifiedProjectDir() {
+        try {
+            spec.selectProject(registry());
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), equalTo("No projects in this build have project directory '" + dir + "'."));
+        }
+    }
+
+    @Test
+    public void selectProjectFailsWhenMultipleProjectsHaveSpecifiedProjectDir() {
+        ProjectIdentifier project1 = project(dir);
+        ProjectIdentifier project2 = project(dir);
+        try {
+            spec.selectProject(registry(project1, project2));
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), startsWith("Multiple projects in this build have project directory '" + dir + "':"));
+        }
+    }
+
+    @Test
+    public void cannotSelectProjectWhenBuildFileIsNotAFile() {
+        dir.delete();
+
+        try {
+            spec.containsProject(registry());
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), equalTo("Project directory '" + dir + "' does not exist."));
+        }
+
+        GFileUtils.writeStringToFile(dir, "file");
+
+        try {
+            spec.containsProject(registry());
+            fail();
+        } catch (InvalidUserDataException e) {
+            assertThat(e.getMessage(), equalTo("Project directory '" + dir + "' is not a directory."));
+        }
+    }
+
+    private IProjectRegistry<ProjectIdentifier> registry(final ProjectIdentifier... projects) {
+        final IProjectRegistry<ProjectIdentifier> registry = context.mock(IProjectRegistry.class, String.valueOf(counter++));
+        context.checking(new Expectations(){{
+            allowing(registry).getAllProjects();
+            will(returnValue(toSet(projects)));
+        }});
+        return registry;
+    }
+
+    private ProjectIdentifier project(final File projectDir) {
+        final ProjectIdentifier projectIdentifier = context.mock(ProjectIdentifier.class, String.valueOf(counter++));
+        context.checking(new Expectations(){{
+            allowing(projectIdentifier).getProjectDir();
+            will(returnValue(projectDir));
+        }});
+        return projectIdentifier;
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessorTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessorTest.java
new file mode 100644
index 0000000..b564131
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/PropertiesLoadingSettingsProcessorTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.initialization;
+
+import org.gradle.api.internal.GradleInternal;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.Expectations;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.gradle.StartParameter;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.api.internal.SettingsInternal;
+
+import java.io.File;
+import java.net.URLClassLoader;
+import java.net.URL;
+
+ at RunWith(JMock.class)
+public class PropertiesLoadingSettingsProcessorTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+
+    @Test
+    public void loadsPropertiesThenDelegatesToBackingSettingsProcessor() {
+        final SettingsProcessor delegate = context.mock(SettingsProcessor.class);
+        final URLClassLoader urlClassLoader = new URLClassLoader(new URL[0]);
+        final IGradlePropertiesLoader propertiesLoader = context.mock(IGradlePropertiesLoader.class);
+        final StartParameter startParameter = new StartParameter();
+        final SettingsInternal settings = context.mock(SettingsInternal.class);
+        final File settingsDir = new File("root");
+        final ScriptSource settingsScriptSource = context.mock(ScriptSource.class);
+        final GradleInternal gradle = context.mock(GradleInternal.class);
+        final SettingsLocation settingsLocation = new SettingsLocation(settingsDir, settingsScriptSource);
+
+        PropertiesLoadingSettingsProcessor processor = new PropertiesLoadingSettingsProcessor(delegate);
+
+        context.checking(new Expectations() {{
+            one(propertiesLoader).loadProperties(settingsDir, startParameter);
+            one(delegate).process(gradle, settingsLocation, urlClassLoader, startParameter, propertiesLoader);
+            will(returnValue(settings));
+        }});
+
+        assertThat(processor.process(gradle, settingsLocation, urlClassLoader, startParameter, propertiesLoader), sameInstance(settings));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/SameLevelDirSettingsFinderStrategyTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/SameLevelDirSettingsFinderStrategyTest.java
new file mode 100644
index 0000000..a9eda9d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/SameLevelDirSettingsFinderStrategyTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.initialization;
+
+import static org.junit.Assert.assertEquals;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class SameLevelDirSettingsFinderStrategyTest extends AbstractSettingsFinderStrategyTest {
+    private SameLevelDirSettingsFinderStrategy sameLevelDirSettingsFinderStrategy;
+    private File rootDir;
+
+    protected ISettingsFileSearchStrategy getStrategy() {
+        return sameLevelDirSettingsFinderStrategy;
+    }
+
+    @Before
+    public void setUp() {
+        sameLevelDirSettingsFinderStrategy = new SameLevelDirSettingsFinderStrategy();
+        rootDir = new File(testDir, "root");
+        rootDir.mkdirs();
+        currentDir = new File(testDir, "current");
+        currentDir.mkdir();
+    }
+
+    @Test
+    public void findExistingSettingsInMasterDirWithSearchUpwardsFalse() {
+        createSettingsFile(rootDir);
+        assertEquals(null, sameLevelDirSettingsFinderStrategy.find(createStartParams(false)));
+    }
+
+    @Test
+    public void findExistingSettingsInMasterDirWithSearchUpwardsTrue() {
+        assertEquals(createSettingsFile(rootDir), sameLevelDirSettingsFinderStrategy.find(createStartParams(true)));
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessorTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessorTest.groovy
new file mode 100644
index 0000000..00102d8
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/ScriptEvaluatingSettingsProcessorTest.groovy
@@ -0,0 +1,97 @@
+/*
+ * 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.initialization
+
+import groovy.mock.interceptor.MockFor
+import org.gradle.StartParameter
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.lib.legacy.ClassImposteriser
+import org.junit.Before
+import org.junit.Test
+import org.gradle.groovy.scripts.*
+
+import static org.junit.Assert.*
+import org.gradle.configuration.ScriptPluginFactory
+import org.gradle.configuration.ScriptPlugin
+import org.gradle.api.internal.GradleInternal
+
+/**
+ * @author Hans Dockter
+ */
+class ScriptEvaluatingSettingsProcessorTest {
+    static final File TEST_ROOT_DIR = new File('rootDir')
+    static final File TEST_CURRENT_DIR = new File('currentDir')
+    ScriptEvaluatingSettingsProcessor settingsProcessor
+    DefaultSettingsFinder expectedSettingsFinder
+    SettingsFactory settingsFactory
+    StartParameter expectedStartParameter
+    DefaultSettings expectedSettings
+    MockFor settingsFactoryMocker
+    ScriptSource scriptSourceMock
+    IGradlePropertiesLoader propertiesLoaderMock
+    ScriptPluginFactory configurerFactoryMock
+    Map expectedGradleProperties
+    URLClassLoader urlClassLoader
+    GradleInternal gradleMock
+
+    JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+
+    @Before public void setUp() {
+        context.setImposteriser(ClassImposteriser.INSTANCE)
+        configurerFactoryMock = context.mock(ScriptPluginFactory)
+        settingsFactory = context.mock(SettingsFactory)
+        settingsProcessor = new ScriptEvaluatingSettingsProcessor(configurerFactoryMock, settingsFactory)
+        expectedSettingsFinder = new DefaultSettingsFinder()
+        scriptSourceMock = context.mock(ScriptSource)
+        gradleMock = context.mock(GradleInternal)
+        expectedStartParameter = new StartParameter()
+        expectedGradleProperties = [a: 'b']
+        propertiesLoaderMock = [getGradleProperties: { expectedGradleProperties }] as IGradlePropertiesLoader
+        urlClassLoader = new URLClassLoader(new URL[0]);
+        initExpectedSettings()
+    }
+
+    private void initExpectedSettings() {
+        expectedSettings = new DefaultSettings()
+        DefaultProjectDescriptorRegistry projectDescriptorRegistry = new DefaultProjectDescriptorRegistry()
+        expectedSettings.setRootProjectDescriptor(new DefaultProjectDescriptor(null, TEST_ROOT_DIR.name,
+                TEST_ROOT_DIR, projectDescriptorRegistry))
+        expectedSettings.setProjectDescriptorRegistry(projectDescriptorRegistry)
+        expectedSettings.setStartParameter(expectedStartParameter)
+        context.checking {
+            one(settingsFactory).createSettings(gradleMock, TEST_ROOT_DIR, scriptSourceMock, expectedGradleProperties, expectedStartParameter, urlClassLoader)
+            will(returnValue(expectedSettings))
+        }
+    }
+
+    @Test public void testProcessWithSettingsFile() {
+        expectedStartParameter.setCurrentDir(TEST_ROOT_DIR)
+        ScriptPlugin configurerMock = context.mock(ScriptPlugin)
+
+        context.checking {
+            one(configurerFactoryMock).create(scriptSourceMock)
+            will(returnValue(configurerMock))
+
+            one(configurerMock).setClassLoader(urlClassLoader)
+            one(configurerMock).setScriptBaseClass(SettingsScript)
+            one(configurerMock).apply(expectedSettings)
+        }
+        
+        SettingsLocation settingsLocation = new SettingsLocation(TEST_ROOT_DIR, scriptSourceMock)
+        assertSame(expectedSettings, settingsProcessor.process(gradleMock, settingsLocation, urlClassLoader, expectedStartParameter, propertiesLoaderMock))
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/SettingsFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/SettingsFactoryTest.java
new file mode 100644
index 0000000..cc435c5
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/SettingsFactoryTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.util.WrapUtil;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.Map;
+import java.net.URLClassLoader;
+import java.net.URL;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(org.jmock.integration.junit4.JMock.class)
+public class SettingsFactoryTest {
+    private JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+
+    @Test
+    public void createSettings() {
+        final File expectedSettingsDir = new File("settingsDir");
+        ScriptSource expectedScriptSource = context.mock(ScriptSource.class);
+        Map<String, String> expectedGradleProperties = WrapUtil.toMap("key", "myvalue");
+        IProjectDescriptorRegistry expectedProjectDescriptorRegistry = new DefaultProjectDescriptorRegistry();
+        StartParameter expectedStartParameter = new StartParameter();
+        SettingsFactory settingsFactory = new SettingsFactory(expectedProjectDescriptorRegistry);
+        final URLClassLoader urlClassLoader = new URLClassLoader(new URL[0]);
+        GradleInternal gradle = context.mock(GradleInternal.class);
+
+        DefaultSettings settings = (DefaultSettings) settingsFactory.createSettings(gradle,
+                expectedSettingsDir, expectedScriptSource, expectedGradleProperties, expectedStartParameter, urlClassLoader);
+
+        assertSame(gradle, settings.getGradle());
+        assertSame(expectedProjectDescriptorRegistry, settings.getProjectDescriptorRegistry());
+        assertEquals(expectedGradleProperties, settings.getAdditionalProperties());
+        assertSame(expectedSettingsDir, settings.getSettingsDir());
+        assertSame(expectedScriptSource, settings.getSettingsScript());
+        assertSame(expectedStartParameter, settings.getStartParameter());
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/SettingsHandlerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/SettingsHandlerTest.java
new file mode 100644
index 0000000..11570d5
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/SettingsHandlerTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.api.internal.SettingsInternal;
+import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.util.MultiParentClassLoader;
+import org.hamcrest.Description;
+import org.hamcrest.Factory;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class SettingsHandlerTest {
+    private JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+    private GradleInternal gradle = context.mock(GradleInternal.class);
+    private IGradlePropertiesLoader gradlePropertiesLoader = context.mock(IGradlePropertiesLoader.class);
+    private SettingsInternal settings = context.mock(SettingsInternal.class);
+    private SettingsLocation settingsLocation = new SettingsLocation(new File("someDir"), null);
+    private StartParameter startParameter = new StartParameter();
+    private URLClassLoader urlClassLoader = new URLClassLoader(new URL[0]);
+    private ISettingsFinder settingsFinder = context.mock(ISettingsFinder.class);
+    private SettingsProcessor settingsProcessor = context.mock(SettingsProcessor.class);
+    private BuildSourceBuilder buildSourceBuilder = context.mock(BuildSourceBuilder.class);
+    private MultiParentClassLoader scriptClassLoader = context.mock(MultiParentClassLoader.class);
+    private SettingsHandler settingsHandler = new SettingsHandler(settingsFinder, settingsProcessor,
+            buildSourceBuilder);
+
+    @org.junit.Test
+    public void findAndLoadSettingsWithExistingSettings() {
+        prepareForExistingSettings();
+        context.checking(new Expectations() {{
+            allowing(buildSourceBuilder).buildAndCreateClassLoader(with(aBuildSrcStartParameter(new File(
+                    settingsLocation.getSettingsDir(), BaseSettings.DEFAULT_BUILD_SRC_DIR))));
+            will(returnValue(urlClassLoader));
+        }});
+        assertThat(settingsHandler.findAndLoadSettings(gradle, gradlePropertiesLoader), sameInstance(settings));
+    }
+
+    private void prepareForExistingSettings() {
+        final ProjectSpec projectSpec = context.mock(ProjectSpec.class);
+        final IProjectRegistry projectRegistry = context.mock(IProjectRegistry.class);
+        startParameter.setDefaultProjectSelector(projectSpec);
+
+        context.checking(new Expectations() {{
+            allowing(settings).getProjectRegistry();
+            will(returnValue(projectRegistry));
+
+            allowing(settings).getClassLoader();
+            will(returnValue(urlClassLoader));
+
+            allowing(gradle).getScriptClassLoader();
+            will(returnValue(scriptClassLoader));
+
+            allowing(projectSpec).containsProject(projectRegistry);
+            will(returnValue(true));
+
+            allowing(gradle).getStartParameter();
+            will(returnValue(startParameter));
+
+            allowing(settingsFinder).find(startParameter);
+            will(returnValue(settingsLocation));
+
+            one(settingsProcessor).process(gradle, settingsLocation, urlClassLoader,
+                    startParameter, gradlePropertiesLoader);
+            will(returnValue(settings));
+
+            one(scriptClassLoader).addParent(urlClassLoader);
+        }});
+    }
+
+    @Factory
+    public static Matcher<StartParameter> aBuildSrcStartParameter(File currentDir) {
+        return new BuildSrcParameterMatcher(currentDir);
+    }
+
+    public static class BuildSrcParameterMatcher extends TypeSafeMatcher<StartParameter> {
+        private File currentDir;
+
+        public BuildSrcParameterMatcher(File currentDir) {
+            this.currentDir = currentDir;
+        }
+
+        public boolean matchesSafely(StartParameter startParameter) {
+            try {
+                return startParameter.getCurrentDir().getCanonicalFile().equals(currentDir.getCanonicalFile());
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        public void describeTo(Description description) {
+            description.appendText("a startparameter with ").appendValue(currentDir);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/UserHomeInitScriptFinderTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/UserHomeInitScriptFinderTest.java
new file mode 100644
index 0000000..981d572
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/initialization/UserHomeInitScriptFinderTest.java
@@ -0,0 +1,79 @@
+/*
+ * 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.initialization;
+
+import org.gradle.StartParameter;
+import org.gradle.api.internal.GradleInternal;
+import org.gradle.groovy.scripts.ScriptSource;
+import org.gradle.groovy.scripts.UriScriptSource;
+import org.gradle.util.TemporaryFolder;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+public class UserHomeInitScriptFinderTest {
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder();
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final GradleInternal gradleMock = context.mock(GradleInternal.class);
+    private final InitScriptFinder initScriptFinderMock = context.mock(InitScriptFinder.class);
+    private final StartParameter testStartParameter = new StartParameter();
+
+    @Before
+    public void setup() {
+        testStartParameter.setGradleUserHomeDir(tmpDir.getDir());
+        context.checking(new Expectations() {{
+            allowing(gradleMock).getStartParameter();
+            will(returnValue(testStartParameter));
+        }});
+    }
+
+    @Test
+    public void addsUserInitScriptWhenItExists() throws IOException {
+        File initScript = tmpDir.file("init.gradle").createFile();
+
+        context.checking(new Expectations() {{
+            allowing(initScriptFinderMock).findScripts(gradleMock);
+            will(returnValue(new ArrayList()));
+        }});
+
+        List<ScriptSource> sourceList = new UserHomeInitScriptFinder(initScriptFinderMock).findScripts(gradleMock);
+        assertThat(sourceList.size(), equalTo(1));
+        assertThat(sourceList.get(0), instanceOf(UriScriptSource.class));
+        assertThat(sourceList.get(0).getResource().getFile(), equalTo(initScript));
+    }
+
+    @Test
+    public void doesNotAddUserInitScriptWhenItDoesNotExist() throws IOException {
+        context.checking(new Expectations() {{
+            allowing(initScriptFinderMock).findScripts(gradleMock);
+            will(returnValue(new ArrayList()));
+        }});
+
+        List<ScriptSource> sourceList = new UserHomeInitScriptFinder(initScriptFinderMock).findScripts(gradleMock);
+        assertThat(sourceList.size(), equalTo(0));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/invocation/DefaultGradleTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/invocation/DefaultGradleTest.java
new file mode 100644
index 0000000..1dc45fe
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/invocation/DefaultGradleTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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.invocation;
+
+import groovy.lang.Closure;
+import org.gradle.StartParameter;
+import org.gradle.api.ProjectEvaluationListener;
+import org.gradle.api.initialization.dsl.ScriptHandler;
+import org.gradle.api.internal.GradleDistributionLocator;
+import org.gradle.api.internal.initialization.ScriptClassLoaderProvider;
+import org.gradle.api.internal.plugins.PluginRegistry;
+import org.gradle.api.internal.project.IProjectRegistry;
+import org.gradle.api.internal.project.ServiceRegistryFactory;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.execution.TaskGraphExecuter;
+import org.gradle.listener.ListenerManager;
+import org.gradle.util.GradleVersion;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.MultiParentClassLoader;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JUnit4.class)
+public class DefaultGradleTest {
+    private final JUnit4Mockery context = new JUnit4Mockery(){{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+    private final StartParameter parameter = new StartParameter();
+    private final ScriptHandler scriptHandlerMock = context.mock(ScriptHandler.class);
+    private final ServiceRegistryFactory serviceRegistryFactoryMock = context.mock(ServiceRegistryFactory.class, "parent");
+    private final ServiceRegistryFactory gradleServiceRegistryMock = context.mock(ServiceRegistryFactory.class, "gradle");
+    private final IProjectRegistry projectRegistry = context.mock(IProjectRegistry.class);
+    private final PluginRegistry pluginRegistry = context.mock(PluginRegistry.class);
+    private final TaskGraphExecuter taskExecuter = context.mock(TaskGraphExecuter.class);
+    private final ListenerManager listenerManager = context.mock(ListenerManager.class);
+    private final Gradle parent = context.mock(Gradle.class, "parentBuild");
+    private final MultiParentClassLoader scriptClassLoaderMock = context.mock(MultiParentClassLoader.class);
+    private final GradleDistributionLocator gradleDistributionLocatorMock = context.mock(GradleDistributionLocator.class);
+    private DefaultGradle gradle;
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations(){{
+            one(serviceRegistryFactoryMock).createFor(with(any(DefaultGradle.class)));
+            will(returnValue(gradleServiceRegistryMock));
+            allowing(gradleServiceRegistryMock).get(ScriptHandler.class);
+            will(returnValue(scriptHandlerMock));
+            allowing(gradleServiceRegistryMock).get(ScriptClassLoaderProvider.class);
+            will(returnValue(context.mock(ScriptClassLoaderProvider.class)));
+            allowing(gradleServiceRegistryMock).get(IProjectRegistry.class);
+            will(returnValue(projectRegistry));
+            allowing(gradleServiceRegistryMock).get(PluginRegistry.class);
+            will(returnValue(pluginRegistry));
+            allowing(gradleServiceRegistryMock).get(TaskGraphExecuter.class);
+            will(returnValue(taskExecuter));
+            allowing(gradleServiceRegistryMock).get(ListenerManager.class);
+            will(returnValue(listenerManager));
+            allowing(gradleServiceRegistryMock).get(MultiParentClassLoader.class);
+            will(returnValue(scriptClassLoaderMock));
+            allowing(gradleServiceRegistryMock).get(GradleDistributionLocator.class);
+            will(returnValue(gradleDistributionLocatorMock));
+        }});
+        gradle = new DefaultGradle(parent, parameter, serviceRegistryFactoryMock);
+    }
+
+    @Test
+    public void defaultValues() {
+        assertThat(gradle.getParent(), sameInstance(parent));
+        assertThat(gradle.getServiceRegistryFactory(), sameInstance(gradleServiceRegistryMock));
+        assertThat(gradle.getProjectRegistry(), sameInstance(projectRegistry));
+        assertThat(gradle.getTaskGraph(), sameInstance(taskExecuter));
+    }
+    
+    @Test
+    public void usesGradleVersion() {
+        assertThat(gradle.getGradleVersion(), equalTo(new GradleVersion().getVersion()));
+    }
+
+    @Test
+    public void usesDistributionLocatorForGradleHomeDir() throws IOException {
+        final File gradleHome = new File("home");
+
+        context.checking(new Expectations() {{
+            one(gradleDistributionLocatorMock).getGradleHome();
+            will(returnValue(gradleHome));
+        }});
+
+        assertThat(gradle.getGradleHomeDir(), equalTo(gradleHome));
+    }
+
+    @Test
+    public void usesStartParameterForUserDir() throws IOException {
+        parameter.setGradleUserHomeDir(new File("user"));
+
+        assertThat(gradle.getGradleUserHomeDir(), equalTo(new File("user").getCanonicalFile()));
+    }
+
+    @Test
+    public void broadcastsProjectEventsToListeners() {
+        final ProjectEvaluationListener listener = context.mock(ProjectEvaluationListener.class, "listener");
+        final ProjectEvaluationListener broadcaster = context.mock(ProjectEvaluationListener.class, "broadcaster");
+        context.checking(new Expectations() {{
+            one(listenerManager).addListener(listener);
+            one(listenerManager).getBroadcaster(ProjectEvaluationListener.class);
+            will(returnValue(broadcaster));
+        }});
+
+        gradle.addListener(listener);
+
+        assertThat(gradle.getProjectEvaluationBroadcaster(), sameInstance(broadcaster));
+    }
+
+    @Test
+    public void broadcastsBeforeProjectEvaluateEventsToClosures() {
+        final Closure closure = HelperUtil.TEST_CLOSURE;
+        context.checking(new Expectations(){{
+            one(listenerManager).addListener(ProjectEvaluationListener.class, "beforeEvaluate", closure);
+        }});
+
+        gradle.beforeProject(closure);
+    }
+
+    @Test
+    public void broadcastsAfterProjectEvaluateEventsToClosures() {
+        final Closure closure = HelperUtil.TEST_CLOSURE;
+        context.checking(new Expectations(){{
+            one(listenerManager).addListener(ProjectEvaluationListener.class, "afterEvaluate", closure);
+        }});
+
+        gradle.afterProject(closure);
+    }
+
+    @Test
+    public void usesSpecifiedLogger() {
+        final Object logger = new Object();
+        context.checking(new Expectations(){{
+            one(listenerManager).useLogger(logger);
+        }});
+        gradle.useLogger(logger);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/listener/AsyncListenerBroadcastTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/listener/AsyncListenerBroadcastTest.groovy
new file mode 100644
index 0000000..aa0f659
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/listener/AsyncListenerBroadcastTest.groovy
@@ -0,0 +1,123 @@
+/*
+ * 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/gradle-core/src/test/groovy/org/gradle/listener/DefaultListenerManagerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/listener/DefaultListenerManagerTest.java
new file mode 100644
index 0000000..e150cea
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/listener/DefaultListenerManagerTest.java
@@ -0,0 +1,225 @@
+/*
+ * 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.jmock.Expectations;
+import org.jmock.Sequence;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+ at RunWith(JMock.class)
+public class DefaultListenerManagerTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final ListenerManager manager = new DefaultListenerManager();
+
+    private final TestFooListener fooListener1 = context.mock(TestFooListener.class, "foo listener 1");
+    private final TestFooListener fooListener2 = context.mock(TestFooListener.class, "foo listener 2");
+    private final TestFooListener fooListener3 = context.mock(TestFooListener.class, "foo listener 3");
+    private final TestFooListener fooListener4 = context.mock(TestFooListener.class, "foo listener 4");
+    private final TestBarListener barListener1 = context.mock(TestBarListener.class, "bar listener 1");
+
+    @Test
+    public void canAddListenerBeforeObtainingBroadcaster() {
+        manager.addListener(fooListener1);
+
+        context.checking(new Expectations() {{
+            one(fooListener1).foo("param");
+        }});
+
+        manager.getBroadcaster(TestFooListener.class).foo("param");
+    }
+
+    @Test
+    public void canAddListenerAfterObtainingBroadcaster() {
+        TestFooListener broadcaster = manager.getBroadcaster(TestFooListener.class);
+
+        manager.addListener(fooListener1);
+
+        context.checking(new Expectations() {{
+            one(fooListener1).foo("param");
+        }});
+
+        broadcaster.foo("param");
+    }
+
+    @Test
+    public void canAddLoggerBeforeObtainingBroadcaster() {
+        manager.useLogger(fooListener1);
+
+        context.checking(new Expectations() {{
+            one(fooListener1).foo("param");
+        }});
+
+        manager.getBroadcaster(TestFooListener.class).foo("param");
+    }
+
+    @Test
+    public void canAddLoggerAfterObtainingBroadcaster() {
+        TestFooListener broadcaster = manager.getBroadcaster(TestFooListener.class);
+
+        manager.useLogger(fooListener1);
+
+        context.checking(new Expectations() {{
+            one(fooListener1).foo("param");
+        }});
+
+        broadcaster.foo("param");
+    }
+
+    @Test
+    public void addedListenersGetMessagesInOrderAdded() {
+        context.checking(new Expectations() {{
+            Sequence sequence = context.sequence("sequence");
+            one(fooListener1).foo("param"); inSequence(sequence);
+            one(fooListener2).foo("param"); inSequence(sequence);
+            one(fooListener3).foo("param"); inSequence(sequence);
+            one(fooListener4).foo("param"); inSequence(sequence);
+        }});
+
+        manager.addListener(fooListener1);
+        manager.addListener(barListener1);
+        manager.addListener(fooListener2);
+        manager.addListener(fooListener3);
+
+        // get the broadcaster and then add more listeners (because broadcasters
+        // are cached and so must be maintained correctly after getting defined
+        TestFooListener broadcaster = manager.getBroadcaster(TestFooListener.class);
+
+        manager.addListener(fooListener4);
+
+        broadcaster.foo("param");
+    }
+
+    @Test
+    public void cachesBroadcasters() {
+        assertSame(manager.getBroadcaster(TestFooListener.class), manager.getBroadcaster(TestFooListener.class));
+    }
+
+    @Test
+    public void removedListenersDontGetMessages() {
+        manager.addListener(fooListener1);
+        manager.addListener(fooListener2);
+
+        manager.removeListener(fooListener2);
+
+        TestFooListener testFooListener = manager.getBroadcaster(TestFooListener.class);
+
+        manager.removeListener(fooListener1);
+
+        testFooListener.foo("param");
+    }
+
+    @Test
+    public void replacedLoggersDontGetMessages() {
+        context.checking(new Expectations() {{
+            one(fooListener4).foo("param");
+        }});
+
+        manager.useLogger(fooListener1);
+        manager.useLogger(fooListener2);
+
+        TestFooListener testFooListener = manager.getBroadcaster(TestFooListener.class);
+
+        manager.useLogger(fooListener3);
+        manager.useLogger(fooListener4);
+
+        testFooListener.foo("param");
+    }
+
+    @Test
+    public void listenerReceivesEventsFromAnonymousBroadcasters() {
+        manager.addListener(fooListener1);
+
+        context.checking(new Expectations() {{
+            one(fooListener1).foo("param");
+        }});
+
+        manager.createAnonymousBroadcaster(TestFooListener.class).getSource().foo("param");
+    }
+
+    @Test
+    public void listenerReceivesEventsFromChildren() {
+        manager.addListener(fooListener1);
+
+        context.checking(new Expectations() {{
+            one(fooListener1).foo("param");
+        }});
+
+        manager.createChild().getBroadcaster(TestFooListener.class).foo("param");
+    }
+    
+    @Test
+    public void listenerDoesNotReceiveEventsFromParent() {
+        manager.createChild().addListener(fooListener1);
+
+        manager.getBroadcaster(TestFooListener.class).foo("param");
+    }
+
+    @Test
+    public void loggerReceivesEventsFromChildren() {
+        manager.useLogger(fooListener1);
+
+        ListenerManager child = manager.createChild();
+        TestFooListener broadcaster = child.getBroadcaster(TestFooListener.class);
+
+        context.checking(new Expectations() {{
+            one(fooListener1).foo("param");
+        }});
+        broadcaster.foo("param");
+
+        manager.useLogger(fooListener2);
+
+        context.checking(new Expectations() {{
+            one(fooListener2).foo("param");
+        }});
+        broadcaster.foo("param");
+    }
+
+    @Test
+    public void loggerDoesNotReceiveEventsFromParent() {
+        manager.createChild().useLogger(fooListener1);
+
+        manager.getBroadcaster(TestFooListener.class).foo("param");
+    }
+
+    @Test
+    public void loggerInChildHasPrecedenceOverLoggerInParent() {
+        manager.useLogger(fooListener1);
+
+        ListenerManager child = manager.createChild();
+        TestFooListener broadcaster = child.getBroadcaster(TestFooListener.class);
+
+        child.useLogger(fooListener2);
+
+        context.checking(new Expectations() {{
+            one(fooListener2).foo("param");
+        }});
+
+        broadcaster.foo("param");
+    }
+
+    public interface TestFooListener {
+        void foo(String param);
+    }
+
+    public interface TestBarListener {
+        void bar(int value);
+    }
+}
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/listener/ListenerBroadcastTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/listener/ListenerBroadcastTest.java
new file mode 100644
index 0000000..1dc5067
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/listener/ListenerBroadcastTest.java
@@ -0,0 +1,334 @@
+/*
+ * 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.Action;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.gradle.util.TestClosure;
+import org.hamcrest.Description;
+import org.jmock.Expectations;
+import org.jmock.api.Invocation;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.gradle.util.HelperUtil.*;
+import static org.gradle.util.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class ListenerBroadcastTest {
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private final ListenerBroadcast<TestListener> broadcast = new ListenerBroadcast<TestListener>(TestListener.class);
+
+    @Test
+    public void createsSourceObject() {
+        assertThat(broadcast.getSource(), notNullValue());
+        assertThat(broadcast.getSource(), strictlyEqual(broadcast.getSource()));
+        assertFalse(broadcast.getSource().equals(new ListenerBroadcast<TestListener>(TestListener.class).getSource()));
+        assertEquals(broadcast.getSource().hashCode(), broadcast.getSource().hashCode());
+        assertThat(broadcast.getSource().toString(), equalTo("TestListener broadcast"));
+    }
+
+    @Test
+    public void getTypeIsCorrect() {
+        assertThat(broadcast.getType(), equalTo(TestListener.class));
+    }
+
+    @Test
+    public void sourceObjectDoesNothingWhenNoListenersAdded() {
+        broadcast.getSource().event1("param");
+    }
+
+    @Test
+    public void sourceObjectNotifiesEachListenerInOrderAdded() {
+        final TestListener listener1 = context.mock(TestListener.class, "listener1");
+        final TestListener listener2 = context.mock(TestListener.class, "listener2");
+
+        context.checking(new Expectations() {{
+            one(listener1).event1("param");
+            one(listener2).event1("param");
+        }});
+
+        broadcast.add(listener1);
+        broadcast.add(listener2);
+
+        broadcast.getSource().event1("param");
+    }
+
+    @Test
+    public void canDispatchEventToListeners() throws NoSuchMethodException {
+        final TestListener listener1 = context.mock(TestListener.class, "listener1");
+        final TestListener listener2 = context.mock(TestListener.class, "listener2");
+
+        context.checking(new Expectations() {{
+            one(listener1).event1("param");
+            one(listener2).event1("param");
+        }});
+
+        broadcast.add(listener1);
+        broadcast.add(listener2);
+
+        MethodInvocation invocation = new MethodInvocation(TestListener.class.getMethod("event1", String.class), new Object[] { "param" });
+        broadcast.dispatch(invocation);
+    }
+
+    @Test
+    public void listenerIsNotUsedAfterItIsRemoved() {
+        TestListener listener = context.mock(TestListener.class);
+
+        broadcast.add(listener);
+        broadcast.remove(listener);
+
+        broadcast.getSource().event1("param");
+    }
+
+    @Test
+    public void canUseDispatchToReceiveNotifications() throws NoSuchMethodException {
+        final Dispatch<MethodInvocation> dispatch1 = context.mock(Dispatch.class, "listener1");
+        final Dispatch<MethodInvocation> dispatch2 = context.mock(Dispatch.class, "listener2");
+        final MethodInvocation invocation = new MethodInvocation(TestListener.class.getMethod("event1", String.class), new Object[] { "param" });
+
+        context.checking(new Expectations() {{
+            one(dispatch1).dispatch(invocation);
+            one(dispatch2).dispatch(invocation);
+        }});
+
+        broadcast.add(dispatch1);
+        broadcast.add(dispatch2);
+
+        broadcast.getSource().event1("param");
+    }
+
+    @Test
+    public void dispatchIsNotUsedAfterItIsRemoved() {
+        Dispatch<MethodInvocation> dispatch = context.mock(Dispatch.class);
+
+        broadcast.add(dispatch);
+        broadcast.remove(dispatch);
+
+        broadcast.getSource().event1("param");
+    }
+
+    @Test
+    public void canUseClosureToReceiveNotificationsForSingleEventMethod() {
+        final TestClosure testClosure = context.mock(TestClosure.class);
+        context.checking(new Expectations() {{
+            one(testClosure).call("param");
+            will(returnValue("ignore me"));
+        }});
+
+        broadcast.add("event1", toClosure(testClosure));
+        broadcast.getSource().event1("param");
+    }
+
+    @Test
+    public void doesNotNotifyClosureForOtherEventMethods() {
+        final TestClosure testClosure = context.mock(TestClosure.class);
+
+        broadcast.add("event1", toClosure(testClosure));
+        broadcast.getSource().event2(9, "param");
+    }
+
+    @Test
+    public void closureCanHaveFewerParametersThanEventMethod() {
+        broadcast.add("event2", toClosure("{ a -> 'result' }"));
+        broadcast.getSource().event2(1, "param");
+        broadcast.getSource().event2(2, null);
+    }
+
+    @Test
+    public void canUseActionForSingleEventMethod() {
+        final Action<String> action = context.mock(Action.class);
+        context.checking(new Expectations() {{
+            one(action).execute("param");
+        }});
+
+        broadcast.add("event1", action);
+        broadcast.getSource().event1("param");
+    }
+
+    @Test
+    public void doesNotNotifyActionForOtherEventMethods() {
+        final Action<String> action = context.mock(Action.class);
+
+        broadcast.add("event1", action);
+        broadcast.getSource().event2(9, "param");
+    }
+
+    @Test
+    public void actionCanHaveFewerParametersThanEventMethod() {
+        final Action<Integer> action = context.mock(Action.class);
+        context.checking(new Expectations(){{
+            one(action).execute(1);
+            one(action).execute(2);
+        }});
+        broadcast.add("event2", action);
+        broadcast.getSource().event2(1, "param");
+        broadcast.getSource().event2(2, null);
+    }
+
+    @Test
+    public void listenerCanAddAnotherListener() {
+        final TestListener listener1 = context.mock(TestListener.class, "listener1");
+        final TestListener listener2 = context.mock(TestListener.class, "listener2");
+        final TestListener listener3 = context.mock(TestListener.class, "listener3");
+
+        broadcast.add(listener1);
+        broadcast.add(listener2);
+
+        context.checking(new Expectations() {{
+            ignoring(listener2);
+            one(listener1).event1("event");
+            will(new org.jmock.api.Action() {
+                public void describeTo(Description description) {
+                    description.appendText("add listener");
+                }
+
+                public Object invoke(Invocation invocation) throws Throwable {
+                    broadcast.add(listener3);
+                    return null;
+                }
+            });
+        }});
+
+        broadcast.getSource().event1("event");
+    }
+    
+    @Test
+    public void wrapsExceptionThrownByListener() {
+        final TestListener listener = context.mock(TestListener.class);
+        final RuntimeException failure = new RuntimeException();
+
+        context.checking(new Expectations() {{
+            one(listener).event1("param");
+            will(throwException(failure));
+        }});
+
+        broadcast.add(listener);
+
+        try {
+            broadcast.getSource().event1("param");
+            fail();
+        } catch (ListenerNotificationException e) {
+            assertThat(e.getMessage(), equalTo("Failed to notify test listener."));
+            assertThat(e.getCause(), sameInstance((Throwable) failure));
+        }
+    }
+
+    @Test
+    public void wrapsExceptionThrownByClosure() {
+        final TestClosure testClosure = context.mock(TestClosure.class);
+        final RuntimeException failure = new RuntimeException();
+
+        context.checking(new Expectations() {{
+            one(testClosure).call("param");
+            will(throwException(failure));
+        }});
+
+        broadcast.add("event1", toClosure(testClosure));
+
+        try {
+            broadcast.getSource().event1("param");
+            fail();
+        } catch (ListenerNotificationException e) {
+            assertThat(e.getMessage(), equalTo("Failed to notify test listener."));
+            assertThat(e.getCause(), sameInstance((Throwable) failure));
+        }
+    }
+
+    @Test
+    public void dispatchWrapsExceptionThrownByListener() throws NoSuchMethodException {
+        final TestListener listener = context.mock(TestListener.class);
+        final RuntimeException failure = new RuntimeException();
+
+        context.checking(new Expectations() {{
+            one(listener).event1("param");
+            will(throwException(failure));
+        }});
+
+        broadcast.add(listener);
+
+        try {
+            broadcast.dispatch(new MethodInvocation(TestListener.class.getMethod("event1", String.class), new Object[]{"param"}));
+            fail();
+        } catch (ListenerNotificationException e) {
+            assertThat(e.getMessage(), equalTo("Failed to notify test listener."));
+            assertThat(e.getCause(), sameInstance((Throwable) failure));
+        }
+    }
+
+    @Test
+    public void attemptsToNotifyAllOtherListenersWhenOneThrowsException() {
+        final TestListener listener1 = context.mock(TestListener.class);
+        final TestListener listener2 = context.mock(TestListener.class);
+        final RuntimeException failure = new RuntimeException();
+
+        context.checking(new Expectations() {{
+            one(listener1).event1("param");
+            will(throwException(failure));
+            one(listener2).event1("param");
+        }});
+
+        broadcast.add(listener1);
+        broadcast.add(listener2);
+
+        try {
+            broadcast.getSource().event1("param");
+            fail();
+        } catch (ListenerNotificationException e) {
+            assertThat(e.getCause(), sameInstance((Throwable) failure));
+        }
+    }
+
+    @Test
+    public void attemptsToNotifyAllOtherListenersWhenMultipleThrowException() {
+        final TestListener listener1 = context.mock(TestListener.class);
+        final TestListener listener2 = context.mock(TestListener.class);
+        final TestListener listener3 = context.mock(TestListener.class);
+        final RuntimeException failure = new RuntimeException();
+
+        context.checking(new Expectations() {{
+            one(listener1).event1("param");
+            will(throwException(failure));
+            one(listener2).event1("param");
+            will(throwException(new RuntimeException()));
+            one(listener3).event1("param");
+        }});
+
+        broadcast.add(listener1);
+        broadcast.add(listener2);
+        broadcast.add(listener3);
+
+        try {
+            broadcast.getSource().event1("param");
+            fail();
+        } catch (ListenerNotificationException e) {
+            assertThat(e.getCause(), sameInstance((Throwable) failure));
+        }
+    }
+
+    public interface TestListener {
+        void event1(String param);
+
+        void event2(int value, String other);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/logging/AnsiConsoleTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/AnsiConsoleTest.groovy
new file mode 100644
index 0000000..a20f7ce
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/AnsiConsoleTest.groovy
@@ -0,0 +1,298 @@
+/*
+ * 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
+
+import org.fusesource.jansi.Ansi
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.Test
+import org.junit.runner.RunWith
+
+ at RunWith(JMock.class)
+class AnsiConsoleTest {
+    private static final String EOL = System.getProperty('line.separator')
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final Ansi ansi = context.mock(Ansi.class)
+    private final Appendable target = {} as Appendable
+    private final Flushable flushable = {} as Flushable
+    private final AnsiConsole console = new AnsiConsole(target, flushable) {
+        def Ansi createAnsi() {
+            return ansi
+        }
+    }
+
+    @Test
+    public void appendsTextToMainArea() {
+        context.checking {
+            one(ansi).a('message')
+        }
+
+        console.mainArea.append('message')
+
+        context.checking {
+            one(ansi).a('message2')
+            one(ansi).a(EOL)
+            one(ansi).a('message3')
+        }
+
+        console.mainArea.append("message2${EOL}message3")
+    }
+
+    @Test
+    public void displaysStatusBarWithNonEmptyText() {
+        def statusBar = console.addStatusBar()
+
+        context.checking {
+            one(ansi).a('text')
+        }
+
+        statusBar.text = 'text'
+    }
+
+    @Test
+    public void displaysStatusBarWhenTextInMainArea() {
+        context.checking {
+            one(ansi).a('message')
+            one(ansi).a(EOL)
+        }
+
+        console.mainArea.append("message${EOL}")
+
+        def statusBar = console.addStatusBar()
+
+        context.checking {
+            one(ansi).a('text')
+        }
+
+        statusBar.text = 'text'
+    }
+
+    @Test
+    public void redrawsStatusBarWhenTextChangesValue() {
+        def statusBar = console.addStatusBar()
+
+        context.checking {
+            one(ansi).a('123')
+        }
+
+        statusBar.text = '123'
+
+        context.checking {
+            one(ansi).cursorLeft(3)
+            one(ansi).a('abc')
+        }
+
+        statusBar.text = 'abc'
+    }
+
+    @Test
+    public void redrawsStatusBarWhenTextChangesSuffix() {
+        def statusBar = console.addStatusBar()
+
+        context.checking {
+            one(ansi).a('text 1')
+        }
+
+        statusBar.text = 'text 1'
+
+        context.checking {
+            one(ansi).cursorLeft(1)
+            one(ansi).a('2')
+        }
+
+        statusBar.text = 'text 2'
+    }
+
+    @Test
+    public void redrawsStatusBarWhenTextAdded() {
+        def statusBar = console.addStatusBar()
+
+        context.checking {
+            one(ansi).a('text')
+        }
+
+        statusBar.text = 'text'
+
+        context.checking {
+            one(ansi).a(' 2')
+        }
+
+        statusBar.text = 'text 2'
+    }
+
+    @Test
+    public void redrawsStatusBarWhenTextRemoved() {
+        def statusBar = console.addStatusBar()
+
+        context.checking {
+            one(ansi).a('text 1')
+        }
+
+        statusBar.text = 'text 1'
+
+        context.checking {
+            one(ansi).cursorLeft(3)
+            one(ansi).eraseLine(Ansi.Erase.FORWARD)
+        }
+
+        statusBar.text = 'tex'
+    }
+    
+    @Test
+    public void redrawsStatusBarWhenTextSetToEmpty() {
+        def statusBar = console.addStatusBar()
+
+        context.checking {
+            one(ansi).a('text')
+        }
+
+        statusBar.text = 'text'
+
+        context.checking {
+            one(ansi).cursorLeft(4)
+            one(ansi).eraseLine(Ansi.Erase.FORWARD)
+        }
+
+        statusBar.text = ''
+    }
+
+    @Test
+    public void removesStatusBarWhenClosed() {
+        def statusBar = console.addStatusBar()
+
+        context.checking {
+            one(ansi).a('text')
+        }
+
+        statusBar.text = 'text'
+
+        context.checking {
+            one(ansi).cursorLeft(4)
+            one(ansi).eraseLine(Ansi.Erase.FORWARD)
+        }
+
+        statusBar.close();
+    }
+
+    @Test
+    public void showsMostRecentlyCreatedStatusBarOnly() {
+        context.checking {
+            one(ansi).a('first')
+        }
+
+        console.addStatusBar().text = 'first'
+
+        context.checking {
+            one(ansi).cursorLeft(5)
+            one(ansi).eraseLine(Ansi.Erase.FORWARD)
+        }
+
+        Label second = console.addStatusBar()
+
+        context.checking {
+            one(ansi).a('second')
+        }
+
+        second.text = 'second'
+
+        context.checking {
+            one(ansi).cursorLeft(6)
+            one(ansi).eraseLine(Ansi.Erase.FORWARD)
+            one(ansi).a('first')
+        }
+
+        second.close()
+    }
+
+    @Test
+    public void appendsTextWhenStatusBarIsPresent() {
+        context.checking {
+            one(ansi).a('status')
+        }
+
+        console.addStatusBar().text = 'status'
+
+        context.checking {
+            one(ansi).cursorLeft(6)
+            one(ansi).eraseLine(Ansi.Erase.FORWARD)
+            one(ansi).a('message')
+            one(ansi).a(EOL)
+            one(ansi).a('status')
+        }
+
+        console.mainArea.append("message$EOL");
+    }
+
+    @Test
+    public void appendsTextWithNoEOLWhenStatusBarIsPresent() {
+        context.checking {
+            one(ansi).a('status')
+        }
+
+        console.addStatusBar().text = 'status'
+
+        context.checking {
+            one(ansi).cursorLeft(6)
+            one(ansi).eraseLine(Ansi.Erase.FORWARD)
+            one(ansi).a('message')
+            one(ansi).newline()
+            one(ansi).a('status')
+        }
+
+        console.mainArea.append('message');
+
+        context.checking {
+            one(ansi).cursorLeft(6)
+            one(ansi).eraseLine(Ansi.Erase.FORWARD)
+            one(ansi).cursorUp(1)
+            one(ansi).cursorRight(7)
+            one(ansi).a('message2')
+            one(ansi).newline()
+            one(ansi).a('status')
+        }
+
+        console.mainArea.append('message2');
+    }
+
+    @Test
+    public void addsStatusBarWhenNoTrailingEOLInMainArea() {
+        context.checking {
+            one(ansi).a('message')
+        }
+
+        console.mainArea.append('message')
+
+        context.checking {
+            one(ansi).newline()
+            one(ansi).a('status')
+        }
+
+        console.addStatusBar().text = 'status'
+
+        context.checking {
+            one(ansi).cursorLeft(6)
+            one(ansi).eraseLine(Ansi.Erase.FORWARD)
+            one(ansi).cursorUp(1)
+            one(ansi).cursorRight(7)
+            one(ansi).a('message2')
+            one(ansi).a(EOL)
+            one(ansi).a('status')
+        }
+
+        console.mainArea.append("message2${EOL}")
+    }
+}
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/logging/BasicProgressLoggingAwareFormatterTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/BasicProgressLoggingAwareFormatterTest.groovy
new file mode 100644
index 0000000..e93132c
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/BasicProgressLoggingAwareFormatterTest.groovy
@@ -0,0 +1,240 @@
+/*
+ * 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
+
+import ch.qos.logback.classic.LoggerContext
+import ch.qos.logback.classic.spi.ILoggingEvent
+import ch.qos.logback.classic.spi.IThrowableProxy
+import ch.qos.logback.classic.spi.ThrowableProxy
+import org.gradle.api.logging.Logging
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.slf4j.LoggerFactory
+import org.slf4j.Marker
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import ch.qos.logback.classic.Level
+import org.gradle.api.logging.StandardOutputListener
+
+ at RunWith(JMock.class)
+class BasicProgressLoggingAwareFormatterTest {
+    private static final String EOL = String.format('%n')
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final StandardOutputListener infoMessage = context.mock(StandardOutputListener.class)
+    private final StandardOutputListener errorMessage = context.mock(StandardOutputListener.class)
+    private final LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory()
+    private final BasicProgressLoggingAwareFormatter formatter = new BasicProgressLoggingAwareFormatter(loggerContext, infoMessage, errorMessage)
+
+    @Test
+    public void logsEventWithMessage() {
+        context.checking {
+            one(infoMessage).onOutput(String.format('message%n'))
+        }
+
+        formatter.format(event('message'))
+    }
+
+    @Test
+    public void logsEventWithMessageAndException() {
+        context.checking {
+            one(infoMessage).onOutput(withParam(allOf(startsWith(String.format('message%n')), containsString('java.lang.RuntimeException: broken'))))
+        }
+
+        formatter.format(event('message', new RuntimeException('broken')))
+    }
+
+    @Test
+    public void logsEventWithErrorMessage() {
+        context.checking {
+            one(errorMessage).onOutput(String.format('message%n'))
+        }
+
+        formatter.format(event('message', Level.ERROR))
+    }
+
+    @Test
+    public void logsProgressMessages() {
+        context.checking {
+            one(infoMessage).onOutput('description')
+            one(infoMessage).onOutput(' ')
+            one(infoMessage).onOutput('complete')
+            one(infoMessage).onOutput(EOL)
+        }
+
+        formatter.format(event('description', Logging.PROGRESS_STARTED))
+        formatter.format(event('complete', Logging.PROGRESS_COMPLETE))
+    }
+
+    @Test
+    public void ignoresProgressStatusMessages() {
+        context.checking {
+            one(infoMessage).onOutput('description')
+            one(infoMessage).onOutput(' ')
+            one(infoMessage).onOutput('complete')
+            one(infoMessage).onOutput(EOL)
+        }
+
+        formatter.format(event('description', Logging.PROGRESS_STARTED))
+        formatter.format(event('tick', Logging.PROGRESS))
+        formatter.format(event('tick', Logging.PROGRESS))
+        formatter.format(event('complete', Logging.PROGRESS_COMPLETE))
+    }
+
+    @Test
+    public void logsNestedProgressMessages() {
+        context.checking {
+            one(infoMessage).onOutput('description1')
+            one(infoMessage).onOutput(EOL)
+            one(infoMessage).onOutput('description2')
+            one(infoMessage).onOutput(' ')
+            one(infoMessage).onOutput('complete2')
+            one(infoMessage).onOutput(EOL)
+            one(infoMessage).onOutput('complete1')
+            one(infoMessage).onOutput(EOL)
+        }
+
+        formatter.format(event('description1', Logging.PROGRESS_STARTED))
+        formatter.format(event('description2', Logging.PROGRESS_STARTED))
+        formatter.format(event('tick', Logging.PROGRESS))
+        formatter.format(event('tick', Logging.PROGRESS))
+        formatter.format(event('complete2', Logging.PROGRESS_COMPLETE))
+        formatter.format(event('complete1', Logging.PROGRESS_COMPLETE))
+    }
+
+    @Test
+    public void logsMixOfProgressAndInfoMessages() {
+        context.checking {
+            one(infoMessage).onOutput('description')
+            one(infoMessage).onOutput(EOL)
+            one(infoMessage).onOutput(String.format('message%n'))
+            one(infoMessage).onOutput('complete')
+            one(infoMessage).onOutput(EOL)
+        }
+
+        formatter.format(event('description', Logging.PROGRESS_STARTED))
+        formatter.format(event('tick', Logging.PROGRESS))
+        formatter.format(event('message'))
+        formatter.format(event('tick', Logging.PROGRESS))
+        formatter.format(event('complete', Logging.PROGRESS_COMPLETE))
+    }
+
+    @Test
+    public void logsMixOfProgressAndErrorMessages() {
+        context.checking {
+            one(infoMessage).onOutput('description')
+            one(infoMessage).onOutput(EOL)
+            one(errorMessage).onOutput(String.format('message%n'))
+            one(infoMessage).onOutput('complete')
+            one(infoMessage).onOutput(EOL)
+        }
+
+        formatter.format(event('description', Logging.PROGRESS_STARTED))
+        formatter.format(event('message', Level.ERROR))
+        formatter.format(event('complete', Logging.PROGRESS_COMPLETE))
+    }
+
+    @Test
+    public void logsProgressMessagesWithNoCompletionStatus() {
+        context.checking {
+            one(infoMessage).onOutput('description')
+            one(infoMessage).onOutput(EOL)
+        }
+
+        formatter.format(event('description', Logging.PROGRESS_STARTED))
+        formatter.format(event('', Logging.PROGRESS_COMPLETE))
+    }
+
+    @Test
+    public void logsProgressMessagesWithNoCompletionStatusAndOtherMessages() {
+        context.checking {
+            one(infoMessage).onOutput('description')
+            one(infoMessage).onOutput(EOL)
+            one(infoMessage).onOutput(String.format('message%n'))
+        }
+
+        formatter.format(event('description', Logging.PROGRESS_STARTED))
+        formatter.format(event('message'))
+        formatter.format(event('', Logging.PROGRESS_COMPLETE))
+    }
+
+    @Test
+    public void logsProgressMessagesWithNoStartStatus() {
+        formatter.format(event('', Logging.PROGRESS_STARTED))
+
+        context.checking {
+            one(infoMessage).onOutput('done')
+            one(infoMessage).onOutput(EOL)
+        }
+
+        formatter.format(event('done', Logging.PROGRESS_COMPLETE))
+    }
+
+    @Test
+    public void logsNestedProgressMessagesWithNoStartStatusAndOtherMessages() {
+        context.checking {
+            one(infoMessage).onOutput('outer')
+        }
+
+        formatter.format(event('outer', Logging.PROGRESS_STARTED))
+
+        formatter.format(event('', Logging.PROGRESS_STARTED))
+
+        context.checking {
+            one(infoMessage).onOutput(EOL)
+            one(errorMessage).onOutput(String.format('message%n'))
+        }
+
+        formatter.format(event('message', Level.ERROR))
+
+        context.checking {
+            one(infoMessage).onOutput('done inner')
+            one(infoMessage).onOutput(EOL)
+        }
+
+        formatter.format(event('done inner', Logging.PROGRESS_COMPLETE))
+
+        context.checking {
+            one(infoMessage).onOutput('done outer')
+            one(infoMessage).onOutput(EOL)
+        }
+
+        formatter.format(event('done outer', Logging.PROGRESS_COMPLETE))
+    }
+
+    private ILoggingEvent event(String text, Marker marker) {
+        event(text, null, marker)
+    }
+
+    private ILoggingEvent event(String text, Level level) {
+        event(text, null, null, level)
+    }
+
+    private ILoggingEvent event(String text, Throwable failure = null, marker = null, Level level = Level.INFO) {
+        IThrowableProxy throwableProxy = failure == null ? null : new ThrowableProxy(failure)
+        [
+                getLevel: {level},
+                getThrowableProxy: {throwableProxy},
+                getFormattedMessage: {text},
+                getMarker: {marker}
+        ] as ILoggingEvent
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/logging/ConsoleBackedFormatterTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/ConsoleBackedFormatterTest.groovy
new file mode 100644
index 0000000..19b8957
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/ConsoleBackedFormatterTest.groovy
@@ -0,0 +1,278 @@
+/*
+ * 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
+
+import ch.qos.logback.classic.spi.ILoggingEvent
+import ch.qos.logback.classic.spi.IThrowableProxy
+import ch.qos.logback.classic.spi.ThrowableProxy
+import org.gradle.api.logging.Logging
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.slf4j.Marker
+import static org.junit.Assert.*
+import static org.hamcrest.Matchers.*
+
+import org.slf4j.LoggerFactory
+import ch.qos.logback.classic.LoggerContext
+import org.junit.Before
+import ch.qos.logback.classic.Level
+
+ at RunWith(JMock.class)
+class ConsoleBackedFormatterTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final Console console = context.mock(Console.class)
+    private final TextArea mainArea = context.mock(TextArea.class)
+    private final Label statusBar = context.mock(Label.class)
+    private ConsoleBackedFormatter formatter
+
+    @Before
+    public void setup() {
+        context.checking {
+            one(console).addStatusBar()
+            will(returnValue(statusBar))
+            allowing(console).getMainArea()
+            will(returnValue(mainArea))
+        }
+
+        formatter = new ConsoleBackedFormatter((LoggerContext) LoggerFactory.getILoggerFactory(), console)
+    }
+
+    @Test
+    public void logsEventWithMessage() {
+        context.checking {
+            one(mainArea).append(String.format('message%n'))
+        }
+
+        formatter.format(event('message'))
+    }
+
+    @Test
+    public void logsEventWithMessageAndException() {
+        context.checking {
+            one(mainArea).append(withParam(anything()))
+            will { message ->
+                assertThat(message.toString(), startsWith(String.format('message%n')))
+                assertThat(message.toString(), containsString('java.lang.RuntimeException: broken'))
+            }
+        }
+
+        formatter.format(event('message', new RuntimeException('broken')))
+    }
+
+    @Test
+    public void logsErrorMessage() {
+        context.checking {
+            one(mainArea).append(String.format('message%n'))
+        }
+
+        formatter.format(event('message', Level.ERROR))
+    }
+
+    @Test
+    public void logsProgressMessages() {
+        context.checking {
+            one(statusBar).setText('')
+        }
+
+        formatter.format(event('description', Logging.PROGRESS_STARTED))
+
+        context.checking {
+            one(statusBar).setText('')
+            one(mainArea).append(String.format('description complete%n'))
+        }
+        
+        formatter.format(event('complete', Logging.PROGRESS_COMPLETE))
+    }
+
+    @Test
+    public void logsProgressStatusMessages() {
+        context.checking {
+            one(statusBar).setText('')
+        }
+
+        formatter.format(event('description', Logging.PROGRESS_STARTED))
+
+        context.checking {
+            one(statusBar).setText('> status')
+        }
+
+        formatter.format(event('status', Logging.PROGRESS))
+    }
+
+    @Test
+    public void logsNestedProgressMessages() {
+        context.checking {
+            one(statusBar).setText('')
+        }
+
+        formatter.format(event('description1', Logging.PROGRESS_STARTED))
+
+        context.checking {
+            one(mainArea).append(String.format('description1%n'))
+            one(statusBar).setText('')
+        }
+
+        formatter.format(event('description2', Logging.PROGRESS_STARTED))
+
+        context.checking {
+            one(statusBar).setText('> tick')
+        }
+
+        formatter.format(event('tick', Logging.PROGRESS))
+
+        context.checking {
+            one(statusBar).setText('')
+            one(mainArea).append(String.format('description2 complete2%n'))
+        }
+
+        formatter.format(event('complete2', Logging.PROGRESS_COMPLETE))
+
+        context.checking {
+            one(statusBar).setText('')
+            one(mainArea).append(String.format('description1 complete1%n'))
+        }
+
+        formatter.format(event('complete1', Logging.PROGRESS_COMPLETE))
+    }
+
+    @Test
+    public void logsMixOfProgressAndOtherMessages() {
+        context.checking {
+            one(statusBar).setText('')
+        }
+
+        formatter.format(event('description', Logging.PROGRESS_STARTED))
+
+        context.checking {
+            one(mainArea).append(String.format('description%n'))
+            one(mainArea).append(String.format('message%n'))
+        }
+
+        formatter.format(event('message'))
+
+        context.checking {
+            one(statusBar).setText('')
+            one(mainArea).append(String.format('description complete%n'))
+        }
+
+        formatter.format(event('complete', Logging.PROGRESS_COMPLETE))
+    }
+
+    @Test
+    public void logsProgressMessagesWithEmptyCompletionStatus() {
+        context.checking {
+            one(statusBar).setText('')
+        }
+
+        formatter.format(event('description', Logging.PROGRESS_STARTED))
+
+        context.checking {
+            one(mainArea).append(String.format('description%n'))
+            one(statusBar).setText('')
+        }
+
+        formatter.format(event('', Logging.PROGRESS_COMPLETE))
+    }
+
+    @Test
+    public void logsProgressMessagesWithEmptyCompletionStatusAndOtherMessages() {
+
+        context.checking {
+            one(statusBar).setText('')
+        }
+
+        formatter.format(event('description', Logging.PROGRESS_STARTED))
+
+        context.checking {
+            one(mainArea).append(String.format('description%n'))
+            one(mainArea).append(String.format('message%n'))
+        }
+
+        formatter.format(event('message'))
+
+        context.checking {
+            one(statusBar).setText('')
+        }
+
+        formatter.format(event('', Logging.PROGRESS_COMPLETE))
+    }
+
+    @Test
+    public void logsProgressMessagesWithEmptyStartAndCompletionStatus() {
+        context.checking {
+            one(statusBar).setText('')
+        }
+
+        formatter.format(event('', Logging.PROGRESS_STARTED))
+
+        context.checking {
+            one(statusBar).setText('> running')
+        }
+        formatter.format(event('running', Logging.PROGRESS))
+
+        context.checking {
+            one(statusBar).setText('')
+        }
+
+        formatter.format(event('', Logging.PROGRESS_COMPLETE))
+    }
+
+    @Test
+    public void logsProgressMessagesWithEmptyStartAndCompletionStatusAndOtherMessages() {
+        context.checking {
+            one(statusBar).setText('')
+        }
+
+        formatter.format(event('', Logging.PROGRESS_STARTED))
+
+        context.checking {
+            one(mainArea).append(String.format('message%n'))
+        }
+
+        formatter.format(event('message'))
+
+        context.checking {
+            one(statusBar).setText('')
+        }
+
+        formatter.format(event('', Logging.PROGRESS_COMPLETE))
+    }
+    
+    private Label statusBar() {
+        return context.mock(Label.class)
+    }
+
+    private ILoggingEvent event(String text, Marker marker) {
+        event(text, null, marker)
+    }
+
+    private ILoggingEvent event(String text, Level level) {
+        event(text, null, null, level)
+    }
+
+    private ILoggingEvent event(String text, Throwable failure = null, marker = null, Level level = Level.INFO) {
+        IThrowableProxy throwableProxy = failure == null ? null : new ThrowableProxy(failure)
+        [getLevel: {level}, getThrowableProxy: {throwableProxy}, getFormattedMessage: {text}, getMarker: {marker}] as ILoggingEvent
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/logging/DefaultLoggingManagerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/DefaultLoggingManagerTest.java
new file mode 100644
index 0000000..7a11312
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/DefaultLoggingManagerTest.java
@@ -0,0 +1,364 @@
+/*
+ * 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;
+
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.LoggingOutput;
+import org.gradle.api.logging.StandardOutputListener;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.gradle.util.RedirectStdOutAndErr;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultLoggingManagerTest {
+    @Rule
+    public final RedirectStdOutAndErr outputs = new RedirectStdOutAndErr();
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery();
+    private final LoggingSystem loggingSystem = context.mock(LoggingSystem.class);
+    private final LoggingSystem stdOutLoggingSystem = context.mock(LoggingSystem.class);
+    private final LoggingSystem stdErrLoggingSystem = context.mock(LoggingSystem.class);
+    private final LoggingOutput loggingOutput = context.mock(LoggingOutput.class);
+    private final DefaultLoggingManager loggingManager = new DefaultLoggingManager(loggingSystem, stdOutLoggingSystem, stdErrLoggingSystem, loggingOutput);
+
+    @Test
+    public void defaultValues() {
+        assertTrue(loggingManager.isStandardOutputCaptureEnabled());
+        assertEquals(LogLevel.QUIET, loggingManager.getStandardOutputCaptureLevel());
+        assertEquals(LogLevel.ERROR, loggingManager.getStandardErrorCaptureLevel());
+        assertNull(loggingManager.getLevel());
+    }
+
+    @Test
+    public void canChangeStdOutCaptureLogLevel() {
+        loggingManager.captureStandardOutput(LogLevel.ERROR);
+        assertTrue(loggingManager.isStandardOutputCaptureEnabled());
+        assertEquals(LogLevel.ERROR, loggingManager.getStandardOutputCaptureLevel());
+    }
+
+    @Test
+    public void canChangeStdErrCaptureLogLevel() {
+        loggingManager.captureStandardError(LogLevel.WARN);
+        assertEquals(LogLevel.WARN, loggingManager.getStandardErrorCaptureLevel());
+    }
+
+    @Test
+    public void canChangeLogLevel() {
+        loggingManager.setLevel(LogLevel.ERROR);
+        assertEquals(LogLevel.ERROR, loggingManager.getLevel());
+    }
+
+    @Test
+    public void canDisableCapture() {
+        loggingManager.disableStandardOutputCapture();
+        assertFalse(loggingManager.isStandardOutputCaptureEnabled());
+        assertNull(loggingManager.getStandardOutputCaptureLevel());
+    }
+
+    @Test
+    public void startStopWithCaptureDisabled() {
+        loggingManager.disableStandardOutputCapture();
+
+        final LoggingSystem.Snapshot stdOutSnapshot = context.mock(LoggingSystem.Snapshot.class);
+        final LoggingSystem.Snapshot stdErrSnapshot = context.mock(LoggingSystem.Snapshot.class);
+        context.checking(new Expectations() {{
+            ignoring(loggingSystem);
+            one(stdOutLoggingSystem).off();
+            will(returnValue(stdOutSnapshot));
+            one(stdErrLoggingSystem).off();
+            will(returnValue(stdErrSnapshot));
+        }});
+
+        loggingManager.start();
+
+        context.checking(new Expectations() {{
+            one(stdOutLoggingSystem).restore(stdOutSnapshot);
+            one(stdErrLoggingSystem).restore(stdErrSnapshot);
+        }});
+
+        loggingManager.stop();
+    }
+
+    @Test
+    public void startStopWithCaptureEnabled() {
+        loggingManager.captureStandardOutput(LogLevel.DEBUG);
+        loggingManager.captureStandardError(LogLevel.INFO);
+
+        final LoggingSystem.Snapshot stdOutSnapshot = context.mock(LoggingSystem.Snapshot.class);
+        final LoggingSystem.Snapshot stdErrSnapshot = context.mock(LoggingSystem.Snapshot.class);
+        context.checking(new Expectations() {{
+            ignoring(loggingSystem);
+            one(stdOutLoggingSystem).on(LogLevel.DEBUG);
+            will(returnValue(stdOutSnapshot));
+            one(stdErrLoggingSystem).on(LogLevel.INFO);
+            will(returnValue(stdErrSnapshot));
+        }});
+
+        loggingManager.start();
+
+        context.checking(new Expectations() {{
+            one(stdOutLoggingSystem).restore(stdOutSnapshot);
+            one(stdErrLoggingSystem).restore(stdErrSnapshot);
+        }});
+
+        loggingManager.stop();
+    }
+
+    @Test
+    public void startStopWithLogLevelSet() {
+        loggingManager.setLevel(LogLevel.DEBUG);
+
+        final LoggingSystem.Snapshot snapshot = context.mock(LoggingSystem.Snapshot.class);
+        context.checking(new Expectations() {{
+            ignoring(stdOutLoggingSystem);
+            ignoring(stdErrLoggingSystem);
+            one(loggingSystem).on(LogLevel.DEBUG);
+            will(returnValue(snapshot));
+        }});
+
+        loggingManager.start();
+
+        context.checking(new Expectations() {{
+            one(loggingSystem).restore(snapshot);
+        }});
+
+        loggingManager.stop();
+    }
+
+    @Test
+    public void startStopWithLogLevelNotSet() {
+        final LoggingSystem.Snapshot snapshot = context.mock(LoggingSystem.Snapshot.class);
+        context.checking(new Expectations() {{
+            ignoring(stdOutLoggingSystem);
+            ignoring(stdErrLoggingSystem);
+            one(loggingSystem).snapshot();
+            will(returnValue(snapshot));
+        }});
+
+        loggingManager.start();
+
+        context.checking(new Expectations() {{
+            one(loggingSystem).restore(snapshot);
+        }});
+
+        loggingManager.stop();
+    }
+
+    @Test
+    public void disableCaptureWhileStarted() {
+        final LoggingSystem.Snapshot stdOutSnapshot = context.mock(LoggingSystem.Snapshot.class);
+        final LoggingSystem.Snapshot stdErrSnapshot = context.mock(LoggingSystem.Snapshot.class);
+        context.checking(new Expectations() {{
+            ignoring(loggingSystem);
+            one(stdOutLoggingSystem).on(LogLevel.DEBUG);
+            will(returnValue(stdOutSnapshot));
+            one(stdErrLoggingSystem).on(LogLevel.INFO);
+            will(returnValue(stdErrSnapshot));
+        }});
+
+        loggingManager.captureStandardOutput(LogLevel.DEBUG);
+        loggingManager.captureStandardError(LogLevel.INFO);
+
+        loggingManager.start();
+
+        context.checking(new Expectations() {{
+            one(stdOutLoggingSystem).off();
+            one(stdErrLoggingSystem).off();
+        }});
+
+        loggingManager.disableStandardOutputCapture();
+
+        context.checking(new Expectations() {{
+            one(stdOutLoggingSystem).restore(stdOutSnapshot);
+            one(stdErrLoggingSystem).restore(stdErrSnapshot);
+        }});
+
+        loggingManager.stop();
+    }
+
+    @Test
+    public void enableCaptureWhileStarted() {
+        final LoggingSystem.Snapshot stdOutSnapshot = context.mock(LoggingSystem.Snapshot.class);
+        final LoggingSystem.Snapshot stdErrSnapshot = context.mock(LoggingSystem.Snapshot.class);
+        context.checking(new Expectations() {{
+            ignoring(loggingSystem);
+            one(stdOutLoggingSystem).off();
+            will(returnValue(stdOutSnapshot));
+            one(stdErrLoggingSystem).off();
+            will(returnValue(stdErrSnapshot));
+        }});
+
+        loggingManager.disableStandardOutputCapture();
+
+        loggingManager.start();
+
+        context.checking(new Expectations() {{
+            one(stdOutLoggingSystem).on(LogLevel.DEBUG);
+            one(stdErrLoggingSystem).on(LogLevel.INFO);
+        }});
+
+        loggingManager.captureStandardOutput(LogLevel.DEBUG);
+        loggingManager.captureStandardError(LogLevel.INFO);
+
+        context.checking(new Expectations() {{
+            one(stdOutLoggingSystem).restore(stdOutSnapshot);
+            one(stdErrLoggingSystem).restore(stdErrSnapshot);
+        }});
+
+        loggingManager.stop();
+    }
+
+    @Test
+    public void changeCaptureLevelWhileStarted() {
+        final LoggingSystem.Snapshot stdOutSnapshot = context.mock(LoggingSystem.Snapshot.class);
+        final LoggingSystem.Snapshot stdErrSnapshot = context.mock(LoggingSystem.Snapshot.class);
+        context.checking(new Expectations() {{
+            ignoring(loggingSystem);
+            one(stdOutLoggingSystem).on(LogLevel.DEBUG);
+            will(returnValue(stdOutSnapshot));
+            one(stdErrLoggingSystem).on(LogLevel.DEBUG);
+            will(returnValue(stdErrSnapshot));
+        }});
+
+        loggingManager.captureStandardOutput(LogLevel.DEBUG);
+        loggingManager.captureStandardError(LogLevel.DEBUG);
+
+        loggingManager.start();
+
+        context.checking(new Expectations() {{
+            one(stdOutLoggingSystem).on(LogLevel.WARN);
+        }});
+
+        loggingManager.captureStandardOutput(LogLevel.WARN);
+
+        context.checking(new Expectations() {{
+            one(stdOutLoggingSystem).restore(stdOutSnapshot);
+            one(stdErrLoggingSystem).restore(stdErrSnapshot);
+        }});
+
+        loggingManager.stop();
+    }
+
+    @Test
+    public void changeLogLevelWhileStarted() {
+        final LoggingSystem.Snapshot snapshot = context.mock(LoggingSystem.Snapshot.class);
+        context.checking(new Expectations() {{
+            ignoring(stdOutLoggingSystem);
+            ignoring(stdErrLoggingSystem);
+            one(loggingSystem).snapshot();
+            will(returnValue(snapshot));
+        }});
+
+        loggingManager.start();
+
+        context.checking(new Expectations() {{
+            ignoring(stdOutLoggingSystem);
+            one(loggingSystem).on(LogLevel.LIFECYCLE);
+            will(returnValue(context.mock(LoggingSystem.Snapshot.class)));
+        }});
+
+        loggingManager.setLevel(LogLevel.LIFECYCLE);
+
+        context.checking(new Expectations() {{
+            one(loggingSystem).restore(snapshot);
+        }});
+
+        loggingManager.stop();
+    }
+
+    @Test
+    public void addsListenersOnStartAndRemovesOnStop() {
+        final StandardOutputListener stdoutListener = context.mock(StandardOutputListener.class);
+        final StandardOutputListener stderrListener = context.mock(StandardOutputListener.class);
+
+        loggingManager.addStandardOutputListener(stdoutListener);
+        loggingManager.addStandardErrorListener(stderrListener);
+
+        context.checking(new Expectations() {{
+            ignoring(loggingSystem);
+            ignoring(stdOutLoggingSystem);
+            ignoring(stdErrLoggingSystem);
+            one(loggingOutput).addStandardOutputListener(stdoutListener);
+            one(loggingOutput).addStandardErrorListener(stderrListener);
+        }});
+
+        loggingManager.start();
+
+        context.checking(new Expectations() {{
+            one(loggingOutput).removeStandardOutputListener(stdoutListener);
+            one(loggingOutput).removeStandardErrorListener(stderrListener);
+        }});
+
+        loggingManager.stop();
+    }
+
+    @Test
+    public void addsListenersWhileStarted() {
+        final StandardOutputListener stdoutListener = context.mock(StandardOutputListener.class);
+        final StandardOutputListener stderrListener = context.mock(StandardOutputListener.class);
+
+        context.checking(new Expectations() {{
+            ignoring(loggingSystem);
+            ignoring(stdOutLoggingSystem);
+            ignoring(stdErrLoggingSystem);
+        }});
+
+        loggingManager.start();
+
+        context.checking(new Expectations() {{
+            one(loggingOutput).addStandardOutputListener(stdoutListener);
+            one(loggingOutput).addStandardErrorListener(stderrListener);
+        }});
+
+        loggingManager.addStandardOutputListener(stdoutListener);
+        loggingManager.addStandardErrorListener(stderrListener);
+    }
+
+    @Test
+    public void removesListenersWhileStarted() {
+        final StandardOutputListener stdoutListener = context.mock(StandardOutputListener.class);
+        final StandardOutputListener stderrListener = context.mock(StandardOutputListener.class);
+
+        loggingManager.addStandardOutputListener(stdoutListener);
+        loggingManager.addStandardErrorListener(stderrListener);
+
+        context.checking(new Expectations() {{
+            ignoring(loggingSystem);
+            ignoring(stdOutLoggingSystem);
+            ignoring(stdErrLoggingSystem);
+            one(loggingOutput).addStandardOutputListener(stdoutListener);
+            one(loggingOutput).addStandardErrorListener(stderrListener);
+        }});
+
+        loggingManager.start();
+
+        context.checking(new Expectations() {{
+            one(loggingOutput).removeStandardOutputListener(stdoutListener);
+            one(loggingOutput).removeStandardErrorListener(stderrListener);
+        }});
+
+        loggingManager.removeStandardOutputListener(stdoutListener);
+        loggingManager.removeStandardErrorListener(stderrListener);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/logging/DefaultProgressLoggerFactoryTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/DefaultProgressLoggerFactoryTest.groovy
new file mode 100644
index 0000000..a8b53ca
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/DefaultProgressLoggerFactoryTest.groovy
@@ -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.logging
+
+import spock.lang.Specification
+import org.gradle.listener.ListenerManager
+
+class DefaultProgressLoggerFactoryTest extends Specification {
+    private final ListenerManager listenerManager = Mock()
+    private final ProgressListener progressListener = Mock()
+    private final ProgressLoggerFactory factory = new DefaultProgressLoggerFactory(listenerManager)
+
+    def progressLoggerBroadcastsEvents() {
+        when:
+        def logger = factory.start('description')
+
+        then:
+        logger != null
+        1 * listenerManager.getBroadcaster(ProgressListener.class) >> progressListener
+        1 * progressListener.started(!null)
+
+        when:
+        logger.progress('progress')
+
+        then:
+        1 * progressListener.progress(!null)
+
+        when:
+        logger.completed()
+
+        then:
+        1 * progressListener.completed(!null)
+    }
+
+    def hasEmptyStatusOnStart() {
+        when:
+        def logger = factory.start('description')
+
+        then:
+        1 * listenerManager.getBroadcaster(ProgressListener.class) >> progressListener
+        logger.description == 'description'
+        logger.status == ''
+    }
+
+    def hasMostRecentStatusOnProgress() {
+        when:
+        def logger = factory.start('description')
+        logger.progress('status')
+
+        then:
+        1 * listenerManager.getBroadcaster(ProgressListener.class) >> progressListener
+        logger.status == 'status'
+    }
+    
+    def hasMostRecentStatusOnComplete() {
+        when:
+        def logger = factory.start('description')
+        logger.completed('done')
+
+        then:
+        1 * listenerManager.getBroadcaster(ProgressListener.class) >> progressListener
+        logger.status == 'done'
+    }
+}
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/logging/DefaultStandardOutputRedirectorTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/DefaultStandardOutputRedirectorTest.groovy
new file mode 100644
index 0000000..080faf9
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/DefaultStandardOutputRedirectorTest.groovy
@@ -0,0 +1,90 @@
+/*
+ * 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
+
+import org.gradle.api.logging.StandardOutputListener
+import org.gradle.util.RedirectStdOutAndErr
+import org.junit.Rule
+import spock.lang.Specification
+
+class DefaultStandardOutputRedirectorTest extends Specification {
+    private static final String EOL = System.getProperty('line.separator')
+    @Rule public final RedirectStdOutAndErr outputs = new RedirectStdOutAndErr()
+    private final DefaultStandardOutputRedirector redirector = new DefaultStandardOutputRedirector()
+    private final StandardOutputListener stdOutListener = Mock()
+    private final StandardOutputListener stdErrListener = Mock()
+
+    def startAndStopDoesNothingWhenNothingRedirected() {
+        when:
+        redirector.start()
+        System.out.println('this is stdout')
+        System.err.println('this is stderr')
+        redirector.stop()
+
+        then:
+        System.out == outputs.stdOutPrintStream
+        System.err == outputs.stdErrPrintStream
+    }
+    
+    def startAndStopRedirectsStdOut() {
+        when:
+        redirector.redirectStandardOutputTo(stdOutListener)
+        redirector.start()
+        System.out.println('this is stdout')
+        System.err.println('this is stderr')
+        redirector.stop()
+
+        then:
+        1 * stdOutListener.onOutput('this is stdout' + EOL)
+        0 * stdOutListener._
+        System.out == outputs.stdOutPrintStream
+        System.err == outputs.stdErrPrintStream
+    }
+
+    def startAndStopRedirectsStdErr() {
+        when:
+        redirector.redirectStandardErrorTo(stdErrListener)
+        redirector.start()
+        System.out.println('this is stdout')
+        System.err.println('this is stderr')
+        redirector.stop()
+
+        then:
+        1 * stdErrListener.onOutput('this is stderr' + EOL)
+        0 * stdErrListener._
+        System.out == outputs.stdOutPrintStream
+        System.err == outputs.stdErrPrintStream
+    }
+    
+    def canRedirectMultipleTimes() {
+        when:
+        redirector.redirectStandardErrorTo(stdErrListener)
+        redirector.start()
+        redirector.stop()
+        redirector.redirectStandardOutputTo(stdOutListener)
+        redirector.start()
+        System.out.println('this is stdout')
+        System.err.println('this is stderr')
+        redirector.stop()
+
+        then:
+        1 * stdOutListener.onOutput('this is stdout' + EOL)
+        0 * stdOutListener._
+        0 * stdErrListener._
+        System.out == outputs.stdOutPrintStream
+        System.err == outputs.stdErrPrintStream
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/logging/JavaUtilLoggingConfigurerTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/JavaUtilLoggingConfigurerTest.groovy
new file mode 100644
index 0000000..562e95f
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/JavaUtilLoggingConfigurerTest.groovy
@@ -0,0 +1,50 @@
+/*
+ * 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
+
+import spock.lang.Specification
+import java.util.logging.LogManager
+import ch.qos.logback.classic.spi.ILoggingEvent
+import ch.qos.logback.core.Appender
+import org.gradle.api.logging.LogLevel
+import java.util.logging.Logger
+import ch.qos.logback.classic.Level
+
+class JavaUtilLoggingConfigurerTest extends Specification {
+    private final Appender<ILoggingEvent> appender = Mock()
+    private final LoggingTestHelper helper = new LoggingTestHelper(appender)
+    private final JavaUtilLoggingConfigurer configurer = new JavaUtilLoggingConfigurer()
+
+    def setup() {
+        helper.attachAppender()
+    }
+
+    def cleanup() {
+        helper.detachAppender()
+        LogManager.getLogManager().reset()
+    }
+
+    def routesJulToSlf4j() {
+        when:
+        configurer.configure(LogLevel.DEBUG)
+        Logger.getLogger('test').info('info message')
+
+        then:
+        1 * appender.doAppend({ILoggingEvent event -> event.level == Level.INFO && event.message == 'info message'})
+        0 * appender._
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/logging/LoggingServiceRegistryTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/LoggingServiceRegistryTest.groovy
new file mode 100644
index 0000000..38d4e0d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/LoggingServiceRegistryTest.groovy
@@ -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.logging
+
+import spock.lang.Specification
+
+class LoggingServiceRegistryTest extends Specification {
+    private final LoggingServiceRegistry registry = new LoggingServiceRegistry()
+    
+    def providesALoggingManagerFactory() {
+        expect:
+        def factory = registry.get(LoggingManagerFactory.class)
+        factory instanceof DefaultLoggingManagerFactory
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/logging/LoggingSystemAdapterTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/LoggingSystemAdapterTest.groovy
new file mode 100644
index 0000000..1cc962a
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/LoggingSystemAdapterTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * 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
+
+import spock.lang.Specification
+
+import org.gradle.api.logging.LogLevel
+
+class LoggingSystemAdapterTest extends Specification {
+    private final LoggingConfigurer loggingConfigurer = Mock()
+    private final LoggingSystemAdapter loggingSystem = new LoggingSystemAdapter(loggingConfigurer)
+
+    def onUsesLoggingConfigurerToSetLoggingLevel() {
+        when:
+        loggingSystem.on(LogLevel.DEBUG)
+
+        then:
+        1 * loggingConfigurer.configure(LogLevel.DEBUG)
+        0 * loggingConfigurer._
+    }
+
+    def offDoesNothing() {
+        when:
+        loggingSystem.off()
+
+        then:
+        0 * loggingConfigurer._
+    }
+
+    def restoreSetsLoggingLevelToPreviousLoggingLevel() {
+        when:
+        def snapshot = loggingSystem.snapshot()
+        loggingSystem.restore(snapshot)
+
+        then:
+        1 * loggingConfigurer.configure(LogLevel.LIFECYCLE)
+    }
+    
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/logging/LoggingTestHelper.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/LoggingTestHelper.groovy
new file mode 100644
index 0000000..f335bed
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/LoggingTestHelper.groovy
@@ -0,0 +1,53 @@
+/*
+ * 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
+
+import ch.qos.logback.classic.spi.ILoggingEvent
+import ch.qos.logback.core.Appender
+import ch.qos.logback.classic.Logger
+import ch.qos.logback.classic.Level
+import org.slf4j.LoggerFactory
+import ch.qos.logback.classic.LoggerContext
+
+/**
+ * Should be a rule, but doesn't work with jmock.
+ */
+class LoggingTestHelper {
+    private final Appender<ILoggingEvent> appender;
+    private Logger logger;
+
+    def LoggingTestHelper(appender) {
+        this.appender = appender;
+    }
+
+    public void attachAppender() {
+        logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger("ROOT");
+        logger.detachAndStopAllAppenders()
+        logger.addAppender(appender)
+        logger.setLevel(Level.ALL)
+    }
+
+    public void detachAppender() {
+        logger.detachAppender(appender)
+        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory()
+        lc.reset()
+    }
+
+    public void setLevel(Level level) {
+        logger.setLevel(level)
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/logging/PrintStreamLoggingSystemTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/PrintStreamLoggingSystemTest.groovy
new file mode 100644
index 0000000..facb5d0
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/PrintStreamLoggingSystemTest.groovy
@@ -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
+
+import ch.qos.logback.classic.Level
+import ch.qos.logback.classic.spi.ILoggingEvent
+import ch.qos.logback.core.Appender
+import org.gradle.api.logging.LogLevel
+import org.gradle.api.logging.Logging
+import spock.lang.Specification
+
+class PrintStreamLoggingSystemTest extends Specification {
+    private final Appender<ILoggingEvent> appender = Mock()
+    private final LoggingTestHelper helper = new LoggingTestHelper(appender)
+    private OutputStream original = new ByteArrayOutputStream()
+    private PrintStream stream = new PrintStream(original)
+    private final PrintStreamLoggingSystem loggingSystem = new PrintStreamLoggingSystem(Logging.getLogger('logger')) {
+        protected PrintStream get() {
+            stream
+        }
+
+        protected void set(PrintStream printStream) {
+            stream = printStream
+        }
+    }
+
+    def setup() {
+        helper.attachAppender()
+    }
+    
+    def teardown() {
+        helper.detachAppender()
+    }
+
+    def onStartsCapturingWhenNotAlreadyCapturing() {
+        when:
+        loggingSystem.on(LogLevel.INFO)
+        stream.println('info')
+
+        then:
+        1 * appender.doAppend({ILoggingEvent event -> event.level == Level.INFO && event.message == 'info'})
+        original.toString() == ''
+        0 * appender._
+    }
+
+    def onChangesLogLevelsWhenAlreadyCapturing() {
+        loggingSystem.on(LogLevel.INFO)
+
+        when:
+        loggingSystem.on(LogLevel.DEBUG)
+        stream.println('info')
+
+        then:
+        1 * appender.doAppend({ILoggingEvent event -> event.level == Level.DEBUG && event.message == 'info'})
+        original.toString() == ''
+        0 * appender._
+    }
+
+    def offDoesNothingWhenNotAlreadyCapturing() {
+        when:
+        loggingSystem.off()
+        stream.println('info')
+
+        then:
+        original.toString() == String.format('info%n')
+        0 * appender._
+    }
+
+    def offStopsCapturingWhenAlreadyCapturing() {
+        loggingSystem.on(LogLevel.WARN)
+
+        when:
+        loggingSystem.off()
+
+        stream.println('info')
+
+        then:
+        original.toString() == String.format('info%n')
+        0 * appender._
+    }
+
+    def restoreStopsCapturingWhenCapturingWasNotInstalledWhenSnapshotTaken() {
+        def snapshot = loggingSystem.snapshot()
+        loggingSystem.on(LogLevel.ERROR)
+
+        when:
+        loggingSystem.restore(snapshot)
+        stream.println('info')
+
+        then:
+        original.toString() == String.format('info%n')
+        0 * appender._
+    }
+
+    def restoreStopsCapturingWhenCapturingWasOffWhenSnapshotTaken() {
+        loggingSystem.on(LogLevel.INFO)
+        loggingSystem.off()
+        def snapshot = loggingSystem.snapshot()
+        loggingSystem.on(LogLevel.ERROR)
+
+        when:
+        loggingSystem.restore(snapshot)
+        stream.println('info')
+
+        then:
+        original.toString() == String.format('info%n')
+        0 * appender._
+    }
+
+    def restoreStartsCapturingWhenCapturingWasOnWhenSnapshotTaken() {
+        loggingSystem.on(LogLevel.WARN)
+        def snapshot = loggingSystem.snapshot()
+        loggingSystem.off()
+
+        when:
+        loggingSystem.restore(snapshot)
+        stream.println('info')
+
+        then:
+        1 * appender.doAppend({ILoggingEvent event -> event.level == Level.WARN && event.message == 'info'})
+        original.toString() == ''
+        0 * appender._
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/logging/Slf4jLoggingConfigurerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/Slf4jLoggingConfigurerTest.java
new file mode 100644
index 0000000..14b19cb
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/logging/Slf4jLoggingConfigurerTest.java
@@ -0,0 +1,311 @@
+/*
+ * 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;
+
+import ch.qos.logback.classic.LoggerContext;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.logging.StandardOutputListener;
+import org.gradle.api.specs.Spec;
+import org.gradle.util.RedirectStdOutAndErr;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.FileDescriptor;
+import java.io.StringWriter;
+
+import static org.gradle.util.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+public class Slf4jLoggingConfigurerTest {
+    private final TerminalDetectorStub terminalDetector = new TerminalDetectorStub();
+    private final ConsoleStub console = new ConsoleStub();
+    private final Slf4jLoggingConfigurer configurer = new Slf4jLoggingConfigurer(terminalDetector) {
+        @Override
+        Console createConsole() {
+            return console;
+        }
+    };
+    private final StandardOutputListener outputListener = new ListenerImpl();
+    private final StandardOutputListener errorListener = new ListenerImpl();
+    private final Logger logger = LoggerFactory.getLogger("cat1");
+    @Rule
+    public final RedirectStdOutAndErr outputs = new RedirectStdOutAndErr();
+
+    @Before
+    public void setUp() {
+        configurer.addStandardOutputListener(outputListener);
+        configurer.addStandardErrorListener(errorListener);
+    }
+
+    @After
+    public void tearDown() {
+        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
+        lc.reset();
+    }
+
+    @Test
+    public void canListenOnStdOutput() {
+        configurer.configure(LogLevel.INFO);
+
+        logger.debug("debug message");
+        logger.info("info message");
+        logger.warn("warn message");
+        logger.error("error message");
+
+        assertThat(outputListener.toString(), equalTo(String.format("info message%nwarn message%n")));
+    }
+
+    @Test
+    public void canListenOnStdError() {
+        configurer.configure(LogLevel.INFO);
+
+        logger.debug("debug message");
+        logger.info("info message");
+        logger.warn("warn message");
+        logger.error("error message");
+
+        assertThat(errorListener.toString(), equalTo(String.format("error message%n")));
+    }
+
+    @Test
+    public void filtersProgressAndLowerWhenConfiguredAtQuietLevel() {
+        configurer.configure(LogLevel.QUIET);
+
+        logger.info(Logging.QUIET, "quiet message");
+        logger.info(Logging.PROGRESS_STARTED, "<start>");
+        logger.info(Logging.PROGRESS, "<progress>");
+        logger.info(Logging.PROGRESS_COMPLETE, "<complete>");
+        logger.info(Logging.LIFECYCLE, "lifecycle message");
+        logger.info("info message");
+        logger.debug("debug message");
+
+        assertThat(outputListener.toString(), equalTo(String.format("quiet message%n")));
+    }
+
+    @Test
+    public void filtersInfoAndLowerWhenConfiguredAtLifecycleLevel() {
+        configurer.configure(LogLevel.LIFECYCLE);
+
+        logger.info(Logging.QUIET, "quiet message");
+        logger.info(Logging.LIFECYCLE, "lifecycle message");
+        logger.info("info message");
+        logger.debug("debug message");
+
+        assertThat(outputListener.toString(), equalTo(String.format("quiet message%nlifecycle message%n")));
+    }
+
+    @Test
+    public void filtersDebugAndLowerWhenConfiguredAtInfoLevel() {
+        configurer.configure(LogLevel.INFO);
+
+        logger.info(Logging.QUIET, "quiet message");
+        logger.info(Logging.LIFECYCLE, "lifecycle message");
+        logger.info("info message");
+        logger.debug("debug message");
+
+        assertThat(outputListener.toString(), equalTo(String.format(
+                "quiet message%nlifecycle message%ninfo message%n")));
+    }
+
+    @Test
+    public void formatsProgressLogMessages() {
+        configurer.configure(LogLevel.LIFECYCLE);
+
+        logger.info(Logging.PROGRESS_STARTED, "<start>");
+        logger.info(Logging.PROGRESS, "<tick>");
+        logger.info(Logging.PROGRESS, "<tick>");
+        logger.info(Logging.PROGRESS_COMPLETE, "<complete>");
+
+        assertThat(outputListener.toString(), equalTo(String.format("<start> <complete>%n")));
+    }
+
+    @Test
+    public void formatsProgressLogMessagesForInfoLevel() {
+        configurer.configure(LogLevel.INFO);
+
+        logger.info(Logging.PROGRESS_STARTED, "<start>");
+        logger.info(Logging.PROGRESS, "<tick>");
+        logger.info(Logging.PROGRESS, "<tick>");
+        logger.info(Logging.PROGRESS_COMPLETE, "<complete>");
+
+        assertThat(outputListener.toString(), equalTo(String.format("<start> <complete>%n")));
+    }
+
+    @Test
+    public void formatsProgressLogMessagesForDebugLevel() {
+        configurer.configure(LogLevel.DEBUG);
+
+        logger.info(Logging.PROGRESS_STARTED, "<start>");
+        logger.info(Logging.PROGRESS, "<tick1>");
+        logger.info(Logging.PROGRESS, "<tick2>");
+        logger.info(Logging.PROGRESS_COMPLETE, "<complete>");
+
+        assertThat(outputListener.toString(), containsLine(endsWith(String.format("<start>"))));
+        assertThat(outputListener.toString(), containsLine(endsWith(String.format("<tick1>"))));
+        assertThat(outputListener.toString(), containsLine(endsWith(String.format("<tick2>"))));
+        assertThat(outputListener.toString(), containsLine(endsWith(String.format("<complete>"))));
+    }
+
+    @Test
+    public void routesLoggingMessagesWhenStdOutAndStdErrAreTerminals() {
+        terminalDetector.stderrIsTerminal().stdoutIsTerminal();
+
+        configurer.configure(LogLevel.LIFECYCLE);
+
+        logger.info(Logging.LIFECYCLE, "lifecycle");
+        logger.error("error");
+
+        assertThat(outputListener.toString(), equalTo(String.format("lifecycle%n")));
+        assertThat(errorListener.toString(), equalTo(String.format("error%n")));
+        assertThat(console.toString(), equalTo(String.format("lifecycle%nerror%n")));
+        assertThat(outputs.getStdOut(), equalTo(""));
+        assertThat(outputs.getStdErr(), equalTo(""));
+    }
+
+    @Test
+    public void routesLoggingMessagesWhenStdOutIsTerminal() {
+        terminalDetector.stdoutIsTerminal();
+
+        configurer.configure(LogLevel.LIFECYCLE);
+
+        logger.info(Logging.LIFECYCLE, "lifecycle");
+        logger.error("error");
+
+        assertThat(outputListener.toString(), equalTo(String.format("lifecycle%n")));
+        assertThat(errorListener.toString(), equalTo(String.format("error%n")));
+        assertThat(console.toString(), equalTo(String.format("lifecycle%n")));
+        assertThat(outputs.getStdOut(), equalTo(""));
+        assertThat(outputs.getStdErr(), equalTo(String.format("error%n")));
+    }
+
+    @Test
+    public void routesLoggingMessagesWhenStdErrIsTerminal() {
+        terminalDetector.stderrIsTerminal();
+
+        configurer.configure(LogLevel.LIFECYCLE);
+
+        logger.info(Logging.LIFECYCLE, "lifecycle");
+        logger.error("error");
+
+        assertThat(outputListener.toString(), equalTo(String.format("lifecycle%n")));
+        assertThat(errorListener.toString(), equalTo(String.format("error%n")));
+        assertThat(console.toString(), equalTo(String.format("error%n")));
+        assertThat(outputs.getStdOut(), equalTo(String.format("lifecycle%n")));
+        assertThat(outputs.getStdErr(), equalTo(""));
+    }
+
+    @Test
+    public void routesLoggingMessagesWhenNeitherStdOutAndStdErrAreTerminals() {
+        configurer.configure(LogLevel.LIFECYCLE);
+
+        logger.info(Logging.LIFECYCLE, "lifecycle");
+        logger.error("error");
+
+        assertThat(outputListener.toString(), equalTo(String.format("lifecycle%n")));
+        assertThat(errorListener.toString(), equalTo(String.format("error%n")));
+        assertThat(console.toString(), equalTo(""));
+        assertThat(outputs.getStdOut(), equalTo(String.format("lifecycle%n")));
+        assertThat(outputs.getStdErr(), equalTo(String.format("error%n")));
+    }
+
+    @Test
+    public void doesNotUseTheConsoleWhenSetToDebugLevel() {
+        terminalDetector.stderrIsTerminal().stdoutIsTerminal();
+
+        configurer.configure(LogLevel.DEBUG);
+
+        logger.info(Logging.LIFECYCLE, "lifecycle");
+        logger.error("error");
+
+        assertThat(outputListener.toString(), containsLine(endsWith("lifecycle")));
+        assertThat(errorListener.toString(), containsLine(endsWith("error")));
+        assertThat(console.toString(), equalTo(""));
+        assertThat(outputs.getStdOut(), containsLine(endsWith("lifecycle")));
+        assertThat(outputs.getStdErr(), containsLine(endsWith("error")));
+    }
+
+    private static class ListenerImpl implements StandardOutputListener {
+        private final StringWriter writer = new StringWriter();
+
+        @Override
+        public String toString() {
+            return writer.toString();
+        }
+
+        public void onOutput(CharSequence output) {
+            writer.append(output);
+        }
+    }
+
+    private static class TerminalDetectorStub implements Spec<FileDescriptor> {
+        boolean stdoutIsTerminal;
+        boolean stderrIsTerminal;
+
+        public boolean isSatisfiedBy(FileDescriptor element) {
+            if (element == FileDescriptor.out) {
+                return stdoutIsTerminal;
+            }
+            if (element == FileDescriptor.err) {
+                return stderrIsTerminal;
+            }
+            return false;
+        }
+
+        public TerminalDetectorStub stderrIsTerminal() {
+            stderrIsTerminal = true;
+            return this;
+        }
+
+        public TerminalDetectorStub stdoutIsTerminal() {
+            stdoutIsTerminal = true;
+            return this;
+        }
+    }
+
+    private static class ConsoleStub implements Console, TextArea {
+        private final StringWriter writer = new StringWriter();
+
+        @Override
+        public String toString() {
+            return writer.toString();
+        }
+
+        public Label addStatusBar() {
+            return new Label() {
+                public void setText(String text) {
+                }
+
+                public void close() {
+                }
+            };
+        }
+
+        public TextArea getMainArea() {
+            return this;
+        }
+
+        public void append(CharSequence text) {
+            writer.append(text);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/actor/internal/DefaultActorFactoryTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/actor/internal/DefaultActorFactoryTest.groovy
new file mode 100644
index 0000000..a445ee2
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/actor/internal/DefaultActorFactoryTest.groovy
@@ -0,0 +1,166 @@
+/*
+ * 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.messaging.actor.Actor
+import org.gradle.messaging.dispatch.DispatchException
+import org.gradle.messaging.dispatch.MethodInvocation
+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 static org.hamcrest.Matchers.*
+
+ at RunWith(JMock.class)
+class DefaultActorFactoryTest extends MultithreadedTestCase {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final TargetObject target = context.mock(TargetObject.class)
+    private final DefaultActorFactory factory = new DefaultActorFactory(executorFactory)
+
+    @After
+    public void tearDown() {
+        factory.stop()
+    }
+
+    @Test
+    public void createsAnActorForATargetObject() {
+        factory.createActor(target) != null
+    }
+
+    @Test
+    public void cachesTheActorForATargetObject() {
+        Actor actor1 = factory.createActor(target)
+        Actor actor2 = factory.createActor(target)
+        assertThat(actor2, sameInstance(actor1))
+    }
+
+    @Test
+    public void returnsTargetObjectIfTargetObjectIsAnActor() {
+        Actor actor1 = factory.createActor(target)
+        Actor actor2 = factory.createActor(actor1)
+        assertThat(actor2, sameInstance(actor1))
+    }
+
+    @Test
+    public void actorDispatchesMethodInvocationToTargetObject() {
+        Actor actor = factory.createActor(target)
+
+        context.checking {
+            one(target).doStuff('param')
+            will {
+                syncAt(1)
+            }
+        }
+
+        run {
+            actor.dispatch(new MethodInvocation(TargetObject.class.getMethod('doStuff', String.class), ['param'] as Object[]))
+            syncAt(1)
+        }
+    }
+
+    @Test
+    public void actorProxyDispatchesMethodCallToTargetObject() {
+        Actor actor = factory.createActor(target)
+        TargetObject proxy = actor.getProxy(TargetObject.class)
+
+        context.checking {
+            one(target).doStuff('param')
+            will {
+                syncAt(1)
+            }
+        }
+
+        run {
+            proxy.doStuff('param')
+            syncAt(1)
+        }
+    }
+
+    @Test
+    public void actorStopPropagatesMethodFailure() {
+        Actor actor = factory.createActor(target)
+        TargetObject proxy = actor.getProxy(TargetObject.class)
+        RuntimeException failure = new RuntimeException()
+
+        context.checking {
+            one(target).doStuff('param')
+            will {
+                throw failure
+            }
+        }
+
+        run {
+            proxy.doStuff('param')
+            try {
+                actor.stop()
+                fail()
+            } catch (DispatchException e) {
+                assertThat(e.message, startsWith('Failed to dispatch message'))
+                assertThat(e.cause, sameInstance(failure))
+            }
+        }
+    }
+
+    @Test
+    public void actorStopBlocksUntilAllMethodCallsComplete() {
+        Actor actor = factory.createActor(target)
+        TargetObject proxy = actor.getProxy(TargetObject.class)
+
+        context.checking {
+            one(target).doStuff('param')
+            will {
+                syncAt(1)
+            }
+        }
+
+        run {
+            proxy.doStuff('param')
+            expectBlocksUntil(1) {
+                actor.stop()
+            }
+        }
+    }
+
+    @Test
+    public void factoryStopBlocksUntilAllMethodCallsComplete() {
+        Actor actor = factory.createActor(target)
+        TargetObject proxy = actor.getProxy(TargetObject.class)
+
+        context.checking {
+            one(target).doStuff('param')
+            will {
+                syncAt(1)
+            }
+        }
+
+        run {
+            proxy.doStuff('param')
+            expectBlocksUntil(1) {
+                factory.stop()
+            }
+        }
+    }
+}
+
+interface TargetObject {
+    void doStuff(String param)
+
+    void stopDoingStuff()
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/concurrent/CompositeStoppableTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/concurrent/CompositeStoppableTest.groovy
new file mode 100644
index 0000000..688c17d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/concurrent/CompositeStoppableTest.groovy
@@ -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.messaging.concurrent
+
+import spock.lang.Specification
+
+class CompositeStoppableTest extends Specification {
+    private final CompositeStoppable stoppable = new CompositeStoppable()
+
+    def stopsAllElementsOnStop() {
+        Stoppable a = Mock()
+        Stoppable b = Mock()
+        stoppable.add(a)
+        stoppable.add(b)
+
+        when:
+        stoppable.stop()
+
+        then:
+        1 * a.stop()
+        1 * b.stop()
+    }
+
+    def stopsAllElementsWhenOneFailsToStop() {
+        Stoppable a = Mock()
+        Stoppable b = Mock()
+        RuntimeException failure = new RuntimeException()
+        stoppable.add(a)
+        stoppable.add(b)
+
+        when:
+        stoppable.stop()
+
+        then:
+        1 * a.stop() >> { throw failure }
+        1 * b.stop()
+        def e = thrown(RuntimeException)
+        e == failure
+    }
+
+    def stopsAllElementsWhenMultipleFailToStop() {
+        Stoppable a = Mock()
+        Stoppable b = Mock()
+        RuntimeException failure1 = new RuntimeException()
+        RuntimeException failure2 = new RuntimeException()
+        stoppable.add(a)
+        stoppable.add(b)
+
+        when:
+        stoppable.stop()
+
+        then:
+        1 * a.stop() >> { throw failure1 }
+        1 * b.stop() >> { throw failure2 }
+        def e = thrown(RuntimeException)
+        e == failure1
+    }
+
+    def closesACloseableElement() {
+        Closeable a = Mock()
+        Stoppable b = Mock()
+
+        stoppable.add(a)
+        stoppable.add(b)
+
+        when:
+        stoppable.stop()
+
+        then:
+        1 * a.close()
+        1 * b.stop()
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/concurrent/DefaultExecutorFactoryTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/concurrent/DefaultExecutorFactoryTest.groovy
new file mode 100644
index 0000000..3ed569b
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/concurrent/DefaultExecutorFactoryTest.groovy
@@ -0,0 +1,159 @@
+/*
+ * 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/gradle-core/src/test/groovy/org/gradle/messaging/dispatch/AsyncDispatchTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/dispatch/AsyncDispatchTest.groovy
new file mode 100644
index 0000000..50a8deb
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/dispatch/AsyncDispatchTest.groovy
@@ -0,0 +1,212 @@
+/*
+ * 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.util.JUnit4GroovyMockery
+import org.gradle.util.MultithreadedTestCase
+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.*
+
+ at RunWith(JMock.class)
+public class AsyncDispatchTest extends MultithreadedTestCase {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final Dispatch<String> target1 = context.mock(Dispatch.class, "target1")
+    private final Dispatch<String> target2 = context.mock(Dispatch.class, "target2")
+    private final AsyncDispatch<String> dispatch = new AsyncDispatch<String>(executor)
+
+    @Test
+    public void dispatchesMessageToAnIdleTarget() {
+        context.checking {
+            one(target1).dispatch('message1')
+            one(target1).dispatch('message2')
+        }
+
+        dispatch.dispatchTo(target1)
+
+        dispatch.dispatch('message1')
+        dispatch.dispatch('message2')
+
+        dispatch.stop()
+    }
+
+    @Test
+    public void dispatchDoesNotBlockWhileNoIdleTargetAvailable() {
+        context.checking {
+            one(target1).dispatch('message1')
+            will {
+                syncAt(1)
+                syncAt(2)
+            }
+            one(target2).dispatch('message2')
+            will {
+                syncAt(2)
+                syncAt(3)
+            }
+            one(target1).dispatch('message3')
+            will {
+                syncAt(3)
+            }
+        }
+
+        run {
+            dispatch.dispatchTo(target1)
+            dispatch.dispatch('message1')
+            syncAt(1)
+
+            dispatch.dispatchTo(target2)
+            dispatch.dispatch('message2')
+            syncAt(2)
+
+            dispatch.dispatch('message3')
+            syncAt(3)
+        }
+
+        dispatch.stop()
+    }
+
+    @Test
+    public void canStopFromMultipleThreads() {
+        dispatch.dispatchTo(target1)
+
+        start {
+            dispatch.stop()
+        }
+        start {
+            dispatch.stop()
+        }
+    }
+
+    @Test
+    public void canRequestStopFromMultipleThreads() {
+        dispatch.dispatchTo(target1)
+
+        start {
+            dispatch.requestStop()
+        }
+        start {
+            dispatch.requestStop()
+        }
+
+        waitForAll()
+        dispatch.stop()
+    }
+
+    @Test
+    public void stopBlocksUntilAllMessagesDispatched() {
+        context.checking {
+            one(target1).dispatch('message1')
+            will {
+                syncAt(1)
+                syncAt(2)
+                syncAt(3)
+            }
+        }
+
+        context.checking {
+            one(target2).dispatch('message2')
+            will {
+                syncAt(2)
+                syncAt(3)
+            }
+        }
+
+        run {
+            dispatch.dispatchTo(target1)
+            dispatch.dispatch('message1')
+            syncAt(1)
+
+            dispatch.dispatchTo(target2)
+            dispatch.dispatch('message2')
+            syncAt(2)
+
+            expectBlocksUntil(3) {
+                dispatch.stop()
+            }
+        }
+    }
+
+    @Test
+    public void requestStopDoesNotBlockWhenMessagesAreQueued() {
+        context.checking {
+            one(target1).dispatch('message1')
+            will {
+                syncAt(1)
+                syncAt(2)
+            }
+        }
+
+        run {
+            dispatch.dispatchTo(target1)
+            dispatch.dispatch('message1')
+            syncAt(1)
+            dispatch.requestStop()
+            shouldBeAt(1)
+            syncAt(2)
+        }
+
+        waitForAll()
+        dispatch.stop()
+    }
+
+    @Test
+    public void stopFailsWhenNoTargetsAvailableToDeliverQueuedMessages() {
+        dispatch.dispatch('message1')
+        try {
+            dispatch.stop()
+            fail()
+        } catch (IllegalStateException e) {
+            assertThat(e.message, equalTo('Cannot wait for messages to be dispatched, as there are no dispatch threads running.'))
+        }
+    }
+
+    @Test
+    public void stopFailsWhenAllTargetsHaveFailed() {
+        context.checking {
+            one(target1).dispatch('message1')
+            will {
+                RuntimeException failure = new RuntimeException()
+                willFailWith(sameInstance(failure))
+                throw failure
+            }
+        }
+        dispatch.dispatchTo(target1)
+        dispatch.dispatch('message1')
+        dispatch.dispatch('message2')
+
+        try {
+            dispatch.stop()
+            fail()
+        } catch (IllegalStateException e) {
+            assertThat(e.message, equalTo('Cannot wait for messages to be dispatched, as there are no dispatch threads running.'))
+        }
+    }
+    
+    @Test
+    public void cannotDispatchMessagesAfterStop() {
+        dispatch.stop()
+        try {
+            dispatch.dispatch('message')
+            fail()
+        } catch (IllegalStateException e) {
+            assertThat(e.message, equalTo('This message dispatch has been stopped.'))
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/dispatch/AsyncReceiveTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/dispatch/AsyncReceiveTest.groovy
new file mode 100644
index 0000000..ffd29d2
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/dispatch/AsyncReceiveTest.groovy
@@ -0,0 +1,122 @@
+/*
+ * 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 static org.hamcrest.Matchers.*
+
+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
+import static org.junit.Assert.*
+
+ at RunWith(JMock.class)
+public class AsyncReceiveTest extends MultithreadedTestCase {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final Dispatch<String> target1 = context.mock(Dispatch.class, "target1")
+    private final Dispatch<String> target2 = context.mock(Dispatch.class, "target2")
+    private final Receive<String> source1 = context.mock(Receive.class, "source1")
+    private final AsyncReceive<String> dispatch = new AsyncReceive<String>(executor, target1)
+
+    @Test
+    public void dispatchesReceivedMessagesToTargetUntilEndOfStreamReached() {
+        clockTick(1).hasParticipants(2)
+
+        context.checking {
+            Sequence receive = context.sequence('receive')
+            Sequence dispatch = context.sequence('dispatch')
+
+            one(source1).receive()
+            will(returnValue('message1'))
+            inSequence(receive)
+
+            one(source1).receive()
+            will(returnValue('message2'))
+            inSequence(receive)
+
+            one(source1).receive()
+            inSequence(receive)
+            will {
+                syncAt(1)
+                return null
+            }
+
+            one(target1).dispatch('message1')
+            inSequence(dispatch)
+
+            one(target1).dispatch('message2')
+            inSequence(dispatch)
+        }
+
+        dispatch.receiveFrom(source1)
+
+        run {
+            syncAt(1)
+        }
+        
+        dispatch.stop()
+    }
+
+    @Test
+    public void stopBlocksUntilAllReceiveCallsHaveReturned() {
+        context.checking {
+            one(source1).receive()
+            will {
+                syncAt(1)
+                return 'message'
+            }
+            one(target1).dispatch('message')
+            will {
+                syncAt(2)
+            }
+        }
+
+        run {
+            dispatch.receiveFrom(source1)
+            syncAt(1)
+            expectBlocksUntil(2) {
+                dispatch.stop()
+            }
+        }
+    }
+
+    @Test
+    public void requestStopDoesNotBlock() {
+        context.checking {
+            one(source1).receive()
+            will {
+                syncAt(1)
+                syncAt(2)
+                return 'message'
+            }
+            one(target1).dispatch('message')
+        }
+
+        run {
+            dispatch.receiveFrom(source1)
+            syncAt(1)
+            dispatch.requestStop()
+            syncAt(2)
+        }
+
+        dispatch.stop()
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/dispatch/ContextClassLoaderDispatchTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/dispatch/ContextClassLoaderDispatchTest.groovy
new file mode 100644
index 0000000..80261c0
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/dispatch/ContextClassLoaderDispatchTest.groovy
@@ -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.dispatch
+
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+ at RunWith(JMock.class)
+class ContextClassLoaderDispatchTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final Dispatch<String> target = context.mock(Dispatch.class)
+    private final ClassLoader appClassLoader = new ClassLoader() {}
+    private ClassLoader original
+    private final ContextClassLoaderDispatch dispatch = new ContextClassLoaderDispatch(target, appClassLoader)
+
+    @Before
+    public void setUp() {
+        original = Thread.currentThread().contextClassLoader
+    }
+
+    @After
+    public void tearDown() {
+        Thread.currentThread().contextClassLoader = original
+    }
+
+    @Test
+    public void setsContextClassLoaderDuringDispatch() {
+        context.checking {
+            one(target).dispatch('message')
+            will {
+                assertThat(Thread.currentThread().contextClassLoader, sameInstance(appClassLoader))
+            }
+        }
+
+        dispatch.dispatch('message')
+        assertThat(Thread.currentThread().contextClassLoader, sameInstance(original))
+    }
+
+    @Test
+    public void cleansUpAfterFailure() {
+        RuntimeException failure = new RuntimeException()
+
+        context.checking {
+            one(target).dispatch('message')
+            will {
+                assertThat(Thread.currentThread().contextClassLoader, sameInstance(appClassLoader))
+                throw failure
+            }
+        }
+
+        try {
+            dispatch.dispatch('message')
+            fail()
+        } catch (RuntimeException e) {
+            assertThat(e, sameInstance(failure))
+        }
+
+        assertThat(Thread.currentThread().contextClassLoader, sameInstance(original))
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/dispatch/ExceptionTrackingDispatchTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/dispatch/ExceptionTrackingDispatchTest.groovy
new file mode 100644
index 0000000..06f1aab
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/dispatch/ExceptionTrackingDispatchTest.groovy
@@ -0,0 +1,40 @@
+/*
+ * 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.api.Action
+import spock.lang.Specification
+
+class ExceptionTrackingDispatchTest extends Specification {
+    private final Dispatch<String> target = Mock()
+    private final Action<RuntimeException> action = Mock()
+    private final ExceptionTrackingDispatch<String> dispatch = new ExceptionTrackingDispatch<String>(target, action)
+
+    def executesActionOnDispatchFailure() {
+        RuntimeException failure = new RuntimeException()
+
+        when:
+        dispatch.dispatch('message')
+
+        then:
+        1 * target.dispatch('message') >> { throw failure }
+        1 * action.execute(!null) >> { args ->
+            assert args[0] instanceof DispatchException
+            assert args[0].cause == failure
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/dispatch/ExceptionTrackingListenerTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/dispatch/ExceptionTrackingListenerTest.groovy
new file mode 100644
index 0000000..32e6d11
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/dispatch/ExceptionTrackingListenerTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * 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.slf4j.Logger
+import spock.lang.Specification
+
+class ExceptionTrackingListenerTest extends Specification {
+    private final Logger logger = Mock()
+    private final ExceptionTrackingListener dispatch = new ExceptionTrackingListener(logger)
+
+    def stopRethrowsFailure() {
+        RuntimeException failure = new RuntimeException()
+
+        when:
+        dispatch.execute(failure)
+        dispatch.stop()
+
+        then:
+        def e = thrown(RuntimeException)
+        e == failure
+        0 * logger._
+    }
+
+    def logsAnySubsequentFailures() {
+        RuntimeException failure1 = new RuntimeException()
+        RuntimeException failure2 = new RuntimeException('broken2')
+
+        when:
+        dispatch.execute(failure1)
+        dispatch.execute(failure2)
+        dispatch.stop()
+
+        then:
+        def e = thrown(RuntimeException)
+        e == failure1
+        1 * logger.error('broken2', failure2)
+        0 * logger._
+    }
+
+    def stopDoesNothingWhenThereWereNoFailures() {
+        when:
+        dispatch.stop()
+
+        then:
+        0 * logger._
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/dispatch/MethodInvocationTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/dispatch/MethodInvocationTest.java
new file mode 100644
index 0000000..7219366
--- /dev/null
+++ b/subprojects/gradle-core/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/gradle-core/src/test/groovy/org/gradle/messaging/dispatch/ProxyDispatchAdapterTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/dispatch/ProxyDispatchAdapterTest.groovy
new file mode 100644
index 0000000..6ac9e26
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/dispatch/ProxyDispatchAdapterTest.groovy
@@ -0,0 +1,49 @@
+/*
+ * 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 spock.lang.Specification
+import static org.gradle.util.Matchers.*
+
+class ProxyDispatchAdapterTest extends Specification {
+    private final Dispatch<MethodInvocation> dispatch = Mock()
+    private final ProxyDispatchAdapter<ProxyTest> adapter = new ProxyDispatchAdapter<ProxyTest>(ProxyTest.class, dispatch)
+
+    def proxyForwardsToDispatch() {
+        when:
+        adapter.getSource().doStuff('param')
+
+        then:
+        1 * dispatch.dispatch(new MethodInvocation(ProxyTest.class.getMethod('doStuff', String.class), ['param'] as Object[]))
+    }
+    
+    def proxyIsEqualWhenItHasTheSameTypeAndDispatch() {
+        def other = new ProxyDispatchAdapter<ProxyTest>(ProxyTest.class, dispatch)
+        def differentType = new ProxyDispatchAdapter<Runnable>(Runnable.class, dispatch)
+        def differentDispatch = new ProxyDispatchAdapter<ProxyTest>(ProxyTest.class, Mock(Dispatch.class))
+
+        expect:
+        ProxyTest proxy = adapter.getSource()
+        strictlyEquals(proxy, other.getSource())
+        proxy != differentType.getSource()
+        proxy != differentDispatch.getSource()
+    }
+}
+
+interface ProxyTest {
+    void doStuff(String param)
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelMessageMarshallingDispatchTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelMessageMarshallingDispatchTest.java
new file mode 100644
index 0000000..f98d492
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelMessageMarshallingDispatchTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.dispatch.Dispatch;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+ at RunWith(JMock.class)
+public class ChannelMessageMarshallingDispatchTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final Dispatch<Message> delegate = context.mock(Dispatch.class);
+    private final ChannelMessageMarshallingDispatch dispatch = new ChannelMessageMarshallingDispatch(delegate);
+
+    @Test
+    public void mapsChannelKeyToIntegerChannelId() {
+        final Message message1 = new TestMessage();
+        final Message message2 = new TestMessage();
+
+        context.checking(new Expectations() {{
+            one(delegate).dispatch(new ChannelMetaInfo("channel", 0));
+            one(delegate).dispatch(new ChannelMessage(0, message1));
+            one(delegate).dispatch(new ChannelMessage(0, message2));
+        }});
+
+        dispatch.dispatch(new ChannelMessage("channel", message1));
+        dispatch.dispatch(new ChannelMessage("channel", message2));
+    }
+
+    @Test
+    public void mapsMultipleChannelsToDifferentIds() {
+        final Message message1 = new TestMessage();
+        final Message message2 = new TestMessage();
+
+        context.checking(new Expectations() {{
+            one(delegate).dispatch(new ChannelMetaInfo("channel1", 0));
+            one(delegate).dispatch(new ChannelMessage(0, message1));
+            one(delegate).dispatch(new ChannelMetaInfo("channel2", 1));
+            one(delegate).dispatch(new ChannelMessage(1, message2));
+        }});
+
+        dispatch.dispatch(new ChannelMessage("channel1", message1));
+        dispatch.dispatch(new ChannelMessage("channel2", message2));
+    }
+
+    @Test
+    public void forwardsUnknownMessages() {
+        final Message message = new TestMessage();
+
+        context.checking(new Expectations() {{
+            one(delegate).dispatch(message);
+        }});
+
+        dispatch.dispatch(message);
+    }
+
+    private static class TestMessage extends Message {
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelMessageUnmarshallingDispatchTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelMessageUnmarshallingDispatchTest.java
new file mode 100644
index 0000000..6b6d331
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/ChannelMessageUnmarshallingDispatchTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.dispatch.Dispatch;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+ at RunWith(JMock.class)
+public class ChannelMessageUnmarshallingDispatchTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final Dispatch<Message> delegate = context.mock(Dispatch.class);
+    private final ChannelMessageUnmarshallingDispatch dispatch = new ChannelMessageUnmarshallingDispatch(delegate);
+
+    @Test
+    public void mapsChannelIdToChannelKey() {
+        final Message message1 = new TestMessage();
+        final Message message2 = new TestMessage();
+
+        context.checking(new Expectations() {{
+            one(delegate).dispatch(new ChannelMessage("channel", message1));
+            one(delegate).dispatch(new ChannelMessage("channel", message2));
+        }});
+
+        dispatch.dispatch(new ChannelMetaInfo("channel", 12));
+        dispatch.dispatch(new ChannelMessage(12, message1));
+        dispatch.dispatch(new ChannelMessage(12, message2));
+    }
+
+    @Test
+    public void mapsMultipleChannelsToDifferentKeys() {
+        final Message message1 = new TestMessage();
+        final Message message2 = new TestMessage();
+
+        context.checking(new Expectations() {{
+            one(delegate).dispatch(new ChannelMessage("channel1", message1));
+            one(delegate).dispatch(new ChannelMessage("channel2", message2));
+        }});
+
+        dispatch.dispatch(new ChannelMetaInfo("channel1", 0));
+        dispatch.dispatch(new ChannelMessage(0, message1));
+        dispatch.dispatch(new ChannelMetaInfo("channel2", 1));
+        dispatch.dispatch(new ChannelMessage(1, message2));
+    }
+
+    @Test
+    public void forwardsUnknownMessages() {
+        final Message message = new TestMessage();
+
+        context.checking(new Expectations() {{
+            one(delegate).dispatch(message);
+        }});
+
+        dispatch.dispatch(message);
+    }
+
+    private static class TestMessage extends Message {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClientTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClientTest.java
new file mode 100644
index 0000000..1efca05
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingClientTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.URI;
+
+import static org.hamcrest.Matchers.*;
+
+ at RunWith(JMock.class)
+public class DefaultMessagingClientTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final MultiChannelConnector connector = context.mock(MultiChannelConnector.class);
+
+    @Test
+    public void createsConnectionOnConstructionAndStopsOnStop() throws Exception {
+        final URI serverAddress = new URI("test:somestuff");
+        final MultiChannelConnection<Message> connection = context.mock(MultiChannelConnection.class);
+
+        context.checking(new Expectations() {{
+            one(connector).connect(with(equalTo(serverAddress)));
+            will(returnValue(connection));
+        }});
+
+        DefaultMessagingClient client = new DefaultMessagingClient(connector, getClass().getClassLoader(), serverAddress);
+
+        context.checking(new Expectations() {{
+            one(connection).stop();
+        }});
+        client.stop();
+    }
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServerTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServerTest.groovy
new file mode 100644
index 0000000..e12948f
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMessagingServerTest.groovy
@@ -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 spock.lang.Specification
+import org.gradle.api.Action
+import org.gradle.messaging.remote.ConnectEvent
+import org.gradle.messaging.remote.ObjectConnection
+
+class DefaultMessagingServerTest extends Specification {
+    private final MultiChannelConnector multiChannelConnector = Mock()
+    private final DefaultMessagingServer server = new DefaultMessagingServer(multiChannelConnector, getClass().classLoader)
+
+
+    def createsConnection() {
+        Action<ConnectEvent<ObjectConnection>> action = Mock()
+        Action<ConnectEvent<MultiChannelConnection<Message>>> wrappedAction = Mock()
+        MultiChannelConnection<Message> connection = Mock()
+
+        when:
+        def uri = server.accept(action)
+
+        then:
+        uri == new URI("test:dest")
+        1 * multiChannelConnector.accept(!null) >> { wrappedAction = it[0]; return new URI("test:dest") }
+
+        when:
+        wrappedAction.execute(new ConnectEvent(connection, new URI("test:local"), new URI("test:remote")))
+
+        then:
+        1 * action.execute(!null) >> {
+            ConnectEvent event = it[0]
+            assert event.connection instanceof DefaultObjectConnection
+            assert event.localAddress == new URI("test:local")
+            assert event.remoteAddress == new URI("test:remote")
+        }
+    }
+
+    def stopsConnectionsOnServerStop() {
+        MultiChannelConnection<Message> channelConnection = Mock()
+        expectConnectionCreated(channelConnection)
+
+        when:
+        server.stop()
+
+        then:
+        1 * channelConnection.stop()
+    }
+
+    def discardsConnectionOnStop() {
+        MultiChannelConnection<Message> channelConnection = Mock()
+        ObjectConnection connection = expectConnectionCreated(channelConnection)
+
+        when:
+        connection.stop()
+
+        then:
+        1 * channelConnection.stop()
+
+        when:
+        server.stop()
+
+        then:
+        0 * channelConnection._
+    }
+
+    private ObjectConnection expectConnectionCreated(MultiChannelConnection<Message> channelConnection) {
+        Action<ConnectEvent<ObjectConnection>> action = Mock()
+        ObjectConnection connection
+
+        1 * multiChannelConnector.accept(!null) >> {
+            def wrappedAction = it[0]
+            wrappedAction.execute(new ConnectEvent(channelConnection, new URI("test:local"), new URI("test:remote")))
+        }
+        1 * action.execute(!null) >> {
+            connection = it[0].connection
+        }
+        server.accept(action)
+        return connection
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnectionTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnectionTest.groovy
new file mode 100644
index 0000000..467e024
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultMultiChannelConnectionTest.groovy
@@ -0,0 +1,276 @@
+/*
+ * 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.dispatch.Dispatch
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.MultithreadedTestCase
+import org.jmock.integration.junit4.JMock
+import org.junit.Test
+import org.junit.runner.RunWith
+
+ at RunWith(JMock.class)
+public class DefaultMultiChannelConnectionTest extends MultithreadedTestCase {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final Connection<Message> target = context.mock(Connection.class)
+    private final TestMessage message = new TestMessage()
+    private DefaultMultiChannelConnection connection
+
+    @Test
+    public void dispatchesOutgoingMessageToTargetConnection() {
+        clockTick(1).hasParticipants(2)
+        context.checking {
+            one(target).receive()
+            will {
+                syncAt(1)
+                return null
+            }
+            one(target).dispatch(new ChannelMetaInfo('channel1', 0))
+            one(target).dispatch(new ChannelMessage(0, message))
+            one(target).dispatch(new EndOfStreamEvent())
+            one(target).stop()
+        }
+
+        connection = new DefaultMultiChannelConnection(executorFactory, 'connection', target, new URI('test:local'), new URI('test:remote'))
+        run {
+            connection.addOutgoingChannel('channel1').dispatch(message)
+            syncAt(1)
+        }
+
+        connection.stop()
+    }
+
+    @Test
+    public void dispatchesIncomingMessageToHandler() {
+        clockTick(1).hasParticipants(2)
+        Dispatch<Message> handler = context.mock(Dispatch.class)
+        context.checking {
+            one(target).receive()
+            will(returnValue(new ChannelMetaInfo('channel1', 0)))
+            one(target).receive()
+            will(returnValue(new ChannelMessage(0, message)))
+            one(handler).dispatch(message)
+            one(target).receive()
+            will {
+                syncAt(1)
+                return null
+            }
+            one(target).dispatch(new EndOfStreamEvent())
+            one(target).stop()
+        }
+
+        connection = new DefaultMultiChannelConnection(executorFactory, 'connection', target, new URI('test:local'), new URI('test:remote'))
+
+        run {
+            connection.addIncomingChannel('channel1', handler)
+
+            syncAt(1)
+        }
+
+        connection.stop()
+    }
+
+    @Test
+    public void stuckHandlerDoesNotBlockOtherHandlers() {
+        Dispatch<Message> handler1 = context.mock(Dispatch.class, 'handler1')
+        Dispatch<Message> handler2 = context.mock(Dispatch.class, 'handler2')
+        TestMessage message2 = new TestMessage()
+        clockTick(1).hasParticipants(3)
+        clockTick(2).hasParticipants(3)
+
+        context.checking {
+            one(target).receive()
+            will(returnValue(new ChannelMetaInfo('channel1', 0)))
+            one(target).receive()
+            will(returnValue(new ChannelMessage(0, message)))
+            one(handler1).dispatch(message)
+            will {
+                syncAt(1)
+                syncAt(2)
+            }
+
+            one(target).receive()
+            will(returnValue(new ChannelMetaInfo('channel2', 1)))
+            one(target).receive()
+            will {
+                syncAt(1)
+                return new ChannelMessage(1, message2)
+            }
+            one(handler2).dispatch(message2)
+            will {
+                shouldBeAt(1)
+                syncAt(2)
+            }
+
+            one(target).receive()
+            will {
+                return null
+            }
+            one(target).dispatch(new EndOfStreamEvent())
+            one(target).stop()
+        }
+
+        connection = new DefaultMultiChannelConnection(executorFactory, 'connection', target, new URI('test:local'), new URI('test:remote'))
+
+        run {
+            connection.addIncomingChannel('channel1', handler1)
+            connection.addIncomingChannel('channel2', handler2)
+
+            syncAt(1)
+            syncAt(2)
+        }
+
+        connection.stop()
+    }
+
+    @Test
+    public void discardsMessageWhenHandlerIsBroken() {
+        clockTick(1).hasParticipants(2)
+        Dispatch<Message> handler = context.mock(Dispatch.class)
+        context.checking {
+            one(target).receive()
+            will(returnValue(new ChannelMetaInfo('channel1', 1)))
+            one(target).receive()
+            will(returnValue(new ChannelMessage(1, message)))
+            one(handler).dispatch(message)
+            will(throwException(new RuntimeException()))
+            one(target).receive()
+            will {
+                syncAt(1)
+                return null
+            }
+            one(target).dispatch(new EndOfStreamEvent())
+            one(target).stop()
+        }
+
+        connection = new DefaultMultiChannelConnection(executorFactory, 'connection', target, new URI('test:local'), new URI('test:remote'))
+
+        run {
+            connection.addIncomingChannel('channel1', handler)
+
+            syncAt(1)
+        }
+
+        connection.stop()
+    }
+
+    @Test
+    public void queuesIncomingChannelMessageUntilHandlerIsAvailable() {
+        Dispatch<Message> handler = context.mock(Dispatch.class)
+        TestMessage message2 = new TestMessage()
+
+        clockTick(1).hasParticipants(2)
+
+        context.checking {
+            one(target).receive()
+            will(returnValue(new ChannelMetaInfo('channel1', 1)))
+            one(target).receive()
+            will(returnValue(new ChannelMessage(1, message)))
+            one(target).receive()
+            will {
+                syncAt(1)
+                return new ChannelMessage(1, message2)
+            }
+            one(handler).dispatch(message)
+            one(handler).dispatch(message2)
+            one(target).receive()
+            will {
+                return null
+            }
+            one(target).dispatch(new EndOfStreamEvent())
+            one(target).stop()
+        }
+
+        connection = new DefaultMultiChannelConnection(executorFactory, 'connection', target, new URI('test:local'), new URI('test:remote'))
+
+        run {
+            syncAt(1)
+            connection.addIncomingChannel('channel1', handler)
+        }
+
+        connection.stop()
+    }
+
+    @Test
+    public void stopBlocksUntilAllIncomingMessagesAreHandled() {
+        Dispatch<Message> handler = context.mock(Dispatch.class)
+        clockTick(1).hasParticipants(2)
+
+        context.checking {
+            one(target).receive()
+            will(returnValue(new ChannelMetaInfo('channel1', 1)))
+            one(target).receive()
+            will(returnValue(new ChannelMessage(1, message)))
+            one(handler).dispatch(message)
+            will {
+                syncAt(1)
+            }
+            one(target).receive()
+            will(returnValue(null))
+            one(target).dispatch(new EndOfStreamEvent())
+            one(target).stop()
+        }
+
+        connection = new DefaultMultiChannelConnection(executorFactory, 'connection', target, new URI('test:local'), new URI('test:remote'))
+
+        run {
+            connection.addIncomingChannel('channel1', handler)
+
+            expectBlocksUntil(1) {
+                connection.stop()
+            }
+        }
+    }
+
+    @Test
+    public void stopBlocksUntilAllOutgoingMessagesAreDispatched() {
+        clockTick(1).hasParticipants(3)
+
+        context.checking {
+            one(target).receive()
+            will {
+                syncAt(1)
+                return null
+            }
+
+            one(target).dispatch(new ChannelMetaInfo('channel1', 0))
+            one(target).dispatch(new ChannelMessage(0, message))
+            will {
+                syncAt(1)
+            }
+            one(target).dispatch(new EndOfStreamEvent())
+            one(target).stop()
+        }
+
+        connection = new DefaultMultiChannelConnection(executorFactory, 'connection', target, new URI('test:local'), new URI('test:remote'))
+
+        run {
+            connection.addOutgoingChannel('channel1').dispatch(message)
+
+            expectBlocksUntil(1) {
+                connection.stop()
+            }
+        }
+    }
+}
+
+class TestMessage extends Message {
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnectionTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnectionTest.java
new file mode 100644
index 0000000..f62c0e1
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/DefaultObjectConnectionTest.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;
+
+import org.gradle.messaging.concurrent.AsyncStoppable;
+import org.gradle.messaging.dispatch.Addressable;
+import org.gradle.messaging.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+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.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.gradle.util.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ 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(getClass().getClassLoader(), connection.getSender());
+        IncomingMethodInvocationHandler receiverIncoming = new IncomingMethodInvocationHandler(
+                getClass().getClassLoader(), 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 {
+        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<Message>> channels = new HashMap<Object, Dispatch<Message>>();
+
+        MultiChannelConnection<Message> getSender() {
+            return new MultiChannelConnection<Message>() {
+                public Dispatch<Message> addOutgoingChannel(Object channelKey) {
+                    return channels.get(channelKey);
+                }
+
+                public void addIncomingChannel(Object channelKey, Dispatch<Message> dispatch) {
+                    throw new UnsupportedOperationException();
+                }
+
+                public void requestStop() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public void stop() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public URI getLocalAddress() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public URI getRemoteAddress() {
+                    throw new UnsupportedOperationException();
+                }
+            };
+        }
+
+        MultiChannelConnection<Message> getReceiver() {
+            return new MultiChannelConnection<Message>() {
+                public Dispatch<Message> addOutgoingChannel(Object channelKey) {
+                    throw new UnsupportedOperationException();
+                }
+
+                public void addIncomingChannel(Object channelKey, Dispatch<Message> dispatch) {
+                    channels.put(channelKey, dispatch);
+                }
+
+                public void requestStop() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public void stop() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public URI getLocalAddress() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public URI 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/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/EndOfStreamDispatchTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/EndOfStreamDispatchTest.groovy
new file mode 100644
index 0000000..b3b876b
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/EndOfStreamDispatchTest.groovy
@@ -0,0 +1,99 @@
+/*
+ * 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.dispatch.Dispatch
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.MultithreadedTestCase
+import org.jmock.integration.junit4.JMock
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.junit.Assert.*
+
+ at RunWith(JMock.class)
+public class EndOfStreamDispatchTest extends MultithreadedTestCase {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final Dispatch<Message> target = context.mock(Dispatch.class)
+    private final EndOfStreamDispatch dispatch = new EndOfStreamDispatch(target)
+
+    @Test
+    public void writesEndOfStreamMessageOnStop() {
+        context.checking {
+            one(target).dispatch(new EndOfStreamEvent())
+        }
+
+        dispatch.stop()
+    }
+
+    @Test
+    public void stopBlocksUntilCurrentlyPendingMessageDelivered() {
+        Message message = new Message() {}
+
+        context.checking {
+            one(target).dispatch(message)
+            will {
+                syncAt(1)
+                syncAt(2)
+            }
+            one(target).dispatch(new EndOfStreamEvent())
+        }
+
+        start {
+            dispatch.dispatch(message)
+        }
+
+        run {
+            syncAt(1)
+            expectBlocksUntil(2) {
+                dispatch.stop()
+            }
+        }
+    }
+
+    @Test
+    public void cannotDispatchAfterStop() {
+        context.checking {
+            one(target).dispatch(new EndOfStreamEvent())
+        }
+
+        dispatch.stop()
+
+        try {
+            dispatch.dispatch(new Message(){})
+            fail()
+        } catch (IllegalStateException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void canStopFromMultipleThreads() {
+        context.checking {
+            one(target).dispatch(new EndOfStreamEvent())
+        }
+
+        start {
+            dispatch.stop()
+        }
+        start {
+            dispatch.stop()
+        }
+
+        waitForAll()
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/EndOfStreamFilterTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/EndOfStreamFilterTest.groovy
new file mode 100644
index 0000000..b7dde8f
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/EndOfStreamFilterTest.groovy
@@ -0,0 +1,68 @@
+/*
+ * 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.dispatch.Dispatch
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.MultithreadedTestCase
+import org.jmock.integration.junit4.JMock
+import org.jmock.lib.legacy.ClassImposteriser
+import org.junit.Test
+import org.junit.runner.RunWith
+
+ at RunWith(JMock.class)
+public class EndOfStreamFilterTest extends MultithreadedTestCase {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery() {{ imposteriser = ClassImposteriser.INSTANCE }}
+    private final Dispatch<Message> target = context.mock(Dispatch.class)
+    private final Runnable action = context.mock(Runnable.class)
+    private final EndOfStreamFilter filter = new EndOfStreamFilter(target, action)
+    
+    @Test
+    public void stopBlocksUntilEndOfStreamReceived() {
+        context.checking {
+            one(action).run()
+        }
+
+        start {
+            expectBlocksUntil(1) {
+                filter.stop()
+            }
+        }
+
+        run {
+            syncAt(1)
+            filter.dispatch(new EndOfStreamEvent())
+        }
+    }
+
+    @Test
+    public void canStopFromMultipleThreads() {
+        context.checking {
+            one(action).run()
+        }
+
+        filter.dispatch(new EndOfStreamEvent())
+
+        start {
+            filter.stop()
+        }
+        run {
+            filter.stop()
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/EndOfStreamReceiveTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/EndOfStreamReceiveTest.groovy
new file mode 100644
index 0000000..33d05c6
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/EndOfStreamReceiveTest.groovy
@@ -0,0 +1,50 @@
+/*
+ * 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 spock.lang.Specification
+import org.gradle.messaging.dispatch.Receive
+
+class EndOfStreamReceiveTest extends Specification {
+    private final Receive<Message> target = Mock()
+    private final EndOfStreamReceive receive = new EndOfStreamReceive(target)
+
+    def receivesMessageFromTarget() {
+        Message message = Mock()
+
+        when:
+        def m = receive.receive()
+
+        then:
+        m == message
+        1 * target.receive() >> message
+    }
+    
+    def returnsEndOfStreamMessageThenNullWhenTargetReturnsNull() {
+        when:
+        def m = receive.receive()
+
+        then:
+        m instanceof EndOfStreamEvent
+        1 * target.receive() >> null
+
+        expect:
+        receive.receive() == null
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnectorTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnectorTest.groovy
new file mode 100644
index 0000000..281cf2f
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeIncomingConnectorTest.groovy
@@ -0,0 +1,77 @@
+/*
+ * 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 spock.lang.Specification
+import org.gradle.api.Action
+import java.util.concurrent.Executor
+import org.gradle.messaging.remote.ConnectEvent
+
+class HandshakeIncomingConnectorTest extends Specification {
+    private final Executor executor = Mock()
+    private final IncomingConnector target = Mock()
+    private final Connection connection = Mock()
+    private final HandshakeIncomingConnector connector = new HandshakeIncomingConnector(target, executor)
+
+    def acceptAllocatesAUriAndStartsListening() {
+        Action<ConnectEvent<Connection<Message>>> action = Mock()
+
+        when:
+        def address = connector.accept(action)
+
+        then:
+        1 * target.accept(!null) >> new URI("test:source")
+        address == new URI("channel:test:source!0")
+    }
+
+    def eachCallToAcceptAllocatesADifferentUri() {
+        Action<ConnectEvent<Connection<Message>>> action = Mock()
+        1 * target.accept(!null) >> new URI("test:source")
+
+        when:
+        def address1 = connector.accept(action)
+        def address2 = connector.accept(action)
+
+        then:
+        address1 == new URI("channel:test:source!0")
+        address2 == new URI("channel:test:source!1")
+    }
+    
+    def performsHandshakeOnAccept() {
+        Action<ConnectEvent<Connection<Message>>> action = Mock()
+        def wrappedAction
+        def handshakeRunnable
+        1 * target.accept(!null) >> { wrappedAction = it[0]; new URI("test:source") }
+
+        def address = connector.accept(action)
+
+        when:
+        wrappedAction.execute(new ConnectEvent<Connection<Message>>(connection, new URI("test:source"), new URI("test:dest")))
+
+        then:
+        1 * executor.execute(!null) >> { handshakeRunnable = it[0] }
+
+        when:
+        handshakeRunnable.run()
+
+        then:
+        1 * connection.receive() >> new ConnectRequest(address)
+        1 * action.execute({ it instanceof ConnectEvent && it.connection == connection && it.localAddress == address})
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnectorTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnectorTest.groovy
new file mode 100644
index 0000000..993ffd2
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/HandshakeOutgoingConnectorTest.groovy
@@ -0,0 +1,60 @@
+/*
+ * 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 spock.lang.Specification
+
+class HandshakeOutgoingConnectorTest extends Specification {
+    private final OutgoingConnector target = Mock()
+    private final Connection<Message> connection = Mock()
+    private final HandshakeOutgoingConnector connector = new HandshakeOutgoingConnector(target)
+
+    def createsConnectionAndPerformsHandshake() {
+        when:
+        def connection = connector.connect(new URI("channel:test:dest!0"))
+
+        then:
+        connection == this.connection
+        1 * target.connect(new URI("test:dest")) >> connection
+        1 * connection.dispatch({it instanceof ConnectRequest && it.destinationAddress == new URI("channel:test:dest!0")})
+    }
+
+    def stopsConnectionOnFailureToPerformHandshake() {
+        RuntimeException failure = new RuntimeException()
+
+        when:
+        connector.connect(new URI("channel:test:dest!0"))
+
+        then:
+        def e = thrown(RuntimeException)
+        e == failure
+        1 * target.connect(new URI("test:dest")) >> connection
+        1 * connection.dispatch({it instanceof ConnectRequest}) >> { throw failure }
+        1 * connection.stop()
+    }
+    
+    def failsWhenURIHasUnknownScheme() {
+        when:
+        connector.connect(new URI("unknown:dest"))
+
+        then:
+        def e = thrown(IllegalArgumentException)
+        e.message == 'Cannot create a connection to destination URI with unknown scheme: unknown:dest.'
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatchTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatchTest.java
new file mode 100644
index 0000000..3ab9f9f
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationMarshallingDispatchTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.jmock.Expectations;
+import org.jmock.Sequence;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Method;
+
+ at RunWith(JMock.class)
+public class MethodInvocationMarshallingDispatchTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final Dispatch<Message> target = context.mock(Dispatch.class);
+    private final MethodInvocationMarshallingDispatch dispatch = new MethodInvocationMarshallingDispatch(target);
+
+    @Test
+    public void sendsAMethodMetaInfoMessageWhenAMethodIsFirstReferenced() throws Exception {
+        final Method method = String.class.getMethod("charAt", Integer.TYPE);
+
+        context.checking(new Expectations() {{
+            Sequence sequence = context.sequence("seq");
+
+            one(target).dispatch(new MethodMetaInfo(0, method));
+            inSequence(sequence);
+
+            one(target).dispatch(new RemoteMethodInvocation(0, new Object[]{17}));
+            inSequence(sequence);
+
+            one(target).dispatch(new RemoteMethodInvocation(0, new Object[]{12}));
+            inSequence(sequence);
+        }});
+
+        dispatch.dispatch(new MethodInvocation(method, new Object[]{17}));
+        dispatch.dispatch(new MethodInvocation(method, new Object[]{12}));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatchTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatchTest.java
new file mode 100644
index 0000000..067b1fd
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/MethodInvocationUnmarshallingDispatchTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.dispatch.Dispatch;
+import org.gradle.messaging.dispatch.MethodInvocation;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Method;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class MethodInvocationUnmarshallingDispatchTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final Dispatch<MethodInvocation> target = context.mock(Dispatch.class);
+    private final MethodInvocationUnmarshallingDispatch dispatch = new MethodInvocationUnmarshallingDispatch(target, getClass().getClassLoader());
+
+    @Test
+    public void doesNotForwardMethodMetaInfoMessages() throws Exception {
+        dispatch.dispatch(new MethodMetaInfo(1, String.class.getMethod("charAt", Integer.TYPE)));
+    }
+
+    @Test
+    public void transformsRemoteMethodInvocationMessage() throws Exception {
+        final Method method = String.class.getMethod("charAt", Integer.TYPE);
+
+        context.checking(new Expectations() {{
+            one(target).dispatch(new MethodInvocation(method, new Object[]{17}));
+        }});
+
+        dispatch.dispatch(new MethodMetaInfo(1, method));
+        dispatch.dispatch(new RemoteMethodInvocation(1, new Object[]{17}));
+    }
+
+    @Test
+    public void failsWhenRemoteMethodInvocationMessageReceivedForUnknownMethod() {
+        try {
+            dispatch.dispatch(new RemoteMethodInvocation(1, new Object[]{17}));
+            fail();
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(), equalTo("Received a method invocation message for an unknown method."));
+        }
+    }
+
+    @Test
+    public void failsWhenUnexpectedMessageReceived() {
+        final Message message = new Message() {
+        };
+
+        try {
+            dispatch.dispatch(message);
+            fail();
+        } catch (IllegalStateException e) {
+            assertThat(e.getMessage(), startsWith("Received an unknown message "));
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/RemoteMethodInvocationTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/RemoteMethodInvocationTest.java
new file mode 100644
index 0000000..805efff
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/RemoteMethodInvocationTest.java
@@ -0,0 +1,178 @@
+/*
+ * 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 groovy.lang.GroovyClassLoader;
+import org.junit.Test;
+
+import java.io.*;
+
+import static org.gradle.util.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+public class RemoteMethodInvocationTest {
+    private final GroovyClassLoader source = new GroovyClassLoader(getClass().getClassLoader());
+    private final GroovyClassLoader dest = new GroovyClassLoader(getClass().getClassLoader());
+
+    @Test
+    public void equalsAndHashCode() throws Exception {
+        RemoteMethodInvocation invocation = new RemoteMethodInvocation(1, new Object[]{"param"});
+        RemoteMethodInvocation equalInvocation = new RemoteMethodInvocation(1, new Object[]{"param"});
+        RemoteMethodInvocation differentMethod = new RemoteMethodInvocation(2, new Object[]{"param"});
+        RemoteMethodInvocation differentArgs = new RemoteMethodInvocation(1, new Object[]{"a", "b"});
+        assertThat(invocation, strictlyEqual(equalInvocation));
+        assertThat(invocation, not(equalTo(differentMethod)));
+        assertThat(invocation, not(equalTo(differentArgs)));
+    }
+
+    @Test
+    public void replacesUnserializableExceptionWithPlaceholder() throws Exception {
+        RuntimeException cause = new RuntimeException("nested");
+        UnserializableException original = new UnserializableException("message", cause);
+        Object transported = transport(original);
+
+        assertThat(transported, instanceOf(PlaceholderException.class));
+        PlaceholderException e = (PlaceholderException) transported;
+        assertThat(e.getMessage(), equalTo(UnserializableException.class.getName() + ": " + original.getMessage()));
+        assertThat(e.getStackTrace(), equalTo(original.getStackTrace()));
+
+        assertThat(e.getCause().getClass(), equalTo((Object) RuntimeException.class));
+        assertThat(e.getCause().getMessage(), equalTo("nested"));
+        assertThat(e.getCause().getStackTrace(), equalTo(cause.getStackTrace()));
+    }
+
+    @Test
+    public void replacesNestedUnserializableExceptionWithPlaceholder() throws Exception {
+        Exception cause = new IOException("nested");
+        UnserializableException original = new UnserializableException("message", cause);
+        Object transported = transport(new RuntimeException(original));
+
+        assertThat(transported, instanceOf(RuntimeException.class));
+        PlaceholderException e = (PlaceholderException) ((RuntimeException) transported).getCause();
+        assertThat(e.getMessage(), equalTo(UnserializableException.class.getName() + ": " + original.getMessage()));
+        assertThat(e.getStackTrace(), equalTo(original.getStackTrace()));
+
+        assertThat(e.getCause().getClass(), equalTo((Object) IOException.class));
+        assertThat(e.getCause().getMessage(), equalTo("nested"));
+        assertThat(e.getCause().getStackTrace(), equalTo(cause.getStackTrace()));
+    }
+
+    @Test
+    public void replacesUndeserializableExceptionWithPlaceholder() throws Exception {
+        RuntimeException cause = new RuntimeException("nested");
+        UndeserializableException original = new UndeserializableException("message", cause);
+        Object transported = transport(original);
+
+        assertThat(transported, instanceOf(RuntimeException.class));
+        PlaceholderException e = (PlaceholderException) transported;
+        assertThat(e.getMessage(), equalTo(UndeserializableException.class.getName() + ": " + original.getMessage()));
+        assertThat(e.getStackTrace(), equalTo(original.getStackTrace()));
+
+        assertThat(e.getCause().getClass(), equalTo((Object) RuntimeException.class));
+        assertThat(e.getCause().getMessage(), equalTo("nested"));
+        assertThat(e.getCause().getStackTrace(), equalTo(cause.getStackTrace()));
+    }
+
+    @Test
+    public void replacesNestedUndeserializableExceptionWithPlaceholder() throws Exception {
+        RuntimeException cause = new RuntimeException("nested");
+        UndeserializableException original = new UndeserializableException("message", cause);
+        Object transported = transport(new RuntimeException(original));
+
+        assertThat(transported, instanceOf(RuntimeException.class));
+        PlaceholderException e = (PlaceholderException) ((RuntimeException) transported).getCause();
+        assertThat(e.getMessage(), equalTo(UndeserializableException.class.getName() + ": " + original.getMessage()));
+        assertThat(e.getStackTrace(), equalTo(original.getStackTrace()));
+
+        assertThat(e.getCause().getClass(), equalTo((Object) RuntimeException.class));
+        assertThat(e.getCause().getMessage(), equalTo("nested"));
+        assertThat(e.getCause().getStackTrace(), equalTo(cause.getStackTrace()));
+    }
+
+    @Test
+    public void replacesIncompatibleExceptionWithLocalVersion() throws Exception {
+        RuntimeException cause = new RuntimeException("nested");
+        Class<? extends RuntimeException> sourceExceptionType = source.parseClass(
+                "package org.gradle; public class TestException extends RuntimeException { public TestException(String msg, Throwable cause) { super(msg, cause); } }");
+        Class<? extends RuntimeException> destExceptionType = dest.parseClass(
+                "package org.gradle; public class TestException extends RuntimeException { private String someField; public TestException(String msg) { super(msg); } }");
+
+        RuntimeException original = sourceExceptionType.getConstructor(String.class, Throwable.class).newInstance("message", cause);
+        Object transported = transport(original);
+
+        assertThat(transported, instanceOf(destExceptionType));
+        RuntimeException e = (RuntimeException) transported;
+        assertThat(e.getMessage(), equalTo(original.getMessage()));
+        assertThat(e.getStackTrace(), equalTo(original.getStackTrace()));
+
+        assertThat(e.getCause().getClass(), equalTo((Object) RuntimeException.class));
+        assertThat(e.getCause().getMessage(), equalTo("nested"));
+        assertThat(e.getCause().getStackTrace(), equalTo(cause.getStackTrace()));
+    }
+
+    @Test
+    public void usesPlaceholderWhenLocalExceptionCannotBeConstructed() throws Exception {
+        RuntimeException cause = new RuntimeException("nested");
+        Class<? extends RuntimeException> sourceExceptionType = source.parseClass(
+                "package org.gradle; public class TestException extends RuntimeException { public TestException(String msg, Throwable cause) { super(msg, cause); } }");
+        Class<? extends RuntimeException> destExceptionType = dest.parseClass(
+                "package org.gradle; public class TestException extends RuntimeException { private String someField; }");
+
+        RuntimeException original = sourceExceptionType.getConstructor(String.class, Throwable.class).newInstance("message", cause);
+        Object transported = transport(original);
+
+        assertThat(transported, instanceOf(PlaceholderException.class));
+        RuntimeException e = (RuntimeException) transported;
+        assertThat(e.getMessage(), equalTo(original.toString()));
+        assertThat(e.getStackTrace(), equalTo(original.getStackTrace()));
+
+        assertThat(e.getCause().getClass(), equalTo((Object) RuntimeException.class));
+        assertThat(e.getCause().getMessage(), equalTo("nested"));
+        assertThat(e.getCause().getStackTrace(), equalTo(cause.getStackTrace()));
+    }
+
+    private Object transport(Object arg) throws Exception {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        new RemoteMethodInvocation(1, new Object[]{arg}).send(outputStream);
+
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
+        RemoteMethodInvocation invocation = (RemoteMethodInvocation) Message.receive(inputStream, dest);
+        return invocation.getArguments()[0];
+    }
+
+    private static class UnserializableException extends RuntimeException {
+        public UnserializableException(String message, Throwable cause) {
+            super(message, cause);
+        }
+
+        private void writeObject(ObjectOutputStream outstr) throws IOException {
+            outstr.writeObject(new Object());
+        }
+    }
+
+    private static class UndeserializableException extends RuntimeException {
+        public UndeserializableException(String message, Throwable cause) {
+            super(message, cause);
+        }
+
+        private void readObject(ObjectInputStream outstr) throws ClassNotFoundException {
+            throw new ClassNotFoundException();
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/TcpConnectorTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/TcpConnectorTest.groovy
new file mode 100644
index 0000000..c545ef7
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/messaging/remote/internal/TcpConnectorTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * 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.util.MultithreadedTestCase
+import org.junit.Test
+import static org.junit.Assert.*
+
+class TcpConnectorTest extends MultithreadedTestCase {
+    @Test
+    public void canConnectToServer() {
+        TcpOutgoingConnector outgoingConnector = new TcpOutgoingConnector(getClass().classLoader)
+        TcpIncomingConnector incomingConnector = new TcpIncomingConnector(executorFactory, getClass().classLoader)
+
+        Action action = { syncAt(1) } as Action
+        def address = incomingConnector.accept(action)
+
+        def connection = outgoingConnector.connect(address)
+        assertNotNull(connection)
+        run { syncAt(1) }
+
+        incomingConnector.requestStop()
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/DefaultExecHandleTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/DefaultExecHandleTest.java
new file mode 100644
index 0000000..e756215
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/DefaultExecHandleTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.util.Jvm;
+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(),
+                System.out,
+                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.", 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(),
+                System.out,
+                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/gradle-core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessFactoryTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessFactoryTest.java
new file mode 100644
index 0000000..dca5319
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessFactoryTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.Action;
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.messaging.remote.MessagingServer;
+import org.gradle.process.internal.child.IsolatedApplicationClassLoaderWorker;
+import org.gradle.process.internal.launcher.GradleWorkerMain;
+import org.gradle.util.IdGenerator;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class DefaultWorkerProcessFactoryTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final MessagingServer messagingServer = context.mock(MessagingServer.class);
+    private final ClassPathRegistry classPathRegistry = context.mock(ClassPathRegistry.class);
+    private final FileResolver fileResolver = context.mock(FileResolver.class);
+    private final IdGenerator<Object> idGenerator = context.mock(IdGenerator.class);
+    private final DefaultWorkerProcessFactory factory = new DefaultWorkerProcessFactory(LogLevel.LIFECYCLE, messagingServer, classPathRegistry, fileResolver,
+            idGenerator);
+
+    @Test
+    public void createsAndConfiguresAWorkerProcess() throws Exception {
+        final Set<File> processClassPath = Collections.singleton(new File("something.jar"));
+
+        context.checking(new Expectations() {{
+            one(classPathRegistry).getClassPathFiles("WORKER_PROCESS");
+            will(returnValue(processClassPath));
+            ignoring(fileResolver);
+        }});
+
+        WorkerProcessBuilder builder = factory.newProcess();
+
+        assertThat(builder.getJavaCommand().getMain(), equalTo(GradleWorkerMain.class.getName()));
+        assertThat(builder.getLogLevel(), equalTo(LogLevel.LIFECYCLE));
+
+        builder.worker(new TestAction());
+        builder.applicationClasspath(Arrays.asList(new File("app.jar")));
+        builder.sharedPackages("package1", "package2");
+
+        final URI serverAddress = new URI("test:something");
+
+        context.checking(new Expectations(){{
+            one(messagingServer).accept(with(notNullValue(Action.class)));
+            will(returnValue(serverAddress));
+            one(idGenerator).generateId();
+            will(returnValue("<id>"));
+        }});
+
+        WorkerProcess process = builder.build();
+
+        assertThat(process, instanceOf(DefaultWorkerProcess.class));
+
+        ObjectInputStream instr = new ObjectInputStream(builder.getJavaCommand().getStandardInput());
+        assertThat(instr.readObject(), instanceOf(IsolatedApplicationClassLoaderWorker.class));
+    }
+
+    private static class TestAction implements Action<WorkerProcessContext>, Serializable {
+        public void execute(WorkerProcessContext workerProcessContext) {
+            throw new UnsupportedOperationException();
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessTest.groovy
new file mode 100644
index 0000000..dd93c7e
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/DefaultWorkerProcessTest.groovy
@@ -0,0 +1,174 @@
+/*
+ * 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.junit.runner.RunWith
+import org.jmock.integration.junit4.JMock
+import org.gradle.util.MultithreadedTestCase
+import org.jmock.Mockery
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.messaging.remote.ConnectEvent
+import org.gradle.messaging.remote.ObjectConnection
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import java.util.concurrent.TimeUnit
+import static org.junit.Assert.*
+import org.gradle.process.ExecResult
+
+ at RunWith(JMock.class)
+class DefaultWorkerProcessTest extends MultithreadedTestCase {
+    private final Mockery context = new JUnit4GroovyMockery()
+    private final ExecHandle execHandle = context.mock(ExecHandle.class)
+    private final ObjectConnection connection = context.mock(ObjectConnection.class)
+    private final DefaultWorkerProcess workerProcess = new DefaultWorkerProcess(1, TimeUnit.SECONDS)
+
+    @Test
+    public void startsChildProcessAndBlocksUntilConnectionEstablished() {
+        expectAttachesListener()
+
+        context.checking {
+            one(execHandle).start()
+            will {
+                start {
+                    expectUnblocks {
+                        workerProcess.getConnectAction().execute(new ConnectEvent<ObjectConnection>(connection, null, null))
+                    }
+                }
+            }
+        }
+
+        expectBlocks {
+            workerProcess.start()
+        }
+    }
+
+    @Test
+    public void startThrowsExceptionOnConnectTimeout() {
+        expectAttachesListener()
+
+        context.checking {
+            one(execHandle).start()
+        }
+
+        expectTimesOut(1, TimeUnit.SECONDS) {
+            try {
+                workerProcess.start()
+                fail()
+            } catch (ExecException e) {
+                assertThat(e.message, equalTo("Timeout waiting for $execHandle to connect." as String))
+            }
+        }
+    }
+
+    @Test
+    public void startThrowsExceptionWhenChildProcessNeverConnects() {
+        def listener = expectAttachesListener()
+        def execResult = context.mock(ExecResult.class)
+
+        context.checking {
+            one(execHandle).start()
+            will {
+                start {
+                    expectUnblocks {
+                        listener.executionFinished(execHandle, execResult)
+                    }
+                }
+            }
+            allowing(execResult).rethrowFailure()
+            will(returnValue(execResult))
+            allowing(execResult).assertNormalExitValue()
+            will(returnValue(execResult))
+        }
+
+        expectBlocks {
+            try {
+                workerProcess.start()
+                fail()
+            } catch (ExecException e) {
+                assertThat(e.message, equalTo("Never received a connection from $execHandle." as String))
+            }
+        }
+    }
+
+    @Test
+    public void startThrowsExceptionOnChildProcessFailure() {
+        def listener = expectAttachesListener()
+        def failure = new ExecException('broken')
+        ExecResult execResult = context.mock(ExecResult.class)
+
+        context.checking {
+            one(execHandle).start()
+            will {
+                start {
+                    expectUnblocks {
+                        listener.executionFinished(execHandle, execResult)
+                    }
+                }
+            }
+            one(execResult).rethrowFailure()
+            will(throwException(failure))
+        }
+
+        expectBlocks {
+            try {
+                workerProcess.start()
+                fail()
+            } catch (ExecException e) {
+                assertThat(e, sameInstance(failure))
+            }
+        }
+    }
+
+    @Test
+    public void waitForStopCleansUpConnection() {
+        expectAttachesListener()
+
+        ExecResult execResult = context.mock(ExecResult.class)
+
+        context.checking {
+            one(execHandle).start()
+            will {
+                workerProcess.getConnectAction().execute(new ConnectEvent<ObjectConnection>(connection, null, null))
+            }
+        }
+
+        workerProcess.start()
+
+        context.checking {
+            one(execHandle).waitForFinish()
+            will(returnValue(execResult))
+            one(connection).stop()
+            one(execResult).assertNormalExitValue()
+        }
+
+        workerProcess.waitForStop()
+    }
+
+    private ExecHandleListener expectAttachesListener() {
+        ExecHandleListener listener
+        context.checking {
+            one(execHandle).addListener(withParam(notNullValue()))
+            will { arg -> listener = arg}
+        }
+        workerProcess.setExecHandle(execHandle)
+
+        return listener
+    }
+}
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/ExecHandleBuilderTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/ExecHandleBuilderTest.groovy
new file mode 100644
index 0000000..8269b3a
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/ExecHandleBuilderTest.groovy
@@ -0,0 +1,41 @@
+/*
+ * 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 spock.lang.Specification
+
+class ExecHandleBuilderTest extends Specification {
+    private final ExecHandleBuilder builder = new ExecHandleBuilder()
+
+    def handlesCommandLineWithNoArgs() {
+        when:
+        builder.commandLine('command')
+
+        then:
+        builder.executable == 'command'
+        builder.args == []
+    }
+    
+    def convertsArgsToString() {
+        when:
+        builder.args(['1', 2, "${3}"])
+
+        then:
+        builder.args == ['1', '2', '3']
+        builder.allArguments == ['1', '2', '3']
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/JavaExecHandleBuilderTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/JavaExecHandleBuilderTest.groovy
new file mode 100644
index 0000000..b42d98a
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/JavaExecHandleBuilderTest.groovy
@@ -0,0 +1,65 @@
+/*
+ * 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.file.FileCollection
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.file.IdentityFileResolver
+import org.gradle.api.internal.file.PathResolvingFileCollection
+import org.gradle.util.Jvm
+import spock.lang.Specification
+import static java.util.Arrays.asList
+
+public class JavaExecHandleBuilderTest extends Specification {
+    FileResolver fileResolver = new IdentityFileResolver()
+    JavaExecHandleBuilder builder = new JavaExecHandleBuilder(fileResolver)
+    
+    public void cannotSetAllJvmArgs() {
+        when:
+        builder.setAllJvmArgs(asList("arg"))
+
+        then:
+        thrown(UnsupportedOperationException)
+    }
+
+    public void buildsCommandLineForJavaProcess() {
+        File jar1 = new File("file1.jar").canonicalFile
+        File jar2 = new File("file2.jar").canonicalFile
+
+        FileCollection classpath = new PathResolvingFileCollection(fileResolver, null, jar1, jar2)
+
+        builder.main = 'mainClass'
+        builder.args("arg1", "arg2")
+        builder.jvmArgs("jvm1", "jvm2")
+        builder.classpath(jar1, jar2)
+        builder.systemProperty("prop", "value")
+
+        when:
+        List jvmArgs = builder.getAllJvmArgs()
+
+        then:
+        jvmArgs == ['jvm1', 'jvm2', '-Dprop=value', '-cp', "$jar1$File.pathSeparator$jar2"]
+
+        when:
+        List commandLine = builder.getCommandLine()
+
+        then:
+        String executable = Jvm.current().getJavaExecutable().getAbsolutePath()
+        commandLine == [executable, 'jvm1', 'jvm2', '-Dprop=value', '-cp', "$jar1$File.pathSeparator$jar2",
+                'mainClass', 'arg1', 'arg2']
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/child/ActionExecutionWorkerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/child/ActionExecutionWorkerTest.java
new file mode 100644
index 0000000..2b58ce1
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/child/ActionExecutionWorkerTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.child;
+
+import org.gradle.api.Action;
+import org.gradle.messaging.remote.MessagingClient;
+import org.gradle.messaging.remote.ObjectConnection;
+import org.gradle.process.internal.WorkerProcessContext;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.gradle.util.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+ at RunWith(JMock.class)
+public class ActionExecutionWorkerTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final Action<WorkerProcessContext> action = context.mock(Action.class);
+    private final MessagingClient client = context.mock(MessagingClient.class);
+    private final WorkerContext workerContext = context.mock(WorkerContext.class);
+    private final ClassLoader appClassLoader = new ClassLoader() {
+    };
+    private final ActionExecutionWorker main = new ActionExecutionWorker(action, 12, "<display name>", null) {
+        @Override
+        MessagingClient createClient() {
+            return client;
+        }
+    };
+
+    @Test
+    public void createsConnectionAndExecutesAction() throws Exception {
+        final ObjectConnection connection = context.mock(ObjectConnection.class);
+        final Collector<WorkerProcessContext> collector = collector();
+
+        context.checking(new Expectations() {{
+            one(action).execute(with(notNullValue(WorkerProcessContext.class)));
+            will(collectTo(collector));
+
+            one(client).stop();
+        }});
+
+        main.execute(workerContext);
+
+        context.checking(new Expectations() {{
+            allowing(workerContext).getApplicationClassLoader();
+            will(returnValue(appClassLoader));
+            allowing(client).getConnection();
+            will(returnValue(connection));
+        }});
+
+        assertThat(collector.get().getServerConnection(), sameInstance(connection));
+        assertThat(collector.get().getApplicationClassLoader(), sameInstance(appClassLoader));
+        assertThat(collector.get().getWorkerId(), equalTo((Object) 12));
+        assertThat(collector.get().getDisplayName(), equalTo("<display name>"));
+    }
+
+    @Test
+    public void cleansUpWhenActionThrowsException() throws Exception {
+        final RuntimeException failure = new RuntimeException();
+
+
+        context.checking(new Expectations() {{
+            one(action).execute(with(notNullValue(WorkerProcessContext.class)));
+            will(throwException(failure));
+
+            one(client).stop();
+        }});
+
+        try {
+            main.execute(workerContext);
+            fail();
+        } catch (RuntimeException e) {
+            assertThat(e, sameInstance(failure));
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorkerTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorkerTest.java
new file mode 100644
index 0000000..80a6de1
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/child/ImplementationClassLoaderWorkerTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.child;
+
+import org.gradle.api.Action;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.logging.LoggingManagerInternal;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.gradle.util.ObservableUrlClassLoader;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.net.URL;
+import java.util.Collection;
+import java.util.List;
+
+import static org.gradle.util.WrapUtil.*;
+
+ at RunWith(JMock.class)
+public class ImplementationClassLoaderWorkerTest {
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private final ClassLoader applicationClassLoader = getClass().getClassLoader();
+    private final LoggingManagerInternal loggingManager = context.mock(LoggingManagerInternal.class);
+    private final ObservableUrlClassLoader implementationClassLoader = new ObservableUrlClassLoader(applicationClassLoader);
+    private final WorkerContext workerContext = context.mock(WorkerContext.class);
+    private final SerializableMockHelper helper = new SerializableMockHelper();
+
+    @Test
+    public void createsClassLoaderAndInstantiatesAndExecutesWorker() throws Exception {
+        final Action<WorkerContext> action = context.mock(Action.class);
+        final List<URL> implementationClassPath = toList(new File(".").toURI().toURL());
+
+        Action<WorkerContext> serializableAction = helper.serializable(action, implementationClassLoader);
+        ImplementationClassLoaderWorker worker = new TestImplementationClassLoaderWorker(LogLevel.DEBUG, toList("a", "b"), implementationClassPath, serializableAction);
+
+        context.checking(new Expectations() {{
+            one(loggingManager).setLevel(LogLevel.DEBUG);
+            will(returnValue(loggingManager));
+            one(loggingManager).start();
+            allowing(workerContext).getApplicationClassLoader();
+            will(returnValue(applicationClassLoader));
+            one(action).execute(workerContext);
+        }});
+
+
+        worker.execute(workerContext);
+    }
+
+    private class TestImplementationClassLoaderWorker extends ImplementationClassLoaderWorker {
+        private TestImplementationClassLoaderWorker(LogLevel logLevel, Collection<String> sharedPackages,
+                                    Collection<URL> implementationClassPath, Action<WorkerContext> workerAction) {
+            super(logLevel, sharedPackages, implementationClassPath, workerAction);
+        }
+
+        @Override
+        protected LoggingManagerInternal createLoggingManager() {
+            return loggingManager;
+        }
+
+        @Override
+        protected ObservableUrlClassLoader createImplementationClassLoader(ClassLoader system,
+                                                                           ClassLoader application) {
+            return implementationClassLoader;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/child/SerializableMockHelper.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/child/SerializableMockHelper.groovy
new file mode 100644
index 0000000..252a31a
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/child/SerializableMockHelper.groovy
@@ -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.process.internal.child
+
+import groovyjarjarasm.asm.ClassVisitor
+import groovyjarjarasm.asm.ClassWriter
+import java.util.concurrent.atomic.AtomicInteger
+import org.codehaus.groovy.ast.ClassNode
+import org.codehaus.groovy.control.CompilationUnit
+import org.codehaus.groovy.control.CompilationUnit.ClassgenCallback
+import org.codehaus.groovy.control.Phases
+import org.gradle.api.Action
+
+class SerializableMockHelper { /**
+ * @author Hans Dockter
+ */
+    static final Map ACTIONS = [:]
+    private final AtomicInteger counter = new AtomicInteger()
+
+    /**
+     * Injects a proxy class into the target ClassLoader, so that when the given action is deserialized using the target
+     * classloader, the proxy class executes the given action.
+     */
+    def <T> Action<T> serializable(Action<T> action, ClassLoader target) {
+        String src = """
+class TestAction implements ${Action.class.name}, ${Serializable.class.name}
+{
+    Object key
+
+    void execute(Object target) {
+        def action = ${SerializableMockHelper.class.name}.ACTIONS.remove(key)
+        action.execute(target)
+    }
+}
+"""
+        CompilationUnit unit = new CompilationUnit(new GroovyClassLoader(target))
+        unit.addSource("action", src)
+        ClassCollector collector = new ClassCollector(target: target)
+        unit.setClassgenCallback(collector);
+        unit.compile(Phases.CLASS_GENERATION);
+
+        Object instance = collector.generated.newInstance()
+        instance.key = counter.getAndIncrement()
+        ACTIONS[instance.key] = action
+        return instance
+    }
+}
+
+class ClassCollector extends ClassgenCallback {
+    Class generated
+    ClassLoader target
+
+    void call(ClassVisitor classVisitor, ClassNode classNode) {
+        def bytes = ((ClassWriter) classVisitor).toByteArray();
+        generated = target.defineClass(classNode.getName(), bytes, 0, bytes.length)
+    }
+}
+
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProviderTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProviderTest.groovy
new file mode 100644
index 0000000..842d231
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/process/internal/child/WorkerProcessClassPathProviderTest.groovy
@@ -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.child
+
+import spock.lang.Specification
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import org.gradle.cache.CacheRepository
+import org.gradle.cache.CacheBuilder
+import org.gradle.cache.PersistentCache
+
+class WorkerProcessClassPathProviderTest extends Specification {
+    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
+    private final CacheRepository cacheRepository = Mock()
+    private final WorkerProcessClassPathProvider provider = new WorkerProcessClassPathProvider(cacheRepository)
+
+    def returnsNullForUnknownClasspath() {
+        expect:
+        provider.findClassPath('unknown') == null
+    }
+
+    def createsTheWorkerClasspathOnDemand() {
+        def cacheDir = tmpDir.dir
+        def classesDir = cacheDir.file('classes')
+        CacheBuilder cacheBuilder = Mock()
+        PersistentCache cache = Mock()
+
+        when:
+        def classpath = provider.findClassPath('WORKER_MAIN')
+
+        then:
+        1 * cacheRepository.cache('workerMain') >> cacheBuilder
+        1 * cacheBuilder.open() >> cache
+        1 * cache.isValid() >> false
+        1 * cache.markValid()
+        _ * cache.getBaseDir() >> cacheDir
+        0 * cache._
+        classpath == [classesDir] as Set
+        classesDir.listFiles().length != 0
+    }
+
+    def reusesTheCacheClasspath() {
+        def cacheDir = tmpDir.dir
+        def classesDir = cacheDir.file('classes')
+        CacheBuilder cacheBuilder = Mock()
+        PersistentCache cache = Mock()
+
+        when:
+        def classpath = provider.findClassPath('WORKER_MAIN')
+
+        then:
+        1 * cacheRepository.cache('workerMain') >> cacheBuilder
+        1 * cacheBuilder.open() >> cache
+        1 * cache.isValid() >> true
+        _ * cache.getBaseDir() >> cacheDir
+        0 * cache._
+        classpath == [classesDir] as Set
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/testfixtures/ProjectBuilderTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/testfixtures/ProjectBuilderTest.groovy
new file mode 100644
index 0000000..2d1b30b
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/testfixtures/ProjectBuilderTest.groovy
@@ -0,0 +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.testfixtures
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.api.tasks.TaskAction
+import org.gradle.util.Resources
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+class ProjectBuilderTest extends Specification {
+    @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder()
+    @Rule public final Resources resources = new Resources()
+
+    def canCreateARootProject() {
+
+        when:
+        def project = ProjectBuilder.builder().build()
+
+        then:
+        project instanceof DefaultProject
+        project.name == 'test'
+        project.path == ':'
+        project.projectDir.parentFile != null
+        project.gradle != null
+        project.gradle.rootProject == project
+        project.gradle.gradleHomeDir == project.file('gradleHome')
+        project.gradle.gradleUserHomeDir == project.file('userHome')
+    }
+
+    def canCreateARootProjectWithAGivenProjectDir() {
+        when:
+        def project = ProjectBuilder.builder().withProjectDir(temporaryFolder.dir).build()
+
+        then:
+        project.projectDir == temporaryFolder.dir
+        project.gradle.gradleHomeDir == project.file('gradleHome')
+        project.gradle.gradleUserHomeDir == project.file('userHome')
+    }
+
+    def canApplyACustomPlugin() {
+        when:
+        def project = ProjectBuilder.builder().withProjectDir(temporaryFolder.dir).build()
+        project.apply plugin: CustomPlugin
+
+        then:
+        project.tasks.hello instanceof DefaultTask
+    }
+
+    def canCreateAndExecuteACustomTask() {
+        when:
+        def project = ProjectBuilder.builder().withProjectDir(temporaryFolder.dir).build()
+        def task = project.task('custom', type: CustomTask)
+        task.doStuff()
+
+        then:
+        task.property == 'some value'
+    }
+
+    def canApplyABuildScript() {
+        when:
+        def project = ProjectBuilder.builder().withProjectDir(temporaryFolder.dir).build()
+        project.apply from: resources.getResource('ProjectBuilderTest.gradle')
+
+        then:
+        project.tasks.hello instanceof DefaultTask
+    }
+}
+
+public class CustomTask extends DefaultTask {
+    def String property
+
+    @TaskAction
+    def doStuff() {
+        property = 'some value'
+    }
+}
+
+public class CustomPlugin implements Plugin<Project> {
+    void apply(Project target) {
+        target.task('hello');
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/ClockTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/util/ClockTest.java
new file mode 100644
index 0000000..4ceb9e9
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/ClockTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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;
+
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import static org.junit.Assert.assertEquals;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+ at RunWith(JMock.class)
+public class ClockTest {
+
+    private static final long TEST_BASE_TIME = 641353121231L;
+
+    private JUnit4Mockery context = new JUnit4Mockery();
+    private TimeProvider timeProvider;
+    private Clock clock;
+
+    @Before
+    public void setUp() {
+        timeProvider = context.mock(TimeProvider.class);
+        setBaseTime();
+        clock = new Clock(timeProvider);
+    }
+
+    @Test public void testOnlySecondsTwoDigits() throws Exception {
+        setDtMs(51243);
+        assertEquals("51.243 secs", clock.getTime());
+    }
+
+    @Test public void testOnlySecondsEvenMs() {
+        setDtMs(4000);
+        assertEquals("4.0 secs", clock.getTime());
+    }
+
+    @Test public void testMinutesAndSeconds() {
+        setDtHrsMinsSecsMillis(0, 32, 40, 322);
+        assertEquals("32 mins 40.322 secs", clock.getTime());
+    }
+
+    @Test public void testHoursMinutesAndSeconds() {
+        setDtHrsMinsSecsMillis(3, 2, 5, 111);
+        assertEquals("3 hrs 2 mins 5.111 secs", clock.getTime());
+    }
+
+    @Test public void testHoursZeroMinutes() {
+        setDtHrsMinsSecsMillis(1, 0, 32, 0);
+        assertEquals("1 hrs 0 mins 32.0 secs", clock.getTime());
+    }
+
+    private void setBaseTime() {
+        returnFromTimeProvider(TEST_BASE_TIME);
+    }
+
+    private void setDtMs(final long deltaT) {
+        returnFromTimeProvider(TEST_BASE_TIME + deltaT);
+    }
+
+    private void setDtHrsMinsSecsMillis(int hours, int minutes, int seconds, int millis) {
+        long dt = (hours * 3600 * 1000) + (minutes * 60 * 1000) + (seconds * 1000) + millis;
+        returnFromTimeProvider(TEST_BASE_TIME + dt);
+    }
+
+    private void returnFromTimeProvider(final long time) {
+        context.checking(new Expectations(){{
+            one(timeProvider).getCurrentTime();
+            will(returnValue(time));
+        }});
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/CompositeIdGeneratorTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/util/CompositeIdGeneratorTest.groovy
new file mode 100644
index 0000000..273ec38
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/CompositeIdGeneratorTest.groovy
@@ -0,0 +1,80 @@
+/*
+ * 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/gradle-core/src/test/groovy/org/gradle/util/ConfigureUtilTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/util/ConfigureUtilTest.groovy
new file mode 100644
index 0000000..d57b323
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/ConfigureUtilTest.groovy
@@ -0,0 +1,82 @@
+/*
+ * 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
+
+import org.junit.Test
+import static org.junit.Assert.*
+import static org.hamcrest.Matchers.*
+
+class ConfigureUtilTest {
+    @Test
+    public void canConfigureObjectUsingClosure() {
+        List obj = []
+        def cl = {
+            add('a');
+            assertThat(size(), equalTo(1));
+            assertThat(obj, equalTo(['a']))
+        }
+        ConfigureUtil.configure(cl, obj)
+        assertThat(obj, equalTo(['a']))
+    }
+
+    @Test
+    public void passesConfiguredObjectToClosureAsParameter() {
+        List obj = []
+        def cl = {
+            assertThat(it, sameInstance(obj))
+        }
+        def cl2 = {List list ->
+            assertThat(list, sameInstance(obj))
+        }
+        def cl3 = {->
+            assertThat(delegate, sameInstance(obj))
+        }
+        ConfigureUtil.configure(cl, obj)
+        ConfigureUtil.configure(cl2, obj)
+        ConfigureUtil.configure(cl3, obj)
+    }
+
+    @Test
+    public void canConfigureObjectPropertyUsingMap() {
+        Bean obj = new Bean()
+
+        ConfigureUtil.configureByMap(obj, prop: 'value')
+        assertThat(obj.prop, equalTo('value'))
+
+        ConfigureUtil.configureByMap(obj, method: 'value2')
+        assertThat(obj.prop, equalTo('value2'))
+    }
+
+    @Test
+    public void throwsExceptionForUnknownProperty() {
+        Bean obj = new Bean()
+
+        try {
+            ConfigureUtil.configureByMap(obj, unknown: 'value')
+            fail()
+        } catch (MissingPropertyException e) {
+            assertThat(e.type, equalTo(Bean.class))
+            assertThat(e.property, equalTo('unknown'))
+        }
+    }
+}
+
+class Bean {
+    String prop
+    def method(String value) {
+        prop = value
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/DiffUtilTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/util/DiffUtilTest.groovy
new file mode 100644
index 0000000..d33459e
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/DiffUtilTest.groovy
@@ -0,0 +1,105 @@
+/*
+ * 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 static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.junit.Test
+import org.junit.runner.RunWith
+
+ at RunWith(JMock.class)
+class DiffUtilTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+
+    @Test
+    public void notifiesListenerOfElementAddedToSet() {
+        ChangeListener<String> listener = context.mock(ChangeListener.class)
+        Set<String> a = ['a', 'b'] as Set
+        Set<String> b = ['a', 'b', 'c'] as Set
+
+        context.checking {
+            one(listener).added('c')
+        }
+
+        DiffUtil.diff(b, a, listener);
+    }
+
+    @Test
+    public void notifiesListenerOfElementRemovedFromSet() {
+        ChangeListener<String> listener = context.mock(ChangeListener.class)
+        Set<String> a = ['a', 'b'] as Set
+        Set<String> b = ['a', 'b', 'c'] as Set
+
+        context.checking {
+            one(listener).removed('c')
+        }
+
+        DiffUtil.diff(a, b, listener);
+    }
+
+    @Test
+    public void notifiesListenerOfElementAddedToMap() {
+        ChangeListener<Map.Entry<String, String>> listener = context.mock(ChangeListener.class)
+        Map<String, String> a = [a: 'value a', b: 'value b']
+        Map<String, String> b = [a: 'value a', b: 'value b', c: 'value c']
+
+        context.checking {
+            one(listener).added(withParam(anything()))
+            will { entry ->
+                assertThat(entry.key, equalTo('c'))
+                assertThat(entry.value, equalTo('value c'))
+            }
+        }
+
+        DiffUtil.diff(b, a, listener);
+    }
+
+    @Test
+    public void notifiesListenerOfElementRemovedFromMap() {
+        ChangeListener<Map.Entry<String, String>> listener = context.mock(ChangeListener.class)
+        Map<String, String> a = [a: 'value a', b: 'value b']
+        Map<String, String> b = [a: 'value a', b: 'value b', c: 'value c']
+
+        context.checking {
+            one(listener).removed(withParam(anything()))
+            will { entry ->
+                assertThat(entry.key, equalTo('c'))
+                assertThat(entry.value, equalTo('value c'))
+            }
+        }
+
+        DiffUtil.diff(a, b, listener);
+    }
+
+    @Test
+    public void notifiesListenerOfChangedElementInMap() {
+        ChangeListener<Map.Entry<String, String>> listener = context.mock(ChangeListener.class)
+        Map<String, String> a = [a: 'value a', b: 'value b']
+        Map<String, String> b = [a: 'value a', b: 'new b']
+
+        context.checking {
+            one(listener).changed(withParam(anything()))
+            will { entry ->
+                assertThat(entry.key, equalTo('b'))
+                assertThat(entry.value, equalTo('new b'))
+            }
+        }
+
+        DiffUtil.diff(b, a, listener);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/FilteringClassLoaderTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/util/FilteringClassLoaderTest.groovy
new file mode 100644
index 0000000..3be139f
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/FilteringClassLoaderTest.groovy
@@ -0,0 +1,132 @@
+/*
+ * 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.hamcrest.Matcher
+import org.jmock.integration.junit4.JMock
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.junit.Before
+
+ at RunWith(JMock.class)
+class FilteringClassLoaderTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final FilteringClassLoader classLoader = new FilteringClassLoader(FilteringClassLoaderTest.class.getClassLoader())
+
+    @Test
+    public void passesThroughSystemClasses() {
+        assertThat(classLoader.loadClass(String.class.name), sameInstance(String.class))
+    }
+
+    @Test
+    public void passesThroughSystemPackages() {
+        assertThat(classLoader.getPackage('java.lang'), notNullValue(Package.class))
+        assertThat(classLoader.getPackages(), hasPackage('java.lang'))
+    }
+
+    private Matcher<Package[]> hasPackage(String name) {
+        Matcher matcher = [matches: {Package p -> p.name == name }, describeTo: {description -> description.appendText("has package '$name'")}] as Matcher
+        return hasItemInArray(matcher)
+    }
+
+    @Test
+    public 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() {
+        classLoader.parent.loadClass(Test.class.name)
+
+        try {
+            classLoader.loadClass(Test.class.name, false)
+            fail()
+        } catch (ClassNotFoundException e) {
+            assertThat(e.message, equalTo("$Test.name not found.".toString()))
+        }
+        try {
+            classLoader.loadClass(Test.class.name)
+            fail()
+        } catch (ClassNotFoundException e) {
+            assertThat(e.message, equalTo("$Test.name not found.".toString()))
+        }
+    }
+
+    @Test
+    public void filtersPackages() {
+        assertThat(classLoader.parent.getPackage('org.junit'), notNullValue())
+
+        assertThat(classLoader.getPackage('org.junit'), nullValue())
+        assertThat(classLoader.getPackages(), not(hasPackage('org.junit')))
+    }
+
+    @Test
+    public 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())
+        assertFalse(classLoader.getResources('org/gradle/util/ClassLoaderTest.txt').hasMoreElements())
+    }
+
+    @Test
+    public 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))
+        assertThat(classLoader.loadClass(BlockJUnit4ClassRunner.class.name), sameInstance(BlockJUnit4ClassRunner.class))
+    }
+
+    @Test
+    public void passesThroughSpecifiedClasses() {
+        classLoader.allowClass(Test.class)
+        assertThat(classLoader.loadClass(Test.class.name), sameInstance(Test.class))
+        try {
+            classLoader.loadClass(Before.class.name)
+            fail()
+        } catch (ClassNotFoundException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void passesThroughSpecifiedPackages() {
+        assertThat(classLoader.getPackage('org.junit'), nullValue())
+        assertThat(classLoader.getPackages(), not(hasPackage('org.junit')))
+
+        classLoader.allowPackage('org.junit')
+
+        assertThat(classLoader.getPackage('org.junit'), notNullValue())
+        assertThat(classLoader.getPackages(), hasPackage('org.junit'))
+        assertThat(classLoader.getPackage('org.junit.runner'), notNullValue())
+        assertThat(classLoader.getPackages(), hasPackage('org.junit.runner'))
+    }
+
+    @Test
+    public void passesThroughResourcesInSpecifiedPackages() {
+        assertThat(classLoader.getResource('org/gradle/util/ClassLoaderTest.txt'), nullValue())
+
+        classLoader.allowPackage('org.gradle')
+
+        assertThat(classLoader.getResource('org/gradle/util/ClassLoaderTest.txt'), notNullValue())
+        assertThat(classLoader.getResourceAsStream('org/gradle/util/ClassLoaderTest.txt'), notNullValue())
+        assertTrue(classLoader.getResources('org/gradle/util/ClassLoaderTest.txt').hasMoreElements())
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/GUtilTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/util/GUtilTest.java
new file mode 100644
index 0000000..a5a89a4
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/GUtilTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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;
+
+import static org.gradle.util.GUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+public class GUtilTest {
+    @Test
+    public void convertStringToCamelCase() {
+        assertThat(toCamelCase(null), equalTo(null));
+        assertThat(toCamelCase(""), equalTo(""));
+        assertThat(toCamelCase("word"), equalTo("Word"));
+        assertThat(toCamelCase("twoWords"), equalTo("TwoWords"));
+        assertThat(toCamelCase("TwoWords"), equalTo("TwoWords"));
+        assertThat(toCamelCase("two-words"), equalTo("TwoWords"));
+        assertThat(toCamelCase("two.words"), equalTo("TwoWords"));
+        assertThat(toCamelCase("two words"), equalTo("TwoWords"));
+        assertThat(toCamelCase("two Words"), equalTo("TwoWords"));
+        assertThat(toCamelCase("Two Words"), equalTo("TwoWords"));
+        assertThat(toCamelCase(" Two  \t words\n"), equalTo("TwoWords"));
+        assertThat(toCamelCase("four or so Words"), equalTo("FourOrSoWords"));
+        assertThat(toCamelCase("trailing-"), equalTo("Trailing"));
+        assertThat(toCamelCase("ABC"), equalTo("ABC"));
+        assertThat(toCamelCase("."), equalTo(""));
+        assertThat(toCamelCase("-"), equalTo(""));
+    }
+
+    @Test
+    public void convertStringToWords() {
+        assertThat(toWords(null), equalTo(null));
+        assertThat(toWords(""), equalTo(""));
+        assertThat(toWords("word"), equalTo("word"));
+        assertThat(toWords("twoWords"), equalTo("two words"));
+        assertThat(toWords("TwoWords"), equalTo("two words"));
+        assertThat(toWords("two words"), equalTo("two words"));
+        assertThat(toWords("Two Words"), equalTo("two words"));
+        assertThat(toWords(" Two  \t words\n"), equalTo("two words"));
+        assertThat(toWords("two_words"), equalTo("two words"));
+        assertThat(toWords("two.words"), equalTo("two words"));
+        assertThat(toWords("two,words"), equalTo("two words"));
+        assertThat(toWords("trailing-"), equalTo("trailing"));
+        assertThat(toWords("ABC"), equalTo("a b c"));
+        assertThat(toWords("."), equalTo(""));
+        assertThat(toWords("_"), equalTo(""));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/GradleVersionTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/util/GradleVersionTest.groovy
new file mode 100644
index 0000000..be9ef82
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/GradleVersionTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * 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.util
+
+import org.codehaus.groovy.runtime.InvokerHelper
+import static org.junit.Assert.*
+import org.junit.Test
+import org.apache.tools.ant.Main
+import org.apache.ivy.Ivy;
+
+/**
+ * @author Hans Dockter
+ */
+class GradleVersionTest {
+    @Test public void testGradleVersion() {
+        GradleVersion gradleVersion = new GradleVersion()
+        assertNotNull(gradleVersion.version)
+        assertNotNull(gradleVersion.buildTime)
+    }
+
+    @Test public void testPrettyPrint() {
+        GradleVersion version = new GradleVersion()
+        String expectedText = """
+------------------------------------------------------------
+Gradle $version.version
+------------------------------------------------------------
+
+Gradle buildtime: $version.buildTime
+Groovy: $InvokerHelper.version
+Ant: $Main.antVersion
+Ivy: ${Ivy.ivyVersion}
+Java: ${System.getProperty("java.version")}
+JVM: ${System.getProperty("java.vm.version")}
+JVM Vendor: ${System.getProperty("java.vm.vendor")}
+OS Name: ${System.getProperty("os.name")}
+"""
+        assertEquals(expectedText, version.prettyPrint())
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/HelperUtil.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/util/HelperUtil.groovy
new file mode 100644
index 0000000..8ab6c8c
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/HelperUtil.groovy
@@ -0,0 +1,182 @@
+/*
+ * 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.rmi.server.UID
+import org.apache.ivy.core.module.descriptor.Configuration
+import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor
+import org.apache.ivy.core.module.descriptor.DefaultExcludeRule
+import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor
+import org.apache.ivy.core.module.id.ArtifactId
+import org.apache.ivy.core.module.id.ModuleId
+import org.apache.ivy.core.module.id.ModuleRevisionId
+import org.apache.ivy.plugins.matcher.ExactPatternMatcher
+import org.apache.ivy.plugins.matcher.PatternMatcher
+import org.codehaus.groovy.control.CompilerConfiguration
+import org.gradle.BuildResult
+import org.gradle.api.Task
+import org.gradle.api.artifacts.ModuleDependency
+import org.gradle.api.internal.ClassGenerator
+import org.gradle.api.internal.GroovySourceGenerationBackedClassGenerator
+import org.gradle.api.internal.artifacts.configurations.DefaultConfiguration
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
+import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory
+import org.gradle.api.internal.project.taskfactory.ITaskFactory
+import org.gradle.api.internal.project.taskfactory.TaskFactory
+import org.gradle.api.specs.AndSpec
+import org.gradle.api.specs.Spec
+import org.gradle.groovy.scripts.DefaultScript
+import org.gradle.groovy.scripts.Script
+import org.gradle.groovy.scripts.ScriptSource
+import org.gradle.groovy.scripts.StringScriptSource
+import org.gradle.testfixtures.ProjectBuilder
+
+/**
+ * @author Hans Dockter
+ */
+class HelperUtil {
+
+    public static final Closure TEST_CLOSURE = {}
+    public static final Spec TEST_SEPC  = new AndSpec()
+    private static final ClassGenerator CLASS_GENERATOR = new GroovySourceGenerationBackedClassGenerator()
+    private static final ITaskFactory TASK_FACTORY = new AnnotationProcessingTaskFactory(new TaskFactory(CLASS_GENERATOR))
+
+    static <T extends Task> T createTask(Class<T> type) {
+        return createTask(type, createRootProject())
+    }
+    
+    static <T extends Task> T createTask(Class<T> type, ProjectInternal project) {
+        return createTask(type, project, 'name')
+    }
+
+    static <T extends Task> T createTask(Class<T> type, ProjectInternal project, String name) {
+        return TASK_FACTORY.createTask(project, [name: name, type: type])
+    }
+
+    static DefaultProject createRootProject() {
+        createRootProject(TemporaryFolder.newInstance().dir)
+    }
+
+    static DefaultProject createRootProject(File rootDir) {
+        return ProjectBuilder.builder().withProjectDir(rootDir).build()
+    }
+
+    static DefaultProject createChildProject(DefaultProject parentProject, String name, File projectDir = null) {
+        DefaultProject project = CLASS_GENERATOR.newInstance(
+                DefaultProject.class,
+                name,
+                parentProject,
+                projectDir ?: new File(parentProject.getProjectDir(), name),
+                new StringScriptSource("test build file", null),
+                parentProject.gradle,
+                parentProject.gradle.serviceRegistryFactory
+        )
+        parentProject.addChildProject project
+        parentProject.projectRegistry.addProject project
+        return project
+    }
+
+    static def pureStringTransform(def collection) {
+        collection.collect {
+            it.toString()
+        }
+    }
+
+    static DefaultExcludeRule getTestExcludeRule() {
+        new DefaultExcludeRule(new ArtifactId(
+                new ModuleId('org', 'module'), PatternMatcher.ANY_EXPRESSION,
+                PatternMatcher.ANY_EXPRESSION,
+                PatternMatcher.ANY_EXPRESSION),
+                ExactPatternMatcher.INSTANCE, null)
+    }
+
+    static DefaultDependencyDescriptor getTestDescriptor() {
+        new DefaultDependencyDescriptor(ModuleRevisionId.newInstance('org', 'name', 'rev'), false)
+    }
+
+    static DefaultModuleDescriptor createModuleDescriptor(Set confs) {
+        DefaultModuleDescriptor moduleDescriptor = new DefaultModuleDescriptor(ModuleRevisionId.newInstance('org', 'name', 'rev'), "status", null)
+        confs.each { moduleDescriptor.addConfiguration(new Configuration(it)) }
+        return moduleDescriptor;
+    }
+
+    static BuildResult createBuildResult(Throwable t) {
+        return new BuildResult(null, t);
+    }
+
+    static ModuleDependency createDependency(String group, String name, String version) {
+      new DefaultExternalModuleDependency(group, name, version)
+    }
+
+    static DefaultPublishArtifact createPublishArtifact(String name, String extension, String type, String classifier) {
+      new DefaultPublishArtifact(name, extension, type, classifier, new Date(), new File(""))
+    }
+
+    static groovy.lang.Script createScript(String code) {
+        new GroovyShell().parse(code)
+    }
+
+    static Object call(String text, Object params) {
+        toClosure(text).call(params)
+    }
+    
+    static Closure toClosure(String text) {
+        return new GroovyShell().evaluate("return " + text)
+    }
+
+    static Closure toClosure(ScriptSource source) {
+        CompilerConfiguration configuration = new CompilerConfiguration();
+        configuration.setScriptBaseClass(TestScript.getName());
+
+        GroovyShell shell = new GroovyShell(configuration)
+        Script script = shell.parse(source.resource.text)
+        script.setScriptSource(source)
+        return script.run()
+    }
+
+    static Closure toClosure(TestClosure closure) {
+        return { param -> closure.call(param) }
+    }
+
+    static Closure returns(Object value) {
+        return { value }
+    }
+
+    static Closure createSetterClosure(String name, String value) {
+        return {
+            "set$name"(value)
+        }
+    }
+
+    static String createUniqueId() {
+        return new UID().toString();
+    }
+
+    static org.gradle.api.artifacts.Configuration createConfiguration(String name) {
+        return new DefaultConfiguration(name, name, null, null)
+    }
+}
+
+public interface TestClosure {
+    Object call(Object param);
+}
+
+public abstract class TestScript extends DefaultScript {
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/JUnit4GroovyMockery.java b/subprojects/gradle-core/src/test/groovy/org/gradle/util/JUnit4GroovyMockery.java
new file mode 100644
index 0000000..5527805
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/JUnit4GroovyMockery.java
@@ -0,0 +1,102 @@
+/*
+ * 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;
+
+/**
+ * @author Hans Dockter
+ */
+
+import groovy.lang.Closure;
+import org.codehaus.groovy.runtime.InvokerInvocationException;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.jmock.Expectations;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class JUnit4GroovyMockery extends JUnit4Mockery {
+    private final ConcurrentMap<String, AtomicInteger> names = new ConcurrentHashMap<String, AtomicInteger>();
+
+    public JUnit4GroovyMockery() {
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }
+
+    @Override
+    public <T> T mock(Class<T> typeToMock) {
+        return mock(typeToMock, typeToMock.getSimpleName());
+    }
+
+    @Override
+    public <T> T mock(Class<T> typeToMock, String name) {
+        names.putIfAbsent(name, new AtomicInteger());
+        int count = names.get(name).getAndIncrement();
+        if (count == 0) {
+            return super.mock(typeToMock, name);
+        } else {
+            return super.mock(typeToMock, name + count);
+        }
+    }
+
+    class ClosureExpectations extends Expectations {
+        void closureInit(Closure cl, Object delegate) {
+            cl.setDelegate(delegate);
+            cl.call();
+        }
+
+        <T> void withParam(Matcher<T> matcher) {
+            this.with(matcher);
+        }
+
+        void will(final Closure cl) {
+            will(new Action() {
+                public void describeTo(Description description) {
+                    description.appendText("execute closure");
+                }
+
+                public Object invoke(Invocation invocation) throws Throwable {
+                    List<Object> params = Arrays.asList(invocation.getParametersAsArray());
+                    Object result;
+                    try {
+                        List<Object> subParams = params.subList(0, Math.min(invocation.getParametersAsArray().length,
+                                cl.getMaximumNumberOfParameters()));
+                        result = cl.call(subParams.toArray(new Object[subParams.size()]));
+                    } catch (InvokerInvocationException e) {
+                        throw e.getCause();
+                    }
+                    if (invocation.getInvokedMethod().getReturnType().isInstance(result)) {
+                        return result;
+                    }
+                    return null;
+                }
+            });
+        }
+    }
+
+    public void checking(Closure c) {
+        ClosureExpectations expectations = new ClosureExpectations();
+        expectations.closureInit(c, expectations);
+        super.checking(expectations);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/JavaMethodTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/util/JavaMethodTest.java
new file mode 100644
index 0000000..7699ffd
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/JavaMethodTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class JavaMethodTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+
+    @Test
+    public void invokesMethodOnObject() {
+        JavaMethod<CharSequence, CharSequence> method = new JavaMethod<CharSequence, CharSequence>(CharSequence.class, CharSequence.class, "subSequence", int.class, int.class);
+        assertThat(method.invoke("string", 0, 3), equalTo((CharSequence) "str"));
+    }
+    
+    @Test
+    public void propagatesExceptionThrownByMethod() {
+        final CharSequence mock = context.mock(CharSequence.class);
+        final RuntimeException failure = new RuntimeException();
+        context.checking(new Expectations() {{
+            one(mock).subSequence(0, 3);
+            will(throwException(failure));
+        }});
+
+        JavaMethod<CharSequence, CharSequence> method = new JavaMethod<CharSequence, CharSequence>(CharSequence.class, CharSequence.class, "subSequence", int.class, int.class);
+        try {
+            method.invoke(mock, 0, 3);
+            fail();
+        } catch (RuntimeException e) {
+            assertThat(e, sameInstance(failure));
+        }
+    }
+
+    @Test
+    public void canAccessProtectedMethod() {
+        final Package[] packages = new Package[0];
+        ClassLoader classLoader = new ClassLoader() {
+            @Override
+            protected Package[] getPackages() {
+                return packages;
+            }
+        };
+
+        JavaMethod<ClassLoader, Package[]> method = new JavaMethod<ClassLoader, Package[]>(ClassLoader.class, Package[].class, "getPackages");
+        assertThat(method.invoke(classLoader), sameInstance(packages));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/JvmTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/util/JvmTest.groovy
new file mode 100644
index 0000000..597f2e2
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/JvmTest.groovy
@@ -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.util
+
+import spock.lang.Specification
+import org.junit.Rule
+
+class JvmTest extends Specification {
+    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
+    @Rule public final SetSystemProperties sysProp = new SetSystemProperties()
+
+    def usesSystemPropertyToDetermineIfCompatibleWithJava5() {
+        println System.properties['java.version']
+
+        expect:
+        System.properties['java.version'] = '1.5'
+        def jvm = new Jvm()
+        jvm.java5Compatible
+        !jvm.java6Compatible
+    }
+
+    def usesSystemPropertyToDetermineIfCompatibleWithJava6() {
+        println System.properties['java.version']
+
+        expect:
+        System.properties['java.version'] = '1.6'
+        def jvm = new Jvm()
+        jvm.java5Compatible
+        jvm.java6Compatible
+    }
+
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/LineBufferingOutputStreamTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/util/LineBufferingOutputStreamTest.java
new file mode 100644
index 0000000..db381aa
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/LineBufferingOutputStreamTest.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.util;
+
+import org.gradle.api.Action;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+ at RunWith(JMock.class)
+public class LineBufferingOutputStreamTest {
+    private final JUnit4Mockery context = new JUnit4GroovyMockery();
+    private Action<String> action = context.mock(Action.class);
+    private LineBufferingOutputStream outputStream = new LineBufferingOutputStream(action, false, 8);
+    private String eol;
+
+    @Before
+    public void setUp() {
+        eol = System.getProperty("line.separator");
+    }
+
+    @After
+    public void tearDown() {
+        System.setProperty("line.separator", eol);
+    }
+
+    @Test
+    public void logsEachLineAsASeparateLogMessage() throws IOException {
+        context.checking(new Expectations() {{
+            one(action).execute("line 1");
+            one(action).execute("line 2");
+        }});
+
+        outputStream.write(String.format("line 1%nline 2%n").getBytes());
+    }
+
+    @Test
+    public void canReceiveEachLineWithSeparator() throws IOException {
+        context.checking(new Expectations() {{
+            one(action).execute(String.format("line 1%n"));
+            one(action).execute(String.format("line 2%n"));
+        }});
+
+        outputStream = new LineBufferingOutputStream(action, true);
+        outputStream.write(String.format("line 1%nline 2%n").getBytes());
+    }
+
+    @Test
+    public void logsEmptyLines() throws IOException {
+        context.checking(new Expectations() {{
+            one(action).execute("");
+            one(action).execute("");
+        }});
+
+        outputStream.write(String.format("%n%n").getBytes());
+    }
+
+    @Test
+    public void handlesSingleCharacterLineSeparator() throws IOException {
+        context.checking(new Expectations() {{
+            one(action).execute("line 1");
+            one(action).execute("line 2");
+        }});
+
+        System.setProperty("line.separator", "-");
+        outputStream = new LineBufferingOutputStream(action, false, 8);
+
+        outputStream.write(String.format("line 1-line 2-").getBytes());
+    }
+    
+    @Test
+    public void handlesMultiCharacterLineSeparator() throws IOException {
+        context.checking(new Expectations() {{
+            one(action).execute("line 1");
+            one(action).execute("line 2");
+        }});
+
+        System.setProperty("line.separator", "----");
+        outputStream = new LineBufferingOutputStream(action, false, 8);
+
+        outputStream.write(String.format("line 1----line 2----").getBytes());
+    }
+
+    @Test
+    public void logsLineWhichIsLongerThanInitialBufferLength() throws IOException {
+        context.checking(new Expectations() {{
+            one(action).execute("a line longer than 8 bytes long");
+            one(action).execute("line 2");
+        }});
+        outputStream.write(String.format("a line longer than 8 bytes long%n").getBytes());
+        outputStream.write("line 2".getBytes());
+        outputStream.close();
+    }
+
+    @Test
+    public void logsPartialLineOnFlush() throws IOException {
+        context.checking(new Expectations() {{
+            one(action).execute("line 1");
+        }});
+
+        outputStream.write("line 1".getBytes());
+        outputStream.flush();
+    }
+
+    @Test
+    public void logsPartialLineOnClose() throws IOException {
+        context.checking(new Expectations() {{
+            one(action).execute("line 1");
+        }});
+        outputStream.write("line 1".getBytes());
+        outputStream.close();
+    }
+    
+    @Test(expected = IOException.class)
+    public void cannotWriteAfterClose() throws IOException {
+        outputStream.close();
+        outputStream.write("ignore me".getBytes());
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/LinePerThreadBufferingOutputStreamTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/util/LinePerThreadBufferingOutputStreamTest.groovy
new file mode 100644
index 0000000..7aa9491
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/LinePerThreadBufferingOutputStreamTest.groovy
@@ -0,0 +1,44 @@
+/*
+ * 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.gradle.api.Action
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+class LinePerThreadBufferingOutputStreamTest extends MultithreadedTestCase {
+    @Test
+    public void interleavesLinesFromEachThread() {
+        List<String> output = [].asSynchronized()
+        Action<String> action = { line -> output << line } as Action
+        LinePerThreadBufferingOutputStream outstr = new LinePerThreadBufferingOutputStream(action)
+        10.times {
+            start {
+                100.times {
+                    outstr.write('write ' as byte[])
+                    outstr.print(it)
+                    outstr.println()
+                }
+            }
+        }
+        waitForAll()
+
+        assertThat(output.size(), equalTo(1000))
+        assertThat(output.findAll({!it.matches('write \\d+')}), equalTo([]))
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/LongIdGeneratorTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/util/LongIdGeneratorTest.groovy
new file mode 100644
index 0000000..87afd67
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/LongIdGeneratorTest.groovy
@@ -0,0 +1,49 @@
+/*
+ * 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/gradle-core/src/test/groovy/org/gradle/util/Matchers.java b/subprojects/gradle-core/src/test/groovy/org/gradle/util/Matchers.java
new file mode 100644
index 0000000..fb1ee1d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/Matchers.java
@@ -0,0 +1,317 @@
+/*
+ * 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.gradle.api.Buildable;
+import org.gradle.api.Task;
+import org.hamcrest.*;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+import org.jmock.internal.ReturnDefaultValueAction;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.*;
+import java.util.regex.Pattern;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+public class Matchers {
+    @Factory
+    public static <T> Matcher<T> reflectionEquals(T equalsTo) {
+        return new ReflectionEqualsMatcher<T>(equalsTo);
+    }
+
+    @Factory
+    public static <T, S extends Iterable<? extends T>> Matcher<S> hasSameItems(final S items) {
+        return new BaseMatcher<S>() {
+            public boolean matches(Object o) {
+                Iterable<? extends T> iterable = (Iterable<? extends T>) o;
+                List<T> actual = new ArrayList<T>();
+                for (T t : iterable) {
+                    actual.add(t);
+                }
+                List<T> expected = new ArrayList<T>();
+                for (T t : items) {
+                    expected.add(t);
+                }
+
+                return expected.equals(actual);
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("an Iterable that has same items as ").appendValue(items);
+            }
+        };
+    }
+
+    @Factory
+    public static <T extends CharSequence> Matcher<T> matchesRegexp(final String pattern) {
+        return new BaseMatcher<T>() {
+            public boolean matches(Object o) {
+                return Pattern.compile(pattern).matcher((CharSequence) o).matches();
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("a CharSequence that matches regexp ").appendValue(pattern);
+            }
+        };
+    }
+
+    @Factory
+    public static <T> Matcher<T> strictlyEqual(final T other) {
+        return new BaseMatcher<T>() {
+            public boolean matches(Object o) {
+                return strictlyEquals(o, other);
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("an Object that strictly equals ").appendValue(other);
+            }
+        };
+    }
+
+    public static boolean strictlyEquals(Object a, Object b) {
+        if (!a.equals(b)) {
+            return false;
+        }
+        if (!b.equals(a)) {
+            return false;
+        }
+        if (!a.equals(a)) {
+            return false;
+        }
+        if (b.equals(null)) {
+            return false;
+        }
+        if (b.equals(new Object())) {
+            return false;
+        }
+        if (a.hashCode() != b.hashCode()) {
+            return false;
+        }
+        return true;
+
+    }
+    @Factory
+    public static Matcher<String> containsLine(final String line) {
+        return new BaseMatcher<String>() {
+            public boolean matches(Object o) {
+                return containsLine(equalTo(line)).matches(o);
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("a String that contains line ").appendValue(line);
+            }
+        };
+    }
+
+    @Factory
+    public static Matcher<String> containsLine(final Matcher<? super String> matcher) {
+        return new BaseMatcher<String>() {
+            public boolean matches(Object o) {
+                String str = (String) o;
+                BufferedReader reader = new BufferedReader(new StringReader(str));
+                String line;
+                try {
+                    while ((line = reader.readLine()) != null) {
+                        if (matcher.matches(line)) {
+                            return true;
+                        }
+                    }
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
+                }
+                return false;
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("a String that contains line that is ").appendDescriptionOf(matcher);
+            }
+        };
+    }
+
+    @Factory
+    public static Matcher<Iterable<?>> isEmpty() {
+        return new BaseMatcher<Iterable<?>>() {
+            public boolean matches(Object o) {
+                Iterable<?> iterable = (Iterable<?>) o;
+                return iterable != null && !iterable.iterator().hasNext();
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("an empty Iterable");
+            }
+        };
+    }
+
+    @Factory
+    public static Matcher<Map<?, ?>> isEmptyMap() {
+        return new BaseMatcher<Map<?, ?>>() {
+            public boolean matches(Object o) {
+                Map<?, ?> map = (Map<?, ?>) o;
+                return map != null && map.isEmpty();
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("an empty map");
+            }
+        };
+    }
+
+    @Factory
+    public static Matcher<Object[]> isEmptyArray() {
+        return new BaseMatcher<Object[]>() {
+            public boolean matches(Object o) {
+                Object[] array = (Object[]) o;
+                return array != null && array.length == 0;
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("an empty array");
+            }
+        };
+    }
+
+    @Factory
+    public static Matcher<Throwable> hasMessage(final Matcher<String> matcher) {
+        return new BaseMatcher<Throwable>() {
+            public boolean matches(Object o) {
+                Throwable t = (Throwable) o;
+                return matcher.matches(t.getMessage());
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("an exception with message that is ").appendDescriptionOf(matcher);
+            }
+        };
+    }
+
+    @Factory
+    public static Matcher<Task> dependsOn(final String... tasks) {
+        return dependsOn(equalTo(new HashSet<String>(Arrays.asList(tasks))));
+    }
+    
+    @Factory
+    public static Matcher<Task> dependsOn(final Matcher<? extends Iterable<String>> matcher) {
+        return new BaseMatcher<Task>() {
+            public boolean matches(Object o) {
+                Task task = (Task) o;
+                Set<String> names = new HashSet<String>();
+                Set<? extends Task> depTasks = task.getTaskDependencies().getDependencies(task);
+                for (Task depTask : depTasks) {
+                    names.add(depTask.getName());
+                }
+                boolean matches = matcher.matches(names);
+                if (!matches) {
+                    StringDescription description = new StringDescription();
+                    matcher.describeTo(description);
+                    System.out.println(String.format("expected %s, got %s.", description.toString(), names));
+                }
+                return matches;
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("a Task that depends on ").appendDescriptionOf(matcher);
+            }
+        };
+    }
+
+    @Factory
+    public static <T extends Buildable> Matcher<T> builtBy(String... tasks) {
+        return builtBy(equalTo(new HashSet<String>(Arrays.asList(tasks))));
+    }
+
+    @Factory
+    public static <T extends Buildable> Matcher<T> builtBy(final Matcher<? extends Iterable<String>> matcher) {
+        return new BaseMatcher<T>() {
+            public boolean matches(Object o) {
+                Buildable task = (Buildable) o;
+                Set<String> names = new HashSet<String>();
+                Set<? extends Task> depTasks = task.getBuildDependencies().getDependencies(null);
+                for (Task depTask : depTasks) {
+                    names.add(depTask.getName());
+                }
+                boolean matches = matcher.matches(names);
+                if (!matches) {
+                    StringDescription description = new StringDescription();
+                    matcher.describeTo(description);
+                    System.out.println(String.format("expected %s, got %s.", description.toString(), names));
+                }
+                return matches;
+            }
+
+            public void describeTo(Description description) {
+                description.appendText("a Buildable that is built by ").appendDescriptionOf(matcher);
+            }
+        };
+    }
+
+    /**
+     * Returns a placeholder for a mock method parameter.
+     */
+    public static <T> Collector<T> collector() {
+        return new Collector<T>();
+    }
+
+    /**
+     * Returns an action which collects the first parameter into the given placeholder.
+     */
+    public static CollectAction collectTo(Collector<?> collector) {
+        return new CollectAction(collector);
+    }
+
+    public static class CollectAction implements Action {
+        private Action action = new ReturnDefaultValueAction();
+        private final Collector<?> collector;
+
+        public CollectAction(Collector<?> collector) {
+            this.collector = collector;
+        }
+
+        public Action then(Action action) {
+            this.action = action;
+            return this;
+        }
+
+        public void describeTo(Description description) {
+            description.appendText("collect parameter then ").appendDescriptionOf(action);
+        }
+
+        public Object invoke(Invocation invocation) throws Throwable {
+            collector.setValue(invocation.getParameter(0));
+            return action.invoke(invocation);
+        }
+    }
+
+    public static class Collector<T> {
+        private T value;
+        private boolean set;
+
+        public T get() {
+            assertTrue(set);
+            return value;
+        }
+
+        void setValue(Object parameter) {
+            value = (T) parameter;
+            set = true;
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/MultiParentClassLoaderTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/util/MultiParentClassLoaderTest.groovy
new file mode 100644
index 0000000..5dce686
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/MultiParentClassLoaderTest.groovy
@@ -0,0 +1,149 @@
+/*
+ * 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
+
+import org.jmock.integration.junit4.JMock
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.jmock.lib.legacy.ClassImposteriser
+
+ at RunWith(JMock.class)
+class MultiParentClassLoaderTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private ClassLoader parent1
+    private ClassLoader parent2
+    private MultiParentClassLoader loader
+
+    @Before
+    public void setup() {
+        context.imposteriser = ClassImposteriser.INSTANCE
+        parent1 = context.mock(ClassLoader, 'parent1')
+        parent2 = context.mock(ClassLoader, 'parent2')
+        loader = new MultiParentClassLoader(parent1, parent2)
+    }
+
+    @Test
+    public void loadsClassFromParentsInOrderSpecified() {
+        Class stringClass = String.class
+        Class integerClass = Integer.class
+
+        context.checking {
+            allowing(parent1).loadClass('string')
+            will(returnValue(stringClass))
+            allowing(parent1).loadClass('integer')
+            will(throwException(new ClassNotFoundException()))
+            allowing(parent2).loadClass('integer')
+            will(returnValue(integerClass))
+        }
+
+        assertThat(loader.loadClass('string'), equalTo(String.class))
+        assertThat(loader.loadClass('string', true), equalTo(String.class))
+        assertThat(loader.loadClass('integer'), equalTo(Integer.class))
+        assertThat(loader.loadClass('integer', true), equalTo(Integer.class))
+    }
+
+    @Test
+    public void throwsCNFExceptionWhenClassNotFound() {
+        context.checking {
+            allowing(parent1).loadClass('string')
+            will(throwException(new ClassNotFoundException()))
+            allowing(parent2).loadClass('string')
+            will(throwException(new ClassNotFoundException()))
+        }
+
+        try {
+            loader.loadClass('string')
+            fail()
+        } catch (ClassNotFoundException e) {
+            assertThat(e.message, equalTo('string not found.'))
+        }
+    }
+    
+    @Test
+    public void loadsPackageFromParentsInOrderSpecified() {
+        Package stringPackage = String.class.getPackage()
+        Package listPackage = List.class.getPackage()
+
+        context.checking {
+            allowing(parent1).getPackage('string')
+            will(returnValue(stringPackage))
+            allowing(parent1).getPackage('list')
+            will(returnValue(null))
+            allowing(parent2).getPackage('list')
+            will(returnValue(listPackage))
+        }
+
+        assertThat(loader.getPackage('string'), sameInstance(stringPackage))
+        assertThat(loader.getPackage('list'), sameInstance(listPackage))
+    }
+
+    @Test
+    public void containsUnionOfPackagesFromAllParents() {
+        Package package1 = context.mock(Package.class, 'p1')
+        Package package2 = context.mock(Package.class, 'p2')
+
+        context.checking {
+            allowing(parent1).getPackages()
+            will(returnValue([package1] as Package[]))
+            allowing(parent2).getPackages()
+            will(returnValue([package2] as Package[]))
+        }
+
+        assertThat(loader.getPackages(), hasItemInArray(package1))
+        assertThat(loader.getPackages(), hasItemInArray(package2))
+    }
+
+    @Test
+    public void loadsResourceFromParentsInOrderSpecified() {
+        URL resource1 = new File('res1').toURI().toURL()
+        URL resource2 = new File('res2').toURI().toURL()
+
+        context.checking {
+            allowing(parent1).getResource('resource1')
+            will(returnValue(resource1))
+            allowing(parent1).getResource('resource2')
+            will(returnValue(null))
+            allowing(parent2).getResource('resource2')
+            will(returnValue(resource2))
+        }
+
+        assertThat(loader.getResource('resource1'), equalTo(resource1))
+        assertThat(loader.getResource('resource2'), equalTo(resource2))
+    }
+    
+    @Test
+    public void containsUnionOfResourcesFromAllParents() {
+        URL resource1 = new File('res1').toURI().toURL()
+        URL resource2 = new File('res2').toURI().toURL()
+
+        context.checking {
+            allowing(parent1).getResources('resource1')
+            will(returnValue(Collections.enumeration([resource1])))
+            allowing(parent2).getResources('resource1')
+            will(returnValue(Collections.enumeration([resource2, resource1])))
+        }
+
+        Enumeration resources = loader.getResources('resource1')
+        assertTrue(resources.hasMoreElements())
+        assertThat(resources.nextElement(), sameInstance(resource1))
+        assertTrue(resources.hasMoreElements())
+        assertThat(resources.nextElement(), sameInstance(resource2))
+        assertFalse(resources.hasMoreElements())
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/MultithreadedTestCase.java b/subprojects/gradle-core/src/test/groovy/org/gradle/util/MultithreadedTestCase.java
new file mode 100644
index 0000000..49f2a2f
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/MultithreadedTestCase.java
@@ -0,0 +1,665 @@
+/*
+ * 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 groovy.lang.Closure;
+import junit.framework.AssertionFailedError;
+import org.codehaus.groovy.runtime.InvokerInvocationException;
+import org.gradle.messaging.concurrent.DefaultExecutorFactory;
+import org.gradle.messaging.concurrent.ExecutorFactory;
+import org.hamcrest.Matcher;
+import org.junit.After;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.concurrent.AbstractExecutorService;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * <p>A base class for testing concurrent code.</p>
+ *
+ * <p>Provides several ways to start and manage threads. You can use the {@link #start(groovy.lang.Closure)} or {@link
+ * #run(groovy.lang.Closure)} methods to execute test code in other threads. You can use {@link #waitForAll()} to wait
+ * for all test threads to complete. In addition, the test tear-down method blocks until all test threads have stopped
+ * and ensures that no exceptions were thrown in any test threads.</p>
+ *
+ * <p>Provides an {@link java.util.concurrent.Executor} implementation, which uses test threads to execute any tasks
+ * submitted to it.</p>
+ *
+ * <p>You can use {@link #syncAt(int)} and {@link #expectBlocksUntil(int, groovy.lang.Closure)} to synchronise between
+ * test threads.</p>
+ */
+public class MultithreadedTestCase {
+    private static final Logger LOGGER = LoggerFactory.getLogger(MultithreadedTestCase.class);
+    private static final int MAX_WAIT_TIME = 5000;
+    private ExecutorImpl executor;
+    private final Lock lock = new ReentrantLock();
+    private final Condition condition = lock.newCondition();
+    private final Set<Thread> active = new HashSet<Thread>();
+    private final Set<Thread> synching = new HashSet<Thread>();
+    private final List<Throwable> failures = new ArrayList<Throwable>();
+    private final Map<Integer, ClockTickImpl> ticks = new HashMap<Integer, ClockTickImpl>();
+    private ClockTickImpl currentTick = getTick(0);
+    private boolean stopped;
+    private final ThreadLocal<Matcher<? extends Throwable>> expectedFailure
+            = new ThreadLocal<Matcher<? extends Throwable>>();
+    private final SyncPoint syncPoint = new SyncPoint();
+
+    /**
+     * Creates an Executor which the test can control.
+     */
+    protected ExecutorService getExecutor() {
+        if (executor == null) {
+            executor = new ExecutorImpl();
+        }
+        return executor;
+    }
+
+    /**
+     * Creates an ExecutorFactory for the test to use.
+     */
+    protected ExecutorFactory getExecutorFactory() {
+        return new DefaultExecutorFactory() {
+            @Override
+            protected ExecutorService createExecutor(String displayName) {
+                return getExecutor();
+            }
+        };
+    }
+
+    /**
+     * Executes the given closure in a test thread.
+     */
+    protected ThreadHandle start(final Closure closure) {
+        Runnable task = new Runnable() {
+            public void run() {
+                closure.call();
+            }
+        };
+
+        return start(task);
+    }
+
+    /**
+     * Executes the given closure in a test thread and waits for it to complete.
+     */
+    protected ThreadHandle run(final Closure closure) {
+        Runnable task = new Runnable() {
+            public void run() {
+                closure.call();
+            }
+        };
+
+        return start(task).waitFor();
+    }
+
+    protected ThreadHandle expectTimesOut(int value, TimeUnit units, Closure closure) {
+        Date start = new Date();
+        ThreadHandle threadHandle = start(closure);
+        threadHandle.waitFor();
+        Date end = new Date();
+        long actual = end.getTime() - start.getTime();
+        long expected = units.toMillis(value);
+        if (actual < expected - 200) {
+            throw new RuntimeException(String.format(
+                    "Action did not block for expected time. Expected ~ %d ms, was %d ms.", expected, actual));
+        }
+        if (actual > expected + 500) {
+            throw new RuntimeException(String.format(
+                    "Action did not complete within expected time. Expected ~ %d ms, was %d ms.", expected, actual));
+        }
+        return threadHandle;
+    }
+
+    /**
+     * Executes the given runnable in a test thread.
+     */
+    protected ThreadHandle start(final Runnable task) {
+        final Thread thread = new Thread() {
+            @Override
+            public String toString() {
+                return "test thread " + getId();
+            }
+
+            public void run() {
+                Throwable failure = null;
+                try {
+                    try {
+                        task.run();
+                    } catch (InvokerInvocationException e) {
+                        failure = e.getCause();
+                    } catch (Throwable throwable) {
+                        failure = throwable;
+                    }
+                } finally {
+                    testThreadFinished(this, failure);
+                }
+            }
+        };
+
+        testThreadStarted(thread);
+        thread.start();
+        return new ThreadHandleImpl(thread);
+    }
+
+    private void testThreadStarted(Thread thread) {
+        lock.lock();
+        try {
+            if (stopped) {
+                throw new IllegalStateException("Cannot start new threads, as this test case has been stopped.");
+            }
+            LOGGER.debug("Started {}", thread);
+            active.add(thread);
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private void testThreadFinished(Thread thread, Throwable failure) {
+        lock.lock();
+        try {
+            active.remove(thread);
+            Matcher<? extends Throwable> matcher = expectedFailure.get();
+            if (failure != null) {
+                if (matcher != null && matcher.matches(failure)) {
+                    LOGGER.debug("Finished {} with expected failure.", thread);
+                } else {
+                    LOGGER.error(String.format("Failure in %s", thread), failure);
+                    failures.add(failure);
+                }
+            } else {
+                if (matcher != null) {
+                    String message = String.format("Did not get expected failure in %s", thread);
+                    LOGGER.error(message);
+                    failures.add(new AssertionFailedError(message));
+                } else {
+                    LOGGER.debug("Finished {}", thread);
+                }
+            }
+            condition.signalAll();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Waits for all asynchronous activity to complete. Applies a timeout, and re-throws any exceptions which occurred.
+     */
+    public void waitForAll() {
+        Date expiry = new Date(System.currentTimeMillis() + 2 * MAX_WAIT_TIME);
+        lock.lock();
+        try {
+            LOGGER.debug("Waiting for test threads complete.");
+
+            if (active.contains(Thread.currentThread())) {
+                throw new RuntimeException("A test thread cannot wait for test threads to complete.");
+            }
+            try {
+                while (!active.isEmpty()) {
+                    boolean signaled = condition.awaitUntil(expiry);
+                    if (!signaled) {
+                        failures.add(new RuntimeException("Timeout waiting for threads to finish."));
+                        break;
+                    }
+                }
+            } catch (InterruptedException e) {
+                throw UncheckedException.asUncheckedException(e);
+            }
+
+            LOGGER.debug("All test threads complete.");
+
+            if (!failures.isEmpty()) {
+                Throwable failure = failures.get(0);
+                failures.clear();
+                if (failure instanceof RuntimeException) {
+                    throw (RuntimeException) failure;
+                }
+                if (failure instanceof Error) {
+                    throw (Error) failure;
+                }
+                throw new RuntimeException("An unexpected exception occurred in a test thread.", failure);
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    @After
+    public void waitForStop() {
+        lock.lock();
+        try {
+            stopped = true;
+        } finally {
+            lock.unlock();
+        }
+        waitForAll();
+    }
+
+    /**
+     * Returns the meta-info for the given clock tick.
+     */
+    public ClockTick clockTick(int tick) {
+        lock.lock();
+        try {
+            return getTick(tick);
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    private ClockTickImpl getTick(int tick) {
+        ClockTickImpl clockTick = ticks.get(tick);
+        if (clockTick == null) {
+            clockTick = new ClockTickImpl(tick);
+            ticks.put(tick, clockTick);
+        }
+        return clockTick;
+    }
+
+    /**
+     * Blocks until the clock has reached the given tick. The clock advances to the given tick when all test threads
+     * have called {@link #syncAt(int)} or {@link #expectBlocksUntil(int, groovy.lang.Closure)} with the given tick, and
+     * there are least 2 test threads.
+     *
+     * @param tick The expected clock tick
+     */
+    public void syncAt(int tick) {
+        LOGGER.debug("Thread {} synching at tick {}", Thread.currentThread(), tick);
+
+        lock.lock();
+        try {
+            ClockTickImpl clockTick = getTick(tick);
+            if (!clockTick.isImmediatelyAfter(currentTick)) {
+                throw new RuntimeException(String.format("Cannot wait for %s, as clock is currently at %s.", clockTick,
+                        currentTick));
+            }
+            if (!active.contains(Thread.currentThread())) {
+                throw new RuntimeException("Cannot wait for clock tick from a thread which is not a test thread.");
+            }
+
+            Date expiry = new Date(System.currentTimeMillis() + MAX_WAIT_TIME);
+            synching.add(Thread.currentThread());
+            condition.signalAll();
+            while (failures.isEmpty() && currentTick != clockTick && !clockTick.allThreadsSynced(synching, active)) {
+                try {
+                    boolean signalled = condition.awaitUntil(expiry);
+                    if (!signalled) {
+                        throw new RuntimeException(String.format(
+                                "Timeout waiting for all threads to reach %s. Currently at %s.", clockTick,
+                                currentTick));
+                    }
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+            if (!failures.isEmpty()) {
+                throw new RuntimeException(String.format(
+                        "Could not wait for all threads to reach %s, as a failure has occurred in another test thread.",
+                        clockTick));
+            }
+            if (clockTick.isImmediatelyAfter(currentTick)) {
+                currentTick = clockTick;
+                synching.clear();
+            }
+        } finally {
+            lock.unlock();
+        }
+
+        LOGGER.debug("Thread {} sync done", Thread.currentThread());
+    }
+
+    /**
+     * Expects that the given tick will be reached at some point in the future. Does not block until the tick has been
+     * reached.
+     *
+     * @param tick The expected clock tick.
+     */
+    private void expectLater(final int tick) {
+        final Thread targetThread = Thread.currentThread();
+        LOGGER.debug("Thread {} expecting tick {}", targetThread, tick);
+        start(new Runnable() {
+            public void run() {
+                try {
+                    Thread.sleep(200L);
+                } catch (InterruptedException e) {
+                    throw new RuntimeException(e);
+                }
+                lock.lock();
+                try {
+                    ClockTickImpl clockTick = getTick(tick);
+                    if (!clockTick.isImmediatelyAfter(currentTick)) {
+                        throw new RuntimeException(String.format("Cannot wait for %s, as clock is currently at %s.",
+                                clockTick, currentTick));
+                    }
+                    if (!active.contains(targetThread)) {
+                        throw new RuntimeException(
+                                "Cannot wait for clock tick from a thread which is not a test thread.");
+                    }
+
+                    synching.add(targetThread);
+                    condition.signalAll();
+                } finally {
+                    lock.unlock();
+                }
+            }
+        });
+    }
+
+    /**
+     * Asserts that the given closure blocks until the given clock tick is reached.
+     *
+     * @param tick The expected clock tick when the closure completes.
+     * @param closure The closure to execute.
+     */
+    public void expectBlocksUntil(int tick, Closure closure) {
+        expectLater(tick);
+        closure.call();
+        shouldBeAt(tick);
+    }
+
+    /**
+     * Asserts that the clock is at the given tick.
+     *
+     * @param tick The expected clock tick.
+     */
+    public void shouldBeAt(int tick) {
+        lock.lock();
+        try {
+            if (currentTick != getTick(tick)) {
+                throw new RuntimeException(String.format("Expected clock to be at %s, but is at %s.", tick,
+                        currentTick));
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Indicates that the current test thread will fail with an exception that matches the given criteria.
+     */
+    public void willFailWith(Matcher<? extends Throwable> matcher) {
+        expectedFailure.set(matcher);
+    }
+
+    /**
+     * Executes the given action in another thread, and asserts that the action blocks until all actions provided to
+     * {@link #expectUnblocks(groovy.lang.Closure)} have been executed.
+     *
+     * @param action The action to execute.
+     */
+    public void expectBlocks(Closure action) {
+        syncPoint.expectBlocks(action);
+    }
+
+    /**
+     * Executes the given action, asserting that it unblocks all actions provided to {@link
+     * #expectBlocks(groovy.lang.Closure)}
+     *
+     * @param action The action to execute.
+     */
+    public void expectUnblocks(Closure action) {
+        syncPoint.expectUnblocks(action);
+    }
+
+    private class ExecutorImpl extends AbstractExecutorService {
+        private final Set<ThreadHandle> threads = new CopyOnWriteArraySet<ThreadHandle>();
+
+        public void execute(Runnable command) {
+            threads.add(start(command));
+        }
+
+        public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+            Date expiry = new Date(System.currentTimeMillis() + unit.toMillis(timeout));
+            for (ThreadHandle thread : threads) {
+                if (!thread.waitUntil(expiry)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public void shutdown() {
+        }
+
+        public List<Runnable> shutdownNow() {
+            return new ArrayList<Runnable>();
+        }
+
+        public boolean isShutdown() {
+            throw new UnsupportedOperationException();
+        }
+
+        public boolean isTerminated() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    public interface ThreadHandle {
+        ThreadHandle waitFor();
+
+        boolean waitUntil(Date expiry);
+
+        boolean isCurrentThread();
+
+        void waitUntilBlocked();
+    }
+
+    public interface ClockTick {
+        ClockTick hasParticipants(int count);
+    }
+
+    private static class ClockTickImpl implements ClockTick {
+        private final int number;
+        private int participants;
+
+        private ClockTickImpl(int number) {
+            this.number = number;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("tick %d", number);
+        }
+
+        public ClockTick hasParticipants(int count) {
+            participants = count;
+            return this;
+        }
+
+        public boolean allThreadsSynced(Set<Thread> synching, Set<Thread> active) {
+            if (participants > 0) {
+                return synching.size() == participants;
+            }
+            return synching.equals(active) && synching.size() > 1;
+        }
+
+        public boolean isImmediatelyAfter(ClockTickImpl other) {
+            return number == other.number + 1;
+        }
+    }
+
+    private enum State {
+        Idle, Blocking, Blocked, Unblocking, Unblocked, Failed
+    }
+
+    private class SyncPoint {
+        private final Lock lock = new ReentrantLock();
+        private final Condition condition = lock.newCondition();
+        private State state = State.Idle;
+        private ThreadHandle blockingThread;
+
+        public void expectBlocks(Closure action) {
+            try {
+                setState(State.Idle, State.Blocking);
+                setBlockingThread(start(action));
+                setState(State.Blocking, State.Blocked);
+                waitForState(State.Unblocked, State.Failed);
+            } catch (InterruptedException e) {
+                throw UncheckedException.asUncheckedException(e);
+            }
+        }
+
+        public void expectUnblocks(Closure action) {
+            try {
+                waitForState(State.Blocked);
+
+                ThreadHandle thread = getBlockingThread();
+                if (thread.isCurrentThread()) {
+                    throw new IllegalStateException("The blocking thread cannot unblock itself.");
+                }
+
+                setState(State.Blocked, State.Unblocking);
+                try {
+                    thread.waitUntilBlocked();
+                    action.call();
+                    boolean completed = thread.waitUntil(new Date(System.currentTimeMillis() + 500L));
+                    if (!completed) {
+                        throw new IllegalStateException("Expected blocking action to unblock, but it did not.");
+                    }
+                    setState(State.Unblocking, State.Unblocked);
+                } catch (Throwable e) {
+                    setState(State.Unblocking, State.Failed);
+                    throw UncheckedException.asUncheckedException(e);
+                } finally {
+                    setBlockingThread(null);
+                }
+            } catch (InterruptedException e) {
+                throw UncheckedException.asUncheckedException(e);
+            }
+        }
+
+        private ThreadHandle getBlockingThread() {
+            lock.lock();
+            try {
+                return blockingThread;
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        private void setBlockingThread(ThreadHandle thread) {
+            lock.lock();
+            try {
+                blockingThread = thread;
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        private State waitForState(State... states) throws InterruptedException {
+            Date expiry = new Date(System.currentTimeMillis() + 4000L);
+            Collection<State> expectedStates = Arrays.asList(states);
+            lock.lock();
+            try {
+                while (!expectedStates.contains(state)) {
+                    if (!condition.awaitUntil(expiry)) {
+                        throw new IllegalStateException(String.format("Timeout waiting for one of: %s",
+                                expectedStates));
+                    }
+                }
+                return state;
+            } finally {
+                lock.unlock();
+            }
+        }
+
+        private void setState(State expected, State newState) {
+            lock.lock();
+            try {
+                if (state != expected) {
+                    throw new IllegalStateException(String.format("In unexpected state. Expected %s, actual %s",
+                            expected, state));
+                }
+                state = newState;
+                condition.signalAll();
+            } finally {
+                lock.unlock();
+            }
+        }
+    }
+
+    private class ThreadHandleImpl implements ThreadHandle {
+        private final Thread thread;
+        private final Set<Thread.State> blockedStates = EnumSet.of(Thread.State.BLOCKED, Thread.State.TIMED_WAITING,
+                Thread.State.WAITING);
+
+        public ThreadHandleImpl(Thread thread) {
+            this.thread = thread;
+        }
+
+        public ThreadHandle waitFor() {
+            Date expiry = new Date(System.currentTimeMillis() + 2 * MAX_WAIT_TIME);
+            if (!waitUntil(expiry)) {
+                throw new RuntimeException("timeout waiting for test thread to stop.");
+            }
+            return this;
+        }
+
+        public boolean waitUntil(Date expiry) {
+            if (isCurrentThread()) {
+                throw new RuntimeException("A test thread cannot wait for itself to complete.");
+            }
+
+            lock.lock();
+            try {
+                while (active.contains(thread)) {
+                    try {
+                        boolean signalled = condition.awaitUntil(expiry);
+                        if (!signalled) {
+                            return false;
+                        }
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            } finally {
+                lock.unlock();
+            }
+
+            return true;
+        }
+
+        public boolean isCurrentThread() {
+            return Thread.currentThread() == thread;
+        }
+
+        public boolean isBlocked() {
+            return blockedStates.contains(thread.getState());
+        }
+
+        public void waitUntilBlocked() {
+            long expiry = System.currentTimeMillis() + 2000L;
+            while (!isBlocked()) {
+                if (System.currentTimeMillis() > expiry) {
+                    throw new IllegalStateException("Timeout waiting for thread to block.");
+                }
+                try {
+                    Thread.sleep(200L);
+                } catch (InterruptedException e) {
+                    throw UncheckedException.asUncheckedException(e);
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/NameMatcherTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/util/NameMatcherTest.java
new file mode 100644
index 0000000..65bd007
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/NameMatcherTest.java
@@ -0,0 +1,178 @@
+/*
+ * 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.List;
+import java.util.Map;
+import org.junit.Test;
+import static java.util.Collections.*;
+import static org.gradle.util.GUtil.*;
+import static org.gradle.util.Matchers.*;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+public class NameMatcherTest {
+    private final NameMatcher matcher = new NameMatcher();
+
+    @Test
+    public void selectsExactMatch() {
+        assertMatches("name", "name");
+        assertMatches("name", "name", "other");
+    }
+
+    @Test
+    public void selectsItemWithMatchingPrefix() {
+        assertMatches("na", "name");
+        assertMatches("na", "name", "other");
+        // Mixed case
+        assertMatches("na", "Name");
+        assertMatches("NA", "name");
+        assertMatches("somena", "someName");
+        assertMatches("somena", "SomeName");
+        assertMatches("somena", "SomeName");
+        assertMatches("some na", "Some Name");
+    }
+
+    @Test
+    public void selectsItemWithMatchingCamelCasePrefix() {
+        assertMatches("sN", "someName");
+        assertMatches("soN", "someName");
+        assertMatches("SN", "someName");
+        assertMatches("SN", "SomeName");
+        assertMatches("SN", "SomeNameWithExtraStuff");
+        assertMatches("so_n", "some_name");
+        assertMatches("so_n", "some_Name");
+        assertMatches("so_n_wi_ext", "some_Name_with_EXTRA");
+        assertMatches("so.n", "some.name");
+        assertMatches("so n", "some name");
+        assertMatches("ABC", "ABC");
+        assertMatches("a9N", "a9Name");
+        assertMatches("a9N", "abc9Name");
+        assertMatches("a9n", "abc9Name");
+    }
+
+    @Test
+    public void prefersExactMatchOverInexactMatch() {
+        assertMatches("name", "name", "Name", "NAME");
+        assertMatches("name", "name", "names");
+        assertMatches("sName", "sName", "someName", "sNames");
+        assertMatches("so Name", "so Name", "some Name", "so name");
+        assertMatches("ABC", "ABC", "AaBbCc");
+    }
+
+    @Test
+    public void prefersFullCamelCaseMatchOverCamelCasePrefix() {
+        assertMatches("sN", "someName", "someNameWithExtra");
+        assertMatches("name", "names", "nameWithExtra");
+        assertMatches("s_n", "some_name", "some_name_with_extra");
+    }
+
+    @Test
+    public void prefersCaseSensitiveMatchOverCaseInsensitiveMatch() {
+        assertMatches("sN", "someName", "sn");
+        assertMatches("SN", "SomeName", "someName");
+        assertMatches("n1", "name1", "Name1", "N1");
+    }
+
+    @Test
+    public void doesNotSelectItemsWhenNoMatches() {
+        assertDoesNotMatch("name");
+        assertDoesNotMatch("name", "other");
+        assertDoesNotMatch("name", "na");
+        assertDoesNotMatch("sN", "otherName");
+        assertDoesNotMatch("sN", "someThing");
+        assertDoesNotMatch("soN", "saN");
+        assertDoesNotMatch("soN", "saName");
+    }
+
+    @Test
+    public void doesNotSelectItemsWhenMultipleMatches() {
+        assertThat(matcher.find("sN", toList("someName", "soNa", "other")), nullValue());
+        assertThat(matcher.getMatches(), equalTo(toSet("someName", "soNa")));
+    }
+
+    @Test
+    public void emptyPatternDoesNotSelectAnything() {
+        assertDoesNotMatch("", "something");
+    }
+
+    @Test
+    public void escapesRegexpChars() {
+        assertDoesNotMatch("name\\othername", "other");
+    }
+
+    @Test
+    public void reportsPotentialMatches() {
+        assertThat(matcher.find("name", toList("tame", "lame", "other")), nullValue());
+        assertThat(matcher.getMatches(), isEmpty());
+        assertThat(matcher.getCandidates(), equalTo(toSet("tame", "lame")));
+    }
+
+    @Test
+    public void doesNotSelectMapEntryWhenNoMatches() {
+        Integer match = matcher.find("soNa", singletonMap("does not match", 9));
+        assertThat(match, nullValue());
+    }
+
+    @Test
+    public void selectsMapEntryWhenExactMatch() {
+        Integer match = matcher.find("name", singletonMap("name", 9));
+        assertThat(match, equalTo(9));
+    }
+
+    @Test
+    public void selectsMapEntryWhenOnePartialMatch() {
+        Integer match = matcher.find("soNa", singletonMap("someName", 9));
+        assertThat(match, equalTo(9));
+    }
+
+    @Test
+    public void doesNotSelectMapEntryWhenMultiplePartialMatches() {
+        Map<String, Integer> items = GUtil.map("someName", 9, "soName", 10);
+        Integer match = matcher.find("soNa", items);
+        assertThat(match, nullValue());
+    }
+
+    @Test
+    public void buildsErrorMessageForNoMatches() {
+        matcher.find("name", toList("other"));
+        assertThat(matcher.formatErrorMessage("thing", "container"), equalTo("Thing 'name' not found in container."));
+    }
+
+    @Test
+    public void buildsErrorMessageForMultipleMatches() {
+        matcher.find("n", toList("number", "name", "other"));
+        assertThat(matcher.formatErrorMessage("thing", "container"), equalTo("Thing 'n' is ambiguous in container. Candidates are: 'name', 'number'."));
+    }
+
+    @Test
+    public void buildsErrorMessageForPotentialMatches() {
+        matcher.find("name", toList("other", "lame", "tame"));
+        assertThat(matcher.formatErrorMessage("thing", "container"), equalTo("Thing 'name' not found in container. Some candidates are: 'lame', 'tame'."));
+    }
+
+    private void assertDoesNotMatch(String name, String... items) {
+        assertThat(matcher.find(name, toList(items)), nullValue());
+        assertThat(matcher.getMatches(), isEmpty());
+    }
+
+    private void assertMatches(String name, String match, String... extraItems) {
+        List<String> allItems = addLists(toList(match), toList(extraItems));
+        assertThat(matcher.find(name, allItems), equalTo(match));
+        assertThat(matcher.getMatches(), equalTo(toSet(match)));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/ObservableUrlClassLoaderTest.java b/subprojects/gradle-core/src/test/groovy/org/gradle/util/ObservableUrlClassLoaderTest.java
new file mode 100644
index 0000000..adf1b19
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/ObservableUrlClassLoaderTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.gradle.api.Action;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+ at RunWith(JMock.class)
+public class ObservableUrlClassLoaderTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final ObservableUrlClassLoader loader = new ObservableUrlClassLoader(null);
+
+    @Test
+    public void notifiesListenerWhenUrlAdded() throws MalformedURLException {
+        final Action<ClassLoader> action = context.mock(Action.class);
+        loader.whenUrlAdded(action);
+
+        context.checking(new Expectations() {{
+            one(action).execute(loader);
+        }});
+
+        loader.addURL(new URL("file:file.txt"));
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/PathHelperTest.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/util/PathHelperTest.groovy
new file mode 100644
index 0000000..bda0d0e
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/PathHelperTest.groovy
@@ -0,0 +1,31 @@
+/*
+ * 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.util
+
+import static org.junit.Assert.assertFalse
+import static org.junit.Assert.assertTrue
+import org.junit.Test
+
+/**
+ * @author Hans Dockter
+ */
+class PathHelperTest {
+    @Test public void absolutePath() {
+        assertTrue(PathHelper.isAbsolutePath(":path"))
+        assertFalse(PathHelper.isAbsolutePath("path"))
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/RedirectStdOutAndErr.java b/subprojects/gradle-core/src/test/groovy/org/gradle/util/RedirectStdOutAndErr.java
new file mode 100644
index 0000000..1732726
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/RedirectStdOutAndErr.java
@@ -0,0 +1,75 @@
+/*
+ * 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.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+/**
+ * A Junit rule which replaces stdout and stderr with mocks for the duration of the test, and restores them at the end
+ * of the test.
+ */
+public class RedirectStdOutAndErr implements MethodRule {
+    private PrintStream originalStdOut;
+    private PrintStream originalStdErr;
+    private ByteArrayOutputStream stdoutContent = new ByteArrayOutputStream();
+    private ByteArrayOutputStream stderrContent = new ByteArrayOutputStream();
+    private PrintStream stdOutPrintStream = new PrintStream(stdoutContent);
+    private PrintStream stdErrPrintStream = new PrintStream(stderrContent);
+
+    public Statement apply(final Statement base, FrameworkMethod method, Object target) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                originalStdOut = System.out;
+                originalStdErr = System.err;
+                try {
+                    System.setOut(stdOutPrintStream);
+                    System.setErr(stdErrPrintStream);
+                    base.evaluate();
+                } finally {
+                    System.setOut(originalStdOut);
+                    System.setErr(originalStdErr);
+                    stdOutPrintStream = null;
+                    stdErrPrintStream = null;
+                    stdoutContent = null;
+                    stderrContent = null;
+                }
+            }
+        };
+    }
+
+    public PrintStream getStdOutPrintStream() {
+        return stdOutPrintStream;
+    }
+
+    public PrintStream getStdErrPrintStream() {
+        return stdErrPrintStream;
+    }
+
+    public String getStdErr() {
+        return stderrContent.toString();
+    }
+
+    public String getStdOut() {
+        return stdoutContent.toString();
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/ReflectionEqualsMatcher.java b/subprojects/gradle-core/src/test/groovy/org/gradle/util/ReflectionEqualsMatcher.java
new file mode 100644
index 0000000..db84653
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/ReflectionEqualsMatcher.java
@@ -0,0 +1,39 @@
+/*
+ * 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.apache.commons.lang.builder.EqualsBuilder;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+
+/**
+ * @author Hans Dockter
+*/
+public class ReflectionEqualsMatcher<T> extends BaseMatcher<T> {
+    private T toMatch;
+
+    ReflectionEqualsMatcher(T toMatch) {
+        this.toMatch = toMatch;
+    }
+
+    public boolean matches(Object o) {
+        return EqualsBuilder.reflectionEquals(toMatch, o);
+    }
+
+    public void describeTo(Description description) {
+        description.appendText("an Object with the same fields as " + toMatch);
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/Resources.java b/subprojects/gradle-core/src/test/groovy/org/gradle/util/Resources.java
new file mode 100644
index 0000000..57ca170
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/Resources.java
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import java.io.File;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import static org.junit.Assert.*;
+
+/**
+ * A JUnit rule which helps locate test resources.
+ */
+public class Resources implements MethodRule {
+    private Class<?> testClass;
+
+    /**
+     * Locates the resource with the given name, relative to the current test class. Asserts that the resource exists.
+     */
+    public TestFile getResource(String name) {
+        assertNotNull(testClass);
+        TestFile file = findResource(name);
+        assertNotNull(String.format("Could not locate resource '%s' for test class %s.", name, testClass.getName()), file);
+        return file;
+    }
+
+    /**
+     * Locates the resource with the given name, relative to the current test class.
+     * @return the resource, or null if not found.
+     */
+    public TestFile findResource(String name) {
+        assertNotNull(testClass);
+        URL resource = testClass.getResource(name);
+        if (resource == null) {
+            return null;
+        }
+        assertEquals("file", resource.getProtocol());
+        File file;
+        try {
+            file = new File(resource.toURI());
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e);
+        }
+        return new TestFile(file);
+    }
+
+    public Statement apply(final Statement statement, FrameworkMethod frameworkMethod, Object o) {
+        testClass = frameworkMethod.getMethod().getDeclaringClass();
+        return statement;
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/SetSystemProperties.java b/subprojects/gradle-core/src/test/groovy/org/gradle/util/SetSystemProperties.java
new file mode 100644
index 0000000..46dbdf6
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/SetSystemProperties.java
@@ -0,0 +1,57 @@
+/*
+ * 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.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * A JUnit rule which restores system properties at the end of the test.
+ */
+public class SetSystemProperties implements MethodRule {
+    private final Properties properties;
+    private final Map<String, Object> customProperties = new HashMap<String, Object>();
+
+    public SetSystemProperties() {
+        properties = new Properties();
+        properties.putAll(System.getProperties());
+    }
+
+    public SetSystemProperties(Map<String, Object> properties) {
+        this();
+        customProperties.putAll(properties);
+    }
+
+    public Statement apply(final Statement base, FrameworkMethod method, Object target) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                System.getProperties().putAll(customProperties);
+                try {
+                    base.evaluate();
+                } finally {
+                    System.setProperties(properties);
+                }
+            }
+        };
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/TemporaryFolder.java b/subprojects/gradle-core/src/test/groovy/org/gradle/util/TemporaryFolder.java
new file mode 100644
index 0000000..15f9774
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/TemporaryFolder.java
@@ -0,0 +1,107 @@
+/*
+ * 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.apache.commons.lang.StringUtils;
+import org.junit.rules.MethodRule;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.Statement;
+
+import java.io.File;
+
+/**
+ * A JUnit rule which provides a unique temporary folder for the test.
+ */
+public class TemporaryFolder implements MethodRule, TestFileContext {
+    private TestFile dir;
+    private String prefix;
+    private static TestFile root;
+
+    static {
+        root = new TestFile(new File("build/tmp/tests"));
+    }
+
+    public TestFile getDir() {
+        if (dir == null) {
+            if (prefix == null) {
+                // This can happen if this is used in a constructor or a @Before method. It also happens when using
+                // @RunWith(SomeRunner) when the runner does not support rules.
+                prefix = determinePrefix();
+            }
+            for (int counter = 1; true; counter++) {
+                dir = root.file(counter == 1 ? prefix : String.format("%s%d", prefix, counter));
+                if (dir.mkdirs()) {
+                    break;
+                }
+            }
+        }
+        return dir;
+    }
+
+    private String determinePrefix() {
+        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
+        for (StackTraceElement element : stackTrace) {
+            if (element.getClassName().endsWith("Test")) {
+                return StringUtils.substringAfterLast(element.getClassName(), ".") + "/unknown-test";
+            }
+        }
+        return "unknown-test-class";
+    }
+
+    public Statement apply(final Statement base, final FrameworkMethod method, final Object target) {
+        init(method, target);
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                base.evaluate();
+                getDir().maybeDeleteDir();
+                // Don't delete on failure
+            }
+        };
+    }
+
+    private void init(FrameworkMethod method, Object target) {
+        if (prefix == null) {
+            prefix = String.format("%s/%s", target.getClass().getSimpleName(), method.getName());
+        }
+    }
+
+    public static TemporaryFolder newInstance() {
+        return new TemporaryFolder();
+    }
+
+    public static TemporaryFolder newInstance(FrameworkMethod method, Object target) {
+        TemporaryFolder temporaryFolder = new TemporaryFolder();
+        temporaryFolder.init(method, target);
+        return temporaryFolder;
+    }
+
+    public TestFile getTestDir() {
+        return getDir();
+    }
+
+    public TestFile file(Object... path) {
+        return getDir().file((Object[]) path);
+    }
+
+    public TestFile createFile(Object... path) {
+        return file((Object[]) path).createFile();
+    }
+
+    public TestFile createDir(Object... path) {
+        return file((Object[]) path).createDir();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/TestDirHelper.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/util/TestDirHelper.groovy
new file mode 100644
index 0000000..a46ac9f
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/TestDirHelper.groovy
@@ -0,0 +1,45 @@
+/*
+ * 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
+
+class TestDirHelper {
+    def TestFile baseDir
+
+    def TestDirHelper(TestFile baseDir) {
+        this.baseDir = baseDir
+    }
+
+    def apply(Closure cl) {
+        cl.delegate = this
+        cl.resolveStrategy = Closure.DELEGATE_FIRST
+        cl()
+    }
+
+    def file(String name) {
+        TestFile file = baseDir.file(name)
+        file.write('some content')
+        file
+    }
+
+    def methodMissing(String name, Object args) {
+        if (args.length == 1 && args[0] instanceof Closure) {
+            baseDir.file(name).create(args[0])
+        }
+        else {
+            throw new MissingMethodException(name, getClass(), args)
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/TestFile.java b/subprojects/gradle-core/src/test/groovy/org/gradle/util/TestFile.java
new file mode 100644
index 0000000..08660ad
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/TestFile.java
@@ -0,0 +1,439 @@
+/*
+ * 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 groovy.lang.Closure;
+import org.apache.commons.io.FileUtils;
+import org.apache.tools.ant.taskdefs.Tar;
+import org.apache.tools.ant.taskdefs.Zip;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.file.DeleteAction;
+import org.gradle.api.internal.file.IdentityFileResolver;
+import org.gradle.api.internal.file.copy.DeleteActionImpl;
+import org.hamcrest.Matcher;
+
+import java.io.*;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.*;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+import static org.junit.Assert.*;
+
+public class TestFile extends File implements TestFileContext {
+    private boolean useNativeTools;
+
+    public TestFile(File file, Object... path) {
+        super(join(file, path).getAbsolutePath());
+    }
+
+    public TestFile(URI uri) {
+        this(new File(uri));
+    }
+
+    public TestFile(String path) {
+        this(new File(path));
+    }
+
+    public TestFile(URL url) {
+        this(toUri(url));
+    }
+
+    public TestFile getTestDir() {
+        return this;
+    }
+
+    public TestFile usingNativeTools() {
+        useNativeTools = true;
+        return this;
+    }
+
+    private static URI toUri(URL url) {
+        try {
+            return url.toURI();
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static File join(File file, Object[] path) {
+        File current = file.getAbsoluteFile();
+        for (Object p : path) {
+            current = new File(current, p.toString());
+        }
+        return GFileUtils.canonicalise(current);
+    }
+
+    public TestFile file(Object... path) {
+        return new TestFile(this, path);
+    }
+
+    public List<TestFile> files(Object... paths) {
+        List<TestFile> files = new ArrayList<TestFile>();
+        for (Object path : paths) {
+            files.add(file(path));
+        }
+        return files;
+    }
+
+    public TestFile writelns(String... lines) {
+        return writelns(Arrays.asList(lines));
+    }
+
+    public TestFile write(Object content) {
+        try {
+            FileUtils.writeStringToFile(this, content.toString());
+        } catch (IOException e) {
+            throw new UncheckedIOException(String.format("Could not write to test file '%s'", this), e);
+        }
+        return this;
+    }
+
+    public TestFile leftShift(Object content) {
+        getParentFile().mkdirs();
+        return write(content);
+    }
+
+    public String getText() {
+        assertIsFile();
+        try {
+            return FileUtils.readFileToString(this);
+        } catch (IOException e) {
+            throw new UncheckedIOException(String.format("Could not read from test file '%s'", this), e);
+        }
+    }
+
+    public Map<String, String> getProperties() {
+        assertIsFile();
+        Properties properties = new Properties();
+        try {
+            FileInputStream inStream = new FileInputStream(this);
+            try {
+                properties.load(inStream);
+            } finally {
+                inStream.close();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        Map<String, String> map = new HashMap<String, String>();
+        for (Object key : properties.keySet()) {
+            map.put(key.toString(), properties.getProperty(key.toString()));
+        }
+        return map;
+    }
+
+    public Manifest getManifest() {
+        assertIsFile();
+        try {
+            JarFile jarFile = new JarFile(this);
+            try {
+                return jarFile.getManifest();
+            } finally {
+                jarFile.close();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public List<String> linesThat(Matcher<? super String> matcher) {
+        try {
+            BufferedReader reader = new BufferedReader(new FileReader(this));
+            try {
+                List<String> lines = new ArrayList<String>();
+                String line;
+                while ((line = reader.readLine()) != null) {
+                    if (matcher.matches(line)) {
+                        lines.add(line);
+                    }
+                }
+                return lines;
+            } finally {
+                reader.close();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public void unzipTo(File target) {
+        assertIsFile();
+        new TestFileHelper(this).unzipTo(target, useNativeTools);
+    }
+
+    public void untarTo(File target) {
+        assertIsFile();
+
+        new TestFileHelper(this).untarTo(target, useNativeTools);
+    }
+
+    public void copyTo(File target) {
+        if (isDirectory()) {
+            try {
+                FileUtils.copyDirectory(this, target);
+            } catch (IOException e) {
+                throw new UncheckedIOException(String.format("Could not copy test directory '%s' to '%s'", this,
+                        target), e);
+            }
+        } else {
+            try {
+                FileUtils.copyFile(this, target);
+            } catch (IOException e) {
+                throw new UncheckedIOException(String.format("Could not copy test file '%s' to '%s'", this, target), e);
+            }
+        }
+    }
+
+    public void copyFrom(File target) {
+        new TestFile(target).copyTo(this);
+    }
+    
+    public void copyFrom(URL resource) {
+        try {
+            FileUtils.copyURLToFile(resource, this);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public TestFile linkTo(File target) {
+        getParentFile().createDir();
+        int retval = PosixUtil.current().symlink(target.getAbsolutePath(), getAbsolutePath());
+        if (retval != 0) {
+            throw new UncheckedIOException(String.format("Could not create link from '%s' to '%s'", target, this));
+        }
+        return this;
+    }
+
+    public TestFile touch() {
+        try {
+            FileUtils.touch(this);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        assertIsFile();
+        return this;
+    }
+
+    /**
+     * Creates a directory structure specified by the given closure.
+     * <pre>
+     * dir.create {
+     *     subdir1 {
+     *        file 'somefile.txt'
+     *     }
+     *     subdir2 { nested { file 'someFile' } }
+     * }
+     * </pre>
+     */
+    public TestFile create(Closure structure) {
+        assertTrue(isDirectory() || mkdirs());
+        new TestDirHelper(this).apply(structure);
+        return this;
+    }
+
+    @Override
+    public TestFile getParentFile() {
+        return new TestFile(super.getParentFile());
+    }
+
+    @Override
+    public String toString() {
+        return getPath();
+    }
+
+    public TestFile writelns(Iterable<String> lines) {
+        Formatter formatter = new Formatter();
+        for (String line : lines) {
+            formatter.format("%s%n", line);
+        }
+        return write(formatter);
+    }
+
+    public TestFile assertExists() {
+        assertTrue(String.format("%s does not exist", this), exists());
+        return this;
+    }
+
+    public TestFile assertIsFile() {
+        assertTrue(String.format("%s is not a file", this), isFile());
+        return this;
+    }
+
+    public TestFile assertIsDir() {
+        assertTrue(String.format("%s is not a directory", this), isDirectory());
+        return this;
+    }
+
+    public TestFile assertDoesNotExist() {
+        assertFalse(String.format("%s should not exist", this), exists());
+        return this;
+    }
+
+    public TestFile assertContents(Matcher<String> matcher) {
+        assertThat(getText(), matcher);
+        return this;
+    }
+
+    public TestFile assertPermissions(Matcher<String> matcher) {
+        if (OperatingSystem.current().isUnix()) {
+            assertThat(String.format("mismatched permissions for '%s'", this), getPermissions(), matcher);
+        }
+        return this;
+    }
+
+    private String getPermissions() {
+        assertExists();
+        return new TestFileHelper(this).getPermissions();
+    }
+
+    public TestFile setPermissions(String permissions) {
+        assertExists();
+        new TestFileHelper(this).setPermissions(permissions);
+        return this;
+    }
+
+    /**
+     * Asserts that this file contains exactly the given set of descendants.
+     */
+    public TestFile assertHasDescendants(String... descendants) {
+        Set<String> actual = new TreeSet<String>();
+        assertIsDir();
+        visit(actual, "", this);
+        Set<String> expected = new TreeSet<String>(Arrays.asList(descendants));
+
+        Set<String> extras = new TreeSet<String>(actual);
+        extras.removeAll(expected);
+        Set<String> missing = new TreeSet<String>(expected);
+        missing.removeAll(actual);
+
+        assertEquals(String.format("Extra files: %s, missing files: %s, expected: %s", extras, missing, expected), expected, actual);
+
+        return this;
+    }
+
+    private void visit(Set<String> names, String prefix, File file) {
+        for (File child : file.listFiles()) {
+            if (child.isFile()) {
+                names.add(prefix + child.getName());
+            } else if (child.isDirectory()) {
+                visit(names, prefix + child.getName() + "/", child);
+            }
+        }
+    }
+
+    public boolean isSelfOrDescendent(File file) {
+        if (file.getAbsolutePath().equals(getAbsolutePath())) {
+            return true;
+        }
+        return file.getAbsolutePath().startsWith(getAbsolutePath() + File.separatorChar);
+    }
+
+    public TestFile createDir() {
+        assertTrue(isDirectory() || mkdirs());
+        return this;
+    }
+
+    public TestFile deleteDir() {
+        DeleteAction delete = new DeleteActionImpl(new IdentityFileResolver());
+        delete.delete(this);
+        return this;
+    }
+
+    /**
+     * Attempts to delete this directory, ignoring failures to do so.
+     * @return this
+     */
+    public TestFile maybeDeleteDir() {
+        try {
+            deleteDir();
+        } catch (UncheckedIOException e) {
+            // Ignore
+        }
+        return this;
+    }
+
+    public TestFile createFile() {
+        new TestFile(getParentFile()).createDir();
+        try {
+            assertTrue(isFile() || createNewFile());
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        return this;
+    }
+
+    public TestFile zipTo(TestFile zipFile) {
+        Zip zip = new Zip();
+        zip.setBasedir(this);
+        zip.setDestFile(zipFile);
+        AntUtil.execute(zip);
+        return this;
+    }
+
+    public TestFile tarTo(TestFile zipFile) {
+        Tar tar = new Tar();
+        tar.setBasedir(this);
+        tar.setDestFile(zipFile);
+        AntUtil.execute(tar);
+        return this;
+    }
+
+    public Snapshot snapshot() {
+        assertIsFile();
+        return new Snapshot();
+    }
+
+    public void assertHasChangedSince(Snapshot snapshot) {
+        Snapshot now = snapshot();
+        assertTrue(now.modTime != snapshot.modTime || !Arrays.equals(now.hash, snapshot.hash));
+    }
+
+    public void assertHasNotChangedSince(Snapshot snapshot) {
+        Snapshot now = snapshot();
+        assertEquals(String.format("last modified time of %s has changed", this), snapshot.modTime, now.modTime);
+        assertArrayEquals(String.format("contents of %s has changed", this), snapshot.hash, now.hash);
+    }
+
+    public void writeProperties(Map<?, ?> properties) {
+        Properties props = new Properties();
+        props.putAll(properties);
+        try {
+            FileOutputStream stream = new FileOutputStream(this);
+            try {
+                props.store(stream, "comment");
+            } finally {
+                stream.close();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public class Snapshot {
+        private final long modTime;
+        private final byte[] hash;
+
+        public Snapshot() {
+            modTime = lastModified();
+            hash = HashUtil.createHash(TestFile.this);
+        }
+    }
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/TestFileContext.java b/subprojects/gradle-core/src/test/groovy/org/gradle/util/TestFileContext.java
new file mode 100644
index 0000000..1e657e7
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/TestFileContext.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+public interface TestFileContext {
+    TestFile getTestDir();
+
+    TestFile file(Object... path);
+}
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/TestFileHelper.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/util/TestFileHelper.groovy
new file mode 100644
index 0000000..128d661
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/TestFileHelper.groovy
@@ -0,0 +1,116 @@
+/*
+ * 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.zip.ZipEntry
+import java.util.zip.ZipInputStream
+import org.apache.commons.lang.StringUtils
+import org.apache.tools.ant.taskdefs.Expand
+import org.apache.tools.ant.taskdefs.Untar
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.jruby.ext.posix.FileStat
+import org.gradle.api.UncheckedIOException
+
+class TestFileHelper {
+    TestFile file
+
+    public TestFileHelper(TestFile file) {
+        this.file = file
+    }
+
+    public void unzipTo(File target, boolean nativeTools) {
+        // Check that each directory in hierarchy is present
+        file.withInputStream {InputStream instr ->
+            Set<String> dirs = [] as Set
+            ZipInputStream zipStr = new ZipInputStream(instr)
+            ZipEntry entry
+            while (entry = zipStr.getNextEntry()) {
+                if (entry.isDirectory()) {
+                    assertTrue("Duplicate directory '$entry.name'", dirs.add(entry.name))
+                }
+                if (!entry.name.contains('/')) {
+                    continue
+                }
+                String parent = StringUtils.substringBeforeLast(entry.name, '/') + '/'
+                assertTrue("Missing dir '$parent'", dirs.contains(parent))
+            }
+        }
+
+        if (nativeTools && OperatingSystem.current().isUnix()) {
+            Process process = ['unzip', '-o', file.absolutePath, '-d', target.absolutePath].execute()
+            process.consumeProcessOutput(System.out, System.err)
+            assertThat(process.waitFor(), equalTo(0))
+            return
+        }
+
+        Expand unzip = new Expand();
+        unzip.src = file;
+        unzip.dest = target;
+        AntUtil.execute(unzip);
+    }
+
+    public void untarTo(File target, boolean nativeTools) {
+        if (nativeTools && OperatingSystem.current().isUnix()) {
+            target.mkdirs()
+            ProcessBuilder builder = new ProcessBuilder(['tar', '-xf', file.absolutePath])
+            builder.directory(target)
+            Process process = builder.start()
+            process.consumeProcessOutput()
+            assertThat(process.waitFor(), equalTo(0))
+            return
+        }
+
+        Untar untar = new Untar();
+        untar.setSrc(file);
+        untar.setDest(target);
+
+        if (file.getName().endsWith(".tgz")) {
+            Untar.UntarCompressionMethod method = new Untar.UntarCompressionMethod();
+            method.setValue("gzip");
+            untar.setCompression(method);
+        } else if (file.getName().endsWith(".tbz2")) {
+            Untar.UntarCompressionMethod method = new Untar.UntarCompressionMethod();
+            method.setValue("bzip2");
+            untar.setCompression(method);
+        }
+
+        AntUtil.execute(untar);
+    }
+
+    public String getPermissions() {
+        FileStat stat = PosixUtil.current().stat(file.absolutePath)
+        [6, 3, 0].collect {
+            def m = stat.mode() >> it
+            [m & 4 ? 'r' : '-', m & 2 ? 'w' : '-', m & 1 ? 'x' : '-']
+        }.flatten().join('')
+    }
+
+    def setPermissions(String permissions) {
+        def m = [6, 3, 0].inject(0) { mode, pos ->
+            mode |= permissions[9 - pos - 3] == 'r' ? 4 << pos : 0
+            mode |= permissions[9 - pos - 2] == 'w' ? 2 << pos : 0
+            mode |= permissions[9 - pos - 1] == 'x' ? 1 << pos : 0
+            return mode
+        }
+        int retval = PosixUtil.current().chmod(file.absolutePath, m)
+        if (retval != 0) {
+            throw new UncheckedIOException("Could not set permissions of '${file}' to '${permissions}'.")
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/groovy/org/gradle/util/TestTask.groovy b/subprojects/gradle-core/src/test/groovy/org/gradle/util/TestTask.groovy
new file mode 100644
index 0000000..eadda17
--- /dev/null
+++ b/subprojects/gradle-core/src/test/groovy/org/gradle/util/TestTask.groovy
@@ -0,0 +1,30 @@
+/*
+ * 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.util
+
+import org.gradle.api.internal.ConventionTask
+
+/**
+ * @author Hans Dockter
+ */
+class TestTask extends ConventionTask  {
+    TestTask self
+    String customProp
+    List list1
+    List list2
+    Map map1
+}
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/resources/org/gradle/api/tasks/ide/eclipse/expectedClasspathFile.txt b/subprojects/gradle-core/src/test/resources/org/gradle/api/tasks/ide/eclipse/expectedClasspathFile.txt
new file mode 100644
index 0000000..99e301d
--- /dev/null
+++ b/subprojects/gradle-core/src/test/resources/org/gradle/api/tasks/ide/eclipse/expectedClasspathFile.txt
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<classpath>
+  <classpathentry kind="src" path="src/main/java"/>
+  <classpathentry kind="src" path="src/main/resources"/>
+  <classpathentry kind="output" path="bin"/>
+  <classpathentry kind="src" path="src/test/java" output="testbin"/>
+  <classpathentry kind="src" path="src/test/resources" output="testbin"/>
+  <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+  <classpathentry kind="src" path="/test" combineaccessrules="false"/>
+  <classpathentry kind="lib" path="lib/a.jar"/>
+  <classpathentry kind="lib" path="lib/b.jar"/>
+</classpath>
diff --git a/subprojects/gradle-core/src/test/resources/org/gradle/api/tasks/ide/eclipse/expectedEmptyProjectFile.txt b/subprojects/gradle-core/src/test/resources/org/gradle/api/tasks/ide/eclipse/expectedEmptyProjectFile.txt
new file mode 100644
index 0000000..3fd0a0b
--- /dev/null
+++ b/subprojects/gradle-core/src/test/resources/org/gradle/api/tasks/ide/eclipse/expectedEmptyProjectFile.txt
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<projectDescription>
+  <name>myProject</name>
+  <comment/>
+  <projects/>
+  <natures/>
+  <buildSpec/>
+</projectDescription>
diff --git a/subprojects/gradle-core/src/test/resources/org/gradle/api/tasks/ide/eclipse/expectedProjectFileWithCustomBuilder.txt b/subprojects/gradle-core/src/test/resources/org/gradle/api/tasks/ide/eclipse/expectedProjectFileWithCustomBuilder.txt
new file mode 100644
index 0000000..6f38240
--- /dev/null
+++ b/subprojects/gradle-core/src/test/resources/org/gradle/api/tasks/ide/eclipse/expectedProjectFileWithCustomBuilder.txt
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<projectDescription>
+  <name>myProject</name>
+  <comment/>
+  <projects/>
+  <natures/>
+  <buildSpec>
+    <buildCommand>
+      <name>org.gradle.test.custom.custombuilder1</name>
+      <arguments/>
+    </buildCommand>
+    <buildCommand>
+      <name>org.gradle.test.custom.custombuilder2</name>
+      <arguments/>
+    </buildCommand>
+  </buildSpec>
+</projectDescription>
diff --git a/subprojects/gradle-core/src/test/resources/org/gradle/api/tasks/ide/eclipse/expectedProjectFileWithCustomNature.txt b/subprojects/gradle-core/src/test/resources/org/gradle/api/tasks/ide/eclipse/expectedProjectFileWithCustomNature.txt
new file mode 100644
index 0000000..b869311
--- /dev/null
+++ b/subprojects/gradle-core/src/test/resources/org/gradle/api/tasks/ide/eclipse/expectedProjectFileWithCustomNature.txt
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<projectDescription>
+  <name>myProject</name>
+  <comment/>
+  <projects/>
+  <natures>
+    <nature>org.gradle.test.natures.CustomNature1</nature>
+    <nature>org.gradle.test.natures.CustomNature2</nature>
+  </natures>
+  <buildSpec/>
+</projectDescription>
diff --git a/subprojects/gradle-core/src/test/resources/org/gradle/api/tasks/ide/eclipse/expectedWtpFile.txt b/subprojects/gradle-core/src/test/resources/org/gradle/api/tasks/ide/eclipse/expectedWtpFile.txt
new file mode 100644
index 0000000..331ad31
--- /dev/null
+++ b/subprojects/gradle-core/src/test/resources/org/gradle/api/tasks/ide/eclipse/expectedWtpFile.txt
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project-modules id="moduleCoreId" project-version="1.5.0">
+  <wb-module deploy-name="name">
+    <property name="context-root" value="name"/>
+    <wb-resource deploy-path="WEB-INF/lib" source-path="conf1"/>
+    <wb-resource deploy-path="WEB-INF/lib" source-path="conf2/child"/>
+    <property name="java-output-path" value="bin"/>
+    <dependent-module deploy-path="/WEB-INF/lib" handle="module:/classpath/lib//lib/a.jar">
+      <dependency-type>uses</dependency-type>
+    </dependent-module>
+    <dependent-module deploy-path="/WEB-INF/lib" handle="module:/classpath/lib//lib/b.jar">
+      <dependency-type>uses</dependency-type>
+    </dependent-module>
+    <dependent-module deploy-path="/WEB-INF/lib" handle="module:/resource/test/test">
+      <dependency-type>uses</dependency-type>
+    </dependent-module>
+  </wb-module>
+</project-modules>
diff --git a/subprojects/gradle-core/src/test/resources/org/gradle/testfixtures/ProjectBuilderTest.gradle b/subprojects/gradle-core/src/test/resources/org/gradle/testfixtures/ProjectBuilderTest.gradle
new file mode 100644
index 0000000..443aab0
--- /dev/null
+++ b/subprojects/gradle-core/src/test/resources/org/gradle/testfixtures/ProjectBuilderTest.gradle
@@ -0,0 +1 @@
+task hello << { println 'hello' }
\ No newline at end of file
diff --git a/subprojects/gradle-core/src/test/resources/org/gradle/util/ClassLoaderTest.txt b/subprojects/gradle-core/src/test/resources/org/gradle/util/ClassLoaderTest.txt
new file mode 100644
index 0000000..48f587a
--- /dev/null
+++ b/subprojects/gradle-core/src/test/resources/org/gradle/util/ClassLoaderTest.txt
@@ -0,0 +1 @@
+a resource
\ No newline at end of file
diff --git a/subprojects/gradle-docs/docs.gradle b/subprojects/gradle-docs/docs.gradle
new file mode 100644
index 0000000..78c73cb
--- /dev/null
+++ b/subprojects/gradle-docs/docs.gradle
@@ -0,0 +1,397 @@
+
+/*
+ * 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.
+ */
+import org.gradle.build.docs.UserGuideTransformTask
+import org.gradle.build.docs.ExtractSnippetsTask
+import org.gradle.build.docs.AssembleSamplesDocTask
+
+apply plugin: 'base'
+
+configurations {
+    ftpAntTask
+    userGuideStyleSheets
+    userGuideTask
+}
+
+dependencies {
+    ftpAntTask module("org.apache.ant:ant-commons-net:1.7.0") {
+        module("commons-net:commons-net:1.4.1") {
+            dependencies("oro:oro:2.0.8 at jar")
+        }
+    }
+
+    userGuideTask 'xalan:xalan:2.7.1', 'xerces:xercesImpl:2.9.1'
+    userGuideTask module('xhtmlrenderer:xhtmlrenderer:R8rc1') {
+        dependency 'itext:itext:2.0.8 at jar'
+    }
+    userGuideTask 'xslthl:xslthl:2.0.1 at jar'
+
+    userGuideStyleSheets 'docbook:docbook-xsl:1.75.2 at zip'
+}
+
+RemoteLocations remoteLocations = new RemoteLocations(version: version)
+
+srcDocsDir = file('src/docs')
+userguideSrcDir = new File(srcDocsDir, 'userguide')
+cssSrcDir = new File(srcDocsDir, 'css')
+
+docsDir = file("$buildDir/docs")
+userguideDir = new File(docsDir, 'userguide')
+distDocsDir = new File(buildDir, 'distDocs')
+samplesDir = file("$buildDir/samples")
+docbookSrc = new File(project.buildDir, 'src/docbook')
+samplesSrcDir = file('src/samples')
+
+tasks.withType(Docbook2Xhtml).allObjects { task->
+    task.dependsOn userguideStyleSheets
+    task.classpath = configurations.userGuideTask
+    task.stylesheetsDir = userguideStyleSheets.destinationDir
+}
+tasks.withType(UserGuideTransformTask).allObjects { task->
+    task.classpath = configurations.userGuideTask
+    task.dependsOn samples
+    task.snippetsDir = samples.snippetsDir
+}
+
+task samples(type: ExtractSnippetsTask) {
+    source samplesSrcDir
+    exclude 'userguideOutput/**'
+    exclude 'userguide/tutorial/antChecksumFiles/**'
+    exclude '**/readme.xml'
+    destDir = samplesDir
+    snippetsDir = new File(buildDir, 'snippets')
+    doLast {
+        copy {
+            from samplesSrcDir
+            into samplesDir
+            include 'userguide/tutorial/antChecksumFiles/**'
+        }
+    }
+}
+
+task userguideStyleSheets(type: Copy) {
+    File stylesheetsDir = new File(srcDocsDir, 'stylesheets')
+    into new File(buildDir, 'stylesheets')
+    from(stylesheetsDir) {
+        include '*.xsl'
+    }
+    from(cssSrcDir) {
+        include '*.css'
+    }
+    from(zipTree(configurations.userGuideStyleSheets.singleFile)) {
+        // Remove the prefix
+        eachFile { fcd -> fcd.path = fcd.path.replaceFirst('^docbook-xsl-[0-9\\.]+/', '') }
+    }
+}
+
+task samplesDocbook(type: AssembleSamplesDocTask) {
+    source samplesSrcDir
+    include '**/readme.xml'
+    destFile = new File(docbookSrc, 'samplesList.xml')
+}
+
+task samplesDocs(type: Docbook2Xhtml, dependsOn: samplesDocbook) {
+    source samplesDocbook.destFile
+    destFile = new File(samples.destDir, 'readme.html')
+    stylesheetName = 'standaloneHtml.xsl'
+}
+
+task userguideDocbook(type: UserGuideTransformTask, dependsOn: [samples, samplesDocbook]) {
+    inputs.files fileTree(dir: userguideSrcDir, includes: ['*.xml'])
+    sourceFile = new File(userguideSrcDir, 'userguide.xml')
+    destFile = new File(docbookSrc, 'userguide.xml')
+    javadocUrl = '../javadoc'
+    groovydocUrl = '../groovydoc'
+    websiteUrl = 'http://www.gradle.org'
+}
+
+task remoteUserguideDocbook(type: UserGuideTransformTask, dependsOn: samples) {
+    sourceFile = new File(userguideSrcDir, 'userguide.xml')
+    destFile = new File(docbookSrc, 'remoteUserguide.xml')
+    doFirst {
+        javadocUrl = remoteLocations.javadocUrl
+        groovydocUrl = remoteLocations.groovydocUrl
+        websiteUrl = 'http://www.gradle.org'
+    }
+}
+
+task userguideHtml(type: Docbook2Xhtml, dependsOn: userguideDocbook) {
+    source userguideDocbook.destFile
+    destDir = userguideDir
+    stylesheetName = 'userGuideHtml.xsl'
+    resources = fileTree {
+        from userguideSrcDir
+        include 'img/*.png'
+    }
+    resources += fileTree {
+        from cssSrcDir
+        include '*.css'
+    }
+}
+
+task userguideSingleHtml(type: Docbook2Xhtml, dependsOn: userguideDocbook) {
+    source userguideDocbook.destFile
+    destFile = new File(userguideDir, 'userguide_single.html')
+    stylesheetName = 'userGuideSingleHtml.xsl'
+    resources = fileTree {
+        from userguideSrcDir
+        include 'img/*.png'
+    }
+    resources += fileTree {
+        from cssSrcDir
+        include '*.css'
+    }
+}
+
+task userguideXhtml(type: Docbook2Xhtml, dependsOn: remoteUserguideDocbook) {
+    source remoteUserguideDocbook.destFile
+    destFile = new File(buildDir, 'tmp/userguidePdf/userguidePdf.html')
+    stylesheetName = 'userGuidePdf.xsl'
+    resources = fileTree {
+        from userguideSrcDir
+        include 'img/*.png'
+    }
+    resources += fileTree {
+        from cssSrcDir
+        include '*.css'
+    }
+}
+
+task userguidePdf(type: Xhtml2Pdf, dependsOn: userguideXhtml) {
+    sourceFile = userguideXhtml.destFile
+    destFile = new File(userguideDir, 'userguide.pdf')
+    classpath = configurations.userGuideTask
+}
+
+task javadoc(type: Javadoc) {
+    source groovyProjects().collect {project -> project.sourceSets.main.allJava }
+    destinationDir = new File(docsDir, 'javadoc')
+    classpath = files(groovyProjects().collect {project -> [project.sourceSets.main.compileClasspath, project.sourceSets.main.classes] })
+    include 'org/gradle/api/**'
+    include 'org/gradle/*'
+    include 'org/gradle/external/javadoc/**'
+    include 'org/gradle/process/**'
+    include 'org/gradle/plugins/**'
+    include 'org/gradle/testfixtures/**'
+    exclude '**/internal/**'
+    options.links("http://java.sun.com/j2se/1.5.0/docs/api", "http://groovy.codehaus.org/gapi/", "http://maven.apache.org/ref/2.2.1/maven-core/apidocs",
+        "http://maven.apache.org/ref/2.2.1/maven-model/apidocs")
+    doFirst {
+        title = "Gradle API $version"
+    }
+}
+
+task groovydoc(type: Groovydoc) {
+    source groovyProjects().collect {project -> project.sourceSets.main.groovy }
+    destinationDir = new File(docsDir, 'groovydoc')
+    includes = javadoc.includes
+    excludes = javadoc.excludes
+    doFirst {
+        title = "Gradle API $version"
+    }
+    groovyClasspath = project(':core').configurations.groovy
+}
+
+task userguideFragmentSrc(type: UserGuideTransformTask, dependsOn: [userguideStyleSheets, samples]) {
+    tags << 'standalone'
+    sourceFile = new File(userguideSrcDir, 'installation.xml')
+    destFile = new File(docbookSrc, 'installation.xml')
+    doFirst {
+        javadocUrl = remoteLocations.javadocUrl
+        groovydocUrl = remoteLocations.groovydocUrl
+        websiteUrl = 'http://www.gradle.org'
+    }
+}
+
+task distDocs(type: Docbook2Xhtml, dependsOn: userguideFragmentSrc) {
+    source userguideFragmentSrc.destFile
+    destFile = new File(distDocsDir, 'getting-started.html')
+    stylesheetName = 'standaloneHtml.xsl'
+}
+
+task websiteDocsSrc(type: UserGuideTransformTask, dependsOn: [userguideStyleSheets, samples, samplesDocbook]) {
+    inputs.files fileTree(dir: userguideSrcDir, includes: ['*.xml'])
+    sourceFile = new File(userguideSrcDir, 'userguide.xml')
+    destFile = new File(docbookSrc, 'website.xml')
+    tags << 'website'
+    doFirst {
+        javadocUrl = remoteLocations.javadocUrl
+        groovydocUrl = remoteLocations.groovydocUrl
+        websiteUrl = ''
+    }
+}
+
+task websiteDocs(type: Docbook2Xhtml, dependsOn: websiteDocsSrc) {
+    File websiteDocs = new File(buildDir, 'websiteDocs')
+    source websiteDocsSrc.destFile
+    destFile = new File(websiteDocs, 'website.html')
+    stylesheetName = 'websiteHtml.xsl'
+    resources = fileTree {
+        from userguideSrcDir
+        include 'img/*.png'
+    }
+    resources += fileTree {
+        from cssSrcDir
+        include '*.css'
+    }
+}
+
+task userguide(dependsOn: [userguideHtml, userguideSingleHtml, userguidePdf], description: 'Generates the userguide')
+
+task docs(dependsOn: [javadoc, groovydoc, userguide, distDocs, samplesDocs])
+
+task uploadDocs(dependsOn: docs) << {
+    ftp(action: 'mkdir', remotedir: remoteLocations.docsRemoteDir)
+    ftp(action: 'delete', remotedir: remoteLocations.docsRemoteDir) {
+        fileset() {
+            include(name: '**/*')
+        }
+    }
+    ftp(action: 'send', remotedir: remoteLocations.docsRemoteDir) {
+        fileset(dir: docsDir)
+    }
+}
+
+void ftp(Map args, Closure antFileset = {}) {
+    ant {
+        taskdef(name: 'ftp',
+                classname: 'org.apache.tools.ant.taskdefs.optional.net.FTP',
+                classpath: configurations.ftpAntTask.asPath)
+        Map ftpArgs = args + [
+                server: 'ftp.gradle.org',
+                userid: websiteFtpUserName,
+                password: websiteFtpUserPassword
+        ]
+        delegate.ftp(ftpArgs) {
+            antFileset.delegate = delegate
+            antFileset()
+        }
+    }
+}
+
+class RemoteLocations {
+    def version
+
+    static final GRADLE_ORG_URL = "http://www.gradle.org"
+
+    String getDistributionUploadUrl() {
+        version.isRelease() ? 'https://dav.codehaus.org/dist/gradle' : 'https://dav.codehaus.org/snapshots.dist/gradle'
+    }
+
+    String getDocsRemoteDir() {
+        (version.isRelease() ? version.toString() : 'latest') + '/docs'
+    }
+
+    String getJavadocUrl() {
+        "$GRADLE_ORG_URL/${getDocsRemoteDir()}/javadoc"
+    }
+
+    String getGroovydocUrl() {
+        "$GRADLE_ORG_URL/${getDocsRemoteDir()}/groovydoc"
+    }
+}
+
+class Docbook2Xhtml extends SourceTask {
+    @InputFiles
+    FileCollection classpath
+
+    @OutputFile @Optional
+    File destFile
+
+    @OutputDirectory @Optional
+    File destDir
+
+    @InputDirectory
+    File stylesheetsDir
+
+    String stylesheetName
+
+    @InputFiles @Optional
+    FileCollection resources
+
+    @TaskAction
+    def transform() {
+        if (!((destFile != null) ^ (destDir != null))) {
+            throw new InvalidUserDataException("Must specify exactly 1 of output file or dir.")
+        }
+
+        source.visit { FileVisitDetails fvd ->
+            if (fvd.isDirectory()) {
+                return
+            }
+
+            ant.java(classname: 'org.apache.xalan.xslt.Process', failonerror: true, fork: true) {
+                jvmarg(value: '-Xmx256m')
+                arg(value: '-in')
+                arg(value: fvd.file)
+                if (destFile) {
+                    arg(value: '-out')
+                    arg(value: destFile)
+                } else {
+                    arg(value: '-out')
+                    File outFile = fvd.relativePath.replaceLastName(fvd.file.name.replaceAll('.xml$', '.html')).getFile(destDir)
+                    outFile.parentFile.mkdirs()
+                    arg(value: outFile)
+                }
+                arg(value: '-xsl')
+                arg(value: new File(stylesheetsDir, stylesheetName))
+                if (destDir) {
+                    arg(line: "-param base.dir ${destDir}/")
+                }
+                sysproperty(key: 'xslthl.config', value: new File("$stylesheetsDir/highlighting/xslthl-config.xml").toURI())
+                sysproperty(key: 'org.apache.xerces.xni.parser.XMLParserConfiguration', value: 'org.apache.xerces.parsers.XIncludeParserConfiguration')
+                classpath {
+                    path(path: classpath.asPath)
+                    path(location: new File(stylesheetsDir, 'extensions/xalan27.jar'))
+                }
+            }
+        }
+
+        if (resources) {
+            project.copy {
+                into this.destDir ?: destFile.parentFile
+                from resources
+            }
+        }
+    }
+}
+
+class Xhtml2Pdf extends DefaultTask {
+    @InputFile
+    File sourceFile
+
+    @OutputFile
+    File destFile
+
+    @InputFiles
+    FileCollection classpath
+
+    def Xhtml2Pdf() {
+        onlyIf { !System.getProperty("os.name").toLowerCase().contains('windows') }
+    }
+
+    @TaskAction
+    def transform() {
+        def uris = classpath.files.collect {it.toURI().toURL()}
+        def classloader = new URLClassLoader(uris as URL[], getClass().classLoader)
+        def renderer = classloader.loadClass('org.xhtmlrenderer.pdf.ITextRenderer').newInstance()
+        renderer.setDocument(sourceFile)
+        renderer.layout()
+        destFile.withOutputStream {
+            renderer.createPDF(it)
+        }
+    }
+}
diff --git a/subprojects/gradle-docs/src/docs/css/base.css b/subprojects/gradle-docs/src/docs/css/base.css
new file mode 100644
index 0000000..0de2bd2
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/css/base.css
@@ -0,0 +1,246 @@
+/*
+ * 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.
+ */
+
+/*
+ * Basic layout
+ */
+
+html {
+    margin: 0;
+    height: 100%;
+    min-height: 100%
+}
+
+body {
+    margin: 0;
+    padding: 0;
+    background-color: white;
+    line-height: 150%;
+}
+
+body, td, div {
+    font-family: 'DejaVu Sans', 'Lucida Grande', 'Verdana', sans-serif;
+    font-size: 11pt;
+    color: #444444;
+}
+
+div {
+    margin: 0;
+    padding: 0;
+}
+
+a {
+    color: #444444;
+}
+
+a:visited {
+    color: #444444;
+}
+
+h1, h2, h3, h4, h5 {
+    color: #444444;
+    line-height: 150%;
+    font-weight: bold;
+    font-family: 'DejaVu Sans', 'Lucida Grande', helvetica, sans-serif;
+}
+
+.heading {
+    font-family: 'DejaVu Sans', 'Lucida Grande', helvetica, sans-serif;
+}
+
+code, pre {
+    font-family: 'Lucida Console', 'DejaVu Sans Mono', Courier, monospace;
+}
+
+/*
+ * Docbook content
+ */
+
+/*
+ * Lists
+ */
+
+.variablelist dt {
+    font-weight: bold;
+}
+
+.variablelist dt a {
+    font-weight: normal;
+}
+
+/*
+ * Examples, Figures
+ */
+
+pre {
+    background-color: #f5f5f5;
+    padding-top: 1em;
+    padding-bottom: 1em;
+    padding-left: 1.2em;
+    padding-right: 1.2em;
+    margin-top: 0.6em;
+    margin-bottom: 0.6em;
+    line-height: 120%;
+}
+
+.programlisting {
+    border-left: solid #d0d0d0 0.5em;
+    /*overflow-x: auto;*/
+}
+
+.screen {
+    border-left: solid #d0d0d0 0.5em;
+    /*overflow-x: auto;*/
+}
+
+.example, .figure, .table {
+    margin-top: 0;
+    margin-bottom: 1.8em;
+}
+
+.example .title, .figure .title, .table .title {
+    white-space: nowrap;
+    /*overflow-x: hidden;*/
+    margin-top: 0;
+    margin-bottom: 0.6em;
+}
+
+.example pre, .figure pre {
+    margin: 0;
+    margin-bottom: 0.6em;
+}
+
+.example-contents > p {
+    display: block;
+    margin: 0;
+    margin-bottom: 0.2em;
+}
+
+.example-break, .figure-break {
+    display: none
+}
+
+.exampleLocation {
+    margin-top: 0.5em;
+    padding-left: 1.7em;
+    background: #FEFEDD;
+    color: #777744;
+}
+
+.exampleLocation p {
+    padding-top: 0.4em;
+    padding-bottom: 0.4em;
+}
+
+.exampleLocation .emphasis em {
+    font-style: normal;
+    font-weight: bold;
+}
+
+.cmdsynopsis {
+    font-family: Courier, monospace;
+    margin-left: 2em;
+}
+
+/*
+ * Tables
+ */
+
+.table table {
+    border-collapse: collapse;
+    font-size: 100%;
+    min-width: 50%;
+    border: solid #d0d0d0 1px;
+}
+
+.table table td {
+    text-align: left;
+    vertical-align: text-top;
+    padding-left: 0.8em;
+    padding-right: 0.8em;
+    padding-top: 0.3em;
+    padding-bottom: 0.3em;
+}
+
+.table table thead td {
+    font-weight: bold;
+    border-bottom: solid #d0d0d0 1px;
+    background-color: #f2f2f2;
+}
+
+/*
+ * Footnotes, notes, tips
+ */
+
+.footnote sup {
+    vertical-align: baseline;
+    font-size: 100%;
+}
+
+.note, .tip {
+    padding-top: 1em;
+    padding-bottom: 0;
+    padding-left: 1.2em;
+    padding-right: 1.2em;
+    margin-bottom: 1em;
+}
+
+.note {
+    background: #ebf4f7;
+    border: solid #c3d9e6 1px;
+}
+
+.note .title {
+    color: #5283a1;
+}
+
+.tip {
+    background: #FAF7F5;
+    border: solid #e2d2c9 1px;
+    float: right;
+    clear: right;
+    margin-left: 2em;
+    margin-top: 1em;
+    margin-bottom: 1em;
+    max-width: 30%;
+    width: 30%;
+}
+
+.tip .title {
+    color: #80614D;
+}
+
+/* Remove top margins on headings inside notes and tips */
+.tip h1, .note h1
+.tip h2, .note h2,
+.tip h3, .note h3,
+.tip h4, .note h4,
+.tip h5, .note h5,
+.tip h6, .note h6
+{
+   margin-top: 0;
+}
+
+/*
+ * Code highlighting
+ */
+.hl-string, .hl-number, .hl-value { color: #51913f; }
+
+.hl-keyword { font-weight: bold; }
+
+.hl-comment, .hl-doccomment { color: #929292; }
+
+.hl-annotation { color: #777744; }
diff --git a/subprojects/gradle-docs/src/docs/css/print.css b/subprojects/gradle-docs/src/docs/css/print.css
new file mode 100644
index 0000000..0bb46dd
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/css/print.css
@@ -0,0 +1,195 @@
+/*
+ * 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.
+ */
+ at page {
+    size: A4;
+    padding: 0;
+    margin: 2cm;
+}
+
+ at page contentpage {
+    margin-bottom: 2.2cm;
+    @bottom-right {
+        content: 'Page ' counter(page) ' of ' counter(pages);
+        font-family: 'Lucida Grande','Lucida Sans Unicode','Lucida Sans','Geneva','Verdana',sans-serif;
+        font-size: 10pt;
+        color: #777777;
+        padding-bottom: 1.2cm;
+        vertical-align: bottom;
+    }
+}
+
+body {
+    font-size: 10pt;
+    margin: 0;
+    margin-bottom: 2cm;
+    padding: 0;
+    padding-bottom: 2cm;
+}
+
+/*
+ * Override colour -> black and white
+ */
+body, div, td {
+    color: black;
+}
+
+h1, h2, h3, h4, h5 {
+    color: black;
+}
+
+a {
+    color: black;
+}
+
+/*
+ * Book title page
+ */
+
+.titlepage h1.title {
+    font-size: 360%;
+    margin: 0;
+    color: black;
+}
+
+.titlepage h2.subtitle {
+    font-size: 240%;
+    margin: 0;
+    color: black;
+}
+
+.titlepage .releaseinfo {
+    margin-top: 50%;
+    font-size: 180%;
+    color: black;
+}
+
+.book > .titlepage > .title {
+    text-align: right;
+    page-break-after: always;
+    border: none;
+    background-color: white;
+    margin: 0;
+    padding-top: 24%;
+    padding-left: 0;
+    padding-right: 0;
+    padding-bottom: 0;
+}
+
+.toc {
+    page-break-before: always;
+}
+
+.toc * {
+    line-height: 120%;
+}
+
+/**
+ * Chapter and Appendix title page
+ */
+
+.chapter {
+    page: contentpage;
+    page-break-before: always;
+}
+
+.appendix {
+    page: contentpage;
+    page-break-before: always;
+}
+
+.chapter > .titlepage h1 {
+    font-size: 240%;
+    text-align: right;
+}
+
+.chapter > .titlepage h2 {
+    font-size: 400%;
+    text-align: right;
+}
+
+.chapter > .titlepage {
+    margin-bottom: 4em;
+}
+
+.appendix > .titlepage h1 {
+    font-size: 240%;
+    text-align: right;
+}
+
+.appendix > .titlepage h2 {
+    font-size: 400%;
+    text-align: right;
+}
+
+.appendix > .titlepage {
+    margin-bottom: 4em;
+}
+
+.section .titlepage {
+    page-break-after: avoid;
+}
+
+p {
+    text-align: justify;
+}
+
+/**
+ * Examples
+ */
+
+.example .title {
+    text-align: left;
+    white-space: nowrap;
+    color: #666666
+}
+
+.example {
+    page-break-inside: avoid;
+}
+
+.figure .title {
+    text-align: left;
+    white-space: nowrap;
+    color: #666666
+}
+
+.figure {
+    page-break-inside: avoid;
+}
+
+.figure img {
+    width: 100%;
+}
+
+.table .title {
+    text-align: left;
+    white-space: nowrap;
+    color: #666666
+}
+
+.table {
+    page-break-inside: avoid;
+}
+
+.programlisting {
+    page-break-inside: avoid;
+    font-size: 90%;
+}
+
+.screen {
+    page-break-inside: avoid;
+    font-size: 90%;
+}
diff --git a/subprojects/gradle-docs/src/docs/css/style.css b/subprojects/gradle-docs/src/docs/css/style.css
new file mode 100644
index 0000000..455e51c
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/css/style.css
@@ -0,0 +1,181 @@
+
+/*
+ * 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.
+ */
+body {
+    margin-top: 2em;
+    margin-bottom: 2em;
+    margin-left: 4em;
+    margin-right: 4em;
+}
+
+a {
+    text-decoration: none;
+    border-bottom: dotted 1px;
+    /*outline: none;*/
+}
+
+p {
+   /* ensure paragraphs remain close to their parent heading */
+   margin-top: 0;
+   margin-bottom: 1em;
+}
+
+h1, h2, h3, h4, h5 {
+    /* ensure paragraphs remain close to their parent heading */
+    margin-bottom: 0.2em;
+    color: #4a9935;
+}
+
+h1 {
+    font-size: 180%;
+}
+
+h2 {
+    font-size: 180%;
+}
+
+h3 {
+    font-size: 110%;
+}
+
+.chapter h1 {
+    margin-bottom: 1.0em;
+}
+
+.appendix h1 {
+    margin-bottom: 1.0em;
+}
+
+.section h2 {
+    font-size: 130%;
+    margin-top: 1.4em;
+}
+
+/*
+ * Table of Contents
+ */
+
+.toc dl, .list-of-examples dl {
+    margin-top: 0;
+    margin-bottom: 0;
+}
+
+.book .toc > dl > dt {
+    margin-top: 0.6em;
+}
+
+.book .toc, .book .list-of-examples {
+    margin-top: 2em;
+}
+
+/*
+ * HEADER/FOOTER
+ */
+
+.navheader {
+    margin-bottom: 3em;
+    padding-bottom: 1em;
+    width: 100%;
+    border-bottom: solid #d0d0d0 0.4em;
+}
+
+.navfooter {
+    margin-top: 3em;
+    padding-top: 1em;
+    width: 100%;
+    border-top: solid #d0d0d0 0.4em;
+}
+
+.navbar {
+    color: #a2a2a2;
+    font-size: 90%;
+    width: 100%;
+    text-align: right;
+}
+
+.navbar a {
+    color: #444444;
+}
+
+.navbar span {
+    padding-left: .8em;
+    padding-right: .8em;
+}
+
+/**
+ * TITLEPAGE
+ */
+
+.book > .titlepage {
+    margin-top: 2.2em;
+    margin-bottom: 2em;
+}
+
+.book .titlepage div.title {
+    border: solid #d0d0d0 1px;
+    background-color: #f5f5f5;
+    margin-left: -1.8em;
+    margin-right: -1.8em;
+    padding-left: 1.8em;
+    padding-right: 1.8em;
+    margin-bottom: 2.2em;
+}
+
+.titlepage h1.title {
+    font-size: 280%;
+    margin-bottom: 0;
+    color: #4a9935;
+}
+
+.titlepage h2.subtitle {
+    font-size: 160%;
+    font-style: italic;
+    margin-top: 0;
+    margin-bottom: 0;
+    color: #4a9935;
+}
+
+.titlepage .releaseinfo {
+    color: #666666;
+    font-size: 120%;
+    margin-top: 2em;
+    margin-bottom: 2.6em;
+}
+
+.titlepage .author {
+    color: #666666;
+}
+
+.titlepage .copyright {
+    color: #a2a2a2;
+    font-size: 90%;
+}
+
+.titlepage .legalnotice {
+    color: #a2a2a2;
+    font-size: 90%;
+}
+
+.literal, .userinput, .filename {
+    white-space: nowrap;
+}
+
+/*
+ * Single page html
+ */
+.book .chapter {
+    margin-top: 4em;
+}
diff --git a/subprojects/gradle-docs/src/docs/stylesheets/standaloneHtml.xsl b/subprojects/gradle-docs/src/docs/stylesheets/standaloneHtml.xsl
new file mode 100644
index 0000000..1baf7ee
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/stylesheets/standaloneHtml.xsl
@@ -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.
+  -->
+<xsl:stylesheet
+        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+        xmlns:xi="http://www.w3.org/2001/XInclude"
+        version="1.0">
+    <xsl:import href="html/docbook.xsl"/>
+    <xsl:import href="userGuideHtmlCommon.xsl"/>
+
+    <xsl:param name="section.autolabel">0</xsl:param>
+    <xsl:param name="chapter.autolabel">0</xsl:param>
+    <xsl:param name="appendix.autolabel">0</xsl:param>
+
+    <xsl:template name="output.html.stylesheets">
+    </xsl:template>
+
+    <xsl:template name="user.head.content">
+        <style type="text/css">
+            <xi:include href="base.css" parse="text"/>
+            <xi:include href="style.css" parse="text"/>
+        </style>
+    </xsl:template>
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/stylesheets/userGuideHtml.xsl b/subprojects/gradle-docs/src/docs/stylesheets/userGuideHtml.xsl
new file mode 100644
index 0000000..98fe4e1
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/stylesheets/userGuideHtml.xsl
@@ -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.
+  -->
+<xsl:stylesheet
+        xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+    <xsl:import href="html/chunkfast.xsl"/>
+    <xsl:import href="userGuideHtmlCommon.xsl"/>
+
+    <xsl:param name="root.filename">userguide</xsl:param>
+    <xsl:param name="chunk.section.depth">0</xsl:param>
+    <xsl:param name="chunk.quietly">1</xsl:param>
+    <xsl:param name="use.id.as.filename">1</xsl:param>
+
+    <!-- HEADERS AND FOOTERS -->
+
+    <!-- Use custom header -->
+    <xsl:template name="header.navigation">
+        <xsl:param name="next"/>
+        <xsl:param name="prev"/>
+        <xsl:if test=". != /book">
+            <div class='navheader'>
+                <xsl:call-template name="navlinks">
+                    <xsl:with-param name="next" select="$next"/>
+                    <xsl:with-param name="prev" select="$prev"/>
+                </xsl:call-template>
+            </div>
+        </xsl:if>
+    </xsl:template>
+
+    <!-- Use custom footer -->
+    <xsl:template name="footer.navigation">
+        <xsl:param name="next"/>
+        <xsl:param name="prev"/>
+        <div class='navfooter'>
+            <xsl:call-template name="navlinks">
+                <xsl:with-param name="next" select="$next"/>
+                <xsl:with-param name="prev" select="$prev"/>
+            </xsl:call-template>
+        </div>
+    </xsl:template>
+
+    <xsl:template name="navlinks">
+        <xsl:param name="next"/>
+        <xsl:param name="prev"/>
+        <div>
+            <div class="navbar">
+                <xsl:if test="count($prev)>0">
+                    <xsl:call-template name="customXref">
+                        <xsl:with-param name="target" select="$prev"/>
+                        <xsl:with-param name="content">
+                            <xsl:text>Previous</xsl:text>
+                        </xsl:with-param>
+                    </xsl:call-template>
+                    <span>|</span>
+                </xsl:if>
+                <xsl:call-template name="customXref">
+                    <xsl:with-param name="target" select="/book"/>
+                    <xsl:with-param name="content">
+                        <xsl:text>Contents</xsl:text>
+                    </xsl:with-param>
+                </xsl:call-template>
+                <xsl:if test="count($next)>0">
+                    <span>|</span>
+                    <xsl:call-template name="customXref">
+                        <xsl:with-param name="target" select="$next"/>
+                        <xsl:with-param name="content">
+                            <xsl:text>Next</xsl:text>
+                        </xsl:with-param>
+                    </xsl:call-template>
+                </xsl:if>
+            </div>
+        </div>
+    </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/stylesheets/userGuideHtmlCommon.xsl b/subprojects/gradle-docs/src/docs/stylesheets/userGuideHtmlCommon.xsl
new file mode 100644
index 0000000..c725057
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/stylesheets/userGuideHtmlCommon.xsl
@@ -0,0 +1,179 @@
+<!--
+  ~ 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.
+  -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:xslthl="http://xslthl.sf.net"
+                version="1.0">
+    <xsl:import href="highlighting/common.xsl"/>
+    <xsl:import href="html/highlight.xsl"/>
+
+    <xsl:param name="use.extensions">1</xsl:param>
+    <xsl:param name="toc.section.depth">1</xsl:param>
+    <xsl:param name="section.autolabel">1</xsl:param>
+    <xsl:param name="section.label.includes.component.label">1</xsl:param>
+    <xsl:param name="css.decoration">0</xsl:param>
+    <xsl:param name="highlight.source" select="1"/>
+
+    <!-- Use custom style sheet content -->
+    <xsl:param name="html.stylesheet">DUMMY</xsl:param>
+    <xsl:template name="output.html.stylesheets">
+        <link href="base.css" rel="stylesheet" type="text/css"/>
+        <link href="style.css" rel="stylesheet" type="text/css"/>
+    </xsl:template>
+
+    <xsl:param name="generate.toc">
+        book toc,title,example
+    </xsl:param>
+
+    <xsl:param name="formal.title.placement">
+        figure before
+        example before
+        equation before
+        table before
+        procedure before
+    </xsl:param>
+
+    <xsl:template name="customXref">
+        <xsl:param name="target"/>
+        <xsl:param name="content">
+            <xsl:apply-templates select="$target" mode="object.title.markup"/>
+        </xsl:param>
+        <a>
+            <xsl:attribute name="href">
+                <xsl:call-template name="href.target">
+                    <xsl:with-param name="object" select="$target"/>
+                </xsl:call-template>
+            </xsl:attribute>
+            <xsl:attribute name="title">
+                <xsl:apply-templates select="$target" mode="object.title.markup.textonly"/>
+            </xsl:attribute>
+            <xsl:value-of select="$content"/>
+        </a>
+    </xsl:template>
+
+    <!-- Overridden to remove standard body attributes -->
+    <xsl:template name="body.attributes">
+    </xsl:template>
+
+    <!-- Overridden to remove title attribute from structural divs -->
+    <xsl:template match="book|chapter|appendix|section|tip|note" mode="html.title.attribute">
+    </xsl:template>
+
+    <!-- ADMONITIONS -->
+
+    <!-- Overridden to remove style from admonitions -->
+    <xsl:param name="admon.style">
+    </xsl:param>
+
+    <xsl:template match="tip[@role='exampleLocation']" mode="class.value"><xsl:value-of select="@role"/></xsl:template>
+    
+    <xsl:param name="admon.textlabel">0</xsl:param>
+    
+    <!-- BOOK TITLEPAGE -->
+
+    <!-- Customise the contents of the book titlepage -->
+    <xsl:template name="book.titlepage">
+        <div class="titlepage">
+            <div class="title">
+                <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/title"/>
+                <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/subtitle"/>
+                <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/releaseinfo"/>
+            </div>
+            <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/author"/>
+            <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/copyright"/>
+            <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/legalnotice"/>
+        </div>
+    </xsl:template>
+
+    <xsl:template match="releaseinfo" mode="titlepage.mode">
+        <h3 class='releaseinfo'>Version <xsl:value-of select="."/></h3>
+    </xsl:template>
+
+    <!-- CHAPTER/APPENDIX TITLES -->
+
+    <!-- Use an <h1> instead of <h2> for chapter titles -->
+    <xsl:template name="component.title">
+        <h1>
+            <xsl:call-template name="anchor">
+	            <xsl:with-param name="node" select=".."/>
+	            <xsl:with-param name="conditional" select="0"/>
+            </xsl:call-template>
+            <xsl:apply-templates select=".." mode="object.title.markup"/>
+        </h1>
+    </xsl:template>
+    
+    <!-- TABLES -->
+
+    <!-- Duplicated from docbook stylesheets, to fix problem where html table does not get a title -->
+    <xsl:template match="table">
+        <xsl:param name="class">
+            <xsl:apply-templates select="." mode="class.value"/>
+        </xsl:param>
+        <div class="{$class}">
+            <xsl:call-template name="formal.object.heading"/>
+            <div class="{$class}-contents">
+                <table>
+                    <xsl:copy-of select="@*[not(local-name()='id')]"/>
+                    <xsl:attribute name="id">
+                        <xsl:call-template name="object.id"/>
+                    </xsl:attribute>
+                    <xsl:call-template name="htmlTable"/>
+                </table>
+            </div>
+        </div>
+    </xsl:template>
+
+    <xsl:template match="title" mode="htmlTable">
+    </xsl:template>
+
+    <!-- CODE HIGHLIGHTING -->
+
+    <xsl:template match='xslthl:keyword' mode="xslthl">
+        <span class="hl-keyword"><xsl:apply-templates mode="xslthl"/></span>
+    </xsl:template>
+
+    <xsl:template match='xslthl:string' mode="xslthl">
+        <span class="hl-string"><xsl:apply-templates mode="xslthl"/></span>
+    </xsl:template>
+
+    <xsl:template match='xslthl:comment' mode="xslthl">
+        <span class="hl-comment"><xsl:apply-templates mode="xslthl"/></span>
+    </xsl:template>
+
+    <xsl:template match='xslthl:number' mode="xslthl">
+        <span class="hl-number"><xsl:apply-templates mode="xslthl"/></span>
+    </xsl:template>
+
+    <xsl:template match='xslthl:annotation' mode="xslthl">
+        <span class="hl-annotation"><xsl:apply-templates mode="xslthl"/></span>
+    </xsl:template>
+
+    <xsl:template match='xslthl:doccomment' mode="xslthl">
+        <span class="hl-doccomment"><xsl:apply-templates mode="xslthl"/></span>
+    </xsl:template>
+
+    <xsl:template match='xslthl:tag' mode="xslthl">
+        <span class="hl-tag"><xsl:apply-templates mode="xslthl"/></span>
+    </xsl:template>
+
+    <xsl:template match='xslthl:attribute' mode="xslthl">
+        <span class="hl-attribute"><xsl:apply-templates mode="xslthl"/></span>
+    </xsl:template>
+
+    <xsl:template match='xslthl:value' mode="xslthl">
+        <span class="hl-value"><xsl:apply-templates mode="xslthl"/></span>
+    </xsl:template>
+
+</xsl:stylesheet>
diff --git a/subprojects/gradle-docs/src/docs/stylesheets/userGuidePdf.xsl b/subprojects/gradle-docs/src/docs/stylesheets/userGuidePdf.xsl
new file mode 100644
index 0000000..73c8578
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/stylesheets/userGuidePdf.xsl
@@ -0,0 +1,57 @@
+<!--
+  ~ 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.
+  -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+    <xsl:import href="xhtml/docbook.xsl"/>
+    <xsl:import href="userGuideHtmlCommon.xsl"/>
+
+    <!-- Use custom <head> content, to include stylesheets and bookmarks -->
+
+    <xsl:template name="output.html.stylesheets">
+        <link href="base.css" rel="stylesheet" type="text/css"/>
+        <link href="style.css" rel="stylesheet" type="text/css"/>
+        <link href="print.css" rel="stylesheet" type="text/css" media="print"/>
+    </xsl:template>
+
+    <xsl:template name="user.head.content">
+        <bookmarks>
+            <xsl:apply-templates select="chapter|appendix" mode="bookmarks"/>
+        </bookmarks>
+    </xsl:template>
+
+    <xsl:template match="*" mode="bookmarks">
+        <bookmark>
+            <xsl:attribute name="name">
+                <xsl:apply-templates select="." mode="object.title.markup"/>
+            </xsl:attribute>
+            <xsl:attribute name="href">#<xsl:call-template name="object.id"/></xsl:attribute>
+            <xsl:apply-templates select="section[parent::chapter|parent::appendix]" mode="bookmarks"/>
+        </bookmark>
+    </xsl:template>
+
+    <!-- Use custom chapter headings -->
+    <xsl:template name="component.title">
+        <h2>
+            <xsl:call-template name="anchor">
+                <xsl:with-param name="node" select=".."/>
+                <xsl:with-param name="conditional" select="0"/>
+            </xsl:call-template>
+            <xsl:apply-templates select=".." mode="label.markup"/>
+        </h2>
+        <h1>
+            <xsl:apply-templates select=".." mode="title.markup"/>
+        </h1>
+    </xsl:template>
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/stylesheets/userGuideSingleHtml.xsl b/subprojects/gradle-docs/src/docs/stylesheets/userGuideSingleHtml.xsl
new file mode 100644
index 0000000..e09e7ce
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/stylesheets/userGuideSingleHtml.xsl
@@ -0,0 +1,20 @@
+<!--
+  ~ 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.
+  -->
+<xsl:stylesheet
+        xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+    <xsl:import href="html/docbook.xsl"/>
+    <xsl:import href="userGuideHtmlCommon.xsl"/>
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/stylesheets/websiteHtml.xsl b/subprojects/gradle-docs/src/docs/stylesheets/websiteHtml.xsl
new file mode 100644
index 0000000..0056c50
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/stylesheets/websiteHtml.xsl
@@ -0,0 +1,44 @@
+<!--
+  ~ 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.
+  -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+    <xsl:import href="html/chunkfast.xsl"/>
+    <xsl:import href="userGuideHtmlCommon.xsl"/>
+
+    <xsl:param name="section.autolabel">0</xsl:param>
+    <xsl:param name="chapter.autolabel">0</xsl:param>
+    <xsl:param name="appendix.autolabel">0</xsl:param>
+    <xsl:param name="root.filename">userguide</xsl:param>
+    <xsl:param name="chunk.section.depth">0</xsl:param>
+    <xsl:param name="chunk.quietly">1</xsl:param>
+    <xsl:param name="use.id.as.filename">1</xsl:param>
+
+    <!-- Override this to remove all the html decorations from each page -->
+    <xsl:template name="chunk-element-content">
+        <xsl:param name="prev"/>
+        <xsl:param name="next"/>
+        <xsl:param name="nav.context"/>
+        <xsl:param name="content">
+            <xsl:apply-imports/>
+        </xsl:param>
+
+        <xsl:copy-of select="$content"/>
+    </xsl:template>
+
+    <!-- Override this to remove markup from xref links -->
+    <xsl:template match="chapter|appendix|section" mode="object.xref.markup">
+        <xsl:value-of select="title"/>
+    </xsl:template>
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/announcePlugin.xml b/subprojects/gradle-docs/src/docs/userguide/announcePlugin.xml
new file mode 100644
index 0000000..d8637dd7
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/announcePlugin.xml
@@ -0,0 +1,127 @@
+<!--
+  ~ 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.
+  -->
+<chapter id='announce_plugin' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>The Announce Plugin</title>
+    <para>The Gradle announce plugin enables you to publish messages on succeeded tasks to your favourite platforms.
+	It supports 
+	<itemizedlist>
+ 		<listitem><ulink url='http://twitter.com'>Twitter</ulink></listitem>
+ 		<listitem><ulink url='http://ubuntu.com'>Ubuntu Notify</ulink></listitem>
+        <listitem><ulink url="http://www.fullphat.net/index.php">Snarl</ulink>, a Windows Notification System</listitem>
+        <listitem><ulink url="http://growl.info/">Growl</ulink>, a Mac OS X Notification System</listitem>
+ 	</itemizedlist>
+	
+	</para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the announce plugin, include in 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">
+            <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">
+            <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>
+    </section>
+   <table>
+       <title>announce plugin targets</title>
+       <thead>
+           <tr>
+               <td>target literal</td>
+               <td>target</td>
+               <td>configuration parameters</td>
+               <td>more information</td>
+           </tr>
+       </thead>
+       <tr>
+           <td>twitter</td>
+           <td>Twitter</td>
+           <td>username , password</td>
+           <td></td>
+       </tr>
+       <tr>
+           <td>snarl</td>
+           <td>Snarl Windows Notification Service</td>
+           <td></td>
+           <td></td>
+       </tr>
+       <tr>
+           <td>growl</td>
+           <td>Growl Mac OS X Notification Service</td>
+           <td></td>
+           <td></td>
+       </tr>
+       <tr>
+           <td>notify-send</td>
+           <td>Notify Ubuntu Notification Service</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>
+       </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>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/ant.xml b/subprojects/gradle-docs/src/docs/userguide/ant.xml
new file mode 100644
index 0000000..20ca034
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/ant.xml
@@ -0,0 +1,190 @@
+<!--
+  ~ 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.
+  -->
+<chapter id="ant">
+    <title>Using Ant from Gradle</title>
+
+    <para>Gradle provides excellent integration with Ant. You can use individual Ant tasks or entire Ant builds in your
+        Gradle builds. In fact, you will find that it's far easier and more powerful using Ant tasks in a Gradle build
+        script, than it is to use Ant's XML format. You could even use Gradle simply as a powerful Ant task scripting
+        tool.
+    </para>
+
+    <para>Ant can be divided into two layers. The first layer is the Ant language. It provides the syntax for the
+        <filename>build.xml</filename>, the handling of the targets, special constructs like macrodefs, and so on.
+        In other words, everything except the Ant tasks and types. Gradle understands this language, and allows you to
+        import your Ant <filename>build.xml</filename> directly into a Gradle project. You can then use the targets of
+        your Ant build as if they were Gradle tasks.
+    </para>
+
+    <para>The second layer of Ant is its wealth of Ant tasks and types, like <literal>javac</literal>,
+        <literal>copy</literal> or <literal>jar</literal>. For this layer Gradle provides integration
+        simply by relying on Groovy, and the fantastic <literal>AntBuilder</literal>.
+    </para>
+
+    <para>Finally, since build scripts are Groovy scripts, you can always execute an Ant build as an external process.
+        Your build script may contain statements like:<literal>"ant clean compile".execute()</literal>.
+        <footnote>
+            <para>In Groovy you can execute Strings. To learn more about executing external processes with Groovy have a
+                look in 'Groovy in Action' 9.3.2 or at the Groovy wiki
+            </para>
+        </footnote>
+    </para>
+
+    <para>You can use Gradle's Ant integration as a path for migrating your build from Ant to Gradle. For example, you
+        could start by importing your existing Ant build. Then you could move your dependency declarations from the Ant
+        script to your build file. Finally, you could move your tasks across to your build file, or replace them with
+        some of Gradle's plugins. This process can be done in parts over time, and you can have a working Gradle build
+        during the entire process.
+    </para>
+
+    <section>
+        <title>Using Ant tasks and types in your build</title>
+
+        <para>In your build script, a property called <literal>ant</literal> is provided by Gradle. This is a reference
+            to an <apilink class="org.gradle.api.AntBuilder"/> instance. This <literal>AntBuilder</literal> is used to
+            access Ant tasks, types and properties from your build script. There is a very simple mapping from Ant's
+            <filename>build.xml</filename> format to Groovy, which is explained below.
+        </para>
+
+        <para>You execute an Ant task by calling a method on the <literal>AntBuilder</literal> instance. You use the task
+            name as the method name. For example, you execute the Ant <literal>echo</literal> task by calling the
+            <literal>ant.echo()</literal> method. The attributes of the Ant task are passed as Map parameters to the
+            method. Below is an example which executes the <literal>echo</literal> task. Notice that we can also mix
+            Groovy code and the Ant task markup. This can be extremely powerful.</para>
+        <sample id="useAntTask" dir="userguide/ant/useAntTask" title="Using an Ant task">
+            <sourcefile file="build.gradle"/>
+            <output args="hello"/>
+        </sample>
+
+        <para>
+            You pass nested text to an Ant task by passing it as a parameter of the task method call. In this example, we
+            pass the message for the <literal>echo</literal> task as nested text:
+        </para>
+        <sample id="taskWithNestedText" dir="userguide/ant/taskWithNestedText" title="Passing nested text to an Ant task">
+            <sourcefile file="build.gradle"/>
+            <output args="hello"/>
+        </sample>
+
+        <para>You pass nested elements to an Ant task inside a closure. Nested elements are defined in the same way
+            as tasks, by calling a method with the same name as the element we want to define.</para>
+        <sample id="taskWithNestedElements" dir="userguide/ant/taskWithNestedElements" title="Passing nested elements to an Ant task">
+            <sourcefile file="build.gradle"/>
+            <test args="zip"/>
+        </sample>
+
+        <para>You can access Ant types in the same way that you access tasks, using the name of the type as the
+            method name. The method call returns the Ant data type, which you can then use directly in your build script. In the
+            following example, we create an Ant <literal>path</literal> object, then iterate over the contents of it.</para>
+        <sample id="useAntType" dir="userguide/ant/useAntType" title="Using an Ant type">
+            <sourcefile file="build.gradle"/>
+            <test args="list"/>
+        </sample>
+
+        <para>More information about <literal>AntBuilder</literal> can be found in 'Groovy in Action' 8.4 or at the
+            <ulink url="http://groovy.codehaus.org/Using+Ant+from+Groovy">Groovy Wiki</ulink>
+        </para>
+
+        <section>
+            <title>Using custom Ant tasks in your build</title>
+            <para>To make custom tasks available in your build, you use the <literal>typedef</literal> Ant task, just
+                as you would in a <literal>build.xml</literal> file. You can then refer to the custom Ant task as you
+                would a built-in Ant task.
+            </para>
+            <sample id="useExternalAntTask" dir="userguide/ant/useExternalAntTask" title="Using a custom Ant task">
+                <sourcefile file="build.gradle"/>
+            </sample>
+            <para>
+                You can use Gradle's dependency management to assemble the classpath to use for the custom tasks.
+                To do this, you need to define a custom configuration for the classpath, then add some dependencies 
+                to the configuration. This is described in more detail in <xref linkend='sec:how_to_declare_your_dependencies'/>.
+            </para>
+            <sample id="useExternalAntTaskWithConfig" dir="userguide/ant/useExternalAntTaskWithConfig" title="Declaring the classpath for a custom Ant task">
+                <sourcefile file="build.gradle" snippet="define-classpath"/>
+            </sample>
+            <para>To use the classpath configuration, use the <literal>asPath</literal> property of the custom configuration.</para>
+            <sample id="useExternalAntTaskWithConfig" dir="userguide/ant/useExternalAntTaskWithConfig" title="Using a custom Ant task and dependency management together">
+                <sourcefile file="build.gradle" snippet="use-classpath"/>
+                <test args="check"/>
+            </sample>
+        </section>
+    </section>
+
+    <section>
+        <title>Importing an Ant build</title>
+        <para>You can use the <literal>ant.importBuild()</literal> method to import an Ant build into your Gradle
+            project. When you import an Ant build, each Ant target is treated as a Gradle task. This means you can
+            manipulate and execute the Ant targets in exactly the same way as Gradle tasks.
+        </para>
+        <sample id="antHello" dir="userguide/ant/hello" title="Importing an Ant build">
+            <sourcefile file="build.gradle"/>
+            <sourcefile file="build.xml"/>
+            <output args="hello"/>
+        </sample>
+        <para>You can add a task which depends on an Ant target:
+        </para>
+        <sample id="dependsOnAntTarget" dir="userguide/ant/dependsOnAntTarget" title="Task that depends on Ant target">
+            <sourcefile file="build.gradle"/>
+            <output args="intro"/>
+        </sample>
+        <para>Or, you can add behaviour to an Ant target:</para>
+        <sample id="addBehaviourToAntTarget" dir="userguide/ant/addBehaviourToAntTarget" title="Adding behaviour to an Ant target">
+            <sourcefile file="build.gradle"/>
+            <output args="hello"/>
+        </sample>
+        <para>It is also possible for an Ant target to depend on a Gradle task:</para>
+        <sample id="dependsOnTask" dir="userguide/ant/dependsOnTask" title="Ant target that depends on Gradle task">
+            <sourcefile file="build.gradle"/>
+            <sourcefile file="build.xml"/>
+            <output args="hello"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>Ant properties and references</title>
+
+        <para>There are several ways to set an Ant property, so that the property can be used by Ant tasks. You can
+            set the property directly on the <literal>AntBuilder</literal> instance. The Ant properties are also
+            available as a Map which you can change. You can also use the Ant <literal>property</literal> task.
+            Below are some examples of how to do this.
+        </para>
+        <sample id="antProperties" dir="userguide/ant/properties" title="Setting an Ant property">
+            <sourcefile file="build.gradle" snippet="set-property"/>
+            <sourcefile file="build.xml" snippet="set-property"/>
+        </sample>
+        <para>Many Ant tasks set properties when they execute. There are several ways to get the value of these properties.
+            You can get the property directly from the <literal>AntBuilder</literal> instance. The Ant properties are
+            also available as a Map. Below are some examples.</para>
+        <sample id="antProperties" dir="userguide/ant/properties" title="Getting an Ant property">
+            <sourcefile file="build.xml" snippet="get-property"/>
+            <sourcefile file="build.gradle" snippet="get-property"/>
+        </sample>
+        <para>There are several ways to set an Ant reference:</para>
+        <sample id="antProperties" dir="userguide/ant/properties" title="Setting an Ant reference">
+            <sourcefile file="build.gradle" snippet="set-reference"/>
+            <sourcefile file="build.xml" snippet="set-reference"/>
+        </sample>
+        <para>There are several ways to get an Ant reference:</para>
+        <sample id="antProperties" dir="userguide/ant/properties" title="Getting an Ant reference">
+            <sourcefile file="build.xml" snippet="get-reference"/>
+            <sourcefile file="build.gradle" snippet="get-reference"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>API</title>
+        <para>The Ant integration is provided by <apilink class="org.gradle.api.AntBuilder"/>.</para>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/antlrPlugin.xml b/subprojects/gradle-docs/src/docs/userguide/antlrPlugin.xml
new file mode 100644
index 0000000..b57bf08
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/antlrPlugin.xml
@@ -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.
+  -->
+
+<chapter id="antlr_plugin">
+    <title>The Antlr Plugin</title>
+
+    <para>The Antlr plugin extends the Java plugin to add support for generating parsers using <ulink url="http://www.antlr.org/">Antlr</ulink>.</para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the Antlr plugin, include in your build script:</para>
+        <sample id="useAntlrPlugin" dir="antlr" title="Using the Antlr plugin">
+            <sourcefile file="build.gradle" snippet="use-plugin"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>Tasks</title>
+        <para>The Antlr plugin adds a number of tasks to your project, as shown below.</para>
+
+        <table>
+            <title>Antlr plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>generateGrammarSource</literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.plugins.antlr.AntlrTask"/></td>
+                <td>Generates the source files for all production Antlr grammars.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>generateTestGrammarSource</literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.plugins.antlr.AntlrTask"/></td>
+                <td>Generates the source files for all test Antlr grammars.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>generate<replaceable>SourceSet</replaceable>GrammarSource</literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.plugins.antlr.AntlrTask"/></td>
+                <td>Generates the source files for all Antlr grammars for the given source set.</td>
+            </tr>
+        </table>
+        <para>The Antlr plugin adds the following dependencies to tasks added by the Java plugin.</para>
+        <table>
+            <title>Antlr plugin - additional task dependencies</title>
+            <thead>
+                <td>Task name</td>
+                <td>Depends on</td>
+            </thead>
+            <tr>
+                <td>compileJava</td>
+                <td>generateGrammarSource</td>
+            </tr>
+            <tr>
+                <td>compileTestJava</td>
+                <td>generateTestGrammarSource</td>
+            </tr>
+            <tr>
+                <td>compile<replaceable>SourceSet</replaceable>Java</td>
+                <td>generate<replaceable>SourceSet</replaceable>GrammarSource</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Project layout</title>
+        <table>
+            <title>Antlr plugin - project layout</title>
+            <thead>
+                <tr>
+                    <td>Directory</td>
+                    <td>Meaning</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <filename>src/main/antlr</filename>
+                </td>
+                <td>Production Antlr grammar files.</td>
+            </tr>
+            <tr>
+                <td>
+                    <filename>src/test/antlr</filename>
+                </td>
+                <td>Test Antlr grammar files.</td>
+            </tr>
+            <tr>
+                <td>
+                    <filename>src/<replaceable>sourceSet</replaceable>/antlr</filename>
+                </td>
+                <td>Antlr grammar files for the given source set.</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Dependency management</title>
+        <para>The Antlr plugin adds an <literal>antlr</literal> dependency configuration. You use this to declare the
+            version of Antlr you wish to use.</para>
+        <sample id="declareAntlrVersion" dir="antlr" title="Declare Antlr version">
+            <sourcefile file="build.gradle" snippet="declare-dependency"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>Convention properties</title>
+        <para>TBD</para>
+    </section>
+    
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/artifactDependenciesTutorial.xml b/subprojects/gradle-docs/src/docs/userguide/artifactDependenciesTutorial.xml
new file mode 100644
index 0000000..9a1e9a3
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/artifactDependenciesTutorial.xml
@@ -0,0 +1,92 @@
+<!--
+  ~ 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.
+  -->
+<chapter id="artifact_dependencies_tutorial" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>Artifact Basics</title>
+
+    <note>
+        <para>This chapter is currently under construction.</para>
+        <para>For all the details of artifact handling see <xref linkend="artifact_management"/>.</para>
+    </note>
+
+    <para>This chapter introduces some of the basics of artifact handling in Gradle.</para>
+
+    <section id="sec:artifact_configurations">
+        <title>Artifact configurations</title>
+        <para>Artifacts are grouped into <firstterm>configurations</firstterm>. A configuration is simply a set of files
+            with a name. You can use them to declare the external dependencies your project has, or to declare the
+            artifacts which your project publishes.</para>
+        <para>To define a configuration:</para>
+        <sample id="defineConfiguration" dir="userguide/artifacts/defineConfiguration" title="Definition of a configuration">
+            <sourcefile file="build.gradle" snippet="define-configuration"/>
+        </sample>
+        <para>To access a configuration:</para>
+        <sample id="defineConfiguration" dir="userguide/artifacts/defineConfiguration" title="Accessing a configuration">
+            <sourcefile file="build.gradle" snippet="lookup-configuration"/>
+        </sample>
+        <para>To configure a configuration:</para>
+        <sample id="defineConfiguration" dir="userguide/artifacts/defineConfiguration" title="Configuration of a configuration">
+            <sourcefile file="build.gradle" snippet="configure-configuration"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>Repositories</title>
+        <para>Artifacts are stored in <firstterm>repositories</firstterm>.</para>
+        <para>To use maven central repository:</para>
+        <sample id="defineRepository" dir="userguide/artifacts/defineRepository" title="Usage of Maven central repository">
+            <sourcefile file="build.gradle" snippet="maven-central"/>
+        </sample>
+        <para>To use a local directory:</para>
+        <sample id="defineRepository" dir="userguide/artifacts/defineRepository" title="Usage of a local directory">
+            <sourcefile file="build.gradle" snippet="flat-dir"/>
+        </sample>
+        <para>You can also use any Ivy resolver. You can have multiple repositories.</para>
+        <para>To access a repository:</para>
+        <sample id="defineRepository" dir="userguide/artifacts/defineRepository" title="Accessing a repository">
+            <sourcefile file="build.gradle" snippet="lookup-resolver"/>
+        </sample>
+        <para>To configure a repository:</para>
+        <sample id="defineRepository" dir="userguide/artifacts/defineRepository" title="Configuration of a repository">
+            <sourcefile file="build.gradle" snippet="configure-resolver"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>External dependencies</title>
+        <para>To define an external dependency, you add a dependency to a configuration:</para>
+        <sample id="externalDependencies" dir="userguide/artifacts/externalDependencies" title="Definition of an external dependency">
+            <sourcefile file="build.gradle" snippet="define-dependency"/>
+        </sample>
+        <para><literal>group</literal> and <literal>version</literal> are optional</para>
+        <para>TBD - configuring an external dependency</para>
+        <para>To use the external dependencies of a configuration:</para>
+        <sample id="externalDependencies" dir="userguide/artifacts/externalDependencies"  title="Usage of external dependency of a configuration">
+            <sourcefile file="build.gradle" snippet="use-configuration"/>
+            <output args="-q listJars"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>Artifact publishing</title>
+        <para>TBD</para>
+    </section>
+
+    <section>
+        <title>API</title>
+        <para>Configurations are contained in a <apilink class="org.gradle.api.artifacts.ConfigurationContainer"/>.
+            Each configuration implements the <apilink class="org.gradle.api.artifacts.Configuration"/>.</para>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/artifactMngmt.xml b/subprojects/gradle-docs/src/docs/userguide/artifactMngmt.xml
new file mode 100644
index 0000000..e81f189
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/artifactMngmt.xml
@@ -0,0 +1,89 @@
+<!--
+  ~ 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.
+  -->
+<chapter id="artifact_management" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>Artifact Management</title>
+    <section>
+        <title>Introduction</title>
+        <para>This chapter is about how you declare what are the artifacts of your project and how to work with
+            them (e.g. upload them). We define the artifacts of the projects as the files the project want to
+            provide to the outside world. This can be a library or a distribution or any other file. Usually artifacts
+            are archives, but not necessarily. In the Maven world a project can provide only one artifact. With Gradle
+            a project can provide as many artifacts as needed.
+        </para>
+    </section>
+    <section id="artifacts_and_configurations">
+        <title>Artifacts and configurations</title>
+        <para>Like dependencies, artifacts are grouped by configurations. In fact, a configuration can contain
+            both, artifacts and dependencies, at the same time. To assign an artifact to a configuration, you can write:
+        </para>
+        <sample id="assignArtifact" dir="userguide/artifacts/uploading" title="Assignment of an artifact to a configuration">
+            <sourcefile file="build.gradle" snippet="assign-artifact"/>
+        </sample>
+        <para>What do you gain by assigning an artifact to a configuration? For each configuration (also for the custom
+        ones added by you) Gradle provides the tasks <code>upload[ConfigurationName]</code> and
+        <code>build[ConfigurationName]</code>.
+        <footnote><para>To be exact, the Base plugin provides those tasks. The BasePlugin is automatically applied, if you use
+        the Java plugin.</para></footnote>
+        Execution of these tasks will build or upload the artifacts belonging to
+        the respective configuration.
+        </para>
+        <para>Table <xref linkend="tab:configurations"/> shows the configurations added by the Java plugin. Two of the
+        configurations are relevant for the usage with artifacts. The <code>archives</code> configuration is the standard
+        configuration to assign your artifacts to. The Java plugin automatically assigns the default jar to this
+        configuration. We will talk more about the <code>default</code> configuration in <xref linkend="project_libraries"/>.
+            As with dependencies, you can declare as many custom configurations as you like and assign artifacts to them.
+        </para>
+        <para>It is important to note that the custom archives you are creating as part of your build are not
+        automatically assigned to any configuration. You have to explicitly do this assignment.</para>
+    </section>
+    <section>
+        <title>Uploading artifacts</title>
+        <para>We have said that there is a specific upload task for each configuration. But before you can do an upload,
+            you have to configure the upload task and define where to upload. The repositories you have defined (as described
+            in <xref linkend="sec:repositories"/>) are not automatically used for uploading. In fact, some of those repositories allow only for artifacts downloading.
+            Here is an example how
+            you can configure the upload task of a configuration:
+        </para>
+        <sample id="uploading" dir="userguide/artifacts/uploading" title="Configuration of the upload task">
+            <sourcefile file="build.gradle" snippet="uploading"/>
+        </sample>
+        <para>As you can see, you can either use a reference to an existing repository or create a new repository.
+        As described in <xref linkend="sub:more_about_ivy_resolvers"/>, you can use all the Ivy resolvers suitable
+        for the purpose of uploading.</para>
+        <para>Uploading to a Maven repository is described in <xref linkend="uploading_to_maven_repositories"/>.</para>
+    </section>
+    <section id="project_libraries">
+        <title>More about project libraries</title>
+        <para>If your project is supposed to be used as a library, you need to define what are the artifacts of this library
+            and what are the dependencies of these artifacts. The Java plugin adds a <code>default</code> configuration for
+            this purpose. This configuration extends both the <code>archives</code> and the <code>runtime</code> configuration,
+            with the implicit assumption that the <code>runtime</code> dependencies are the dependencies of the <code>archives</code>
+            configuration. Of course this is fully customizable. You can add your own custom configuration or let the the
+            existing configurations extends from other configurations. You might have different group of artifacts which have
+            a different set of dependencies. This mechanism is very powerful and flexible.
+            </para>
+        <para>If someone wants to use your project as a library, she simply needs to declare on which configuration of
+            the dependency to depend on.
+            A Gradle dependency offers the <code>configuration</code> property to declare this. If this
+            is not specified, the <code>default</code> configuration is used (see <xref linkend="sec:dependency_configurations"/>).
+            Using your project as a library
+            can either happen from within a multi-project build or by retrieving your project from a repository. In
+            the latter case, an ivy.xml descriptor in the repository is supposed to contain all the neccesary information. If you
+            work with Maven repositories you don't have the flexibility as described above. For how to publish to a Maven
+            repository, see the section <xref linkend="uploading_to_maven_repositories"/>.
+        </para>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/buildLifecycle.xml b/subprojects/gradle-docs/src/docs/userguide/buildLifecycle.xml
new file mode 100644
index 0000000..a195c13
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/buildLifecycle.xml
@@ -0,0 +1,298 @@
+<!--
+  ~ 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.
+  -->
+<chapter id='build_lifecycle' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>The Build Lifecycle</title>
+    <para>We said earlier, that the core of Gradle is a language for dependency based programming. In Gradle terms this
+        means that you can define tasks and dependencies between tasks. Gradle guarantees that these tasks are executed
+        in the order of their dependencies, and that each task is executed only once. Those tasks form a
+        <ulink url='http://en.wikipedia.org/wiki/Directed_acyclic_graph'>Directed Acyclic Graph</ulink>. There are
+        build tools that build up such a dependency graph as they execute their tasks. Gradle builds the complete
+        dependency graph <emphasis>before</emphasis> any task is executed. This lies at the heart of Gradle and makes
+        many things possible which would not be possible otherwise.
+    </para>
+    <para>Your build scripts configure this dependency graph. Therefore they are strictly speaking <emphasis>build
+        configuration scripts</emphasis>.
+    </para>
+    <section id='sec:build_phases'>
+        <title>Build phases</title>
+        <para>A Gradle build has three distinct phases.
+        </para>
+        <variablelist>
+            <varlistentry>
+                <term>Initialization</term>
+                <listitem>
+                    <para>Gradle supports single and multi-project builds. During the initialization phase, Gradle
+                        determines which projects are going to take part in the build, and creates a
+                        <apilink class="org.gradle.api.Project"/> instance for each of these projects.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>Configuration</term>
+                <listitem>
+                    <para>The build scripts of <emphasis>all</emphasis> projects which are part of the build are
+                        executed. This configures the project objects.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>Execution</term>
+                <listitem>
+                    <para>Gradle determines the subset of the tasks, created and configured during the configuration
+                        phase, to be executed. The subset is determined by the task name arguments passed to the
+                        <command>gradle</command> command and the current directory. Gradle then executes each of the
+                        selected tasks.
+                    </para>
+                </listitem>
+            </varlistentry>
+        </variablelist>
+    </section>
+    <section id='sec:settings_file'>
+        <title>Settings file</title>
+        <para>Beside the build script files, Gradle defines a settings file. The settings file is determined by Gradle
+            via a naming convention. The default name for this file is <filename>settings.gradle</filename>. Later in
+            this chapter we explain, how Gradle looks for a settings file.
+        </para>
+        <para>The settings file gets executed during the initialization phase. A multiproject build must have a
+            <filename>settings.gradle</filename>
+            file in the root project of the multiproject hierarchy. It is required because in the
+            settings file it is defined, which projects are taking part in the multi-project build (see
+            <xref linkend='multi_project_builds'/>). For a single-project build, a settings file is optional.
+            You might need it for example, to add libraries to your build script classpath (see <xref
+                    linkend='organizing_build_logic'/>). Let's first do some introspection with a single project
+            build:
+        </para>
+        <sample id="buildlifecycle" dir="userguide/buildlifecycle/basic" title="Single project build">
+            <sourcefile file="settings.gradle"/>
+            <sourcefile file="build.gradle"/>
+            <output args="test"/>
+        </sample>
+        <para>For a build script, the property access and method calls are delegated to a project object. Similarly
+            property access and method calls within the settings file is delegated to a settings object. Have a look at
+            <apilink class='org.gradle.api.initialization.Settings'/>.
+        </para>
+    </section>
+    <section id='sec:multi_project_builds'>
+        <title>Multi-project builds</title>
+        <para>A multi-project build is a build where you build more than one project during a single execution of
+            Gradle. You have to declare the projects taking part in the multiproject build in the settings file. There
+            is much more to say about multi-project builds in the chapter dedicated to this topic (see <xref
+                    linkend='multi_project_builds'/>).
+        </para>
+        <section id='sub:project_locations'>
+            <title>Project locations</title>
+            <para>Multi-project builds are always represented by a tree with a single root. Each element in the tree
+                represent a project. A project has a virtual and a physical path. The virtual path denotes the position
+                of the project in the multi-project build tree. The project tree is created in the
+                <filename>settings.gradle</filename> file. By default it is assumed that the location of the settings
+                file is also the location of the root project. But you can redefine the location of the root project
+                in the settings file.
+            </para>
+        </section>
+        <section id='sub:building_the_tree'>
+            <title>Building the tree</title>
+            <para>In the settings file you can use a set of methods to build the project tree. Hierarchical and flat
+                physical layouts get special support.
+            </para>
+            <section>
+                <title>Hierarchical layouts</title>
+                <sample id="standardLayouts" dir="userguide/multiproject/standardLayouts" title="Hierarchical layout">
+                    <sourcefile file="settings.gradle" snippet="hierarchical-layout"/>
+                </sample>
+                <para>The <literal>include</literal> method takes as an argument a relative virtual path to the root
+                    project. This relative virtual path is assumed to be equals to the relative physical path of the
+                    subproject to the root project. You only need to specify the leafs of the tree. Each parent path of
+                    the leaf project is assumed to be another subproject which obeys to the physical path assumption
+                    described above.
+                </para>
+            </section>
+            <section>
+                <title>Flat layouts</title>
+                <sample id="standardLayouts" dir="userguide/multiproject/standardLayouts" title="Flat layout">
+                    <sourcefile file="settings.gradle" snippet="flat-layout"/>
+                </sample>
+                <para>The <literal>includeFlat</literal> method takes directory names as an argument. Those directories
+                    need to exist at the same level as the root project directory. The location of those directories
+                    are considered as child projects of the root project in the virtual multi-project tree.
+                </para>
+            </section>
+        </section>
+        <section id='sub:modifying_element_of_the_project_tree'>
+            <title>Modifying elements of the project tree</title>
+            <para>The multi-project tree created in the settings file is made up of so called <firstterm>project descriptors</firstterm>.
+                You can modify these descriptors in the settings file at any time. To access a descriptor you can do:
+            </para>
+            <sample id="customLayout" dir="userguide/multiproject/customLayout" title="Modification of elements of the project tree">
+                <sourcefile file="settings.gradle" snippet="lookup-project"/>
+            </sample>
+            <para>Using this descriptor you can change the name, project directory and build file of a project.
+            </para>
+            <sample id="customLayout" dir="userguide/multiproject/customLayout" title="Modification of elements of the project tree">
+                <sourcefile file="settings.gradle" snippet="change-project"/>
+            </sample>
+            <para>Have a look at <apilink class="org.gradle.api.initialization.ProjectDescriptor"/> for more details.</para>
+        </section>
+    </section>
+    <section id='sec:initialization'>
+        <title>Initialization</title>
+        <para>How does Gradle know whether to do a single or multiproject build? If you trigger a multiproject build
+            from the directory where the settings file is, things are easy. But Gradle also allows you to execute the
+            build from within any subproject taking part in the build.
+            <footnote>
+                <para>Gradle supports partial multiproject builds (see <xref linkend='multi_project_builds'/>).
+                </para>
+            </footnote>
+            If you execute Gradle from within a project that has no <filename>settings.gradle</filename> file,
+            Gradle does the following:
+        </para>
+        <itemizedlist>
+            <listitem>
+                <para>It searches for a <filename>settings.gradle</filename> in a directory called <filename>master</filename>
+                    which has the same nesting level as the current dir.
+                </para>
+            </listitem>
+            <listitem>
+                <para>If no <filename>settings.gradle</filename> is found, it searches the parent directories for the existence of a
+                    <filename>settings.gradle</filename> file.
+                </para>
+            </listitem>
+            <listitem>
+                <para>If no <filename>settings.gradle</filename> file is found, the build is executed as a single project build.
+                </para>
+            </listitem>
+            <listitem>
+                <para>If a <filename>settings.gradle</filename> file is found, Gradle checks if the current project is part of the
+                    multiproject hierarchy defined in the found <filename>settings.gradle</filename> file. If not, the build is executed as a
+                    single project build. Otherwise a multiproject build is executed.
+                </para>
+            </listitem>
+        </itemizedlist>
+        <para>What is the purpose of this behavior? Somehow Gradle has to find out, whether the project you are into, is
+            a subproject of a multiproject build or not. Of course, if it is a subproject, only the subproject and its
+            dependent projects are build. But Gradle needs to create the build configuration for the whole multiproject
+            build (see <xref linkend='multi_project_builds'/>). Via the <option>-u</option>
+            command line option, you can tell Gradle not to look in the parent hierarchy for a <filename>settings.gradle</filename> file. The
+            current project is then always build as a single project build. If the current project contains a
+            <filename>settings.gradle</filename> file, the <option>-u</option> option has no meaning. Such a build is always executed as:
+        </para>
+        <itemizedlist>
+            <listitem>
+                <para>a single project build, if the <filename>settings.gradle</filename> file does not define a multiproject hierarchy
+                </para>
+            </listitem>
+            <listitem>
+                <para>a multiproject build, if the <filename>settings.gradle</filename> file does define a multiproject hierarchy.
+                </para>
+            </listitem>
+        </itemizedlist>
+        <para>The auto search for a settings file does only work for multi-project builds with a physical hierarchical
+            or flat layout. For a flat layout you must additionally obey to the naming convention described above.
+            Gradle supports arbitrary physical layouts for a multiproject build. But for such arbitrary layouts you need
+            to execute the build from the directory where the settings file is located. For how to run partial builds
+            from the root see <xref linkend='sec:running_partial_build_from_the_root'/>. In our next release we want to
+            enable partial builds from subprojects by specifying the location of the settings file as a command line
+            parameter. Gradle creates Project objects for every project taking part in the build. For a single
+            project build this is only one project. For a multi-project build these are the projects specified in
+            Settings object (plus the root project). Each project object has by default a name equals to the name of its
+            top level directory. Every project except the root project has a parent project and might have child projects.
+        </para>
+    </section>
+    <section id='sec:configuration_and_execution_of_a_single_project_build'>
+        <title>Configuration and execution of a single project build</title>
+        <para>For a single project build, the workflow of the <emphasis>after initialization</emphasis>
+            phases are pretty simple. The build script is executed against the project object that was created during
+            the initialization phase. Then Gradle looks for tasks with names equal to those passed as command line
+            arguments. If these task names exist, they are executed as a separate build in the order you have passed
+            them. The configuration and execution for multi-project builds is discussed in <xref linkend='multi_project_builds'/>.
+        </para>
+    </section>
+    <section id="build_lifecycle_events">
+        <title>Responding to the lifecycle in the build script</title>
+
+        <para>Your build script can receive notifications as the build progresses through its lifecyle. These
+            notifications generally take 2 forms: You can either implement a particular listener interface, or you can
+            provide a closure to execute when the notification is fired. The examples below use closures. For details on
+            how to use the listener interfaces, refer to the API documentation.
+        </para>
+
+        <section>
+            <title>Project evaluation</title>
+            <para>You can receive a notification immediately before and after a project is evaluated. This can be used
+                to do things like performing additional configuration once all the definitions in a build script have
+                been applied, or for some custom logging or profiling.
+            </para>
+            <para>Below is an example which adds a <literal>test</literal> task to each project with the
+                <literal>hasTests</literal> property set to true.
+            </para>
+            <sample id="projectEvaluateEvents" dir="userguide/buildlifecycle/projectEvaluateEvents" title="Adding of test task to each project which has certain property set">
+                <sourcefile file="build.gradle" snippet="after-evaluate"/>
+                <sourcefile file="projectA.gradle"/>
+                <output args="-q test"/>
+            </sample>
+            <para>This example uses method <literal>Project.afterEvaluate()</literal> to add a closure which is executed
+                after the project is evaluated.</para>
+            <para>It is also possible to receive notifications when any project is evaluated. This example performs
+                some custom logging of project evaluation. Notice that the <literal>afterProject</literal> notification
+                is received regardless of whether the project evaluates successfully or fails with an exception.
+            </para>
+            <sample id="buildProjectEvaluateEvents" dir="userguide/buildlifecycle/buildProjectEvaluateEvents" title="Notifications">
+                <sourcefile file="build.gradle" snippet="evaluate-events"/>
+                <output args="-q test"/>
+            </sample>
+            <para>You can also add a <apilink class="org.gradle.api.ProjectEvaluationListener"/> to the
+                <apilink class="org.gradle.api.invocation.Gradle"/> to receive these events.
+            </para>
+        </section>
+
+        <section>
+            <title>Task creation</title>
+            <para>You can receive a notification immediately after a task is added to a project. This can be used
+                to set some default values or add behaviour before the task is made available in the build file.</para>
+            <para>The following example sets the <literal>srcDir</literal> property of each task as it is created.</para>
+            <sample id="taskCreationEvents" dir="userguide/buildlifecycle/taskCreationEvents" title="Setting of certain property to all tasks">
+                <sourcefile file="build.gradle"/>
+                <output args="-q a"/>
+            </sample>
+            <para>You can also add an <apilink class="org.gradle.api.Action"/> to a
+                <apilink class="org.gradle.api.tasks.TaskContainer"/> to receive these events.</para>
+        </section>
+
+        <section>
+            <title>Task execution graph ready</title>
+            <para>You can receive a notification immediately after the task execution graph has been populated. We
+                have seen this already in <xref linkend="configure-by-dag"/>.
+            </para>
+            <para>You can also add a <apilink class="org.gradle.api.execution.TaskExecutionGraphListener"/> to the
+                <apilink class="org.gradle.api.execution.TaskExecutionGraph"/> to receive these events.
+            </para>
+        </section>
+
+        <section>
+            <title>Task execution</title>
+            <para>You can receive a notification immediately before and after any task is executed.</para>
+            <para>The following example logs the start and end of each task execution. Notice that the
+                <literal>afterTask</literal> notification is received regardless of whether the task completes
+                successfully or fails with an exception.</para>
+            <sample id="taskExecutionEvents" dir="userguide/buildlifecycle/taskExecutionEvents" title="Logging of start and end of each task execution">
+                <sourcefile file="build.gradle"/>
+                <output args="-q broken"/>
+            </sample>
+            <para>You can also use a <apilink class="org.gradle.api.execution.TaskExecutionListener"/> to the
+                <apilink class="org.gradle.api.execution.TaskExecutionGraph"/> to receive these events.
+            </para>
+        </section>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/buildScriptsTutorial.xml b/subprojects/gradle-docs/src/docs/userguide/buildScriptsTutorial.xml
new file mode 100644
index 0000000..2dec7e2
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/buildScriptsTutorial.xml
@@ -0,0 +1,237 @@
+<!--
+  ~ 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.
+  -->
+<chapter id='tutorial_using_tasks' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>Build Script Basics</title>
+    <section><title>Projects and tasks</title>
+    <para>Everything in Gradle sits on top of two basic concepts: <firstterm>projects</firstterm> and <firstterm>tasks</firstterm>.</para>
+    <para>
+        Every Gradle build is made up of one or more <firstterm>projects</firstterm>. A project represents
+        some component of your software which can be built. What this means exactly depends on what it is that you are
+        building. For example, a project might represent a library JAR or a web application. It might represent a
+        distribution ZIP assembled from the JARs produced by other projects. A project does not necessarily represent
+        a thing to be built. It might represent a thing to be done, such as deploying your application to staging or
+        production environments. Don't worry if this seems a little vague for now. Gradle's build-by-convention support adds
+        a more concrete definition for what a project is.
+    </para>
+    <para>Each project is made up of one or more <firstterm>tasks</firstterm>. A task represents some atomic piece
+        of work which a build performs. This might be compiling some classes, creating a JAR, generating javadoc,
+        or publishing some archives to a repository.</para>
+    <para>For now, we will look at defining some simple tasks in a build with one project. Later chapters will look at
+        working with multiple projects and more about working with projects and tasks.</para>
+    </section>
+    <section>
+        <title>Hello world</title>
+        <para>You run a Gradle build using the <command>gradle</command> command. The <command>gradle</command> command
+            looks for a file called <filename>build.gradle</filename> in the current directory.
+            <footnote><para>There are command line switches to change this behavior. See <xref linkend='gradle_command_line'/>)</para></footnote>
+            We call this <filename>build.gradle</filename> file a <firstterm>build script</firstterm>, although strictly speaking it is
+            a build configuration script, as we will see later. The build script defines a project and its tasks.
+        </para>
+        <para>To try this out, create the following build script named <filename>build.gradle</filename>.
+        </para>
+        <sample id="hello" dir="userguide/tutorial/hello" title="The first build script">
+            <sourcefile file="build.gradle"/>
+        </sample>
+        <para>In a command-line shell, enter into the containing directory and execute the build script by running
+            <userinput>gradle -q hello</userinput>:
+        </para>
+        <tip>
+            <title>What does <option>-q</option> do?</title>
+            <para>Most of the examples in this user guide are run with the <option>-q</option> command-line option.
+                This suppresses Gradle's log messages, so that only the output of the tasks is shown. This keeps the example
+                output in this user guide a little clearer. You don't need to use this option if you don't want.
+                See <xref linkend="logging"/> for more details about the command-line options which affect Gradle's output.
+            </para>
+        </tip>
+        <sample id="hello" dir="userguide/tutorial/hello" title="Execution of a build script">
+            <output args="-q hello"/>
+        </sample>
+        <para>What's going on here? This build script defines a single task, called <literal>hello</literal>, and
+            adds an action to it. When you run <userinput>gradle hello</userinput>, Gradle executes the
+            <literal>hello</literal> task, which in turn executes the action you've provided. The action is simply a
+            closure containing some Groovy code to execute.
+        </para>
+        <para>If you think this looks similar to Ant's targets, well, you are right. Gradle tasks are the equivalent to
+            Ant targets. But as you will see, they are much more powerful. We have used a different terminology than Ant
+            as we think the word <emphasis>task</emphasis> is more expressive than the word <emphasis>target</emphasis>.
+            Unfortunately this introduces a terminology clash with Ant, as Ant calls its commands, such as
+            <literal>javac</literal> or <literal>copy</literal>, tasks. So when we talk about tasks,
+            we <emphasis>always</emphasis> mean Gradle tasks, which are the equivalent to Ant's targets. If we talk
+            about Ant tasks (Ant commands), we explicitly say <emphasis>ant task</emphasis>.
+        </para>
+    </section>
+    <section>
+        <title>A shortcut task definition</title>
+        <para>There is a shorthand way to define a task like our <literal>hello</literal> task above, which is more
+            concise.</para>
+        <sample id="helloShortcut" dir="userguide/tutorial/helloShortcut" title="A task definition shortcut">
+            <sourcefile file="build.gradle"/>
+        </sample>
+        <para>Again, this defines a task called <literal>hello</literal> with a single closure to execute.
+            We will use this task definition style throughout the user guide.</para>
+    </section>
+    <section>
+        <title>Build scripts are code</title>
+        <para>Gradle's build scripts expose to you the full power of Groovy. As an appetizer, have a look at this:
+        </para>
+        <sample id="upper" dir="userguide/tutorial/upper" title="Using Groovy in Gradle's tasks">
+            <sourcefile file="build.gradle"/>
+            <output args="-q upper"/>
+        </sample>
+        <para>or</para>
+        <sample id="count" dir="userguide/tutorial/count" title="Using Groovy in Gradle's tasks">
+            <sourcefile file="build.gradle"/>
+            <output args="-q count"/>
+        </sample>
+    </section>
+    <section id='sec:task_dependencies'>
+        <title>Task dependencies</title>
+        <para>As you probably have guessed, you can declare dependencies between your tasks.
+        </para>
+        <sample id="intro" dir="userguide/tutorial/intro" title="Declaration of dependencies between tasks">
+            <sourcefile file="build.gradle"/>
+            <output args="-q intro"/>
+        </sample>
+        <para>To add a dependency, the corresponding task does not need to exist.</para>
+        <sample id="lazyDependsOn" dir="userguide/tutorial/lazyDependsOn" title="Lazy dependsOn - the other task does not exist (yet)">
+            <sourcefile file="build.gradle"/>
+            <output args="-q taskX"/>
+        </sample>
+        <para>The dependency of <literal>taskX</literal> to <literal>taskY</literal> is declared before
+            <literal>taskY</literal> is defined. This is very important for multi-project builds. Task dependencies are
+            discussed in more detail in <xref linkend='sec:adding_dependencies_to_tasks'/>.
+        </para>
+        <para>Please notice, that you can't use a shortcut notation (see <xref linkend='sec:shortcut_notations' />) when referring to task, which is not defined yet.</para>
+    </section>
+    <section>
+        <title>Dynamic tasks</title>
+        <para>The power of Groovy can be used for more than defining what a task does. For example, you can also use it
+            to dynamically create tasks.
+        </para>
+        <sample id="dynamic" dir="userguide/tutorial/dynamic" title="Dynamic creation of a task">
+            <sourcefile file="build.gradle"/>
+            <output args="-q task1"/>
+        </sample>
+    </section>
+    <section>
+        <title>Manipulating existing tasks</title>
+        <para>Once tasks are created they can be accessed via an <emphasis>API</emphasis>. This is different to Ant. For
+            example you can create additional dependencies.
+        </para>
+        <sample id="dynamicDepends" dir="userguide/tutorial/dynamicDepends" title="Accessing a task via API - adding a dependency">
+            <sourcefile file="build.gradle"/>
+            <output args="-q task0"/>
+        </sample>
+        <para>Or you can add behavior to an existing task.</para>
+        <sample id="helloEnhanced" dir="userguide/tutorial/helloEnhanced" title="Accessing a task via API - adding behaviour">
+            <sourcefile file="build.gradle"/>
+            <output args="-q hello"/>
+        </sample>
+        <para>The calls <literal>doFirst</literal> and <literal>doLast</literal> can be executed multiple times.
+            They add an action to the beginning or the end of the task's actions list. When the task executes, the
+            actions in the action list are executed in order. The <literal><<</literal> operator is simply an
+            alias for <literal>doLast</literal>.
+        </para>
+    </section>
+    <section id='sec:shortcut_notations'>
+        <title>Shortcut notations</title>
+        <para>As you might have noticed in the previous examples, there is a convenient notation for accessing an
+            <emphasis>existing</emphasis> task. Each task is available as a property of the build script:
+        </para>
+        <sample id="helloWithShortCut" dir="userguide/tutorial/helloWithShortCut" title="Accessing task as a property of the build script">
+            <sourcefile file="build.gradle"/>
+            <output args="-q hello"/>
+        </sample>
+        <para>This enables very readable code, especially when using the out of the box tasks provided by the plugins
+            (e.g. <literal>compile</literal>).</para>
+    </section>
+    <section id='sec:dynamic_properties'>
+        <title>Dynamic task properties</title>
+        <para>You can assign arbitrary <emphasis>new</emphasis> properties to any task.
+        </para>
+        <sample id="dynamicProperties" dir="userguide/tutorial/dynamicProperties" title="Assigning properties to a task">
+            <sourcefile file="build.gradle"/>
+            <output args="-q showProps"/>
+        </sample>
+    </section>
+    <section>
+        <title>Using Ant Tasks</title>
+        <para>Ant tasks are first-class citizens in Gradle. Gradle provides excellent integration for Ant tasks simply
+            by relying on Groovy. Groovy is shipped with the fantastic <literal>AntBuilder</literal>. Using Ant tasks
+            from Gradle is as convenient and more powerful than using Ant tasks from a <filename>build.xml</filename>
+            file. Let's look at an example:
+        </para>
+        <sample id="antChecksum" dir="userguide/tutorial/antChecksum" title="Using AntBuilder to execute ant.checksum target">
+            <sourcefile file="build.gradle"/>
+            <output args="-q checksum"/>
+        </sample>
+
+        <para>There is lots more you can do with Ant in your build scripts. You can find out more in <xref linkend="ant"/>.</para>
+    </section>
+    <section>
+        <title>Using methods</title>
+        <para>Gradle scales in how you can organize your build logic. The first level of organizing your build logic for
+            the example above, is extracting a method.
+        </para>
+        <sample id="antChecksumWithMethod" dir="userguide/tutorial/antChecksumWithMethod" title="Using methods to organize your build logic">
+            <sourcefile file="build.gradle"/>
+            <output args="-q checksum"/>
+        </sample>
+        <para>Later you will see that such methods can be shared among subprojects in multi-project builds. If your
+            build logic becomes more complex, Gradle offers you other very convenient ways to organize it. We have
+            devoted a whole chapter to this. See <xref linkend='organizing_build_logic'/>.
+        </para>
+    </section>
+    <section id='sec:default_tasks'>
+        <title>Default tasks</title>
+        <para>Gradle allows you to define one or more default tasks for your build.
+        </para>
+        <sample id="defaultTasks" dir="userguide/tutorial/defaultTasks" title="Defining a default tasks">
+            <sourcefile file="build.gradle"/>
+            <output args="-q"/>
+        </sample>
+        <para>This is equivalent to running <userinput>gradle clean run</userinput>. In a multi-project build every
+            subproject can have its own specific default tasks. If a subproject does not specify default tasks, the
+            default tasks of the parent project are used (if defined).
+        </para>
+    </section>
+    <section id="configure-by-dag">
+        <title>Configure by DAG</title>
+        <para>As we describe in full detail later (See <xref linkend='build_lifecycle'/>) Gradle has a
+            configuration phase and an execution phase. After the configuration phase Gradle knows all tasks that should
+            be executed. Gradle offers you a hook to make use of this information. A use-case for this would be to check
+            if the release task is part of the tasks to be executed. Depending on this you can assign different values
+            to some variables.
+        </para>
+        <para>In the following example, execution of <literal>distribution</literal> and <literal>release</literal> tasks results in different value of <literal>version</literal> variable.</para>
+        <sample id="configByDagNoRelease" dir="userguide/tutorial/configByDag" title="Different outcomes of build depending on chosen tasks">
+            <sourcefile file="build.gradle"/>
+            <output args="-q distribution"/>
+            <output args="-q release" outputFile="configByDag.out"/>
+        </sample>
+        <para>The important thing is, that the fact that the release task has been chosen, has an effect
+            <emphasis>before</emphasis> the release task gets executed. Nor has the release task to be the
+            <emphasis>primary</emphasis> task (i.e. the task passed to the <command>gradle</command> command).
+        </para>
+    </section>
+    <section>
+        <title>Summary</title>
+        <para>This is not the end of the story for tasks. So far we have worked with simple tasks. Tasks will be
+            revisited in <xref linkend='more_about_tasks'/> and when we look at the Java plugin in
+            <xref linkend='java_plugin'/>.
+        </para>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/codeQualityPlugin.xml b/subprojects/gradle-docs/src/docs/userguide/codeQualityPlugin.xml
new file mode 100644
index 0000000..d40ddba
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/codeQualityPlugin.xml
@@ -0,0 +1,322 @@
+<!--
+  ~ 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.
+  -->
+<chapter id="code_quality_plugin">
+    <title>The Code Quality Plugin</title>
+
+    <para>The code quality plugin adds tasks which perform code quality checks and generate reports from these checks.
+        The following tools are supported:
+        <itemizedlist>
+            <listitem><para><ulink url="http://checkstyle.sourceforge.net/index.html">Checkstyle</ulink></para></listitem>
+            <listitem><para><ulink url="http://codenarc.sourceforge.net/index.html">CodeNarc</ulink></para></listitem>
+        </itemizedlist>
+    </para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the code quality plugin, include in your build script:</para>
+        <sample id="useCodeQualityPlugin" dir="codeQuality" title="Using the code quality plugin">
+            <sourcefile file="build.gradle" snippet="use-plugin"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>Tasks</title>
+        <para>When used with the Java plugin, the code quality plugin adds the following tasks to the project:</para>
+        <table>
+            <title>Code quality plugin - Java tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>checkstyleMain</literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.plugins.quality.Checkstyle"/></td>
+                <td>Runs Checkstyle against the production Java source files.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>checkstyleTest</literal>
+                </td>
+                <td><literal>compile</literal></td>
+                <td><apilink class="org.gradle.api.plugins.quality.Checkstyle"/></td>
+                <td>Runs Checkstyle against the test Java source files.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>checkstyle<replaceable>SourceSet</replaceable></literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.plugins.quality.Checkstyle"/></td>
+                <td>Runs Checkstyle against the given source set's Java source files.</td>
+            </tr>
+        </table>
+
+        <para>When used with the Groovy plugin, the code quality plugin adds the following tasks to the project:</para>
+
+        <table>
+            <title>Code quality plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>codenarcMain</td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.plugins.quality.CodeNarc"/></td>
+                <td>Runs CodeNarc against the production Groovy source files.</td>
+            </tr>
+            <tr>
+                <td>codenarcTest</td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.plugins.quality.CodeNarc"/></td>
+                <td>Runs CodeNarc against the test Groovy source files.</td>
+            </tr>
+            <tr>
+                <td>codenarc<replaceable>SourceSet</replaceable></td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.plugins.quality.CodeNarc"/></td>
+                <td>Runs CodeNarc against the given source set's Groovy source files.</td>
+            </tr>
+        </table>
+        <para>The Code quality plugin adds the following dependencies to tasks added by the Java plugin.</para>
+        <table>
+            <title>Code quality plugin - additional task dependencies</title>
+            <thead>
+                <td>Task name</td>
+                <td>Depends on</td>
+            </thead>
+            <tr>
+                <td>check</td>
+                <td>All Checkstyle and CodeNarc tasks, including <literal>checkstyleMain</literal>,
+                    <literal>checkstyleTest</literal>,
+                    <literal>codenarcMain</literal> and
+                    <literal>codenarcTest</literal>
+                </td>
+            </tr>
+        </table>
+        <figure>
+            <title>Code quality plugin - tasks</title>
+            <imageobject>
+                <imagedata fileref="img/codeQualityPluginTasks.png"/>
+            </imageobject>
+        </figure>
+    </section>
+
+    <section>
+        <title>Project layout</title>
+        <para>The code quality plugin expects the following project layout:</para>
+        <table>
+            <title>Code quality plugin - project layout</title>
+            <thead>
+                <tr>
+                    <td>File</td>
+                    <td>Meaning</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <filename>config/checkstyle/checkstyle.xml</filename>
+                </td>
+                <td>Checkstyle configuration file</td>
+            </tr>
+            <tr>
+                <td>
+                    <filename>config/codenarc/codenarc.xml</filename>
+                </td>
+                <td>CodeNarc configuration file</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Dependency management</title>
+        <para>The code quality plugin does not add any dependency configurations.</para>
+    </section>
+
+    <section>
+        <title>Convention properties</title>
+        <para>When used with the Java plugin, the code quality plugin adds the following convention properties to the project:</para>
+        <table>
+            <title>Code quality plugin - convention properties</title>
+            <thead>
+                <tr>
+                    <td>Property name</td>
+                    <td>Type</td>
+                    <td>Default value</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>checkstyleConfigFileName</literal>
+                </td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td>
+                    <literal>config/checkstyle/checkstyle.xml</literal>
+                </td>
+                <td>
+                    The location of the Checkstyle configuration file, relative to the project directory.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>checkstyleConfigFile</literal>
+                </td>
+                <td>
+                    <classname>File</classname> (read-only)
+                </td>
+                <td>
+                    <literal><replaceable>projectDir</replaceable>/<replaceable>checkstyleConfigFileName</replaceable></literal>
+                </td>
+                <td>
+                    The Checkstyle configuration file.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>checkstyleResultsDirName</literal>
+                </td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td>
+                    <literal>checkstyle</literal>
+                </td>
+                <td>
+                    The name of the directory to generate Checkstyle results into, relative to the build directory.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>checkstyleResultsDir</literal>
+                </td>
+                <td>
+                    <classname>File</classname> (read-only)
+                </td>
+                <td>
+                    <literal><replaceable>buildDir</replaceable>/<replaceable>checkstyleResultsDirName</replaceable></literal>
+                </td>
+                <td>
+                    The directory to generate Checkstyle results into.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>checkstyleProperties</literal>
+                </td>
+                <td>
+                    <classname>Map</classname>
+                </td>
+                <td>
+                    <literal>[:]</literal>
+                </td>
+                <td>
+                    The properties to use when loading the Checkstyle configuration.
+                </td>
+            </tr>
+        </table>
+
+        <para>These convention properties are provided by a convention object of type
+            <apilink class="org.gradle.api.plugins.quality.JavaCodeQualityPluginConvention" lang="groovy"/>.</para>
+
+        <para>When used with the Groovy plugin, the code quality plugin adds the following convention properties to the project:</para>
+
+        <table>
+            <title>Code quality plugin - convention properties</title>
+            <thead>
+                <tr>
+                    <td>Property name</td>
+                    <td>Type</td>
+                    <td>Default value</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>codeNarcConfigFileName</literal>
+                </td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td>
+                    <literal>config/codenarc/codenarc.xml</literal>
+                </td>
+                <td>
+                    The location of the CodeNarc configuration file, relative to the project directory.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>codeNarcConfigFile</literal>
+                </td>
+                <td>
+                    <classname>File</classname> (read-only)
+                </td>
+                <td>
+                    <literal><replaceable>projectDir</replaceable>/<replaceable>codeNarcConfigFileName</replaceable></literal>
+                </td>
+                <td>
+                    The CodeNarc configuration file.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>codeNarcReportsDirName</literal>
+                </td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td>
+                    <literal>codenarc</literal>
+                </td>
+                <td>
+                    The name of the directory to generate CodeNarc reports into, relative to the reports directory.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>codeNarcReportsDir</literal>
+                </td>
+                <td>
+                    <classname>File</classname> (read-only)
+                </td>
+                <td>
+                    <literal><replaceable>reportsDir</replaceable>/<replaceable>codeNarcReportsDirName</replaceable></literal>
+                </td>
+                <td>
+                    The directory to generate CodeNarc reports into.
+                </td>
+            </tr>
+        </table>
+
+        <para>These convention properties are provided by a convention object of type
+            <apilink class="org.gradle.api.plugins.quality.GroovyCodeQualityPluginConvention" lang="groovy"/>.</para>
+    </section>
+
+</chapter>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/commandLine.xml b/subprojects/gradle-docs/src/docs/userguide/commandLine.xml
new file mode 100644
index 0000000..3d932d9
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/commandLine.xml
@@ -0,0 +1,153 @@
+<!--
+  ~ 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.
+  -->
+<appendix id='gradle_command_line' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>Gradle Command Line</title>
+    <para>The <command>gradle</command> command has the following usage:
+        <cmdsynopsis>
+            <command>gradle</command>
+            <arg choice="opt" rep="repeat">option</arg>
+            <arg choice="opt" rep="repeat">task-name</arg>
+        </cmdsynopsis>
+        The command-line options available for the <command>gradle</command> command are listed below:
+    </para>
+    <variablelist>
+        <varlistentry>
+            <term><option>-?</option>, <option>-h</option>, <option>--help</option></term>
+            <listitem><para>Shows a help message.</para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-C</option>, <option>--cache</option></term>
+            <listitem><para>Specifies how compiled build scripts should be cached. Possible values are:
+                <literal>rebuild</literal> or <literal>on</literal>. Default value is
+                <literal>on</literal>. See <xref linkend="sec:caching"/>.
+            </para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-D</option>, <option>--system-prop</option></term>
+            <listitem><para>Sets a system property of the JVM, for example <literal>-Dmyprop=myvalue</literal>.
+            </para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-I</option>, <option>--init-script</option></term>
+            <listitem><para>Specifies an initialization script. See <xref linkend="init_scripts"/>.</para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-P</option>, <option>--project-prop</option></term>
+            <listitem><para>Sets a project property of the root project, for example
+                <literal>-Pmyprop=myvalue</literal>.
+            </para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-S</option>, <option>--full-stacktrace</option></term>
+            <listitem><para>Print out the full (very verbose) stacktrace for any exceptions. See <xref linkend="logging"/>.
+            </para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-a</option>, <option>--no-rebuild</option></term>
+            <listitem><para>Do not rebuild project dependencies.
+            </para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>--all</option></term>
+            <listitem><para>Shows additional detail in the task listing. See <xref linkend="sec:listing_tasks"/>.
+            </para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-b</option>, <option>--build-file</option></term>
+            <listitem><para>Specifies the build file. See <xref linkend="sec:selecting_build"/>.
+            </para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-c</option>, <option>--settings-file</option></term>
+            <listitem><para>Specifies the settings file.</para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-d</option>, <option>--debug</option></term>
+            <listitem><para>Log in debug mode (includes normal stacktrace). See <xref linkend="logging"/>.
+            </para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-e</option>, <option>--embedded</option></term>
+            <listitem><para>Specify an embedded build script.
+            </para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-g</option>, <option>--gradle-user-home</option></term>
+            <listitem><para>Specifies the Gradle user home directory.
+            </para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>--gui</option></term>
+            <listitem><para>Launches the Gradle GUI. See <xref linkend="tutorial_gradle_gui"/>.
+            </para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-i</option>, <option>--info</option></term>
+            <listitem><para>Set log level to info. See <xref linkend="logging"/>.
+            </para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-m</option>, <option>--dry-run</option></term>
+            <listitem><para>Runs the build with all task actions disabled.</para> </listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-n</option>, <option>--dependencies</option></term>
+            <listitem><para>Show list of all project dependencies. See <xref linkend="sec:listing_dependencies"/>.
+            </para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-p</option>, <option>--project-dir</option></term>
+            <listitem><para>Specifies the start directory for Gradle. Defaults to current directory.
+                See <xref linkend="sec:selecting_build"/>.
+            </para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-q</option>, <option>--quiet</option></term>
+            <listitem><para>Log errors only. See <xref linkend="logging"/>.</para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-r</option>, <option>--properties</option></term>
+            <listitem><para>Show list of all available project properties. See <xref linkend="sec:listing_properties"/>.</para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-s</option>, <option>--stacktrace</option></term>
+            <listitem><para>Print out the stacktrace also for user exceptions (e.g. compile error). See <xref linkend="logging"/>.
+            </para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-t</option>, <option>--tasks</option></term>
+            <listitem><para>Show list of all available tasks. See <xref linkend="sec:listing_tasks"/>.
+            </para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-u</option>, <option>--no-search-upwards</option></term>
+            <listitem><para>Don't search in parent directories for a <filename>settings.gradle</filename> file.
+            </para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-v</option>, <option>--version</option></term>
+            <listitem><para>Prints version info.
+            </para></listitem>
+        </varlistentry>
+        <varlistentry>
+            <term><option>-x</option>, <option>--exclude-task</option></term>
+            <listitem><para>Specifies a task to be excluded from execution. See <xref linkend="sec:excluding_tasks_from_the_command_line"/>.
+            </para></listitem>
+        </varlistentry>
+    </variablelist>
+    
+    <para>The same information is printed to the console when you execute <userinput>gradle -h</userinput>.</para>
+</appendix>
diff --git a/subprojects/gradle-docs/src/docs/userguide/commandLineTutorial.xml b/subprojects/gradle-docs/src/docs/userguide/commandLineTutorial.xml
new file mode 100644
index 0000000..b7526b4
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/commandLineTutorial.xml
@@ -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.
+  -->
+<chapter id="tutorial_gradle_command_line">
+    <title>Using the Gradle Command-Line</title>
+    <para>This chapter introduces the basics of the Gradle command-line. You run a build using the
+        <command>gradle</command> command, which you have already seen in action in previous chapters.</para>
+
+    <section>
+        <title>Executing multiple tasks</title>
+        <para>You can execute multiple tasks in a single build by listing each of the tasks on the command-line. For example,
+            the command <userinput>gradle compile test</userinput> will execute the <literal>compile</literal> and
+            <literal>test</literal> tasks. Gradle will execute the tasks in the order that they are listed on the
+            command-line, and will also execute the dependencies for each task. Each task is executed once only,
+            regardless of why it is included in the build: whether it was specified on the command-line, or it a
+            dependency of another task, or both. Let's look at an example.</para>
+        <para>
+            Below four tasks are defined. Both <literal>dist</literal> and <literal>test</literal> depend on the
+            <literal>compile</literal> task. Running <userinput>gradle dist test</userinput> for this build script
+            results in the <literal>compile</literal> task being executed only once.</para>
+        <figure>
+            <title>Task dependencies</title>
+            <imageobject>
+                <imagedata fileref="img/commandLineTutorialTasks.png"/>
+            </imageobject>
+        </figure>
+        <sample id="multipleTasksFromCommandLine" dir="userguide/tutorial/excludeTasks" title="Executing multiple tasks">
+            <sourcefile file="build.gradle"/>
+            <output args="dist test"/>
+        </sample>
+        <para>
+            Because each task is executed once only, executing <userinput>gradle test test</userinput> is exactly the same
+            as executing <userinput>gradle test</userinput>.</para>
+    </section>
+
+    <section id="sec:excluding_tasks_from_the_command_line">
+        <title>Excluding tasks</title>
+        <para>You can exclude a task from being executed using the <option>-x</option> command-line option and providing
+            the name of the task to exclude. Let's try this with the sample build file above.</para>
+        <sample id="excludeTask" dir="userguide/tutorial/excludeTasks" title="Excluding tasks">
+            <output args="dist -x test"/>
+        </sample>
+        <para>You can see from the output of this example, that the <literal>test</literal> task is not executed, even
+            though it is a dependency of the <literal>dist</literal> task. You will also notice that the
+            <literal>test</literal> task's dependencies, such as <literal>compileTest</literal>
+            are not executed either. Those dependencies of <literal>test</literal> that are required by another task, such as
+            <literal>compile</literal>, are still executed.</para>
+    </section>
+
+    <section>
+        <title>Task name abbreviation</title>
+        <para>When you specify tasks on the command-line, you don't have to provide the full name of the task. You only need to provide enough of the
+            task name to uniquely identify the task. For example, in the sample build above, you can execute task
+            <literal>dist</literal> by running <userinput>gradle d</userinput>:</para>
+        <sample id="abbreviateTaskName" dir="userguide/tutorial/excludeTasks" title="Abbreviated task name">
+            <output args="d"/>
+        </sample>
+        <para>You can also abbreviate each word in a camel case task name. For example, you can execute task <literal>compileTest</literal>
+            by running <userinput>gradle compTest</userinput> or even <userinput>gradle cT</userinput></para>
+        <sample id="abbreviateCamelCaseTaskName" dir="userguide/tutorial/excludeTasks" title="Abbreviated camel case task name">
+            <output args="cT"/>
+        </sample>
+        <para>You can also use these abbreviations with the <option>-x</option> command-line option.</para>
+    </section>
+
+    <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:
+        </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>
+        <sample id="selectProjectUsingProjectDir" dir="userguide/tutorial/selectProject" title="Selecting the project using project directory">
+            <output args="-q -p subdir hello"/>
+        </sample>
+    </section>
+
+    <section id="sec:obtaining_information_about_your_build">
+        <title>Obtaining information about your build</title>
+        <para>Gradle provides several command-line options which show particular details of your build. This can be
+            useful for understanding the structure and dependencies of your build, and for debugging problems.
+        </para>
+        <para>If a report option is used without additional parameters, the respective report is generated for the current project.
+           If you add append <literal>?</literal> to the option the report is generated for the current project and all its subprojects.
+           If you add append a <link linkend="sec:project_and_task_paths">project path</link> the report will be generated for the
+            specified project.
+        </para>
+        <para>In addition to the command-line options shown below, you can also use the
+            <link linkend="project_reports_plugin">project report plugin</link> to add tasks to your project which will
+            generate these reports.
+        </para>
+        <section id="sec:listing_tasks">
+            <title>Listing tasks</title>
+            <para>Running <userinput>gradle --tasks</userinput> or <userinput>gradle -t</userinput>
+                gives you a list of the main tasks which make up the selected project. This report shows the default
+                tasks for the project, if any, and a description for each task. Below is an example of this report:
+            </para>
+            <sample id="taskListReport" dir="userguide/tutorial/projectReports" title="Obtaining information about tasks">
+                <output args="-q -t"/>
+            </sample>
+            <para>By default, this report shows only those tasks which have been assigned to a task group. You can do this
+                by setting the <literal>group</literal> property for the task. You can also set the <literal>description</literal>
+                property, to provide a description to be included in the report for the task.
+            </para>
+            <sample id="taskListReport" dir="userguide/tutorial/projectReports" title="Changing the content of the task report">
+                <sourcefile file="build.gradle" snippet="add-task-to-report"/>
+            </sample>
+            <para>You can obtain more information in the task listing by adding the <option>--all</option> option. With
+                this option, the task report lists all tasks in the project, grouped by main task, and the dependencies
+                for each task. Here is an example:
+            </para>
+            <sample id="taskListAllReport" dir="userguide/tutorial/projectReports" title="Obtaining more information about tasks">
+                <output args="-q -t --all"/>
+            </sample>
+        </section>
+        <section id="sec:listing_dependencies">
+            <title>Listing project dependencies</title>
+            <para id="para:commandline_dependency_report">Running <userinput>gradle --dependencies</userinput>
+                gives you a list of the dependencies of the selected project, broken down by configuration.  For each
+                configuration, the direct and transitive dependencies of that configuration are shown in a tree. Below
+                is an example of this report:
+            </para>
+            <sample id="dependencyListReport" dir="userguide/tutorial/projectReports" title="Obtaining information about dependencies">
+                <output args="-q --dependencies ?"/>
+            </sample>
+        </section>
+        <section id="sec:listing_properties">
+            <title>Listing project properties</title>
+            <para>Running <userinput>gradle --properties</userinput> gives you a list of the properties of the selected
+                project. This is a snippet from the output:
+            </para>
+            <sample id="propertyListReport" dir="userguide/tutorial/projectReports" title="Information about properties">
+                <output args="-q --properties :api" ignoreExtraLines="true"/>
+            </sample>
+        </section>
+    </section>
+
+    <section>
+        <title>Dry Run</title>
+        <para>Sometimes you are interested in which tasks are executed in which order for a given set of tasks specified on the
+            command line, but you don't want the tasks to be executed. You can use the <option>-m</option> option for this.
+            For example <userinput>gradle -m clean compile</userinput> shows you all tasks to be executed as
+            part of the <literal>clean</literal> and <literal>compile</literal> tasks.
+            This is complementary to the <option>-t</option> option, which shows you the tasks which are available for
+            execution.
+        </para>
+    </section>
+
+    <section>
+        <title>Summary</title>
+        <para>In this chapter, you have seen some of the things you can do with Gradle from the command-line. You can
+            find out more about the <command>gradle</command> command in <xref linkend="gradle_command_line"/>.</para>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/customPlugins.xml b/subprojects/gradle-docs/src/docs/userguide/customPlugins.xml
new file mode 100644
index 0000000..451dac2
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/customPlugins.xml
@@ -0,0 +1,104 @@
+<!--
+  ~ 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.
+  -->
+<chapter id="custom_plugins" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>Writing Custom Plugins</title>
+
+    <para>A Gradle plugin packages up reusable pieces of build logic, which can be used across many different
+        projects and builds. Gradle allows you to implement your own custom plugins, so you can reuse your
+        build logic, and potentially share it with others.
+    </para>
+
+    <para>
+        You can implement a custom plugin in any language you like, provided the implementation ends up compiled as
+        bytecode. For the examples here, we are going to use Groovy as the implementation language. You could use
+        Java or Scala instead, if you want.
+    </para>
+
+    <section>
+        <title>Packaging a plugin</title>
+        <para>There are several places where you can put the source for the plugin. Two convenient options
+            are to add the task implementation to your build script, or to put the source in the
+            <filename><replaceable>rootProjectDir</replaceable>/buildSrc/src/main/groovy</filename> directory. Gradle will
+            take care of compiling the task and making it available on the classpath of the build script.
+            See <xref linkend="organizing_build_logic"/> for more details, and some other options.
+            In our examples, we will put the task implementation in the build script, to keep things simple.
+        </para>
+    
+    </section>
+
+    <section>
+        <title>Writing a simple plugin</title>
+        <para>To create a custom plugin, you need to write an implementation of <apilink class="org.gradle.api.Plugin"/>.
+            Gradle instantiates the plugin and calls the plugin instance's <literal>apply()</literal> method when the plugin
+            is used with a project. The project
+            object is passed as a parameter, which the plugin can use to configure the project however it needs to.
+            The following sample contains a greeting plugin, which adds a <literal>hello</literal> task to the project.
+        </para>
+
+        <sample id="customPlugin" dir="userguide/organizeBuildLogic/customPlugin" title="A custom plugin">
+            <sourcefile file="build.gradle"/>
+            <output args="-q hello"/>
+        </sample>
+
+        <para>One thing to note is that only one instance of a given plugin is created for a given build. The same plugin
+            instance is used for all projects in the build.
+        </para>
+    </section>
+
+    <section>
+        <title>Getting input from the build</title>
+        <para>Most plugins need to read user defined input from the build script. Plugins read values using convention objects. 
+             The Gradle <apilink class="org.gradle.api.Project"/> has a <apilink class="org.gradle.api.plugins.Convention"/> object 
+             that helps keep track of all the settings and properties being passed to plugins. You can capture user input by telling
+             the Project Convention about your plugin. To capture input, simply add a Java Bean compliant class into the Convention's list of 
+             plugins. Groovy is a good language choice for a plugin because plain old Groovy objects contain all the getter and setter methods
+             that a Java Bean requires. 
+        </para>
+
+        <para>Let's add a simple convention object to the project. Here we add a <literal>greeting</literal> property to the
+            project, which allows you to configure the greeting.
+        </para>
+
+        <sample id="customPluginWithConvention" dir="userguide/organizeBuildLogic/customPluginWithConvention" title="A custom plugin convention">
+            <sourcefile file="build.gradle"/>
+            <output args="-q hello"/>
+        </sample>
+
+        <para>In this example, GreetingPluginConvention is a plain old Groovy object with a field called <literal>greeting</literal>. 
+            The convention object is added to the plugin list with the name <literal>greet</literal>. The name of the variable in
+            the build needs to match the name of the field in the convention object.  The name you choose for your plugin 
+            (<literal>greet</literal>) is arbitrary and can be whatever you choose. 
+        </para>
+
+        <para>Oftentimes, you have several related properties you need to specify on a single plugin. With Groovy plugins it is easy
+            to offer a configuration closure block to group settings together. The following example shows you how to do this. 
+        </para>
+
+        <sample id="customPluginWithAdvancedConvention" dir="userguide/organizeBuildLogic/customPluginWithAdvancedConvention" title="A custom plugin with closure convention">
+            <sourcefile file="build.gradle"/>
+            <output args="-q hello"/>
+        </sample>
+
+        <para>In this example, several convention settings can be grouped together within the <literal>greet</literal> closure. 
+            The name of the closure block in the build script (<literal>greet</literal>) needs a matching method on 
+            the convention object, and that method must take a closure as an argument. Then, when the closure is executed, 
+            the fields on the convention object will be mapped to the variables within the closure based on the standard
+            Groovy closure delegate feature. This technique is possible in other JVM languages but may not be as convenient 
+            as in Groovy. 
+        </para>
+
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/customTasks.xml b/subprojects/gradle-docs/src/docs/userguide/customTasks.xml
new file mode 100644
index 0000000..0d3b045
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/customTasks.xml
@@ -0,0 +1,144 @@
+<!--
+  ~ 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.
+  -->
+<chapter id="custom_tasks">
+    <title>Writing Custom Tasks</title>
+
+    <para>Gradle supports two types of task. One such type is the simple task, where you define the task with an
+        action closure. We have seen these in <xref linkend="tutorial_using_tasks"/>. For this type of task, the action
+        closure determines the behaviour of the task. This type of task is good for implementing one-off tasks in your
+        build script.
+    </para>
+    <para>
+        The other type of task is the enhanced task, where the behaviour is built into the task, and the task provides some
+        properties which you can use to configure the behaviour. We have seen these in
+        <xref linkend="more_about_tasks"/>. Most Gradle plugins use enhanced tasks. With enhanced tasks, you don't need
+        to implement the task behaviour as you do with simple tasks. You simply declare and configure the task using
+        its properties.
+        In this way, enhanced tasks let you reuse a piece of behaviour in many different places, possibly
+        across different builds.
+    </para>
+    <para>Implementing your own custom enhanced tasks in Gradle is easy.
+        You can implement a custom task in pretty much any language you like, provided it ends up compiled to bytecode.
+        In our examples, we are going to use Groovy as the implementation language, but you could use, for example, Java
+        or Scala.
+    </para>
+
+    <section>
+        <title>Packaging a task</title>
+        <para>There are several places where you can put the source for the task.
+        </para>
+        <variablelist>
+            <varlistentry>
+                <term>Build script</term>
+                <listitem>
+                    <para>You can include the task implementation directly in the build script. This has the
+                        benefit that the task class is automatically compiled and included in the classpath of the
+                        build script without you having to do anything. However, the task class is not visible outside the
+                        build script, and so you cannot reuse the task class outside the build script it is defined in.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term><filename>buildSrc</filename> project</term>
+                <listitem>
+                    <para>You can put the source for the task implementation in the
+                        <filename><replaceable>rootProjectDir</replaceable>/buildSrc/src/main/groovy</filename> directory.
+                        Gradle will take care of compiling and testing the task class and making it available on the
+                        classpath of the build script. The task class is visible to every build script used by the build.
+                        However, it is not visible outside the build, and so you cannot reuse the task class outside the
+                        build it is defined in.
+                        Using the <filename>buildSrc</filename> project approach keeps separate
+                        the task declaration - that is, what the task should do - from the task implementation - that is,
+                        how the task does it.</para>
+                    <para>
+                        See <xref linkend="organizing_build_logic"/> for more details about the <filename>buildSrc</filename>
+                        project.</para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>Standalone project</term>
+                <listitem>
+                    <para>You can create a separate project for your task implementation. This project produces and
+                        publishes a JAR which you can then use in multiple builds and share with others. Generally, this JAR
+                        might include a custom plugin, or bundle several related custom tasks into a single library. Or
+                        some combination.
+                    </para>
+                </listitem>
+            </varlistentry>
+        </variablelist>
+        <para> In our examples, we will start with the task implementation in the build script, to keep things simple.
+            Then we will look at creating a standalone project.
+        </para>
+    </section>
+
+    <section>
+        <title>Writing a simple task</title>
+        <para>To implement a custom task, you extend <apilink class="org.gradle.api.DefaultTask" lang="groovy"/>.
+        </para>
+        <sample id="customTask" dir="userguide/tasks/customTask" title="Defining a custom task">
+            <sourcefile file="build.gradle" snippet="define-task"/>
+        </sample>
+
+        <para>This task doesn't do anything useful, so let's add some behaviour. To do so, we add a method to the task
+            and mark it with the <apilink class="org.gradle.api.tasks.TaskAction"/> annotation. Gradle will call the
+            method when the task executes. You don't have to use a method to define the behaviour for the task. You
+            could, for instance, call <literal>doFirst()</literal> or <literal>doLast()</literal> with a closure in the
+            task constructor to add behaviour.
+        </para>
+        <sample id="customTaskWithAction" dir="userguide/tasks/customTask" title="A hello world task">
+            <sourcefile file="build.gradle" snippet="add-action"/>
+            <output args="-q hello"/>
+        </sample>
+
+        <para>Let's add a property to the task, so we can customize it. Tasks are simply POGOs, and when you declare a
+            task, you can set the properties or call methods on the task object. Here we add a <literal>greeting</literal>
+            property, and set the value when we declare the <literal>greeting</literal> task.
+        </para>
+        <sample id="customTaskWithProperty" dir="userguide/tasks/customTaskWithProperty" title="A customizable hello world task">
+            <sourcefile file="build.gradle" snippet="add-property"/>
+            <output args="-q hello greeting"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>A standalone project</title>
+        <para>Now we will move our task to a standalone project, so we can publish it and share it with others.
+            This project is simply a Groovy project that produces a JAR containing the task implementation.
+            Here is a simple build script for the project. It applies the Groovy plugin, and adds the Gradle API
+            as a compile-time dependency.
+        </para>
+
+        <sample id="customTaskStandalone" dir="customPlugin" title="A build for a custom task" includeLocation="true">
+            <sourcefile file="build.gradle" snippet="use-plugin"/>
+        </sample>
+
+        <para>We just follow the convention for where the source for the task should go.</para>
+
+        <sample id="customTaskStandalone" dir="customPlugin" title="A custom task">
+            <sourcefile file="src/main/groovy/org/gradle/GreetingTask.groovy"/>
+        </sample>
+
+        <section>
+            <title>Writing tests</title>
+            <para>You can use the <apilink class="org.gradle.testfixtures.ProjectBuilder"/> class to create
+                <apilink class="org.gradle.api.Project"/> instances to use when you test your task implementation.
+            </para>
+            <sample id="customTaskStandalone" dir="customPlugin" title="Testing a custom task">
+                <sourcefile file="src/test/groovy/org/gradle/GreetingTaskTest.groovy" snippet="test-task"/>
+            </sample>
+        </section>
+    </section>
+</chapter>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/depMngmt.xml b/subprojects/gradle-docs/src/docs/userguide/depMngmt.xml
new file mode 100644
index 0000000..c54e24f
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/depMngmt.xml
@@ -0,0 +1,634 @@
+<!--
+  ~ 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.
+  -->
+<chapter id='dependency_management' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>Dependency Management</title>
+    <section id='sec:Introduction'>
+        <title>Introduction</title>
+        <para>This chapter gives an overview of issues related with dependency management and presents how Gradle can be used to overcome them.
+        </para>
+        <para>Gradle offers a very good support for dependency management. If you are familiar with Maven or Ivy approach you will be delighted to learn that:
+        <itemizedlist>
+            <listitem>
+                <para>All the concepts that you already know and like are still there and are fully supported by Gradle. The current dependency management solutions all require to work with XML descriptor files and are usually
+        based on remote repositories for downloading the dependencies. Gradle fully supports this approach.
+                </para>
+            </listitem>
+            <listitem>
+                <para>Gradle works <emphasis>perfectly</emphasis> with your existent dependency management
+                    infrastructure, be it Maven or Ivy. All the repositories you have set up with your custom POM or
+                    ivy files can be used as they are. No changes necessary.
+                </para>
+            </listitem>
+            <listitem>
+                <para>Additionally, Gradle offers a simpler approach, which might be better suited for some projects.
+                </para>
+            </listitem>
+        </itemizedlist>
+        </para>
+    </section>
+    <section id='sec:dependency_management_overview'>
+        <title>Dependency management overview</title>
+        <para>We think dependency management is very important for almost any project. Yet the kind of dependency
+            management you need depends on the complexity and the environment of your project. Is your project a
+            distribution or a library? Is it part of an enterprise environment, where it is integrated into other
+            projects builds or not? But all types of projects share the following requirements:
+        </para>
+        <itemizedlist>
+            <listitem>
+                <para>The version of the jar must be easy to recognize. Sometimes the version is in the Manifest file of
+                    the jar, often not. And even if, it is rather painful to always look into the Manifest file to learn
+                    about the version. Therefore we think that you should only use jars which have their version as part
+                    of their file name.
+                </para>
+            </listitem>
+            <listitem>
+                <para>It hopes to be clear what are the first level dependencies and what are the transitive ones. There
+                    are different ways to achieve this. We will look at this later.
+                </para>
+            </listitem>
+            <listitem>
+                <para>Conflicting versions of the same jar should be detected and either resolved or cause an exception.
+                </para>
+            </listitem>
+        </itemizedlist>
+        <section id='sub:versioning_the_jar_name'>
+            <title>Versioning the jar name</title>
+            <para>Why do we think this is necessary? Without a dependency management as described above, your are likely
+                to burn your fingers sooner or later. If it is unclear which version of a jar your are using, this can
+                introduce subtle bugs which are very hard to find. For example there might be a project which uses
+                Hibernate 3.0.4. There are some problems with Hibernate so a developer installs version 3.0.5 of
+                Hibernate on her machine. This did not solve the problem but she forgot to roll back Hibernate to 3.0.4.
+                Weeks later there is an exception on the integration machine which can't be reproduced on the developer
+                machine. Without a version in the jar name this problem might take a long time to debug. Version in the
+                jar names increases the expressiveness of your project and makes it easier to maintain.
+            </para>
+        </section>
+        <section id='sub:transitive_dependency_management'>
+            <title>Transitive dependency management</title>
+            <para>Why is transitive dependency management so important? If you don't know which dependencies are first
+                level dependencies and which ones are transitive you will soon lose control over your build. Even
+                Gradle has already 20+ jars. An enterprise project using Spring, Hibernate, etc. easily ends up with
+                100+ jars. There is no way to memorize where all these jars come from. If you want to get rid of a first
+                level dependency you can't be sure which other jars you should remove. Because a dependency of a
+                first level dependency might also be a first level dependency itself. Or it might be a transitive
+                dependency of another of your first level dependencies. Many first level dependencies are runtime
+                dependencies and the transitive dependencies are of course all runtime dependencies. So the compiler
+                won't help you much here. The end of the story is, as we have seen very often, no one dares to remove
+                any jar any longer. The project classpath is a complete mess and if a classpath problem arises, hell on
+                earth invites you for a ride. In one of my former projects, I found some ldap related jar in the
+                classpath, whose sheer presence, as I found out after much research, accelerated LDAP access. So
+                removing this jar would not have led to any errors at compile or runtime.
+            </para>
+            <para>Gradle offers you different ways to express what are first level and what are transitive dependencies.
+                Gradle allows you for example to store your jars in CVS or SVN without XML descriptor files and still
+                use transitive dependency management. Gradle also validates your dependency hierarchy against the
+                reality of your code by using only the first level dependencies for compiling.
+            </para>
+        </section>
+        <section id='sub:version_conflicts'>
+            <title>Version conflicts</title>
+            <para>In your dependency description you tell Gradle which version of a dependency is needed by another
+                dependency. This frequently leads to conflicts. Different dependencies rely on different versions of
+                another dependency. The JVM unfortunately does not offer yet any easy way, to have different versions of
+                the same jar in the classpath (see <xref linkend='sub:dependency_management_and_java'/>). What Gradle
+                offers you is a resolution strategy, by default the newest version is used. To deal with problems due to
+                version conflicts, reports with dependency graphs are also very helpful. Such reports are another
+                feature of dependency management.
+            </para>
+        </section>
+        <section id='sub:dependency_management_and_java'>
+            <title>Dependency management and Java</title>
+            <para>Traditionally, Java has offered no support at all for dealing with libraries and versions. There are
+                no standard ways to say that
+                <literal>foo-1.0.jar</literal>
+                depends on a <literal>bar-2.0.jar</literal>. This has led to proprietary solutions. The most popular ones
+                are Maven and Ivy. Maven is a complete build system whereas Ivy focuses solely on dependency management.
+            </para>
+            <para>Both approaches rely on descriptor XML files, which contains information about the dependencies of a
+                particular jar. Both also use repositories where the actual jars are placed together with their
+                descriptor files. And both offer resolution for conflicting jar versions in one form or the other. Yet
+                we think the differences of both approaches are significant
+                in terms of flexibility and maintainability. Beside this, Ivy fully supports the Maven dependency
+                handling. So with Ivy you have access to both worlds. We like Ivy very much. Gradle uses it under the
+                hood for its dependency management. Ivy is most often used via Ant and XML descriptors. But it also has
+                an API. We integrate deeply with Ivy via its API. This enables us to build new concepts on top
+                of Ivy which Ivy does not offer itself.
+            </para>
+            <para>Right now there is a lot of movement in the field of dependency handling. There is OSGi and there is
+                JSR-294.
+                <footnote>
+                    <para>JSR 294: Improved Modularity Support in the JavaTM Programming Language, <ulink url='http://jcp.org/en/jsr/detail?id=294'/>
+                    </para>
+                </footnote>
+                OSGi is available already, JSR-294 is supposed to be shipped with Java 7. These technologies
+                deal, amongst many other things, also with a painful problem which is neither solved by Maven nor by Ivy. This is enabling different
+                versions of the same jar to be used at runtime.
+            </para>
+        </section>
+    </section>
+    <section id='sec:how_to_declare_your_dependencies'>
+        <title>How to declare your dependencies</title>
+        <para>People who know Ivy have come across most of the concepts we are going to introduce now. But Gradle does not
+            use any XML for declaring the dependencies (e.g. no <literal>ivy.xml</literal> file). It has its own
+            notation which is part of the Gradle build file.
+        </para>
+        <section id='sub:configurations'>
+            <title>Configurations</title>
+            <para>Dependencies are grouped in configurations. Configurations have a name, a number of other properties,
+                and they can extend each other. For examples see: <xref linkend='sec:artifact_configurations'/>.
+                If you use the Java plugin, Gradle adds a number of pre-defined configurations to your build. The
+                plugin also assigns configurations to tasks. See <xref linkend='sec:java_plugin_and_dependency_management'/>
+                for details. Of course you can add your add custom configurations on top of that. There are many use cases
+                for custom configurations. This is very handy for example for adding dependencies not needed for
+                building or testing your software (e.g. additional JDBC drivers to be shipped with your distribution).
+                The configurations are managed by a <literal>configurations</literal> object. The closure you pass to
+                the configurations object is applied against its API. To learn more about this API have a look at
+                <apilink class='org.gradle.api.artifacts.ConfigurationContainer'/>.
+            </para>
+        </section>
+        <section id='sub:module_dependencies'>
+            <title>Module dependencies</title>
+            <para>Module dependencies are the most common dependencies. They correspond to a dependency in an external
+                repository.
+            </para>
+            <sample id="moduleDependencies" dir="userguide/artifacts/externalDependencies" title="Module dependencies">
+                <sourcefile file="build.gradle" snippet="module-dependencies"/>
+            </sample>
+            <para>Gradle provides different notations for module dependencies. There is a string notation and
+                a map notation. A module dependency has an API which allows for further configuration. Have a look at
+                <apilink class='org.gradle.api.artifacts.ExternalModuleDependency'/> to learn all about the API.
+                This API provides properties and configuration methods. Via the string notation you can define a subset
+                the properties. With the map notation you can define all properties. To have access to the complete API,
+                either with the map or with the string notation, you can assign a single dependency to a configuration
+                together with a closure.
+            </para>
+            <para>If you declare a module dependency, Gradle looks for a corresponding module descriptor file (<literal>pom.xml</literal> or
+                <literal>ivy.xml</literal>) in the repositories. If such a module descriptor file exists, it is parsed and the artifacts of
+                this module (e.g. <literal>hibernate-3.0.5.jar</literal>) as well as its dependencies (e.g. cglib) are downloaded. If no such
+                module descriptor file exists, Gradle looks for a file called <literal>hibernate-3.0.5.jar</literal> to retrieve. In Maven
+                a module can only have one and only one artifact. In Gradle and Ivy a module can have multiple artifacts.
+                Each artifact can have a different set of dependencies.
+            </para>
+            <section id='ssub:artifact_dependencies'>
+                <title>Artifact only notation</title>
+                <para>As said above, if no module descriptor file can be found, Gradle by default
+                    downloads a jar with the name of the module. But sometimes, even if the repository contains module descriptors,  you want to download only the artifact jar, without
+                    the dependencies.
+                    <footnote>
+                        <para>Gradle supports partial multiproject builds (see<xref linkend='multi_project_builds'/>).
+                        </para>
+                    </footnote>
+                    And sometimes you want to download a zip from a repository, that does not have module descriptors.
+                    Gradle provides an <emphasis>artifact only</emphasis> notation for those use cases - simply prefix the extension that you want to be downloaded with <literal>'@'</literal> sign:
+                    <sample id="artifactOnly" dir="userguide/artifacts/externalDependencies" title="Artifact only notation">
+                        <sourcefile file="build.gradle" snippet="artifact-only"/>
+                    </sample>
+                    An artifact only notation creates a module dependency which downloads only the artifact file with
+                    the specified extension. Existing module descriptors are ignored.
+                </para>
+            </section>
+            <section id='ssub:classifiers'>
+                <title>Classifiers</title>
+                <para>The Maven dependency management has the notion of classifiers.
+                    <footnote>
+                        <para>
+                            <ulink url='http://www.sonatype.com/books/maven-book/reference/pom-relationships-sect-project-relationships.html'/>
+                        </para>
+                    </footnote>
+                    Gradle supports this. To retrieve classified dependencies from a maven repository you can write:
+                </para>
+                <sample id="classifier" dir="userguide/artifacts/excludesAndClassifiers" title="Dependency with classifier">
+                        <sourcefile file="build.gradle" snippet="classifier"/>
+                </sample>
+                <para>As you can see in the example, classifiers can be used together with setting
+                an explicit extension (artifact only notation).</para>
+            </section>
+        </section>
+        <section id='sub:client_module_dependencies'>
+            <title>Client module dependencies</title>
+            <para>Client module dependencies enable you to declare <emphasis>transitive</emphasis>
+                dependencies directly in your build script. They are a replacement for a module descriptor XML file in
+                an external repository.
+            </para>
+            <sample id="client-modules" dir="userguide/artifacts/externalDependencies" title="Client module dependencies - transitive dependencies">
+                <sourcefile file="build.gradle" snippet="client-modules"/>
+            </sample>
+            <para>This declares a dependency of your project on Groovy. Groovy itself has dependencies. But Gradle does
+                not look for an XML descriptor to figure them out but gets the information from the build file. The
+                dependencies of a client module can be normal module dependencies or artifact dependencies or another
+                client module. Have also a look at the javadoc: <apilink class='org.gradle.api.artifacts.ClientModule'/>
+            </para>
+            <para>In the current release client modules have one limitation. Let's say your project is a library and
+                you want this library to be uploaded to your company's Maven or Ivy repository. Gradle uploads the
+                jars of your project to the company repository together with the XML descriptor file of the dependencies.
+                If you use client modules the dependency declaration in the XML descriptor file is not correct. We will
+                improve this in a future release of Gradle.
+            </para>
+        </section>
+        <section id='sub:project_dependencies'>
+            <title>Project dependencies</title>
+            <para>Gradle distinguishes between external dependencies and dependencies on projects which are part of the
+                same multi-project build. For the latter you can declare <firstterm>Project Dependencies</firstterm>.
+            </para>
+            <sample id="project-dependencies" dir="java/multiproject/api" title="Project dependencies">
+                <sourcefile file="build.gradle" snippet="project-dependencies"/>
+            </sample>
+            <para>For more information see the javadoc for <apilink class="org.gradle.api.artifacts.ProjectDependency"/>
+            </para>
+            <para>Multi-project builds are discussed in <xref linkend='multi_project_builds'/>.
+            </para>
+        </section>
+        <section>
+            <title>File dependencies</title>
+            <para>File dependencies allow you to directly add a set of files to a configuration, without first adding
+                them to a repository. This can be useful if you cannot, or do not want to, place certain files in a
+                repository. Or if you do not want to use any repositories at all for storing your dependencies.
+            </para>
+            <para>To add some files as a dependency for a configuration, you simply pass a
+                <link linkend="sec:file_collections">file collection</link> as a dependency:</para>
+            <sample id="file-dependencies" dir="userguide/artifacts/externalDependencies" title="File dependencies">
+                <sourcefile file="build.gradle" snippet="file-dependencies"/>
+            </sample>
+            <para>File dependencies are not included in the published dependency descriptor for your project.
+                However, file dependencies are included in transitive project dependencies within the same build.
+                This means they cannot be used outside the current build, but they can be used with the same build.
+            </para>
+            <para>
+                You can declare which tasks produce the files for a file dependency. You might do this when, for example,
+                the files are generated by the build.
+            </para>
+            <sample id="generatedFileDependencies" dir="userguide/artifacts/generatedFileDependencies" title="Generated file dependencies">
+                <sourcefile file="build.gradle" snippet="generated-file-dependencies"/>
+                <output args="-q list"/>
+            </sample>
+        </section>
+        <section>
+            <title>Gradle API Dependency</title>
+            <para>You can declare a dependency on the API of the current version of Gradle by using the
+                <apilink class="org.gradle.api.artifacts.dsl.DependencyHandler" method="gradleApi"/> method. This is
+                useful when you are developing custom Gradle tasks or plugins.</para>
+            <sample id="gradle-api-dependencies" dir="java/multiproject/buildSrc" title="Gradle API dependencies">
+                <sourcefile file="build.gradle" snippet="gradle-api-dependencies"/>
+            </sample>
+        </section>
+        <section id='sub:exclude_transitive_dependencies'>
+            <title>Excluding transitive dependencies</title>
+            <para>You can exclude a <emphasis>transitive</emphasis> dependency either by configuration or by dependency:
+            </para>
+            <sample id="exclude-dependencies" dir="userguide/artifacts/excludesAndClassifiers" title="Excluding transitive dependencies">
+                <sourcefile file="build.gradle" snippet="exclude-dependencies"/>
+            </sample>
+            <para>If you define
+                an exclude for a particular configuration, the excluded transitive dependency will be filtered for all
+                dependencies when resolving this configuration or any inheriting configuration.
+                If you want to exclude a transitive dependency from all your
+                configurations you can use the Groovy spread-dot operator to express this in a concise way, as shown in the example.
+                When defining an exclude, you can
+                specify either only the organization or only the module name or both.
+                Have also a look at the javadoc of <apilink class="org.gradle.api.artifacts.Dependency"/> and
+                <apilink class="org.gradle.api.artifacts.Configuration"/>.
+            </para>
+        </section>
+        <section>
+            <title>Optional attributes</title>
+            <para id="para:dependencies_with_empty_attributes">All attributes for a dependency are optional, except the name. It depends on the repository type,
+                which information is need for actually finding the dependencies in the repository.
+                See <xref linkend='sec:repositories'/>. If you work for example with Maven repositories, you need to define the
+                group, name and version. If you work with filesystem repositories you might only need the name or the name
+                and the version.
+            </para>
+            <sample id="dependenciesWithEmptyAttributes" dir="userguide/artifacts/externalDependencies" title="Optional attributes of dependencies">
+                <sourcefile file="build.gradle" snippet="dependencies-with-empty-attributes"/>
+            </sample>
+            <para id="para:notation_collections">You can also assign collections or arrays of dependency notations to a configuration:
+            </para>
+            <sample id="listGrouping" dir="userguide/artifacts/externalDependencies" title="Collections and arrays of dependencies">
+                <sourcefile file="build.gradle" snippet="list-grouping"/>
+            </sample>
+        </section>
+        <section id="sec:dependency_configurations">
+            <title>Dependency configurations</title>
+            <para>In Gradle a dependency can have different configurations (as your project can have different configurations). If you
+            don't specify anything explicitly, Gradle uses the default configuration of the dependency. For dependencies
+            from a Maven repository, the default configuration is the only available one anyway. If you work with Ivy repositories and
+            want to declare a non-default configuration for your dependency you have to use the map notation and declare:
+            </para>
+            <sample id="dependencyConfigurations" dir="userguide/artifacts/externalDependencies" title="Dependency configurations">
+                <sourcefile file="build.gradle" snippet="dependency-configurations"/>
+            </sample>
+            <para>To do the same for project dependencies you need to declare:</para>
+            <sample id="dependencyConfigurationsProjects" dir="/java/multiproject/services/webservice"  title="Dependency configurations for project">
+                <sourcefile file="build.gradle" snippet="dependency-configurations"/>
+            </sample>
+        </section>
+        <section>
+            <title>Dependency reports</title>
+            <para>You can generate dependency reports from the command line (see <xref linkend="para:commandline_dependency_report"/>).
+                With the help of the Project report plugin (see <xref linkend="project_reports_plugin"/>) such a report can be created by your build.
+            </para>
+        </section>
+    </section>
+    <section>
+        <title>Working with dependencies</title>
+        <para>For the examples below we have the following dependencies setup:</para>
+        <sample id="configurationHandlingSetup" dir="userguide/artifacts/configurationHandling" title="Configuration.copy">
+            <sourcefile file="build.gradle" snippet="setup"/>
+        </sample>
+        <para>The dependencies have the following transitive dependencies:</para>
+        <para>shark-1.0 -> seal-2.0, tuna-1.0</para>
+        <para>orca-1.0 -> seal-1.0</para>
+        <para>tuna-1.0 -> herring-1.0</para>
+        <para>You can use the configuration to access the declared dependencies or a subset of those:
+        </para>
+        <sample id="configurationHandlingDependencies" dir="userguide/artifacts/configurationHandling" title="Accessing declared dependencies">
+            <sourcefile file="build.gradle" snippet="dependencies"/>
+            <output args="-q dependencies"/>
+        </sample>
+        <para><code>dependencies</code> returns only the dependencies belonging explicitly to the configuration.
+            <code>allDependencies</code> includes the dependencies from extended
+            configurations.
+        </para>
+        <para>To get the library files of the configuration dependencies you can do:
+        </para>
+        <sample id="configurationHandlingAllFiles" dir="userguide/artifacts/configurationHandling" title="Configuration.files">
+            <sourcefile file="build.gradle" snippet="allFiles"/>
+            <output args="-q allFiles"/>
+        </sample>
+        <para>Sometimes you want the library files of a subset of the configuration dependencies (e.g. of a single dependency).
+        </para>
+        <sample id="configurationHandlingFiles" dir="userguide/artifacts/configurationHandling" title="Configuration.files with spec">
+            <sourcefile file="build.gradle" snippet="files"/>
+            <output args="-q files"/>
+        </sample>
+        <para>The <code>Configuration.files</code> method always retrieves all artifacts of the <emphasis>whole</emphasis> configuration. It
+        then filters the retrieved files by specified dpendencies. As you can see in the example, transitive dependencies are included.
+        </para>
+        <para>You can also copy a configuration. You can optionally specify that only a subset of dependencies from the orginal configuration
+            should be copied. The copying methods come in two flavors. The <code>copy</code> method copies only the dependencies belonging
+            explicitly to the configuration. The <code>copyRecursive</code> methode copies all the dependencies, including the dependencies from extended
+            configurations.
+        </para>
+        <sample id="configurationHandlingCopy" dir="userguide/artifacts/configurationHandling" title="Configuration.copy">
+            <sourcefile file="build.gradle" snippet="copy"/>
+             <output args="-q copy"/>
+        </sample>
+        <para>It is important to note that the returned files of the copied configuration
+            are often but not always the same than the returned files of the dependency subset of the original configuration.
+            In case of version conflicts between
+            dependencies of the subset and dependencies not belonging to the subset the resolve result might be different.</para>
+        <sample id="configurationHandlingCopyVsFiles" dir="userguide/artifacts/configurationHandling" title="Configuration.copy vs. Configuration.files">
+            <sourcefile file="build.gradle" snippet="copyVsFiles"/>
+             <output args="-q copyVsFiles"/>
+        </sample>
+        <para>In the example above, <code>orca</code> has a dependency on <code>seal-1.0</code> whereas
+            <code>shark</code> has a dependency on <code>seal-2.0</code>. The original configuration has therefore a version
+            conflict which is resolved to the newer <code>seal-2.0</code> version. The <code>files</code> method therefore
+            returns <code>seal-2.0</code> as a transitive dependency of <code>orca</code>. The copied configuration only has <code>orca</code>
+            as a dependency and therefore there is no version conflict and <code>seal-1.0</code> is returned as a transitive
+            dependency.
+        </para>
+        <para>Once a configuration is resolved it is immutable. Changing its state or the state of one of its dependencies
+            will cause an exception. You can always copy a resolved configuration. The copied configuration is in the unresolved
+            state and can be freshly resolved.
+        </para>
+        <para>To learn more about the API of the configuration class see the javadoc:
+            <apilink class='org.gradle.api.artifacts.Configuration'/>.
+        </para>
+    </section>
+    <section id='sec:repositories'>
+        <title>Repositories</title>
+        <section id='sub:introduction'>
+            <title>Introduction</title>
+            <para>The Gradle repository management, based on Apache Ivy, gives you have a lot of freedom
+                regarding the repository layout and the retrieval policies. Additionally Gradle provides a couple of convenience
+                method to add preconfigured repositories.
+            </para>
+        </section>
+        <section id='sub:maven_repo'>
+            <title>Maven repositories</title>
+            <para>To add the central Maven2 repository (<ulink url='http://repo1.maven.org/maven2'/>) simply
+                type:
+            </para>
+            <sample id="mavenCentral" dir="userguide/artifacts/defineRepository" title="Adding central Maven repository">
+                <sourcefile file="build.gradle" snippet="maven-central"/>
+            </sample>
+            <para>Now Gradle looks for your dependencies in this repository.
+            </para>
+            <para>Quite often certain jars are not in the official Maven repository for licensing reasons (e.g. JTA),
+                but its POMs are.
+            </para>
+            <sample id="mavenCentralJarRepo" dir="userguide/artifacts/defineRepository" title="Adding many Maven repositories">
+                <sourcefile file="build.gradle" snippet="maven-central-jar-repo"/>
+            </sample>
+            <para>Gradle looks first in the central Maven repository for the POM and the JAR. If the JAR can't be
+                found there, its looks for it in the other repositories.
+            </para>
+            <para>For adding a custom Maven repository you can say:
+            </para>
+            <sample id="mavenLikeRepo" dir="userguide/artifacts/defineRepository" title="Adding custom Maven repository">
+                <sourcefile file="build.gradle" snippet="maven-like-repo"/>
+            </sample>
+            <para>To declare additional repositories to look for jars (like above in the example
+                for the central Maven repository), you can say:
+            </para>
+            <sample id="mavenLikeRepoWithJarRepo" dir="userguide/artifacts/defineRepository" title="Adding additional Maven repositories for JAR files">
+                <sourcefile file="build.gradle" snippet="maven-like-repo-with-jar-repo"/>
+            </sample>
+            <para>The first URL is used to look for POMs and JARs. The subsequent URLs are used to look for JARs.
+            </para>
+            <section>
+            	<title>Accessing password protected Maven repositories</title>
+            	<para>To access a password protected Maven repository (basic authentication) you need to use one of Ivy features:</para>
+    	        <sample id="mavenPasswordProtectedRepo" dir="userguide/artifacts/defineRepository" title="Accessing password protected Maven repository">
+	                <sourcefile file="build.gradle" snippet="maven-password-protected-repo"/>
+        	    </sample>
+        	    <para>Host name should not include <literal>"http://"</literal> prefix. It is advisable to keep your login and password in <filename>gradle.properties</filename> rather than directly in the build file.</para>
+            </section>
+        </section>
+        <section id='sec:flat_dir_resolver'>
+            <title>Flat directory resolver</title>
+            <para>If you want to use a (flat) filesytem directory as a repository, simply type:
+            </para>
+            <sample id="flatDirMulti" dir="userguide/artifacts/defineRepository" title="Flat repository resolver">
+                <sourcefile file="build.gradle" snippet="flat-dir-multi"/>
+            </sample>
+            <para>This adds repositories which look into one or more directories for finding dependencies. If you only
+                work with flat directory resolvers you don't need to set all attributes of a dependency.
+                See <xref linkend='para:dependencies_with_empty_attributes'/>
+            </para>
+        </section>
+        <section>
+            <title>More about preconfigured repositories</title>
+            <para>The methods above for creating preconfigured repositories share some common behavior. For all of them, defining
+                a name for the repository is optional. If no name is defined a default name is calculated, depending on the
+                type of the repository. You might want to assign a name, if you want to access the declared repository. For example
+                if you want to use it also for uploading your project artifacts. An explicit name might also be helpful when
+                studying the debug output.
+            </para>
+            <para>The values passed as arguments to the repository methods can be of any type, not just String. The value
+                that is actually used, is the <code>toString</code> result of the argument object.
+            </para>
+        </section>
+        <section id='sub:cache'>
+            <title>Cache</title>
+            <para>When Gradle downloads dependencies from remote repositories it stores them in a local cache located at
+                <literal>USER_HOME/.gradle/cache</literal>. When Gradle downloads dependencies from one of its
+                predefined local resolvers (e.g. Flat Directory resolver), the cache is not used as an intermediate
+                storage for dependency artifacts. The cache is always used for caching module descriptors.
+            </para>
+        </section>
+        <section id='sub:more_about_ivy_resolvers'>
+            <title>More about Ivy resolvers</title>
+            <para>Gradle, thanks to Ivy under its hood, is extremely flexible regarding repositories:
+            </para>
+            <itemizedlist>
+                <listitem>
+                    <para>There are many options for the protocol to communicate with the repository (e.g. filesystem,
+                        http, ssh, ...)
+                    </para>
+                </listitem>
+                <listitem>
+                    <para>Each repository can have its own layout.
+                    </para>
+                </listitem>
+            </itemizedlist>
+            <para>Let's say, you declare a dependency on the
+                <literal>junit:junit:3.8.2</literal> library.
+                Now how does Gradle find it in the repositories? Somehow the dependency information has to be mapped to a
+                path. In contrast to Maven, where this path is fixed, with Gradle you can define a pattern that defines
+                what the path will look like. Here are some examples:
+                <footnote>
+                    <para>At
+                        <ulink url='http://ant.apache.org/ivy/history/latest-milestone/concept.html'/>
+                        you can learn more about ivy patterns.
+                    </para>
+                </footnote>
+            </para>
+            <programlisting><![CDATA[
+// Maven2 layout (if a repository is marked as Maven2 compatible, the organization (group) is split into subfolders according to the dots.)
+someroot/[organisation]/[module]/[revision]/[module]-[revision].[ext]
+
+// Typical layout for an ivy repository (the organization is not split into subfolder)
+someroot/[organisation]/[module]/[revision]/[type]s/[artifact].[ext]
+
+// Simple layout (the organization is not used, no nested folders.)
+someroot/[artifact]-[revision].[ext]
+]]></programlisting>
+            <para>To add any kind of repository (you can pretty easy write your own ones) you can do:
+            </para>
+            <sample id="fileSystemResolver" dir="userguide/artifacts/excludesAndClassifiers" title="Definition of a custom repository">
+                <sourcefile file="build.gradle" snippet="file-system-resolver"/>
+            </sample>
+            <para>An overview of which Resolvers are offered by Ivy and thus also by Gradle can be found
+                <ulink url='http://ant.apache.org/ivy/history/latest-milestone/settings/resolvers.html'>here</ulink>. With
+                Gradle you just don't configure them via XML but directly via their API.
+            </para>
+        </section>
+    </section>
+    <section id='sec:strategies_of_transitive_dependency_management'>
+        <title>Strategies for transitive dependency management</title>
+        <para>Many projects rely on the <ulink url='http://repo1.maven.org/maven2'>Maven2 repository</ulink>. This is not
+            without problems.
+        </para>
+        <itemizedlist>
+            <listitem>
+                <para>The IBibilio repository can be down or has a very long response time.
+                </para>
+            </listitem>
+            <listitem>
+                <para>The <literal>pom.xml</literal>'s of many projects have wrong information (as one example, the POM of
+                    <literal>commons-httpclient-3.0</literal> declares JUnit as a runtime dependency).
+                </para>
+            </listitem>
+            <listitem>
+                <para>For many projects there is not one right set of dependencies (as more or less imposed by the
+                    <literal>pom</literal>
+                    format).
+                </para>
+            </listitem>
+        </itemizedlist>
+        <para>If your project relies on the IBibilio repository you are likely to need an additional custom repository,
+            because:
+        </para>
+        <itemizedlist>
+            <listitem>
+                <para>You might need dependencies that are not uploaded to IBibilio yet.
+                </para>
+            </listitem>
+            <listitem>
+                <para>You want to deal properly with wrong metadata in a IBibilio <literal>pom.xml</literal>.
+                </para>
+            </listitem>
+            <listitem>
+                <para>You don't want to expose people who want to build your project, to the
+                    downtimes or sometimes very long response times of IBibilio.
+                </para>
+            </listitem>
+        </itemizedlist>
+        <para>It is not a big deal to set-up a custom repository.
+            <footnote>
+                <para>If you want to shield your project from the downtimes of IBibilio things get more complicated. You
+                    probably want to set-up a repository proxy for this. In an enterprise environment this is rather
+                    common. For an open source project it looks like overkill.
+                </para>
+            </footnote>
+            But it can be tedious, to keep it up to date. For a new version, you have always to create the new XML
+            descriptor and the directories. And your custom repository is another infrastructure element which might
+            have downtimes and needs to be updated. To enable historical builds, you need to keep all the past
+            libraries and you need a backup. It is another layer of indirection. Another source of information
+            you have to lookup. All this is not really a big deal but in its sum it has an impact. Repository Manager like
+            Artifactory or Nexus make this easier. But for example open source projects don't usually have a host for those products.
+        </para>
+        <para>This is a reason why some projects prefer to store their libraries in their version control system. This
+            approach is fully supported by Gradle. The libraries can be stored in a flat directory without any XML module
+            descriptor files.  Yet Gradle offers complete transitive dependency management. You can use either client module
+            dependencies to express the dependency relations, or artifact dependencies in case a first level dependency has no
+            transitive dependencies. People can check out such a project from svn and have everything necessary to build it.
+        </para>
+        <para>If you are working with a distributed version control system like Git you probably don't want to
+        use the version control system to store libraries as people check out the whole history. But even here the flexibility
+        of Gradle can make your life easier. For example you can use a shared flat directory without XML descriptors and
+        yet you can have full transitive dependency management as described above.</para>
+        <para>You could also have a mixed strategy. If your main concern is bad metadata in the <literal>pom.xml</literal> and maintaining
+            custom XML descriptors,
+            <emphasis>Client Modules</emphasis>
+            offer an alternative. But you can of course still use Maven2 repo and your custom repository as a
+            repository for
+            <emphasis>jars only</emphasis>
+            and still enjoy
+            <emphasis>transitive</emphasis>
+            dependency management. Or you can only provide client modules for POMs with bad metadata. For the
+            jars and the correct POMs you still use the remote repository.
+        </para>
+        <section id='sub:implicit_transitive_dependencies'>
+            <title>Implicit transitive dependencies</title>
+            <para>There is another way to deal with transitive dependencies
+                <emphasis>without</emphasis>
+                XML descriptor files. You can do this with Gradle, but we don't recommend it. We mention it for the sake
+                of completeness and comparison with other build tools.
+            </para>
+            <para>The trick is to use only artifact dependencies and group them in lists. That way you have somehow
+                expressed, what are your first level dependencies and what are transitive dependencies (see
+                <xref linkend="para:notation_collections"/>).
+                But the draw-back is, that for the Gradle dependency management all dependencies are considered first level dependencies. The
+                dependency reports don't show your real dependency graph and the
+                <literal>compile</literal>
+                task uses all dependencies, not just the first level dependencies. All in all, your build is less
+                maintainable and reliable than it could be when using client modules. And you don't gain anything.
+            </para>
+        </section>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/eclipsePlugin.xml b/subprojects/gradle-docs/src/docs/userguide/eclipsePlugin.xml
new file mode 100644
index 0000000..0ef720f
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/eclipsePlugin.xml
@@ -0,0 +1,450 @@
+<!--
+  ~ 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.
+  -->
+
+<chapter id="eclipse_plugin">
+    <title>The Eclipse Plugin</title>
+    
+    <para>The Eclipse plugin generates files that are used by <ulink url="http://eclipse.org">Eclipse IDE</ulink>, thus
+        making it possible to import the project into Eclipse (<guimenuitem>File</guimenuitem> - <guimenuitem>Import...</guimenuitem> - <guimenuitem>Existing Projects into Workspace</guimenuitem>).
+        Both external and project dependencies are considered.</para>
+
+    <para>The Eclipse plugin will create different files depending on the other plugins used. If used together with
+        the <link linkend="java_plugin">Java plugin</link>, <filename>.project</filename> and <filename>.classpath</filename>
+        files will be generated. If used with the <link linkend="war_plugin">War plugin</link>, additional wtp files
+        will be generated.</para>
+<section>
+        <title>Usage</title>
+    <para>To use the Eclipse plugin, include in your build script:</para>
+    <sample id="useEclipsePlugin" dir="eclipse" title="Using the Eclipse plugin">
+        <sourcefile file="build.gradle" snippet="use-plugin"/>
+    </sample>
+    <para>There are several tasks (presented in <xref linkend='eclipsetasks'/>) that the Eclipse plugin provides. Some tasks
+        are preconfigured to use the <literal>GRADLE_CACHE</literal> eclipse classpath variable. To make use of this variable
+        you need to define it in Eclipse pointing to <literal><USER_HOME>/.gradle/cache</literal>.
+        In a future release of Gradle this will be automated (<ulink url="http://jira.codehaus.org/browse/GRADLE-1074">Issue GRADLE-1074</ulink>).
+</para>
+    </section>
+    <section>
+        <title>Tasks</title>
+
+        <para>The Eclipse plugin adds the tasks shown below to a project.</para>
+
+        <table id='eclipsetasks'>
+            <title>Eclipse plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>eclipse</literal>
+                </td>
+                <td><literal>eclipseProject</literal>, <literal>eclipseClasspath</literal>, <literal>eclipseWtp</literal></td>
+                <td><literal>-</literal></td>
+                <td>Generates all the eclipse configuration files</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>cleanEclipse</literal>
+                </td>
+                <td>
+                    <literal>cleanEclipseProject</literal>, <literal>cleanEclipseClasspath</literal>, <literal>cleanEclipseWtp</literal>
+                </td>
+                <td><apilink class="org.gradle.api.tasks.Delete"/></td>
+                <td>Removes all eclipse configuration files</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>cleanEclipseProject</literal>
+                </td>
+                <td>
+                    <literal>-</literal>
+                </td>
+                <td><apilink class="org.gradle.api.tasks.Delete"/></td>
+                <td>Removes the eclipse project file</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>cleanEclipseClasspath</literal>
+                </td>
+                <td>
+                    <literal>-</literal>
+                </td>
+                <td><apilink class="org.gradle.api.tasks.Delete"/></td>
+                <td>Removes the eclipse classpath file</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>cleanEclipseWtp</literal>
+                </td>
+                <td>
+                    <literal>-</literal>
+                </td>
+                <td><apilink class="org.gradle.api.tasks.Delete"/></td>
+                <td>Removes the eclipse .settings directory</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>eclipseProject</literal>
+                </td>
+                <td>
+                    <literal>-</literal>
+                </td>
+                <td><apilink class="org.gradle.plugins.eclipse.EclipseProject" lang="groovy"/></td>
+                <td>Generates the eclipse project file.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>eclipseClasspath</literal>
+                </td>
+                <td>
+                    <literal>-</literal>
+                </td>
+                <td><apilink class="org.gradle.plugins.eclipse.EclipseClasspath" lang="groovy"/></td>
+                <td>Generates the Eclipse classpath file.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>eclipseWtp</literal>
+                </td>
+                <td>
+                    <literal>-</literal>
+                </td>
+                <td><apilink class="org.gradle.plugins.eclipse.EclipseWtp" lang="groovy"/></td>
+                <td>Generates the <filename>org.eclipse.wst.common.component</filename> and 
+                    <filename>org.eclipse.wst.common.project.facet.core</filename> files.</td>
+            </tr>
+        </table>
+
+        <table id='eclipse-project'>
+            <title>EclipseProject task</title>
+            <thead>
+                <tr>
+                    <td>Property</td>
+                    <td>Type</td>
+                    <td>Default Value</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>projectName</literal>
+                </td>
+                <td>String</td>
+                <td><literal>project.name</literal></td>
+                <td>The name of the Eclipse project. Must not be null.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>comment</literal>
+                </td>
+                <td>String</td>
+                <td>null</td>
+                <td>A comment for the eclipse project.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>referencedProjects</literal>
+                </td>
+                <td>Set<String></td>
+                <td>empty set</td>
+                <td>The referenced projects of the Eclipse project.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>natures</literal>
+                </td>
+                <td>List<String></td>
+                <td>The default is an empty set. Applying Java, Groovy, Scala or War plugin
+                will add additional natures.</td>
+                <td>The natures of the Eclipse project.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>buildCommands</literal>
+                </td>
+                <td>List<BuildCommand></td>
+                <td>The default is an empty set. Applying Java, Groovy, Scala or War plugin
+                will add additional build commands.</td>
+                <td>The build commands of the Eclipse project.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>links</literal>
+                </td>
+                <td>Set<Link></td>
+                <td>empty set</td>
+                <td>The links for this Eclipse project.</td>
+            </tr>
+        </table>
+
+        <table id='eclipse-classpath'>
+            <title>EclipseClasspath task</title>
+            <thead>
+                <tr>
+                    <td>Property</td>
+                    <td>Type</td>
+                    <td>Default Value</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>sourceSets</literal>
+                </td>
+                <td>DomainObjectContainer</td>
+                <td>
+                    <literal>sourceSets</literal>
+                </td>
+                <td>The source sets which source directories should be added to the Eclipse classpath.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>containers</literal>
+                </td>
+                <td>Set<String></td>
+                <td>empty set</td>
+                <td>The containers to be added to the Eclipse classpath.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>plusConfigurations</literal>
+                </td>
+                <td>Set<Configuration></td>
+                <td><literal>[configurations.testRuntime]</literal></td>
+                <td>The configurations, which files are to be transformed into classpath entries.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>minusConfigurations</literal>
+                </td>
+                <td>Set<Configuration></td>
+                <td><literal>empty set</literal></td>
+                <td>The configurations which files are to be excluded from the classpath entries.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>downloadSources</literal>
+                </td>
+                <td>boolean</td>
+                <td>true</td>
+                <td>Whether to download sources for the external dependencies.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>downloadJavadoc</literal>
+                </td>
+                <td>boolean</td>
+                <td>false</td>
+                <td>Whether to download javadoc for the external dependencies.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>variables</literal>
+                </td>
+                <td>Map<String,String></td>
+                <td><literal>[GRADLE_CACHE: <path to gradle cache>]</literal></td>
+                <td>If the beginning of the absolute path of a library matches a value of a variable,
+                a variable entry is created. The matching part of the library path is replaced with the variable name.</td>
+            </tr>
+        </table>
+
+        <table id='eclipse-wtp'>
+            <title>EclipseWtp task</title>
+            <thead>
+                <tr>
+                    <td>Property</td>
+                    <td>Type</td>
+                    <td>Default Value</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>sourceSets</literal>
+                </td>
+                <td>DomainObjectContainer</td>
+                <td>
+                    <literal>sourceSets</literal>
+                </td>
+                <td>The source sets which source directories should be added to the Eclipse classpath.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>deployName</literal>
+                </td>
+                <td>String</td>
+                <td><literal>project.name</literal></td>
+                <td>The deploy name to be used in the org.eclipse.wst.common.component file.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>plusConfigurations</literal>
+                </td>
+                <td>Set<Configuration></td>
+                <td><literal>[configurations.testRuntime]</literal></td>
+                <td>The configurations, which files are to be transformed into classpath entries.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>minusConfigurations</literal>
+                </td>
+                <td>Set<Configuration></td>
+                <td><literal>[configurations.providedRuntime]</literal></td>
+                <td>The configurations which files are to be excluded from the classpath entries.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>variables</literal>
+                </td>
+                <td>Map<String,String></td>
+                <td><literal>[GRADLE_CACHE: <path to gradle cache>]</literal></td>
+                <td>If the beginning of the absolute path of a library matches a value of a variable,
+                a variable entry is created. The matching part of the library path is replaced with the variable name.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>facets</literal>
+                </td>
+                <td>List<Facet></td>
+                <td><literal>jst.java</literal> and <literal>jst.web</literal> facet</td>
+                <td>The facets to be added as installed elements to the org.eclipse.wst.common.project.facet.core file.</td>
+            </tr>
+        </table>
+
+
+        <table id='eclipse-task-hooks'>
+            <title>Task Hooks</title>
+            <thead>
+                <tr>
+                    <td>Method</td>
+                    <td>EclipseProject Task Argument</td>
+                    <td>EclipseClasspath Task Argument</td>
+                    <td>EclipseWtp Task Argument</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal><code>beforeConfigured { arg -> }</code></literal>
+                </td>
+                <td><apilink class="org.gradle.plugins.eclipse.model.Project" lang="groovy"/></td>
+                <td><apilink class="org.gradle.plugins.eclipse.model.Classpath" lang="groovy"/></td>
+                <td><apilink class="org.gradle.plugins.eclipse.model.Wtp" lang="groovy"/></td>
+                <td>Gets called directly after the domain objects have been populated with the content of the
+                    existing XML file (if there is one).</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal><code>whenConfigured { arg -> }</code></literal>
+                </td>
+                <td><apilink class="org.gradle.plugins.eclipse.model.Project" lang="groovy"/></td>
+                <td><apilink class="org.gradle.plugins.eclipse.model.Classpath" lang="groovy"/></td>
+                <td><apilink class="org.gradle.plugins.eclipse.model.Wtp" lang="groovy"/></td>
+                <td>Gets called after the domain objects have been populated with the content of the
+                    existing XML file and the content from the build script.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal><code>withXml { arg -> }</code></literal>
+                </td>
+                <td><code>groovy.util.Node</code></td>
+                <td><code>groovy.util.Node</code></td>
+                <td><code>['org.eclipse.wst.common.component': groovy.util.Node, 'org.eclipse.wst.common.project.facet.core': groovy.util.Node]</code></td>
+                <td>The root node of the XML just before the XML is written to disk.</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Customizing the generated files</title>
+        <para>All the tasks of the eclipse plugin provide the same hooks and behavior for customizing the generated content.</para>
+        <para>The tasks recognize existing eclipse files. If an eclipse project, classpath or wtp file does not exists,
+        default values are used. Otherwise the existing ones are merged.</para>
+        <section>
+            <title>Merging</title>
+            <para>The first option to customize the Eclipse files is to have an existing Eclipse file before the tasks are run.
+            Existing files will be merged together with the generated content.
+            Any section of the existing Eclipse files that are not the target of generated content will neither be changed nor removed.</para>
+            <section id="sec:complete-overwrite">
+                <title>Disabling merging with a complete overwrite</title>
+                <para>If you want Gradle to completely overwrite existing Eclipse files you can specify this on the command line by
+                    executing something like <userinput>gradle cleanEclipse eclipse</userinput> or <userinput>gradle cleanEclipseClasspath eclipseClasspath</userinput>.
+                    You can specify this also in the build script. Just make the generating tasks depending on the deleting tasks. You can tailor this
+                    to your needs. You could make the <literal>eclipse</literal> task dependent on the <literal>cleanEclipse</literal> task. If you only want
+                    to overwrite for example the classpath files you simply make the <literal>eclipseClasspath</literal> task dependent on the
+                    <literal>cleanEclipseClasspath</literal> task.
+                </para>
+            </section>
+        </section>
+        <section>
+            <title>Hooking into the generation lifecycle</title>
+            <para>The Eclipse plugin provides a couple of domain classes that model the Eclipse files.
+                But only the sections that are autogenerated by Gradle. The generation lifecycle is the following.
+                If there is an existing Eclipse file, its whole XML is parsed and stored in memory. Then the domain
+                objects are populated with the relevant content of the the existing XML. After that the build script information
+                is used to further populate those objects (e.g. add additional dependencies).
+                Then all sections modelled by the domain objects are removed from the XML in memory. After that the domain objects are used to inject
+                their content into the XML. Finally the XML is written to disk. The lifecycle provides hooks to modify the result according to your needs.
+            </para>
+            <section id="sec:partial-overwrite">
+                <title>Partial Overwrite</title>
+                <para>Doing a <link linkend="sec:complete-overwrite">complete overwrite</link> removes all your manual customizations.
+                    This might be not what you want.
+                    Therefore Gradle provides the option for a partial overwrite. You can hook into the phase just after the existing
+                    Eclipse files have populated the domain objects. You could then for example remove all the dependencies
+                    from the <literal>classpath</literal> object.
+                    <sample id="partialOverwrites" dir="eclipse"
+                            title="Partial Overwrite for Module">
+                        <sourcefile file="build.gradle" snippet="module-before-configured"/>
+                    </sample>
+                    The generated XML will have all sections of the existing Eclipse classpath file,
+                    except for the dependencies, where only the information of the build script is used. You could do something
+                    similar for the project file.
+                    <sample id="partialOverwritesProject" dir="eclipse"
+                            title="Partial Overwrite for Project">
+                        <sourcefile file="build.gradle" snippet="project-before-configured"/>
+                    </sample>
+                </para>
+            </section>
+            <section>
+                <title>Modifying the fully populated domain objects</title>
+                <para>You can also hook into the phase after the existing Eclipse files and the build script metadata have
+                    populated the domain objects. You could then for example disable export of all the dependencies
+                    of the <literal>module</literal> object.
+                    <sample id="exportDependencies" dir="eclipse"
+                            title="Export Dependencies">
+                        <sourcefile file="build.gradle" snippet="module-when-configured"/>
+                    </sample>
+
+                </para>
+            </section>
+            <section>
+                <title>Modifying the XML</title>
+                <para>You can also hook into the phase after the XML DOM is fully populated but not written to disk.
+                    That hook provides total control over what is generated.
+                    <sample id="file-dependencies" dir="eclipse"
+                            title="Customizing the XML">
+                        <sourcefile file="build.gradle" snippet="wtp-with-xml"/>
+                    </sample>
+                </para>
+            </section>
+        </section>
+    </section>
+</chapter>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/embedding.xml b/subprojects/gradle-docs/src/docs/userguide/embedding.xml
new file mode 100644
index 0000000..c67c1aa
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/embedding.xml
@@ -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.
+  -->
+<chapter id="embedding" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>Embedding Gradle</title>
+
+    <para>t.b.d.
+    </para>
+
+</chapter>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/glossary.xml b/subprojects/gradle-docs/src/docs/userguide/glossary.xml
new file mode 100644
index 0000000..636addf
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/glossary.xml
@@ -0,0 +1,37 @@
+<glossary>
+    <title>Glossary</title>
+    <glossdiv>
+        <title>D</title>
+        <glossentry>
+            <glossterm>DAG</glossterm>
+            <glosssee otherterm="dag"/>
+        </glossentry>
+        <glossentry id="dag">
+            <glossterm>Directed Acyclic Graph</glossterm>
+            <acronym>DAG</acronym>
+            <glossdef>
+                <para>A directed acyclic graph is a directed graph that contains no cycles. In Gradle each task to
+                execute represents a node in the graph. A dependsOn relation to another task will add this
+                other task as a node (if it is not in the graph already) and create a directed edge between
+                those two nodes. Any dependsOn relation will be validated for cycles. There must be no way
+                to start at certain node, follow a sequence of edges and end up at the original node.</para>
+            </glossdef>
+        </glossentry>
+        <glossentry id="dsl">
+            <glossterm>Domain Specific Language</glossterm>
+            <acronym>DSL</acronym>
+            <glossdef>
+                <para>A domain-specific language is a programming language or specification language dedicated
+                    to a particular problem domain, a particular problem representation technique, and/or a particular
+                    solution technique. The concept isn't new—special-purpose programming languages and all kinds of
+                    modeling/specification languages have always existed, but the term has become more popular due to
+                    the rise of domain-specific modeling.
+                </para>
+            </glossdef>
+        </glossentry>
+        <glossentry>
+            <glossterm>DSL</glossterm>
+            <glosssee otherterm="dsl"/>
+        </glossentry>
+    </glossdiv>
+</glossary>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/gradleWrapper.xml b/subprojects/gradle-docs/src/docs/userguide/gradleWrapper.xml
new file mode 100644
index 0000000..debcc8f
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/gradleWrapper.xml
@@ -0,0 +1,135 @@
+<!--
+  ~ 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.
+  -->
+<chapter id='gradle_wrapper' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>The Gradle Wrapper</title>
+    <para>Gradle is a new tool. You can't expect it to be installed on machines beyond your sphere of influence. An
+        example are continuous integration server where Gradle is not installed and where you have no admin rights for
+        the machine. Or what if you provide an open source project and you want to make it as easy as possible for your
+        users to build it?
+    </para>
+    <para>There is a simple and good news. Gradle provides a solution for this. It ships with a
+        <emphasis>Wrapper</emphasis>
+        task.
+        <footnote>
+            <para>If you download the Gradle source distribution or check out Gradle from SVN, you can build Gradle via
+                the Gradle wrapper.
+            </para>
+        </footnote>
+        <footnote>
+            <para>Gradle itself is continuously built by Bamboo and Teamcity via this wrapper. See
+                <ulink url='website:ci-server.html'/>
+            </para>
+        </footnote>
+        You can create such a task in your build script.
+    </para>
+    <sample id="wrapperSimple" dir="userguide/wrapper" title="Wrapper task">
+        <sourcefile file="build.gradle" snippet="wrapper-simple"/>
+    </sample>
+    <para>The build master usually explicitly executes this task. After such
+        an execution you find the following new or updated files in your project directory (in case the default configuration of the wrapper task is
+        used).
+    </para>
+    <screen><![CDATA[
+project-root/
+  gradlew
+  gradlew.bat
+  gradle-wrapper.jar
+  gradle-wrapper.properties
+]]></screen>
+    <para>All these files must be submitted to your version control system. The <command>gradlew</command> command
+        can be used <emphasis>exactly</emphasis> the same way as the <command>gradle</command> command.
+    </para>
+    <para>If you want to switch to a new version of Gradle you don't need to rerun the wrapper task. It is good enough
+        to change the respective entry in the <literal>gradle-wrapper.properties</literal> file. But if there is for
+        example an improvement in the gradle-wrapper functionality you need to regenerate the wrapper files.
+    </para>
+    <section id='sec:configuration'>
+        <title>Configuration</title>
+        <para>If you run Gradle with <command>gradlew</command>, Gradle checks if a Gradle distribution for the wrapper
+            is available. If not it tries to download it, otherwise it delegates to the <command>gradle</command>
+            command of this distribution with all the arguments passed originally to the <command>gradlew</command>
+            command.
+        </para>
+        <para>You can specify where the wrapper files should be stored (within your project directory):</para>
+        <sample id="wrapperCustomized" dir="userguide/wrapper" title="Configuration of wrapper task">
+            <sourcefile file="build.gradle"/>
+        </sample>
+        <screen><![CDATA[
+project-root/
+  gradlew
+  gradlew.bat
+  wrapper/
+    gradle-wrapper.jar
+    gradle-wrapper.properties
+]]></screen>
+        <para>
+            You can specify the download URL of the wrapper distribution. You can also specify where the wrapper distribution
+            should be stored and unpacked (either within the project or within the gradle user home dir). If the wrapper
+            is run and there is local archive of the wrapper distribution Gradle tries to download it and stores it at
+            the specified place. If there is no unpacked wrapper distribution Gradle unpacks the local archive of the
+            wrapper distribution at the specified place. All the configuration options have defaults except the version of the wrapper distribution.</para>
+        <para>For the details on how to configure the wrapper, see <apilink class="org.gradle.api.tasks.wrapper.Wrapper"/>
+        </para>
+        <para>If you don't
+            want any download to happen when your project is build via <command>gradlew</command>, simply add the Gradle
+            distribution zip to your version control at the location specified by your wrapper configuration.
+        </para>
+        <para>If you build via the wrapper, any existing Gradle distribution installed on the machine is ignored.
+        </para>
+    </section>
+    <section id='sec:unix_file_permissions'>
+        <title>Unix file permissions</title>
+        <para>The Wrapper task adds appropriate file permissions to allow the execution for the gradlew *NIX command.
+            Subversion preserves this file permission. We are not sure how other version control systems deal with this.
+            What should always work is to execute <literal>sh gradlew</literal>.
+        </para>
+    </section>
+    <section id='sec:environment_variable'>
+        <title>Environment variable</title>
+        <para>Some rather exotic use cases might occur when working with the Gradle Wrapper. For example the continuos
+            integration server goes down during unzipping the Gradle distribution. As the distribution directory exists
+            <command>gradlew</command>
+            delegates to it but the distribution is corrupt. Or the zip-distribution was not properly downloaded. When
+            you have no admin right on the continuous integration server to remove the corrupt files, Gradle offers a
+            solution via environment variables.
+        </para>
+        <table>
+            <title>Gradle wrapper environment variables</title>
+            <thead>
+                <tr>
+                    <td>Variable Name</td>
+                    <td>Meaning</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>GRADLE_WRAPPER_ALWAYS_UNPACK</td>
+                <td>If set to <literal>true</literal>, the distribution directory gets always deleted when
+                    <command>gradlew</command>
+                    is run and the distribution zip is freshly unpacked. If the zip is not there, Gradle tries to
+                    download it.
+                </td>
+            </tr>
+            <tr>
+                <td>GRADLE_WRAPPER_ALWAYS_DOWNLOAD</td>
+                <td>If set to <literal>true</literal>, the distribution directory and the distribution zip gets always
+                    deleted when <command>gradlew</command>
+                    is run and the distribution zip is freshly downloaded.
+                </td>
+            </tr>
+
+        </table>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/groovyPlugin.xml b/subprojects/gradle-docs/src/docs/userguide/groovyPlugin.xml
new file mode 100644
index 0000000..c1f05b4
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/groovyPlugin.xml
@@ -0,0 +1,320 @@
+<!--
+  ~ 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.
+  -->
+<chapter id='groovy_plugin' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>The Groovy Plugin</title>
+    <para>The Groovy plugin extends the Java plugin to add support for Groovy projects. It can deal with Groovy-only projects and
+        with mixed Java/Groovy projects. It can even deal with Java-only projects.
+        <footnote>
+            <para>We don't recommend this, as the Groovy plugin uses the <emphasis>Groovyc</emphasis>
+                Ant task to compile the sources. For pure Java projects you might rather stick with
+                <literal>javac</literal>. In particular as you would have to supply a groovy jar for doing this.
+            </para>
+        </footnote>
+        The Groovy plugin supports joint compilation of Java and Groovy. This means that your project can contain Groovy
+        classes which use Java classes, and vice versa.
+    </para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the Groovy plugin, include in your build script:</para>
+        <sample id="useGroovyPlugin" dir="groovy/quickstart" title="Using the Groovy plugin">
+            <sourcefile file="build.gradle" snippet="use-plugin"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>Tasks</title>
+        <para>The Groovy plugin adds the following tasks to the project.</para>
+        <table>
+            <title>Groovy plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td><literal>compileGroovy</literal></td>
+                <td><literal>compileJava</literal></td>
+                <td><apilink class="org.gradle.api.tasks.compile.GroovyCompile"/></td>
+                <td>Compiles production Groovy source files using groovyc.</td>
+            </tr>
+            <tr>
+                <td><literal>compileTestGroovy</literal></td>
+                <td><literal>compileTestJava</literal></td>
+                <td><apilink class="org.gradle.api.tasks.compile.GroovyCompile"/></td>
+                <td>Compiles test Groovy source files using groovyc.</td>
+            </tr>
+            <tr>
+                <td><literal>compile<replaceable>SourceSet</replaceable>Groovy</literal></td>
+                <td><literal>compile<replaceable>SourceSet</replaceable>Java</literal></td>
+                <td><apilink class="org.gradle.api.tasks.compile.GroovyCompile"/></td>
+                <td>Compiles the given source set's Groovy source files using groovyc.</td>
+            </tr>
+            <tr>
+                <td><literal>groovydoc</literal></td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.tasks.javadoc.Groovydoc"/></td>
+                <td>Generates API documentation for the production Groovy source files using groovydoc.</td>
+            </tr>
+        </table>
+        <para>The Groovy plugin adds the following dependencies to tasks added by the Java plugin.</para>
+        <table>
+            <title>Groovy plugin - additional task dependencies</title>
+            <thead>
+                <td>Task name</td>
+                <td>Depends on</td>
+            </thead>
+            <tr>
+                <td>classes</td>
+                <td>compileGroovy</td>
+            </tr>
+            <tr>
+                <td>testClasses</td>
+                <td>compileTestGroovy</td>
+            </tr>
+            <tr>
+                <td><replaceable>sourceSet</replaceable>Classes</td>
+                <td>compile<replaceable>SourceSet</replaceable>Groovy</td>
+            </tr>
+        </table>
+        <figure>
+            <title>Groovy plugin - tasks</title>
+            <imageobject>
+                <imagedata fileref="img/groovyPluginTasks.png"/>
+            </imageobject>
+        </figure>
+    </section>
+
+    <section>
+        <title>Project layout</title>
+        <para>The Groovy plugin assumes the project layout shown in <xref linkend='groovylayout'/>. All the Groovy
+            source directories can contain Groovy <emphasis>and</emphasis> Java code. The Java source directories may
+            only contain Java source code.
+            <footnote>
+                <para>We are using the same conventions as introduced by Russel Winders Gant tool (<ulink
+                        url='http://gant.codehaus.org'/>).
+                </para>
+            </footnote>
+            None of these directories need exist or have anything in them. The Groovy plugin will compile whatever it
+            finds, and handles anything which is missing.
+        </para>
+
+        <table id='groovylayout'>
+            <title>Groovy plugin - project layout</title>
+            <thead>
+                <tr>
+                    <td>Directory</td>
+                    <td>Meaning</td>
+                </tr>
+            </thead>
+            <xi:include href="javaProjectMainLayout.xml"/>
+            <tr>
+                <td>
+                    <filename>src/main/groovy</filename>
+                </td>
+                <td>Production Groovy source. May also contain Java source for joint compilation.</td>
+            </tr>
+            <xi:include href="javaProjectTestLayout.xml"/>
+            <tr>
+                <td>
+                    <filename>src/test/groovy</filename>
+                </td>
+                <td>Test Groovy source. May also contain Java source for joint compilation.</td>
+            </tr>
+            <xi:include href="javaProjectGenericLayout.xml"/>
+            <tr>
+                <td>
+                    <filename>src/<replaceable>sourceSet</replaceable>/groovy</filename>
+                </td>
+                <td>Groovy source for the given source set. May also contain Java source for joint compilation.</td>
+            </tr>
+        </table>
+
+        <section>
+            <title>Changing the project layout</title>
+            <para>TBD</para>
+            <sample id="customGroovySourceLayout" dir="groovy/customizedLayout" title="Custom Groovy source layout">
+                <sourcefile file="build.gradle" snippet="define-main"/>
+            </sample>
+        </section>
+        
+    </section>
+
+    <section>
+        <title>Dependency management</title>
+        <para>The Groovy plugin adds a dependency configuration called <literal>groovy</literal>.</para>
+        <para>Gradle is written in Groovy and allows you to write your build scripts in Groovy. But this is an internal
+            aspect of Gradle which is strictly separated from building Groovy projects. You are free to choose the Groovy
+            version your project should be build with. This Groovy version is not just used for compiling your code and
+            running your tests. The <literal>groovyc</literal> compiler and the the <literal>groovydoc</literal>
+            tool are also taken from the Groovy version you provide. As usual, with freedom comes responsibility ;). You are
+            not just free to choose a Groovy version, you have to provide one. Gradle expects that the groovy libraries are
+            assigned to the <literal>groovy</literal> dependency configuration. Here is an example using the public Maven
+            repository:
+        </para>
+        <sample id="quickstartGroovyDependency" dir="groovy/quickstart" title="Configuration of Groovy plugin">
+            <sourcefile file="build.gradle" snippet="groovy-dependency"/>
+        </sample>
+        <para>And here is an example using the Groovy JARs checked into the <filename>lib</filename> directory of the source
+            tree:</para>
+        <sample id="flatDirGroovyDependency" dir="userguide/tutorial/groovyWithFlatDir" title="Configuration of Groovy plugin">
+            <sourcefile file="build.gradle" snippet="groovy-dependency"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>Convention properties</title>
+        <para>The Groovy plugin does not add any convention properties to the project.</para>
+    </section>
+
+    <section>
+        <title>Source set properties</title>
+        <para>The Groovy plugin adds the following convention properties to each source set in the project. You can
+            use these properties in your build script as though they were properties of the source set object (see
+            <xref linkend="sub:more_about_convention_objects"/>).</para>
+        <table>
+            <title>Groovy plugin - source set properties</title>
+            <thead>
+                <tr>
+                    <td>Property name</td>
+                    <td>Type</td>
+                    <td>Default value</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>groovy</literal>
+                </td>
+                <td>
+                    <apilink class="org.gradle.api.file.SourceDirectorySet"/> (read-only)
+                </td>
+                <td>
+                    Not null
+                </td>
+                <td>
+                    The Groovy source files of this source set. Contains all <filename>.groovy</filename> and
+                    <filename>.java</filename> files found in the Groovy source directories, and excludes all other
+                    types of files.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>groovy.srcDirs</literal>
+                </td>
+                <td>
+                    <classname>Set<File></classname>. Can set using anything described in <xref linkend="sec:specifying_multiple_files"/>.
+                </td>
+                <td>
+                    <literal>[<replaceable>projectDir</replaceable>/src/<replaceable>name</replaceable>/groovy]</literal>
+                </td>
+                <td>
+                    The source directories containing the Groovy source files of this source set. May also contain
+                    Java source files for joint compilation.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>allGroovy</literal>
+                </td>
+                <td>
+                    <apilink class="org.gradle.api.file.FileTree"/> (read-only)
+                </td>
+                <td>
+                    Not null
+                </td>
+                <td>
+                    All Groovy source files of this source set. Contains only the <filename>.groovy</filename> files
+                    found in the Groovy source directories.
+                </td>
+            </tr>
+        </table>
+        <para>These properties are provided by a convention object of type <apilink class="org.gradle.api.tasks.GroovySourceSet"/>.</para>
+        <para>The Groovy plugin also modifies some source set properties:</para>
+        <table>
+            <title>Groovy plugin - source set properties</title>
+            <thead>
+                <tr>
+                    <td>Property name</td>
+                    <td>Change</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>allJava</literal>
+                </td>
+                <td>Adds all <filename>.java</filename> files found in the Groovy source directories.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>allSource</literal>
+                </td>
+                <td>Adds all source files found in the Groovy source directories.</td>
+            </tr>
+        </table>
+
+    </section>
+
+    <section id='sec:groovyCompile'>
+        <title>CompileGroovy</title>
+        <para>The Groovy plugin adds a <apilink class="org.gradle.api.tasks.compile.GroovyCompile"/> instance for
+            each source set in the project. The task type extends the <literal>Compile</literal>
+            task (see <xref linkend='sec:compile'/>). The compile task delegates to the Ant Groovyc task to do the
+            compile. Via the compile task you can set most of the properties of Ants Groovyc task.
+        </para>
+        <table>
+            <title>Groovy plugin - CompileGroovy properties</title>
+            <thead>
+                <tr>
+                    <td>Task Property</td>
+                    <td>Type</td>
+                    <td>Default Value</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>classpath</literal>
+                </td>
+                <td><apilink class="org.gradle.api.file.FileCollection"/></td>
+                <td><literal><replaceable>sourceSet</replaceable>.compileClasspath</literal></td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>source</literal>
+                </td>
+                <td><apilink class="org.gradle.api.file.FileTree"/>. Can set using anything described in <xref linkend="sec:specifying_multiple_files"/>.</td>
+                <td><literal><replaceable>sourceSet</replaceable>.groovy</literal></td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>destinationDir</literal>
+                </td>
+                <td><classname>File</classname>.</td>
+                <td><literal><replaceable>sourceSet</replaceable>.classesDir</literal></td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>groovyClasspath</literal>
+                </td>
+                <td><apilink class="org.gradle.api.file.FileCollection"/></td>
+                <td><literal>groovy</literal> configuration</td>
+            </tr>
+        </table>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/groovyTutorial.xml b/subprojects/gradle-docs/src/docs/userguide/groovyTutorial.xml
new file mode 100644
index 0000000..bf432f4
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/groovyTutorial.xml
@@ -0,0 +1,65 @@
+<!--
+  ~ 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.
+  -->
+<chapter id="tutorial_groovy_projects">
+    <title>Groovy Quickstart</title>
+
+    <para>To build a Groovy project, you use the <firstterm>Groovy plugin</firstterm>. This plugin extends the Java
+        plugin to add Groovy compilation capabilties to your project. Your project can contain Groovy source code,
+        Java source code, or a mix of the two. In every other respect, a Groovy project is identical to a Java project,
+        which we have already seen in <xref linkend="tutorial_java_projects"/>.
+    </para>
+
+    <section>
+        <title>A basic Groovy project</title>
+
+        <para>Let's look at an example. To use the Groovy plugin, add the following to your build file:</para>
+        <sample id="groovyQuickstart" dir="groovy/quickstart" includeLocation="true" title="Groovy plugin">
+            <sourcefile file="build.gradle" snippet="use-plugin"/>
+        </sample>
+        <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
+            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
+            from the public Maven repository:</para>
+        <sample id="groovyQuickstart" dir="groovy/quickstart" title="Dependency on Groovy 1.6.0">
+            <sourcefile file="build.gradle" snippet="groovy-dependency"/>
+        </sample>
+        <para>Here is our complete build file:</para>
+        <sample id="groovyQuickstart" dir="groovy/quickstart" title="Groovy example - complete build file">
+            <sourcefile file="build.gradle"/>
+        </sample>
+        <para>Running <userinput>gradle build</userinput> will compile, test and JAR your project.</para>
+    </section>
+
+    <section>
+        <title>Summary</title>
+        <para>This chapter describes a very simple Groovy project. Usually, a real project will require more than this.
+            Because a Groovy project <emphasis>is</emphasis> a Java project, whatever you can do with a Java project,
+            you can also do with a Groovy project.
+        </para>
+        <para>
+            You can find out more about the Groovy plugin in <xref linkend="groovy_plugin"/>, and you can find more
+            sample Groovy projects in the <filename>samples/groovy</filename> directory in the Gradle distribution.
+        </para>
+    </section>
+
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/guiTutorial.xml b/subprojects/gradle-docs/src/docs/userguide/guiTutorial.xml
new file mode 100644
index 0000000..a95bed6
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/guiTutorial.xml
@@ -0,0 +1,126 @@
+<!--
+  ~ 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.
+  -->
+<chapter id="tutorial_gradle_gui">
+    <title>Using the Gradle Graphical User Interface</title>
+    <para>In addition to supporting a traditional command line interface, gradle offers
+    a graphical user interface.  This is a stand alone user interface that can be launched with the
+    <command>--gui</command> option.</para>
+    <example>
+        <title>Launching the GUI</title>
+        <programlisting><![CDATA[
+gradle --gui
+]]></programlisting>
+    </example>
+    <para>Note that this command blocks until the gradle GUI is closed.  Under *nix it is probably preferable to run
+        this as a background task (<command>gradle --gui&</command>)</para>
+    <para>If you run this from your gradle project working directory, you should see a tree of tasks.</para>
+    <figure>
+        <title>GUI Task Tree</title>
+        <imageobject>
+            <imagedata fileref="img/guiTaskTree.png" width="586px" depth="597px"/>
+        </imageobject>
+    </figure>
+    <para>It is preferable to run this command from your gradle project directory so that the settings of the UI will be
+        stored in your project directory. However, you can run it then change the working directory via the Setup tab in
+        the UI.
+    </para>
+    <para>The UI displays 4 tabs along the top and an output window along the bottom.</para>
+    <section>
+        <title>Task Tree</title>
+        <para>
+            The Task Tree shows a hierarchical display of all projects and their tasks.
+            Double clicking a task executes it.
+        </para>
+        <para>
+            There is also a filter so that uncommon tasks can be hidden. You can toggle the filter via the Filter button. 
+            Editing the filter allows you to configure which tasks and projects are shown. Hidden tasks show up in red.
+            Note: newly created tasks will show up by default (versus being hidden by default).
+        </para>
+        <para>The Task Tree context menu provides the following options:</para>
+        <itemizedlist>
+            <listitem><para>Execute ignoring dependencies. This does not require dependent projects to be rebuilt (same as the -a option).</para></listitem>
+            <listitem><para>Add tasks to the favorites (see Favorites tab)</para></listitem>
+            <listitem><para>Hide the selected tasks. This adds them to the filter.</para></listitem>
+            <listitem><para>Edit the build.gradle file.
+                Note: this requires Java 1.6 or higher and requires that you have .gradle files associated in your OS.
+            </para></listitem>
+        </itemizedlist>        
+    </section>
+    <section>
+        <title>Favorites</title>
+        <para>
+            The Favorites tab is place to store commonly-executed commands. These can be complex commands
+            (anything that's legal to gradle) and you can provide them with a display name. This is useful for creating,
+            say, a custom build command that explicitly skips tests, documentation, and samples that you could call
+            "fast build".
+        </para>
+        <para>
+            You can reorder favorites to your liking and even export them to disk so they can imported by others.
+            If you edit them, you are given options to "Always Show Live Output."
+            This only applies if you have 'Only Show Output When Errors Occur'.
+            This override always forces the output to be shown.
+        </para>
+    </section>
+    <section>
+        <title>Command Line</title>
+        <para>
+            The Command Line tab is place to execute a single gradle command directly.
+            Just enter whatever you would normally enter after 'gradle' on the command line.  This also provides
+            a place to try out commands before adding them to favorites.
+        </para>
+    </section>
+    <section>
+        <title>Setup</title>
+        <para>The Setup tab allows configuration of some general settings.</para>
+        <figure>
+            <title>GUI Setup</title>
+            <imageobject>
+                <imagedata fileref="img/guiSetup.png" width="586px" depth="597px"/>
+            </imageobject>
+        </figure>
+
+        <itemizedlist>
+            <listitem>
+                <para>Current Directory</para>
+                <para>Defines the root directory of your gradle project (typically where build.gradle is located).</para>
+            </listitem>
+
+            <listitem>
+                <para>Stack Trace Output</para>
+                <para>
+                    This determines how much information to write out stack traces when errors occur.
+                    Note: if you specify a stack trace level on either  the Command Line or Favorites tab, it will override
+                    this stack trace level.
+                </para>
+            </listitem>
+
+            <listitem>
+                <para>Only Show Output When Errors Occur</para>
+                <para>Enabling this option hides any output when a task is executed unless the build fails.</para>
+            </listitem>
+
+            <listitem>
+                <para>Use Custom Gradle Executor - Advanced feature</para>
+                <para>
+                    This provides you with an alternate way to launch gradle commands.
+                    This is useful if your project requires some extra setup that is done inside another batch file or shell script
+                    (such as specifying an init script).
+                </para>
+            </listitem>
+        </itemizedlist>
+
+    </section>
+</chapter>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/ideSupport.xml b/subprojects/gradle-docs/src/docs/userguide/ideSupport.xml
new file mode 100644
index 0000000..f99e552
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/ideSupport.xml
@@ -0,0 +1,58 @@
+<!--
+  ~ 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.
+  -->
+<appendix id='ide_support' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>Existing IDE Support and how to cope without it</title>
+    <section id='sec:intellij'>
+        <title>IntelliJ</title>
+        <para>Gradle has been mainly developed with Idea IntelliJ and its very good Groovy plugin. Gradle's build script
+            <footnote>
+                <para>Gradle is built with Gradle
+                </para>
+            </footnote>
+            has also been developed with the support of this IDE. IntelliJ allows you to define any filepattern to be
+            interpreted as a Groovy script. In the case of Gradle you can define such a pattern for
+            <filename>build.gradle</filename>
+            and <filename>settings.gradle</filename>. This will already help very much. What is missing is the classpath
+            to the Gradle binaries to offer content assistance for the Gradle classes. You might add the Gradle jar
+            (which you can find in your distribution) to your project's classpath. It does not really belong there, but
+            if you do this you have a fantastic IDE support for developing Gradle scripts. Of course if you use
+            additional libraries for your build scripts they would further pollute your project classpath.
+        </para>
+        <para>We hope that in the future <filename>*.gradle</filename> files
+            get special treatment by IntelliJ and you will be able to define a specific classpath for them.
+        </para>
+    </section>
+    <section>
+        <title>Eclipse</title>
+        <para>There is a Groovy plugin for eclipse. We don't know in what state it is and how it would support Gradle.
+            In the next edition of this user guide we can hopefully write more about this.
+        </para>
+    </section>
+    <section id='sec:using_gradle_without_ide_support'>
+        <title>Using Gradle without IDE support</title>
+        <para>What we can do for you is to spare you typing things like
+            <literal>throw new org.gradle.api.tasks.StopExecutionException()</literal>
+            and just type
+            <literal>throw new StopExecutionException()</literal>
+            instead. We do this by automatically adding a set of import statements to the Gradle scripts before Gradle
+            executes them. Listed below are the imports added to each script.
+        </para>
+        <figure>
+            <title>gradle-imports</title>
+            <programlisting><xi:include href='../../../../../subprojects/gradle-core/src/main/resources/org/gradle/configuration/default-imports.txt' parse='text'/></programlisting>
+        </figure>
+    </section>
+</appendix>
diff --git a/subprojects/gradle-docs/src/docs/userguide/ideaPlugin.xml b/subprojects/gradle-docs/src/docs/userguide/ideaPlugin.xml
new file mode 100644
index 0000000..4ba1fe2
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/ideaPlugin.xml
@@ -0,0 +1,468 @@
+<!--
+  ~ 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.
+  -->
+
+<chapter id="idea_plugin">
+    <title>The Idea Plugin</title>
+
+    <para>The Idea plugin generates files that are used by <ulink url="http://www.jetbrains.com/idea/">IntelliJ Idea IDE</ulink>, thus
+        making it possible to open the project from Idea (<guimenuitem>File</guimenuitem> - <guimenuitem>Open Project</guimenuitem>).
+        Both external (including associated source and javadoc files) and project dependencies are considered.</para>
+
+    <para>The Idea plugin will create different content depending on what other plugins being used.</para>
+    
+<section>
+        <title>Usage</title>
+    <para>To use the Idea plugin, include in your build script:</para>
+    <sample id="useIdeaPlugin" dir="idea" title="Using the Idea plugin">
+        <sourcefile file="build.gradle" snippet="use-plugin"/>
+    </sample>
+    <para>One focus of the Idea tasks is to be open to customizations. They provide a couple of hooks to add or remove content from
+        the generated files.
+</para>
+    </section>
+    <section>
+        <title>Tasks</title>
+
+        <para>The Idea plugin adds the tasks shown below to a project.</para>
+
+        <table id='ideatasks'>
+            <title>Idea plugin - Tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>idea</literal>
+                </td>
+                <td><literal>ideaProject</literal>, <literal>ideaModule</literal>, <literal>ideaWorkspace</literal></td>
+                <td><literal>-</literal></td>
+                <td>Generates all the idea configuration files</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>cleanIdea</literal>
+                </td>
+                <td>
+                    <literal>cleanIdeaProject</literal>, <literal>cleanIdeaModule</literal>, <literal>cleanIdeaWorkspace</literal>
+                </td>
+                <td><apilink class="org.gradle.api.tasks.Delete"/></td>
+                <td>Removes all idea configuration files</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>cleanIdeaProject</literal>
+                </td>
+                <td>
+                    <literal>-</literal>
+                </td>
+                <td><apilink class="org.gradle.api.tasks.Delete"/></td>
+                <td>Removes the idea project file</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>cleanIdeaModule</literal>
+                </td>
+                <td>
+                    <literal>-</literal>
+                </td>
+                <td><apilink class="org.gradle.api.tasks.Delete"/></td>
+                <td>Removes the idea module file</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>cleanIdeaWorkspace</literal>
+                </td>
+                <td>
+                    <literal>-</literal>
+                </td>
+                <td><apilink class="org.gradle.api.tasks.Delete"/></td>
+                <td>Removes the idea workspace file</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>ideaProject</literal>
+                </td>
+                <td>
+                    <literal>-</literal>
+                </td>
+                <td><apilink class="org.gradle.plugins.idea.IdeaProject" lang="groovy"/></td>
+                <td>Generates the <filename>.ipr</filename> file. The Idea plugin adds this task only to the root project.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>ideaModule</literal>
+                </td>
+                <td>
+                    <literal>-</literal>
+                </td>
+                <td><apilink class="org.gradle.plugins.idea.IdeaModule" lang="groovy"/></td>
+                <td>Generates the <filename>.iml</filename> file</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>ideaWorkspace</literal>
+                </td>
+                <td>
+                    <literal>-</literal>
+                </td>
+                <td><apilink class="org.gradle.plugins.idea.IdeaWorkspace" lang="groovy"/></td>
+                <td>Generates the <filename>.iws</filename> file</td>
+            </tr>
+        </table>
+
+        <table id='idea-module'>
+            <title>IdeaModule task</title>
+            <thead>
+                <tr>
+                    <td>Property</td>
+                    <td>Type</td>
+                    <td>Default Value</td>
+                    <td>Default Value with Java Plugin</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>moduleDir</literal>
+                </td>
+                <td>File</td>
+                <td><literal>projectDir</literal></td>
+                <td><literal>projectDir</literal></td>
+                <td>The content root directory of the module. Must not be null.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>outputFile</literal>
+                </td>
+                <td>File</td>
+                <td><replaceable>projectDir</replaceable>/<replaceable><project.name></replaceable><literal>.iml</literal></td>
+                <td>-</td>
+                <td>The iml file. Used to look for existing files and as the target for generation. Must not be null.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>sourceDirs</literal>
+                </td>
+                <td>Set<File></td>
+                <td>empty set</td>
+                <td>The source dirs of <literal>sourceSets.main</literal></td>
+                <td>The dirs containing the production sources. Must not be null.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>testSourceDirs</literal>
+                </td>
+                <td>Set<File></td>
+                <td>empty set</td>
+                <td>The source dirs of <literal>sourceSets.test</literal></td>
+                <td>The dirs containing the test sources. Must not be null.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>excludeDirs</literal>
+                </td>
+                <td>Set<File></td>
+                <td>empty set</td>
+                <td>-</td>
+                <td>The dirs to be excluded by idea. Must not be null.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>outputDir</literal>
+                </td>
+                <td>File</td>
+                <td>null</td>
+                <td><literal>sourceSets.main.classesDir</literal></td>
+                <td>The idea output dir for the production sources. If null no entry for output dirs is created.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>testOutputDir</literal>
+                </td>
+                <td>File</td>
+                <td>null</td>
+                <td><literal>sourceSets.test.classesDir</literal></td>
+                <td>The idea output dir for the test sources. If null no entry for test output dirs is created.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>javaVersion</literal>
+                </td>
+                <td>String</td>
+                <td>null</td>
+                <td>-</td>
+                <td>If this is null the value of the existing or default ipr XML (inherited) is used. If it is set
+                to <literal>inherited</literal>, the project SDK is used. Otherwise the SDK for the corresponding
+                value of java version is used for this module</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>downloadSources</literal>
+                </td>
+                <td>boolean</td>
+                <td>true</td>
+                <td>-</td>
+                <td>Whether to download and add source jars associated with the dependency jars.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>downloadJavadoc</literal>
+                </td>
+                <td>boolean</td>
+                <td>false</td>
+                <td>-</td>
+                <td>Whether to download and add javadoc jars associated with the dependency jars. </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>scopes</literal>
+                </td>
+                <td>Map</td>
+                <td>[:]</td>
+                <td>COMPILE(compile), RUNTIME(runtime - compile), TEST(testRuntime - runtime)</td>
+                <td>The keys of this map are the Idea scopes. Each key points to another map that has two keys, plus and minus.
+                    The values of those keys are sets of <link linkend="sub:configurations">Configuration</link> objects. The files of the
+                    plus configurations are added minus the files from the minus configurations. </td>
+                </tr>
+            <tr>
+                <td>
+                    <literal>gradleCacheVariable</literal>
+                </td>
+                <td>String</td>
+                <td>null</td>
+                <td>-</td>
+                <td>If this variable is set, dependencies in the existing iml file will be parsed for this variable. If they use it,
+                    it will be replaced with a path that has the $MODULE_DIR$ variable as a root and
+                    then a relative path to  {@link #gradleCacheHome} . That way Gradle can recognize equal dependencies.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>gradleCacheVariable</literal>
+                </td>
+                <td>File</td>
+                <td>null</td>
+                <td>The gradle cache dir</td>
+                <td>This property is used in conjunction with <code>gradleCacheVariable</code></td>
+            </tr>
+        </table>
+     
+        <table id='idea-project'>
+            <title>IdeaProject task</title>
+            <thead>
+                <tr>
+                    <td>Property</td>
+                    <td>Type</td>
+                    <td>Default Value</td>
+                    <td>Default Value with Java Plugin</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>subprojects</literal>
+                </td>
+                <td>Set<Project></td>
+                <td><literal>rootProject.allprojects</literal></td>
+                <td>-</td>
+                <td>The subprojects that should be mapped to modules in the <literal>ipr</literal>
+                    file. The subprojects will only be mapped, if the Idea plugin has been
+                    applied to them.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>outputFile</literal>
+                </td>
+                <td>File</td>
+                <td><replaceable>projectDir</replaceable>/<replaceable><project.name></replaceable><literal>.ipr</literal></td>
+                <td>-</td>
+                <td>The ipr file. Used to look for existing files and as the target for generation. Must not be null.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>javaVersion</literal>
+                </td>
+                <td>String</td>
+                <td>1.6</td>
+                <td>-</td>
+                <td>The java version used for defining the project sdk.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>wildcards</literal>
+                </td>
+                <td>Set<String></td>
+                <td>['!?*.java', '!?*.groovy']</td>
+                <td>-</td>
+                <td>The wildcard resource patterns. Must not be null.</td>
+            </tr>
+        </table>
+
+        <table id='idea-workspace'>
+            <title>IdeaWorkspace task</title>
+            <thead>
+                <tr>
+                    <td>Property</td>
+                    <td>Type</td>
+                    <td>Default Value</td>
+                    <td>Default Value with Java Plugin</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>outputFile</literal>
+                </td>
+                <td>File</td>
+                <td><replaceable>projectDir</replaceable>/<replaceable><project.name></replaceable><literal>.ipr</literal></td>
+                <td>-</td>
+                <td>The iws file. Used to look for existing files and as the target for generation. Must not be null.</td>
+            </tr>
+        </table>
+
+
+        <table id='idea-task-hooks'>
+            <title>Task Hooks</title>
+            <thead>
+                <tr>
+                    <td>Method</td>
+                    <td>Project Task Argument</td>
+                    <td>Module Task Argument</td>
+                    <td>Workspace Task Argument</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal><code>beforeConfigured { arg -> }</code></literal>
+                </td>
+                <td><apilink class="org.gradle.plugins.idea.model.Project" lang="groovy"/></td>
+                <td><apilink class="org.gradle.plugins.idea.model.Module" lang="groovy"/></td>
+                <td>n/a</td>
+                <td>Gets called directly after the domain objects have been populated with the content of the
+                    existing XML file (if there is one).</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal><code>whenConfigured { arg -> }</code></literal>
+                </td>
+                <td><apilink class="org.gradle.plugins.idea.model.Project" lang="groovy"/></td>
+                <td><apilink class="org.gradle.plugins.idea.model.Module" lang="groovy"/></td>
+                <td>n/a</td>
+                <td>Gets called after the domain objects have been populated with the content of the
+                    existing XML file and the content from the build script.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal><code>withXml { arg -> }</code></literal>
+                </td>
+                <td><code>groovy.util.Node</code></td>
+                <td><code>groovy.util.Node</code></td>
+                <td><code>groovy.util.Node</code></td>
+                <td>The root node of the XML just before the XML is written to disk.</td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Customizing the generated files</title>
+        <para>Both, the <literal>IdeaProject</literal> and the <literal>IdeaModule</literal> task provide the
+            same hooks and behavior for customizing the generated content. The <literal>IdeaWorkspace</literal> only provides a hook
+            for direct XML manipulation.</para>
+        <para>The plugin recognizes existing idea files. If an idea module, project or workspace file does not exists,
+        a default one is used. Otherwise the existing ones are merged.</para>
+        <section>
+            <title>Merging</title>
+            <para>The first option to customize the Idea files is to have an existing Idea <literal>ipr</literal> or
+            <literal>iml</literal> file before the tasks are run.
+            Existing files will be merged together with the generated content.
+            Any section of the existing Idea files that are not the target of generated content will neither be changed nor removed.</para>
+            <section id="sec:complete-overwrite">
+                <title>Disabling merging with a complete overwrite</title>
+                <para>If you want Gradle to completely overwrite existing Idea files you can specify this on the command line by
+                    executing something like <userinput>gradle cleanIdea idea</userinput> or <userinput>gradle cleanIdeaModule ideaModule</userinput>.
+                    You can specify this also in the build script. Just make the generating tasks depending on the deleting tasks. You can tailor this
+                    to your needs. You could make the <literal>idea</literal> task dependent on the <literal>cleanIdea</literal> task. If you only want
+                    to overwrite for example the module files you simply make the <literal>ideaModule</literal> task dependent on the
+                    <literal>cleanIdeaModule</literal> task.
+                </para>
+            </section>
+        </section>
+        <section>
+            <title>Hooking into the generation lifecycle</title>
+            <para>The Idea plugin provides a couple of domain classes that model Idea project and module files.
+                But only the sections that are autogenerated by Gradle. The generation lifecycle is the following.
+                If there is an existing Idea file, its whole XML is parsed and stored in memory. Then the domain
+                objects are populated with the relevant content of the the existing XML. After that the build script information
+                is used to further populate those objects (e.g. add additional dependencies).
+                Then all sections modelled by the domain objects are removed from the XML in memory. After that the domain objects are used to inject
+                their content into the XML. Finally the XML is written to disk. The lifecycle provides hooks to modify the result according to your needs.
+            </para>
+            <section id="sec:partial-overwrite">
+                <title>Partial Overwrite</title>
+                <para>Doing a <link linkend="sec:complete-overwrite">complete overwrite</link> removes all your manual customizations.
+                    This might be not what you want.
+                    Therefore Gradle provides the option for a partial overwrite. You can hook into the phase just after the existing
+                    Idea files have populated the domain objects. You could then for example remove all the dependencies
+                    from the <literal>module</literal> object.
+                    <sample id="partialOverwrites" dir="idea"
+                            title="Partial Overwrite for Module">
+                        <sourcefile file="build.gradle" snippet="module-before-configured"/>
+                    </sample>
+                    The generated XML will have all sections of the existing Idea module file,
+                    except for the dependencies, where only the information of the build script is used. You could do something
+                    similar for the project file.
+                    <sample id="partialOverwritesProject" dir="idea"
+                            title="Partial Overwrite for Project">
+                        <sourcefile file="build.gradle" snippet="project-before-configured"/>
+                    </sample>
+                </para>
+            </section>
+            <section>
+                <title>Modifying the fully populated domain objects</title>
+                <para>You can also hook into the phase after the existing Idea files and the build script metadata have
+                    populated the domain objects. You could then for example export all the dependencies
+                    of the <literal>module</literal> object.
+                    <sample id="exportDependencies" dir="idea"
+                            title="Export Dependencies">
+                        <sourcefile file="build.gradle" snippet="module-when-configured"/>
+                    </sample>
+                    
+                </para>
+            </section>
+            <section>
+                <title>Modifying the XML</title>
+                <para>You can also hook into the phase after the XML DOM is fully populated but not written to disk.
+                    That hook provides total control over what is generated.
+                    <sample id="file-dependencies" dir="idea"
+                            title="Customizing the XML">
+                        <sourcefile file="build.gradle" snippet="project-with-xml"/>
+                    </sample>
+                </para>
+            </section>
+        </section>
+    </section>
+    <section>
+        <title>Further things to consider</title>
+        <para>The paths of the dependencies in the generated Idea files are absolute. If you manually define a path variable,
+            pointing to the gradle dependency cache, Idea will automatically replace the absolute dependency paths with
+            this path variable. If you use such a path variable, you need to tell the ideaModule task the name of this variable,
+            so that it can do a proper merge without creating duplicates.</para>
+    </section>
+</chapter>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/img/codeQualityPluginTasks.graphml b/subprojects/gradle-docs/src/docs/userguide/img/codeQualityPluginTasks.graphml
new file mode 100644
index 0000000..69309ff
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/img/codeQualityPluginTasks.graphml
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns/graphml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns/graphml http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd">
+  <key attr.name="description" attr.type="string" for="node" id="d0"/>
+  <key for="node" id="d1" yfiles.type="nodegraphics"/>
+  <key attr.name="description" attr.type="string" for="edge" id="d2"/>
+  <key for="edge" id="d3" yfiles.type="edgegraphics"/>
+  <key for="graphml" id="d4" yfiles.type="resources"/>
+  <graph edgedefault="directed" id="G" parse.edges="5" parse.nodes="6" parse.order="free">
+    <node id="n0">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="0.0" y="150.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="44.013671875" x="47.9931640625" y="6.296875">classes</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n1">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="360.0" y="75.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="35.34765625" x="52.326171875" y="6.296875">check</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n2">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="180.0" y="100.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="86.03125" x="26.984375" y="6.296875">checkstyleMain</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n3">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="180.0" y="150.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="83.359375" x="28.3203125" y="6.296875">checkstyleTest</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n4">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="180.0" y="0.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="79.375" x="30.3125" y="6.296875">codenarcMain</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n5">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="180.0" y="50.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="76.703125" x="31.6484375" y="6.296875">codenarcTest</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <edge id="e0" source="n0" target="n3">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e1" source="n2" target="n1">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="3.75"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e2" source="n3" target="n1">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="11.25"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e3" source="n5" target="n1">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-3.75"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e4" source="n4" target="n1">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-11.25"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+  </graph>
+  <data key="d4">
+    <y:Resources/>
+  </data>
+</graphml>
diff --git a/subprojects/gradle-docs/src/docs/userguide/img/codeQualityPluginTasks.png b/subprojects/gradle-docs/src/docs/userguide/img/codeQualityPluginTasks.png
new file mode 100644
index 0000000..3914893
Binary files /dev/null and b/subprojects/gradle-docs/src/docs/userguide/img/codeQualityPluginTasks.png differ
diff --git a/subprojects/gradle-docs/src/docs/userguide/img/commandLineTutorialTasks.graphml b/subprojects/gradle-docs/src/docs/userguide/img/commandLineTutorialTasks.graphml
new file mode 100644
index 0000000..b70c1b1
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/img/commandLineTutorialTasks.graphml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns/graphml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns/graphml http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd">
+  <key attr.name="description" attr.type="string" for="node" id="d0"/>
+  <key for="node" id="d1" yfiles.type="nodegraphics"/>
+  <key attr.name="description" attr.type="string" for="edge" id="d2"/>
+  <key for="edge" id="d3" yfiles.type="edgegraphics"/>
+  <key for="graphml" id="d4" yfiles.type="resources"/>
+  <graph edgedefault="directed" id="G" parse.edges="5" parse.nodes="4" parse.order="free">
+    <node id="n0">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="180.0" y="12.5"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="68.6875" x="35.65625" y="6.296875">compileTest</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n1">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="360.0" y="25.535714285714285"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="23.341796875" x="58.3291015625" y="6.296875">test</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n2">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="540.0" y="7.035714285714285"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="22.673828125" x="58.6630859375" y="6.296875">dist</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n3">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="0.0" y="12.5"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="45.349609375" x="47.3251953125" y="6.296875">compile</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <edge id="e0" source="n0" target="n1">
+      <data key="d2"/>
+      <data key="d3">
+        <y:PolyLineEdge>
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-7.5"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e1" source="n1" target="n2">
+      <data key="d2"/>
+      <data key="d3">
+        <y:PolyLineEdge>
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="7.5"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e2" source="n3" target="n0">
+      <data key="d2"/>
+      <data key="d3">
+        <y:PolyLineEdge>
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e3" source="n3" target="n1">
+      <data key="d2"/>
+      <data key="d3">
+        <y:PolyLineEdge>
+          <y:Path sx="70.0" sy="10.0" tx="-70.0" ty="7.5">
+            <y:Point x="180.0" y="53.0"/>
+            <y:Point x="320.0" y="53.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e4" source="n3" target="n2">
+      <data key="d2"/>
+      <data key="d3">
+        <y:PolyLineEdge>
+          <y:Path sx="70.0" sy="-10.0" tx="-70.0" ty="-7.5">
+            <y:Point x="180.0" y="0.0"/>
+            <y:Point x="500.0" y="0.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="4.0" modelName="free" modelPosition="anywhere" preferredPlacement="on_edge" ratio="0.5" textColor="#000000" visible="true" width="4.0" x="288.0" y="-19.5"></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+  </graph>
+  <data key="d4">
+    <y:Resources/>
+  </data>
+</graphml>
diff --git a/subprojects/gradle-docs/src/docs/userguide/img/commandLineTutorialTasks.png b/subprojects/gradle-docs/src/docs/userguide/img/commandLineTutorialTasks.png
new file mode 100644
index 0000000..a957e99
Binary files /dev/null and b/subprojects/gradle-docs/src/docs/userguide/img/commandLineTutorialTasks.png differ
diff --git a/subprojects/gradle-docs/src/docs/userguide/img/groovyPluginTasks.graphml b/subprojects/gradle-docs/src/docs/userguide/img/groovyPluginTasks.graphml
new file mode 100644
index 0000000..7ef1103
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/img/groovyPluginTasks.graphml
@@ -0,0 +1,226 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns/graphml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns/graphml http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd">
+  <key attr.name="description" attr.type="string" for="node" id="d0"/>
+  <key for="node" id="d1" yfiles.type="nodegraphics"/>
+  <key attr.name="description" attr.type="string" for="edge" id="d2"/>
+  <key for="edge" id="d3" yfiles.type="edgegraphics"/>
+  <key for="graphml" id="d4" yfiles.type="resources"/>
+  <graph edgedefault="directed" id="G" parse.edges="10" parse.nodes="9" parse.order="free">
+    <node id="n0">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="0.0" y="176.11363636363637"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="59.365234375" x="40.3173828125" y="6.296875">groovydoc</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n1">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="180.0" y="101.11363636363636"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="84.02734375" x="27.986328125" y="6.296875">compileGroovy</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n2">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="720.0" y="49.99999999999999"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="110.03125" x="14.984375" y="6.296875">CompileTestGroovy</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n3">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="180.0" y="51.11363636363636"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="103.375" x="18.3125" y="6.296875">processResources</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n4">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="900.0" y="45.86363636363636"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="66.021484375" x="36.9892578125" y="6.296875">testClasses</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n5">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="720.0" y="0.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="126.712890625" x="6.6435546875" y="6.296875">processTestResources</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n6">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="360.0" y="51.11363636363636"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="44.013671875" x="47.9931640625" y="6.296875">classes</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n7">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="0.0" y="56.68181818181817"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="70.697265625" x="34.6513671875" y="6.296875">compileJava</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n8">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="540.0" y="68.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="94.03515625" x="22.982421875" y="6.296875">compileTestJava</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <edge id="e0" source="n3" target="n6">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e1" source="n5" target="n4">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-10.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e2" source="n6" target="n8">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="7.5" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e3" source="n7" target="n6">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-7.5" tx="-70.0" ty="-10.0">
+            <y:Point x="180.0" y="40.61363636363636"/>
+            <y:Point x="320.0" y="40.61363636363636"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e4" source="n8" target="n4">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="7.5" tx="-70.0" ty="10.0">
+            <y:Point x="860.0" y="90.5"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e5" source="n1" target="n6">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="10.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e6" source="n7" target="n1">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="7.5" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e7" source="n2" target="n4">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e8" source="n8" target="n2">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-7.5" tx="-70.0" ty="7.5"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e9" source="n6" target="n2">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-7.5" tx="-70.0" ty="-7.5"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+  </graph>
+  <data key="d4">
+    <y:Resources/>
+  </data>
+</graphml>
diff --git a/subprojects/gradle-docs/src/docs/userguide/img/groovyPluginTasks.png b/subprojects/gradle-docs/src/docs/userguide/img/groovyPluginTasks.png
new file mode 100644
index 0000000..661d0e3
Binary files /dev/null and b/subprojects/gradle-docs/src/docs/userguide/img/groovyPluginTasks.png differ
diff --git a/subprojects/gradle-docs/src/docs/userguide/img/guiSetup.png b/subprojects/gradle-docs/src/docs/userguide/img/guiSetup.png
new file mode 100644
index 0000000..d56a2ee
Binary files /dev/null and b/subprojects/gradle-docs/src/docs/userguide/img/guiSetup.png differ
diff --git a/subprojects/gradle-docs/src/docs/userguide/img/guiTaskTree.png b/subprojects/gradle-docs/src/docs/userguide/img/guiTaskTree.png
new file mode 100644
index 0000000..1b10d38
Binary files /dev/null and b/subprojects/gradle-docs/src/docs/userguide/img/guiTaskTree.png differ
diff --git a/subprojects/gradle-docs/src/docs/userguide/img/javaPluginConfigurations.graphml b/subprojects/gradle-docs/src/docs/userguide/img/javaPluginConfigurations.graphml
new file mode 100644
index 0000000..33b0e28
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/img/javaPluginConfigurations.graphml
@@ -0,0 +1,275 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns/graphml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns/graphml http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd">
+  <key attr.name="description" attr.type="string" for="node" id="d0"/>
+  <key for="node" id="d1" yfiles.type="nodegraphics"/>
+  <key attr.name="description" attr.type="string" for="edge" id="d2"/>
+  <key for="edge" id="d3" yfiles.type="edgegraphics"/>
+  <key for="graphml" id="d4" yfiles.type="resources"/>
+  <graph edgedefault="directed" id="G" parse.edges="11" parse.nodes="11" parse.order="free">
+    <node id="n0">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="540.255859375" y="143.5"/>
+          <y:Fill color="#96E880" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="45.349609375" x="47.3251953125" y="6.296875">compile</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n1">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="270.833984375" y="111.0"/>
+          <y:Fill color="#96E880" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="44.013671875" x="47.9931640625" y="6.296875">runtime</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n2">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="270.833984375" y="211.0"/>
+          <y:Fill color="#96E880" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="67.357421875" x="36.3212890625" y="6.296875">testCompile</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n3">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="0.0" y="162.23863636363637"/>
+          <y:Fill color="#96E880" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="68.025390625" x="35.9873046875" y="6.296875">testRuntime</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n4">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="270.833984375" y="48.761363636363654"/>
+          <y:Fill color="#96E880" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="48.68359375" x="45.658203125" y="6.296875">archives</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n5">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="0.0" y="100.0"/>
+          <y:Fill color="#96E880" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="40.029296875" x="49.9853515625" y="6.296875">default</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n6">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="769.677734375" y="143.5"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="96.0390625" x="21.98046875" y="6.296875">compileJava task</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n7">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="270.833984375" y="161.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="48.68359375" x="45.658203125" y="6.296875">test task</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n8">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="0.0" y="50.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="42.677734375" x="48.6611328125" y="6.296875">jar task</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n9">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="540.255859375" y="220.783203125"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="119.376953125" x="10.3115234375" y="6.296875">compileTestJava task</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n10">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="0.0" y="0.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="111.390625" x="14.3046875" y="6.296875">uploadArchives task</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <edge id="e0" source="n2" target="n0">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-7.5" tx="-70.0" ty="7.5">
+            <y:Point x="450.833984375" y="204.75"/>
+            <y:Point x="500.255859375" y="204.75"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="white_delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e1" source="n1" target="n0">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-7.5">
+            <y:Point x="500.255859375" y="126.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="white_delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e2" source="n3" target="n2">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="10.0" tx="-70.0" ty="-0.0">
+            <y:Point x="180.0" y="226.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="white_delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e3" source="n3" target="n1">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-10.0" tx="-70.0" ty="7.5">
+            <y:Point x="180.0" y="132.73863636363637"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="white_delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e4" source="n5" target="n1">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="7.5" tx="-70.0" ty="-7.5"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="white_delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e5" source="n5" target="n4">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-7.5" tx="-70.0" ty="10.0">
+            <y:Point x="230.833984375" y="108.26136363636365"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="white_delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e6" source="n0" target="n6">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="free" modelPosition="anywhere" preferredPlacement="on_edge" ratio="0.5" textColor="#000000" visible="true" width="49.421875" x="20.0" y="-9.06640625">used by</y:EdgeLabel>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e7" source="n8" target="n4">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="free" modelPosition="anywhere" preferredPlacement="on_edge" ratio="0.5" textColor="#000000" visible="true" width="50.833984375" x="40.0" y="-9.06640625">adds jar</y:EdgeLabel>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e8" source="n3" target="n7">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="free" modelPosition="anywhere" preferredPlacement="on_edge" ratio="0.5" textColor="#000000" visible="true" width="49.421875" x="40.7060546875" y="-10.305038452148438">used by</y:EdgeLabel>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e9" source="n2" target="n9">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="7.5" tx="-70.0" ty="-0.0">
+            <y:Point x="450.833984375" y="235.783203125"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="free" modelPosition="anywhere" preferredPlacement="on_edge" ratio="0.5" textColor="#000000" visible="true" width="49.421875" x="40.0" y="-6.783203125">used by</y:EdgeLabel>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e10" source="n10" target="n4">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-10.0">
+            <y:Point x="230.4853515625" y="15.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="18.1328125" modelName="free" modelPosition="anywhere" preferredPlacement="on_edge" ratio="0.5" textColor="#000000" visible="true" width="50.13671875" x="40.3486328125" y="-9.06640625">uploads</y:EdgeLabel>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+  </graph>
+  <data key="d4">
+    <y:Resources/>
+  </data>
+</graphml>
diff --git a/subprojects/gradle-docs/src/docs/userguide/img/javaPluginConfigurations.png b/subprojects/gradle-docs/src/docs/userguide/img/javaPluginConfigurations.png
new file mode 100644
index 0000000..f8f4575
Binary files /dev/null and b/subprojects/gradle-docs/src/docs/userguide/img/javaPluginConfigurations.png differ
diff --git a/subprojects/gradle-docs/src/docs/userguide/img/javaPluginTasks.graphml b/subprojects/gradle-docs/src/docs/userguide/img/javaPluginTasks.graphml
new file mode 100644
index 0000000..dd1f23d
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/img/javaPluginTasks.graphml
@@ -0,0 +1,326 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns/graphml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns/graphml http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd">
+  <key attr.name="description" attr.type="string" for="node" id="d0"/>
+  <key for="node" id="d1" yfiles.type="nodegraphics"/>
+  <key attr.name="description" attr.type="string" for="edge" id="d2"/>
+  <key for="edge" id="d3" yfiles.type="edgegraphics"/>
+  <key for="graphml" id="d4" yfiles.type="resources"/>
+  <graph edgedefault="directed" id="G" parse.edges="14" parse.nodes="14" parse.order="free">
+    <node id="n0">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="0.0" y="73.32352941176471"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="103.375" x="18.3125" y="6.296875">processResources</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n1">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="540.0" y="69.07352941176471"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="66.021484375" x="36.9892578125" y="6.296875">testClasses</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n2">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="360.0" y="101.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="126.712890625" x="6.6435546875" y="6.296875">processTestResources</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n3">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="360.0" y="161.5735294117647"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="17.3359375" x="61.33203125" y="6.296875">jar</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n4">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="720.0" y="49.83823529411765"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="23.341796875" x="58.3291015625" y="6.296875">test</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n5">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="360.0" y="0.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="45.361328125" x="47.3193359375" y="6.296875">javadoc</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n6">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="540.0" y="193.5"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="55.357421875" x="42.3212890625" y="6.296875">assemble</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n7">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="0.0" y="268.5"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="32.6875" x="53.65625" y="6.296875">clean</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n8">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="1080.0" y="103.58823529411765"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="29.353515625" x="55.3232421875" y="6.296875">build</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n9">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="180.0" y="38.25"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="44.013671875" x="47.9931640625" y="6.296875">classes</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n10">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="0.0" y="23.32352941176471"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="70.697265625" x="34.6513671875" y="6.296875">compileJava</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n11">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="360.0" y="51.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="94.03515625" x="22.982421875" y="6.296875">compileTestJava</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n12">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="900.0" y="49.83823529411765"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="35.34765625" x="52.326171875" y="6.296875">check</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n13">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="540.0" y="143.5"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="86.048828125" x="26.9755859375" y="6.296875">uploadArchives</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <edge id="e0" source="n1" target="n4">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="7.5"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e1" source="n3" target="n6">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="7.5" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e2" source="n9" target="n3">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="11.25" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e3" source="n9" target="n4">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-3.75" tx="-70.0" ty="-7.5">
+            <y:Point x="360.0" y="40.5"/>
+            <y:Point x="680.0" y="40.5"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e4" source="n10" target="n9">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-7.5"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e5" source="n0" target="n9">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="7.5"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e6" source="n2" target="n1">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="7.5"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e7" source="n11" target="n1">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-7.5"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e8" source="n9" target="n11">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="3.75" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e9" source="n6" target="n8">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="7.5">
+            <y:Point x="1040.0" y="208.5"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e10" source="n4" target="n12">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e11" source="n12" target="n8">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-7.5"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e12" source="n3" target="n13">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-7.5" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e13" source="n9" target="n5">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-11.25" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+  </graph>
+  <data key="d4">
+    <y:Resources/>
+  </data>
+</graphml>
diff --git a/subprojects/gradle-docs/src/docs/userguide/img/javaPluginTasks.png b/subprojects/gradle-docs/src/docs/userguide/img/javaPluginTasks.png
new file mode 100644
index 0000000..fd0e698
Binary files /dev/null and b/subprojects/gradle-docs/src/docs/userguide/img/javaPluginTasks.png differ
diff --git a/subprojects/gradle-docs/src/docs/userguide/img/jettyPluginTasks.graphml b/subprojects/gradle-docs/src/docs/userguide/img/jettyPluginTasks.graphml
new file mode 100644
index 0000000..c77580d
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/img/jettyPluginTasks.graphml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns/graphml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns/graphml http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd">
+  <key attr.name="description" attr.type="string" for="node" id="d0"/>
+  <key for="node" id="d1" yfiles.type="nodegraphics"/>
+  <key attr.name="description" attr.type="string" for="edge" id="d2"/>
+  <key for="edge" id="d3" yfiles.type="edgegraphics"/>
+  <key for="graphml" id="d4" yfiles.type="resources"/>
+  <graph edgedefault="directed" id="G" parse.edges="3" parse.nodes="5" parse.order="free">
+    <node id="n0">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="180.0" y="50.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="23.3359375" x="58.33203125" y="6.296875">war</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n1">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="0.0" y="25.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="44.013671875" x="47.9931640625" y="6.296875">classes</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n2">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="180.0" y="0.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="48.021484375" x="45.9892578125" y="6.296875">jettyRun</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n3">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="360.0" y="50.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="70.017578125" x="34.9912109375" y="6.296875">jettyRunWar</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n4">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="0.0" y="125.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="50.693359375" x="44.6533203125" y="6.296875">jettyStop</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <edge id="e0" source="n1" target="n0">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="7.5" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e1" source="n1" target="n2">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-7.5" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e2" source="n0" target="n3">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+  </graph>
+  <data key="d4">
+    <y:Resources/>
+  </data>
+</graphml>
diff --git a/subprojects/gradle-docs/src/docs/userguide/img/jettyPluginTasks.png b/subprojects/gradle-docs/src/docs/userguide/img/jettyPluginTasks.png
new file mode 100644
index 0000000..b9cc069
Binary files /dev/null and b/subprojects/gradle-docs/src/docs/userguide/img/jettyPluginTasks.png differ
diff --git a/subprojects/gradle-docs/src/docs/userguide/img/scalaPluginTasks.graphml b/subprojects/gradle-docs/src/docs/userguide/img/scalaPluginTasks.graphml
new file mode 100644
index 0000000..47ccc92
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/img/scalaPluginTasks.graphml
@@ -0,0 +1,226 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns/graphml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns/graphml http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd">
+  <key attr.name="description" attr.type="string" for="node" id="d0"/>
+  <key for="node" id="d1" yfiles.type="nodegraphics"/>
+  <key attr.name="description" attr.type="string" for="edge" id="d2"/>
+  <key for="edge" id="d3" yfiles.type="edgegraphics"/>
+  <key for="graphml" id="d4" yfiles.type="resources"/>
+  <graph edgedefault="directed" id="G" parse.edges="10" parse.nodes="9" parse.order="free">
+    <node id="n0">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="0.0" y="176.11363636363637"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="51.361328125" x="44.3193359375" y="6.296875">scaladoc</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n1">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="180.0" y="101.11363636363636"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="75.3671875" x="32.31640625" y="6.296875">compileScala</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n2">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="720.0" y="49.99999999999999"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="98.705078125" x="20.6474609375" y="6.296875">compileTestScala</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n3">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="180.0" y="51.11363636363636"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="103.375" x="18.3125" y="6.296875">processResources</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n4">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="900.0" y="45.86363636363636"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="66.021484375" x="36.9892578125" y="6.296875">testClasses</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n5">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="720.0" y="0.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="126.712890625" x="6.6435546875" y="6.296875">processTestResources</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n6">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="360.0" y="51.11363636363636"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="44.013671875" x="47.9931640625" y="6.296875">classes</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n7">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="0.0" y="56.68181818181817"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="70.697265625" x="34.6513671875" y="6.296875">compileJava</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n8">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="540.0" y="68.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="94.03515625" x="22.982421875" y="6.296875">compileTestJava</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <edge id="e0" source="n3" target="n6">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e1" source="n5" target="n4">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-10.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e2" source="n6" target="n8">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="7.5" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e3" source="n7" target="n6">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-7.5" tx="-70.0" ty="-10.0">
+            <y:Point x="180.0" y="40.61363636363636"/>
+            <y:Point x="320.0" y="40.61363636363636"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e4" source="n8" target="n4">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="7.5" tx="-70.0" ty="10.0">
+            <y:Point x="860.0" y="90.5"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e5" source="n1" target="n6">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="10.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e6" source="n7" target="n1">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="7.5" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e7" source="n2" target="n4">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e8" source="n8" target="n2">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-7.5" tx="-70.0" ty="7.5"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e9" source="n6" target="n2">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-7.5" tx="-70.0" ty="-7.5"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+  </graph>
+  <data key="d4">
+    <y:Resources/>
+  </data>
+</graphml>
diff --git a/subprojects/gradle-docs/src/docs/userguide/img/scalaPluginTasks.png b/subprojects/gradle-docs/src/docs/userguide/img/scalaPluginTasks.png
new file mode 100644
index 0000000..d3c99e2
Binary files /dev/null and b/subprojects/gradle-docs/src/docs/userguide/img/scalaPluginTasks.png differ
diff --git a/subprojects/gradle-docs/src/docs/userguide/img/warPluginTasks.graphml b/subprojects/gradle-docs/src/docs/userguide/img/warPluginTasks.graphml
new file mode 100644
index 0000000..736faad
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/img/warPluginTasks.graphml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns/graphml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns/graphml http://www.yworks.com/xml/schema/graphml/1.0/ygraphml.xsd">
+  <key attr.name="description" attr.type="string" for="node" id="d0"/>
+  <key for="node" id="d1" yfiles.type="nodegraphics"/>
+  <key attr.name="description" attr.type="string" for="edge" id="d2"/>
+  <key for="edge" id="d3" yfiles.type="edgegraphics"/>
+  <key for="graphml" id="d4" yfiles.type="resources"/>
+  <graph edgedefault="directed" id="G" parse.edges="2" parse.nodes="3" parse.order="free">
+    <node id="n0">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="180.0" y="0.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="23.3359375" x="58.33203125" y="6.296875">war</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n1">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="360.0" y="0.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="55.357421875" x="42.3212890625" y="6.296875">assemble</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <node id="n2">
+      <data key="d0"/>
+      <data key="d1">
+        <y:ShapeNode>
+          <y:Geometry height="30.0" width="140.0" x="0.0" y="0.0"/>
+          <y:Fill color="#C3D9E6" transparent="false"/>
+          <y:BorderStyle color="#000000" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Arial" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="17.40625" modelName="internal" modelPosition="c" textColor="#555555" visible="true" width="44.013671875" x="47.9931640625" y="6.296875">classes</y:NodeLabel>
+          <y:Shape type="roundrectangle"/>
+        </y:ShapeNode>
+      </data>
+    </node>
+    <edge id="e0" source="n0" target="n1">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+    <edge id="e1" source="n2" target="n0">
+      <data key="d2"/>
+      <data key="d3">
+        <y:QuadCurveEdge straightness="0.1">
+          <y:Path sx="70.0" sy="-0.0" tx="-70.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+        </y:QuadCurveEdge>
+      </data>
+    </edge>
+  </graph>
+  <data key="d4">
+    <y:Resources/>
+  </data>
+</graphml>
diff --git a/subprojects/gradle-docs/src/docs/userguide/img/warPluginTasks.png b/subprojects/gradle-docs/src/docs/userguide/img/warPluginTasks.png
new file mode 100644
index 0000000..79590bf
Binary files /dev/null and b/subprojects/gradle-docs/src/docs/userguide/img/warPluginTasks.png differ
diff --git a/subprojects/gradle-docs/src/docs/userguide/initscripts.xml b/subprojects/gradle-docs/src/docs/userguide/initscripts.xml
new file mode 100644
index 0000000..6c775e4
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/initscripts.xml
@@ -0,0 +1,94 @@
+<!--
+  ~ 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.
+  -->
+<chapter id='init_scripts' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>Initialization Scripts</title>
+    <para>Gradle provides a powerful mechanism to allow customizing the build based on the current environment.  This
+        mechanism also supports tools that wish to integrate with Gradle.
+    </para>
+    <section id='sec:basic_usage'>
+        <title>Basic usage</title>
+        <para>Initialization scripts (a.k.a. <firstterm>init scripts</firstterm>) are similar to other scripts in Gradle.
+            These scripts, however, are run before the build starts.  Here are several possible uses:
+            <itemizedlist>
+                <listitem>
+                    <para>Set up properties based on the current environment (such as a developer's machine vs. a
+                        continuous integration server).
+                    </para>
+                </listitem>
+                <listitem>
+                    <para>Supply personal information about the user to the build, such as repository or database
+                        authentication credentials.
+                    </para>
+                </listitem>
+                <listitem>
+                    <para>Define machine specific details, such as where JDKs are installed.
+                    </para>
+                </listitem>
+                <listitem>
+                    <para>Register build listeners.  External tools that wish to listen to Gradle events might find
+                        this useful.
+                    </para>
+                </listitem>
+                <listitem>
+                    <para>Register build loggers.  You might wish to customise how Gradle logs the events that it generates.
+                    </para>
+                </listitem>
+            </itemizedlist>
+            One main limitation of init scripts is that they cannot access classes in the buildSrc project (see
+            <xref linkend='sec:build_sources'/> for details of this feature).
+        </para>
+        <para>There are two ways to use init scripts.  Either put a file called <filename>init.gradle</filename> in
+            <filename><replaceable>USER_HOME</replaceable>/.gradle</filename>,
+            or specify the file on the command line.  The command line option is <option>-I</option> or
+            <option>--init-script</option> followed by the path to the script.  The command line option can appear
+            more than once, each time adding another init script. If more than one init script is found they will all be
+            executed. This allows for a tool to specify an init script and the user to put home in their home directory
+            for defining the environment and both scripts will run when gradle is executed.
+        </para>
+    </section>
+    <section>
+        <title>Writing an init script</title>
+        <para>
+            <para>Similar to a Gradle build script, an init script is a groovy script. Each init script has a
+                <apilink class="org.gradle.api.invocation.Gradle"/> instance associated with it. Any property reference
+                and method call in the init script will delegate to this <classname>Gradle</classname> instance.
+            </para>
+            <para>Each init script also implements the <apilink class="org.gradle.api.Script"/> interface.</para>
+        </para>
+    </section>
+    <section id='sec:custom_classpath'>
+        <title>External dependencies for the init script</title>
+        <para>In <xref linkend='sec:external_dependencies'/> is was explained how to add external dependencies to a
+            build script. Init scripts can similarly have external dependencies defined.  You do this using the
+            <literal>initscript()</literal> method, passing in a closure which declares the init script classpath.
+        </para>
+        <sample id="declareExternalInitDependency" dir="userguide/initScripts/externalDependency" title="Declaring external dependencies for an init script">
+            <sourcefile file="init.gradle" snippet="declare-classpath"/>
+        </sample>
+        <para>The closure passed to the <literal>initscript()</literal> method configures a
+            <apilink class="org.gradle.api.initialization.dsl.ScriptHandler"/> instance. You declare the init script
+            classpath by adding dependencies to the <literal>classpath</literal> configuration. This is the same way
+            you declare, for example, the Java compilation classpath. You can use any of the dependency types described
+            in <xref linkend='sec:how_to_declare_your_dependencies'/>, except project dependencies.</para>
+        <para>Having declared the init script classpath, you can use the classes in your init script as you would
+            any other classes on the classpath. The following example adds to the previous example, and uses classes
+            from the init script classpath.</para>
+        <sample id="externalInitDependency" dir="userguide/initScripts/externalDependency" title="An init script with external dependencies">
+            <sourcefile file="init.gradle"/>
+            <output args="--init-script init.gradle -q doNothing"/>
+        </sample>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/installation.xml b/subprojects/gradle-docs/src/docs/userguide/installation.xml
new file mode 100644
index 0000000..14d3263
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/installation.xml
@@ -0,0 +1,125 @@
+<!--
+  ~ 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.
+  -->
+
+<chapter id="installation">
+<title>Installing Gradle</title>
+
+<section>
+    <title>Prerequisites</title>
+
+    <para>Gradle requires a Java JDK to be installed. Gradle ships with its own Groovy library, therefore no Groovy
+        needs to be installed. Any existing Groovy installation is ignored by Gradle. The standard Gradle distribution
+        requires a JDK 1.5 or higher. We also provide a distinct JDK 1.4 compatible distribution.
+    </para>
+
+    <para>Gradle uses whichever JDK it finds in your path (to check, use <userinput>java -version</userinput>).
+        Alternatively, you can set the <envar>JAVA_HOME</envar> environment variable to point to the install directory
+        of the desired JDK.
+    </para>
+</section>
+
+<section>
+    <title>Download</title>
+    <para>You can download one of the Gradle distributions from the <ulink url="website:downloads.html">Gradle web site</ulink>.</para>
+</section>
+
+<section>
+<title>Unpacking</title>
+
+<para>The Gradle distribution comes packaged as a ZIP. The full distribution contains:</para>
+
+<itemizedlist>
+    <listitem><para>The Gradle binaries.</para></listitem>
+    <listitem><para>The user guide (HTML and PDF).</para></listitem>
+    <listitem><para>The API documentation (Javadoc and Groovydoc).</para></listitem>
+    <listitem>
+        <para>Extensive samples, including the examples referenced in the user guide, along with some complete and more
+            complex builds you can use the starting point for your own build.
+        </para>
+    </listitem>
+    <listitem>
+        <para>The binary sources (If you want to build Gradle you need to download the source distribution or checkout
+            the sources from the source repository).
+        </para>
+    </listitem>
+</itemizedlist>
+
+<note>
+<title>For Un*x users</title>
+
+<para>You need a GNU compatible tool to unzip Gradle, if you want the file permissions to be properly set. We mention this as
+some zip front ends for Mac OS X don't restore the file permissions properly.
+</para>
+</note>
+</section>
+
+<section>
+<title>Environment variables</title>
+
+<para>For running Gradle, add <filename><replaceable>GRADLE_HOME</replaceable>/bin</filename> to your <envar>PATH</envar>
+    environment variable. Usually, this is sufficient to run Gradle. Optionally, you may also want to set the
+    <envar>GRADLE_HOME</envar> environment variable to point to the root directory of your Gradle installation.
+</para>
+</section>
+
+<section>
+<title>Running and testing your installation</title>
+
+<para>You run Gradle via the <command>gradle</command> command. To check if Gradle is properly installed just type
+<command>gradle -v</command> and you should get an output like:
+</para>
+
+<screen>
+------------------------------------------------------------
+Gradle 0.9-rc-1
+------------------------------------------------------------
+
+Gradle buildtime: Tuesday, 3 August 2010 6:59:23 PM EST
+Groovy: 1.7.3
+Ant: Apache Ant version 1.8.1 compiled on April 30 2010
+Ivy: 2.2.0-rc1
+Java: 1.6.0_20
+JVM: 16.3-b01
+JVM Vendor: Sun Microsystems Inc.
+OS Name: Linux
+</screen>
+
+</section>
+
+<section>
+<title>JVM options</title>
+
+<para>JVM options for running Gradle can be set via environment variables. You can use <envar>GRADLE_OPTS</envar>
+or <envar>JAVA_OPTS</envar>. Those variables can be used together. <envar>JAVA_OPTS</envar> is by convention an environment
+variable shared by many Java applications. A typical use case would be to set the HTTP proxy in <envar>JAVA_OPTS</envar>
+and the memory options in <envar>GRADLE_OPTS</envar>. Those variables can also be set at the beginning
+of the <command>gradle</command> or <command>gradlew</command> script.
+</para>
+
+</section>
+
+<section condition="standalone">
+<title>Getting help</title>
+
+<para>You might check the user guide at <filename><replaceable>GRADLE_HOME</replaceable>/docs/userguide/userguide.html</filename>.
+It is also available on the <ulink url="website:userguide.html">Gradle web site</ulink>.
+Typing <command>gradle -h</command> prints the command line help. Typing <command>gradle -t</command> shows all the
+tasks of a Gradle build.
+</para>
+
+</section>
+
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/introduction.xml b/subprojects/gradle-docs/src/docs/userguide/introduction.xml
new file mode 100644
index 0000000..162f0a8
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/introduction.xml
@@ -0,0 +1,80 @@
+<!--
+  ~ 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.
+  -->
+
+<chapter id="introduction">
+    <title>Introduction</title>
+    <para>We would like to introduce Gradle to you, a build system that we think is a quantum leap for build technology
+        in the Java (JVM) world. Gradle provides:
+    </para>
+    <itemizedlist>
+        <listitem>
+            <para>A very flexible general purpose build tool like Ant.
+            </para>
+        </listitem>
+        <listitem>
+            <para>Switchable, build-by-convention frameworks a la Maven. But we never lock you in!
+            </para>
+        </listitem>
+        <listitem>
+            <para>Very powerful support for multi-project builds.
+            </para>
+        </listitem>
+        <listitem>
+            <para>Very powerful dependency management (based on Apache Ivy).
+            </para>
+        </listitem>
+        <listitem>
+            <para>Full support for your existing Maven or Ivy repository infrastructure.
+            </para>
+        </listitem>
+        <listitem>
+            <para>Support for transitive dependency management without the need for remote repositories or
+                <literal>pom.xml</literal> and <literal>ivy.xml</literal>
+                files.
+            </para>
+        </listitem>
+        <listitem>
+            <para>Ant tasks and builds as first class citizens.
+            </para>
+        </listitem>
+        <listitem>
+            <para>
+                <emphasis>Groovy</emphasis> build scripts.
+            </para>
+        </listitem>
+        <listitem>
+            <para>A rich domain model for describing your build.
+            </para>
+        </listitem>
+    </itemizedlist>
+    <para>
+        In <xref linkend='overview'/> you will find a detailed overview of Gradle. Otherwise, the
+        <link linkend="tutorials">tutorials</link> are waiting, have fun :)
+    </para>
+    <section>
+        <title>About this user guide</title>
+        <para>This user guide, like Gradle itself, is under very active development. Some parts of Gradle aren't
+            documented as completely as they need to be. Some of the content presented won't be entirely clear or
+            will assume that you know more about Gradle than you do. We need your help to improve this user
+            guide. You can find out more about contributing to the documentation at the
+            <ulink url="website:contributing.html">Gradle web site</ulink>.
+        </para>
+        <para>You can find more examples, and some additions to this user guide, on the
+            <ulink url="http://docs.codehaus.org/display/GRADLE/User+guide">wiki</ulink>.
+            You can also contribute your own examples and extra content there.
+        </para>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/javaPlugin.xml b/subprojects/gradle-docs/src/docs/userguide/javaPlugin.xml
new file mode 100644
index 0000000..67dd18b
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/javaPlugin.xml
@@ -0,0 +1,1264 @@
+<!--
+  ~ 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.
+  -->
+
+<chapter id='java_plugin' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>The Java Plugin</title>
+
+    <para>The Java plugin adds Java compilation, testing and bundling capabilities to a project. It serves as the basis
+        for many of the other Gradle plugins.
+    </para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the Java plugin, include in your build script:</para>
+        <sample id="useJavaPlugin" dir="java/quickstart" title="Using the Java plugin">
+            <sourcefile file="build.gradle" snippet="use-plugin"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>Source sets</title>
+        <para>The Java plugin introduces the concept of a <firstterm>source set</firstterm>. A source set is a group of
+            source files which are compiled and executed together. These source files may include Java source files and
+            resource files. Other plugins add the ability to include Groovy and Scala source files in a source set.
+            A source set has an associated compile classpath, and runtime classpath.
+        </para>
+        <para>
+            You might use a source set to define an integration test suite, or for the API classes of your project, or
+            to separate source which needs to be compiled against different Java versions.
+        </para>
+        <para>The Java plugin defines two standard source sets, called <literal>main</literal> and <literal>test</literal>.
+            The <literal>main</literal> source set contains your production source code, which is compiled and assembled
+            into a JAR file. The <literal>test</literal> source set contains your unit test source code, which is
+            compiled and executed using JUnit or TestNG.
+        </para>
+    </section>
+    
+    <section>
+        <title>Tasks</title>
+        <para>The Java plugin adds a number of tasks to your project, as shown below.</para>
+
+        <table>
+            <title>Java plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>compileJava</literal>
+                </td>
+                <td>All tasks which produce the compile classpath. This includes the <literal>jar</literal> task for
+                    project dependencies included in the <literal>compile</literal> configuration.
+                </td>
+                <td><apilink class="org.gradle.api.tasks.compile.Compile"/></td>
+                <td>Compiles production Java source files using javac.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>processResources</literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.tasks.Copy"/></td>
+                <td>Copies production resources into the production classes directory.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>classes</literal>
+                </td>
+                <td>
+                    <literal>compileJava</literal> and <literal>processResources</literal>.
+                    Some plugins add additional compilation tasks.
+                </td>
+                <td><apilink class="org.gradle.api.Task"/></td>
+                <td>Assembles the production classes directory.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>compileTestJava</literal>
+                </td>
+                <td>
+                    <literal>compile</literal>, plus all tasks which produce the test compile classpath.
+                </td>
+                <td><apilink class="org.gradle.api.tasks.compile.Compile"/></td>
+                <td>Compiles test Java source files using javac.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>processTestResources</literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.tasks.Copy"/></td>
+                <td>Copies test resources into the test classes directory.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>testClasses</literal>
+                </td>
+                <td>
+                    <literal>compileTestJava</literal> and <literal>processTestResources</literal>.
+                    Some plugins add additional test compilation tasks.
+                </td>
+                <td><apilink class="org.gradle.api.Task"/></td>
+                <td>Assembles the test classes directory.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>jar</literal>
+                </td>
+                <td>
+                    <literal>compile</literal>
+                </td>
+                <td><apilink class="org.gradle.api.tasks.bundling.Jar" lang="groovy"/></td>
+                <td>Assembles the JAR file</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>javadoc</literal>
+                </td>
+                <td><literal>compile</literal></td>
+                <td><apilink class="org.gradle.api.tasks.javadoc.Javadoc"/></td>
+                <td>Generates API documentation for the production Java source, using Javadoc </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>test</literal>
+                </td>
+                <td>
+                    <literal>compile</literal>,
+                    <literal>compileTest</literal>,
+                    plus all tasks which produce the test runtime classpath.
+                </td>
+                <td><apilink class="org.gradle.api.tasks.testing.Test"/></td>
+                <td>Runs the unit tests using JUnit or TestNG.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>uploadArchives</literal>
+                </td>
+                <td>
+                    The tasks which produce the artifacts in the <literal>archives</literal> configuration, including <literal>jar</literal>.
+                </td>
+                <td><apilink class="org.gradle.api.tasks.Upload"/></td>
+                <td>Uploads the artifacts in the <literal>archives</literal> configuration, including the JAR file.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>clean</literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.tasks.Delete"/></td>
+                <td>Deletes the project build directory.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>clean<replaceable>TaskName</replaceable></literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.tasks.Delete"/></td>
+                <td>Deletes the output files produced by the specified task. For example <literal>cleanJar</literal>
+                    will delete the JAR file created by the <literal>jar</literal> task, and
+                    <literal>cleanTest</literal> will delete the test results created by the <literal>test</literal> task.
+                </td>
+            </tr>
+        </table>
+
+        <para>For each source set you add to the project, the Java plugin adds the following compilation tasks:</para>
+        <table id="java_source_set_tasks">
+            <title>Java plugin - source set tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>compile<replaceable>SourceSet</replaceable>Java</literal>
+                </td>
+                <td>
+                    All tasks which produce the source set's compile classpath.
+                </td>
+                <td><apilink class="org.gradle.api.tasks.compile.Compile"/></td>
+                <td>Compiles the given source set's Java source files using javac.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>process<replaceable>SourceSet</replaceable>Resources</literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.tasks.Copy"/></td>
+                <td>Copies the given source set's resources into the classes directory.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal><replaceable>sourceSet</replaceable>Classes</literal>
+                </td>
+                <td>
+                    <literal>compile<replaceable>SourceSet</replaceable>Java</literal> and
+                    <literal>process<replaceable>SourceSet</replaceable>Resources</literal>.
+                    Some plugins add additional compilation tasks for the source set.
+                </td>
+                <td><apilink class="org.gradle.api.Task"/></td>
+                <td>Assembles the given source set's classes directory.</td>
+            </tr>
+        </table>
+        
+        <para>The Java plugin also adds a number of tasks which form a lifecycle for the project:</para>
+
+        <table>
+            <title>Java plugin - lifecycle tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>assemble</literal>
+                </td>
+                <td>
+                    All archive tasks in the project, including <literal>jar</literal>. Some plugins add additional
+                    archive tasks to the project.
+                </td>
+                <td><apilink class="org.gradle.api.Task"/></td>
+                <td>Assembles all the archives in the project.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>check</literal>
+                </td>
+                <td>
+                    All verification tasks in the project, including <literal>test</literal>. Some plugins add
+                    additional verification tasks to the project.
+                </td>
+                <td><apilink class="org.gradle.api.Task"/></td>
+                <td>Performs all verification tasks in the project.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>build</literal>
+                </td>
+                <td>
+                    <literal>check</literal> and <literal>assemble</literal>
+                </td>
+                <td><apilink class="org.gradle.api.Task"/></td>
+                <td>Performs a full build of the project.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>buildNeeded</literal>
+                </td>
+                <td>
+                    <literal>build</literal> and <literal>build</literal> tasks in all project lib dependencies of the
+                    <literal>testRuntime</literal> configuration.
+                </td>
+                <td><apilink class="org.gradle.api.Task"/></td>
+                <td>Performs a full build of the project and all projects it depends on.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>buildDependents</literal>
+                </td>
+                <td>
+                    <literal>build</literal> and <literal>build</literal> tasks in all projects with a project lib
+                    dependency on this project in a <literal>testRuntime</literal> configuration.
+                </td>
+                <td><apilink class="org.gradle.api.Task"/></td>
+                <td>Performs a full build of the project and all projects which depend on it.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>build<replaceable>ConfigurationName</replaceable></literal>
+                </td>
+                <td>
+                    The tasks which produce the artifacts in configuration <replaceable>ConfigurationName</replaceable>.
+                </td>
+                <td><apilink class="org.gradle.api.Task"/></td>
+                <td>Assembles the artifacts in the specified configuration.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>upload<replaceable>ConfigurationName</replaceable></literal>
+                </td>
+                <td>
+                    The tasks which uploads the artifacts in configuration <replaceable>ConfigurationName</replaceable>.
+                </td>
+                <td><apilink class="org.gradle.api.tasks.Upload"/></td>
+                <td>Assembles and uploads the artifacts in the specified configuration.</td>
+            </tr>
+        </table>
+
+        <para>The following diagram shows the relationships between these tasks.</para>
+
+        <figure>
+            <title>Java plugin - tasks</title>
+            <imageobject>
+                <imagedata fileref="img/javaPluginTasks.png"/>
+            </imageobject>
+        </figure>
+    </section>
+
+    <section>
+        <title>Project layout</title>
+        <para>The Java plugin assumes the project layout shown below. None of these directories need exist or have
+            anything in them. The Java plugin will compile whatever it finds, and handles anything which is missing.
+        </para>
+
+        <table id='javalayout'>
+            <title>Java plugin - default project layout</title>
+            <thead>
+                <tr>
+                    <td>Directory</td>
+                    <td>Meaning</td>
+                </tr>
+            </thead>
+            <xi:include href="javaProjectMainLayout.xml"/>
+            <xi:include href="javaProjectTestLayout.xml"/>
+            <xi:include href="javaProjectGenericLayout.xml"/>
+        </table>
+
+        <section>
+            <title>Changing the project layout</title>
+            <para>You configure the project layout by configuring the appropriate source set. This is discussed in
+                more detail in the following sections. Here is a brief example which changes the main Java and resource
+                source directories.
+            </para>
+            <sample id="customJavaSourceLayout" dir="java/customizedLayout" title="Custom Java source layout">
+                <sourcefile file="build.gradle" snippet="define-main"/>
+            </sample>
+        </section>
+    </section>
+
+    <section id="sec:java_plugin_and_dependency_management">
+        <title>Dependency management</title>
+        <para>The Java plugin adds a number of dependency configurations to your project, as shown below. It assigns
+            those configurations to tasks such as <literal>compileJava</literal> and <literal>test</literal>.
+            To learn more about configurations see <xref linkend="sub:configurations"/> and
+            <xref linkend="artifacts_and_configurations"/>.
+        </para>
+        <table id='tab:configurations'>
+            <title>Java plugin - dependency configurations</title>
+            <thead>
+                <tr>
+                    <td>Name</td>
+                    <td>Extends</td>
+                    <td>Used by tasks</td>
+                    <td>Meaning</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>compile</td>
+                <td>-</td>
+                <td>compileJava</td>
+                <td>Compile time dependencies</td>
+            </tr>
+            <tr>
+                <td>runtime</td>
+                <td>compile</td>
+                <td>-</td>
+                <td>Runtime dependencies</td>
+            </tr>
+            <tr>
+                <td>testCompile</td>
+                <td>compile</td>
+                <td>compileTestJava</td>
+                <td>Additional dependencies for compiling tests.</td>
+            </tr>
+            <tr>
+                <td>testRuntime</td>
+                <td>runtime, testCompile</td>
+                <td>test</td>
+                <td>Additional dependencies for running tests only.</td>
+            </tr>
+            <tr>
+                <td>archives</td>
+                <td>-</td>
+                <td>uploadArchives</td>
+                <td>Artifacts (e.g. jars) produced by this project.</td>
+            </tr>
+            <tr>
+                <td>default</td>
+                <td>runtime, archives</td>
+                <td>-</td>
+                <td>Artifacts produced <literal>and</literal> dependencies required by this project.
+                </td>
+            </tr>
+        </table>
+        <figure>
+            <title>Java plugin - dependency configurations</title>
+            <imageobject>
+                <imagedata fileref="img/javaPluginConfigurations.png"/>
+            </imageobject>
+        </figure>
+    </section>
+
+    <section>
+        <title>Convention properties</title>
+        <para>
+            The Java plugin adds a number of convention properties to the project, shown below. You can use these properties
+            in your build script as though they were properties of the project object (see
+            <xref linkend="sub:more_about_convention_objects"/>).
+        </para>
+
+        <table id='javaconventionDir'>
+            <title>Java plugin - directory properties</title>
+            <thead>
+                <tr>
+                    <td>Property name</td>
+                    <td>Type</td>
+                    <td>Default value</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <xi:include href="reportingBasePluginProperties.xml"/>
+            <tr>
+                <td>
+                    <literal>testResultsDirName</literal>
+                </td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td>
+                    <literal>test-results</literal>
+                </td>
+                <td>
+                    The name of the directory to generate test result .xml files into, relative to the build directory.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>testResultsDir</literal>
+                </td>
+                <td>
+                    <classname>File</classname> (read-only)
+                </td>
+                <td>
+                    <literal><replaceable>buildDir</replaceable>/<replaceable>testResultsDirName</replaceable></literal>
+                </td>
+                <td>
+                    The directory to generate test result .xml files into.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>testReportDirName</literal>
+                </td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td>
+                    <literal>tests</literal>
+                </td>
+                <td>
+                    The name of the directory to generate the test report into, relative to the reports directory.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>testReportDir</literal>
+                </td>
+                <td>
+                    <classname>File</classname> (read-only)
+                </td>
+                <td>
+                    <literal><replaceable>reportsDir</replaceable>/<literal>testReportDirName</literal></literal>
+                </td>
+                <td>
+                    The directory to generate the test report into.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>libsDirName</literal>
+                </td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td>
+                    <literal>libs</literal>
+                </td>
+                <td>
+                    The name of the directory to generate libraries into, relative to the build directory.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>libsDir</literal>
+                </td>
+                <td>
+                    <classname>File</classname> (read-only)
+                </td>
+                <td>
+                    <literal><replaceable>buildDir</replaceable>/<replaceable>libsDirName</replaceable></literal>
+                </td>
+                <td>
+                    The directory to generate libraries into.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>distsDirName</literal>
+                </td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td>
+                    <literal>distributions</literal>
+                </td>
+                <td>
+                    The name of the directory to generate distributions into, relative to the build directory.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>distsDir</literal>
+                </td>
+                <td>
+                    <classname>File</classname> (read-only)
+                </td>
+                <td>
+                    <literal><replaceable>buildDir</replaceable>/<replaceable>distsDirName</replaceable></literal>
+                </td>
+                <td>
+                    The directory to generate distributions into.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>docsDirName</literal>
+                </td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td>
+                    <literal>docs</literal>
+                </td>
+                <td>
+                    The name of the directory to generate documentation into, relative to the build directory.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>docsDir</literal>
+                </td>
+                <td>
+                    <classname>File</classname> (read-only)
+                </td>
+                <td>
+                    <literal><replaceable>buildDir</replaceable>/<replaceable>docsDirName</replaceable></literal>
+                </td>
+                <td>
+                    The directory to generate documentation into.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>dependencyCacheDirName</literal>
+                </td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td>
+                    <literal>dependency-cache</literal>
+                </td>
+                <td>
+                    The name of the directory to use to cache source dependency information, relative to the build directory.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>dependencyCacheDir</literal>
+                </td>
+                <td>
+                    <classname>File</classname> (read-only)
+                </td>
+                <td>
+                    <literal><replaceable>buildDir</replaceable>/<replaceable>dependencyCacheDirName</replaceable></literal>
+                </td>
+                <td>
+                    The directory to use to cache source dependency information.
+                </td>
+            </tr>
+        </table>
+
+        <table id='javaconventionNonDir'>
+            <title>Java plugin - other properties</title>
+            <thead>
+                <tr>
+                    <td>Property name</td>
+                    <td>Type</td>
+                    <td>Default value</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>sourceSets</literal>
+                </td>
+                <td><apilink class="org.gradle.api.tasks.SourceSetContainer"/> (read-only)</td>
+                <td>Not null</td>
+                <td>Contains the project's source sets.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>sourceCompatibility</literal>
+                </td>
+                <td><apilink class="org.gradle.api.JavaVersion"/>. Can also set using a String or a Number, eg
+                    <literal>'1.5'</literal> or <literal>1.5</literal>.
+                </td>
+                <td>
+                    <literal>1.5</literal>
+                </td>
+                <td>Java version compatibility to use when compiling Java source.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>targetCompatibility</literal>
+                </td>
+                <td><apilink class="org.gradle.api.JavaVersion"/>. Can also set using a String or Number, eg
+                    <literal>'1.5'</literal> or <literal>1.5</literal>.
+                </td>
+                <td>
+                    <literal><replaceable>sourceCompatibility</replaceable></literal>
+                </td>
+                <td>Java version to generate classes for.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>archivesBaseName</literal>
+                </td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td><literal><replaceable>projectName</replaceable></literal></td>
+                <td>The basename to use for archives, such as JAR or ZIP files.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>manifest</literal>
+                </td>
+                <td><apilink class="org.gradle.api.java.archives.Manifest" lang="java"/></td>
+                <td>an empty manifest</td>
+                <td>The manifest to include in all JAR files.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>metaInf</literal>
+                </td>
+                <td>
+                    <classname>List</classname>
+                </td>
+                <td><literal>[]</literal></td>
+                <td>A set of <link linkend="sec:file_collections">file collections</link> which specify the files to
+                    include in the <filename>META-INF</filename> directory or all JAR files.</td>
+            </tr>
+        </table>
+
+        <para>
+            These properties are provided by convention objects of type <apilink class="org.gradle.api.plugins.JavaPluginConvention" lang="groovy"/>,
+            <apilink class="org.gradle.api.plugins.BasePluginConvention" lang="groovy"/> and
+            <apilink class="org.gradle.api.plugins.ReportingBasePluginConvention" lang="groovy"/>.
+        </para>
+    </section>
+
+    <section id="sec:source_sets">
+        <title>Working with source sets</title>
+        <para>You can access the source sets of a project using the <literal>sourceSets</literal> property. This
+            is a container for the project's source sets, of type <apilink class="org.gradle.api.tasks.SourceSetContainer"/>.
+            There is also a <literal>sourceSets()</literal> method, which you can pass a closure to which configures the
+            source set container. The source set container works pretty much the same way as other containers, such
+            as <literal>tasks</literal>.
+        </para>
+        <sample id="defineSourceSet" dir="userguide/java/sourceSets" title="Accessing a source set">
+            <sourcefile file="build.gradle" snippet="access-source-set"/>
+        </sample>
+        <para>To configure an existing source set, you simply use one of the above access methods to set the
+            properties of the source set. The properties are described below. Here is an example which configures the
+            main Java and resources directories:</para>
+        <sample id="configureSourceSet" dir="java/customizedLayout" title="Configuring the source directories of a source set">
+            <sourcefile file="build.gradle" snippet="define-main"/>
+        </sample>
+        <para>To define a new source set, you simply reference it in the <literal>sourceSets { }</literal> block.
+            When you define a source set, the Java plugin adds a number of tasks which assemble the classes for the
+            source set, as shown in <xref linkend="java_source_set_tasks"/>. For example, if you add a source set called
+            <literal>intTest</literal>, the Java plugin adds <literal>compileIntTestJava</literal>, <literal>processIntTestResources</literal>
+            and <literal>intTestClasses</literal> tasks.
+        </para>
+        <sample id="defineSourceSet" dir="userguide/java/sourceSets" title="Defining a source set">
+            <sourcefile file="build.gradle" snippet="define-source-set"/>
+        </sample>
+
+        <section>
+            <title>Source set properties</title>
+            <para>The following table lists some of the important properties of a source set.
+                You can find more details in the API documentation for <apilink class="org.gradle.api.tasks.SourceSet"/>.
+            </para>
+            <table>
+                <title>Java plugin - source set properties</title>
+                <thead>
+                    <tr>
+                        <td>Property name</td>
+                        <td>Type</td>
+                        <td>Default value</td>
+                        <td>Description</td>
+                    </tr>
+                </thead>
+                <tr>
+                    <td>
+                        <literal>name</literal>
+                    </td>
+                    <td>
+                        <classname>String</classname> (read-only)
+                    </td>
+                    <td>
+                        Not null
+                    </td>
+                    <td>
+                        The name of the source set, used to identify it.
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <literal>classesDir</literal>
+                    </td>
+                    <td>
+                        <classname>File</classname>
+                    </td>
+                    <td>
+                        <literal><replaceable>buildDir</replaceable>/classes/<replaceable>name</replaceable></literal>
+                    </td>
+                    <td>
+                        The directory to generate the classes of this source set into.
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <literal>compileClasspath</literal>
+                    </td>
+                    <td>
+                        <apilink class="org.gradle.api.file.FileCollection"/>
+                    </td>
+                    <td>
+                        <literal>compile</literal> Configuration.
+                    </td>
+                    <td>
+                        The classpath to use when compiling the source files of this source set.
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <literal>runtimeClasspath</literal>
+                    </td>
+                    <td>
+                        <apilink class="org.gradle.api.file.FileCollection"/>
+                    </td>
+                    <td>
+                        <literal>classesDir</literal> + <literal>runtime</literal> Configuration.
+                    </td>
+                    <td>
+                        The classpath to use when executing the classes of this source set.
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <literal>java</literal>
+                    </td>
+                    <td>
+                        <apilink class="org.gradle.api.file.SourceDirectorySet"/> (read-only)
+                    </td>
+                    <td>
+                        Not null
+                    </td>
+                    <td>
+                        The Java source files of this source set. Contains only <filename>.java</filename> files
+                        found in the Java source directories, and excludes all other files.
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <literal>java.srcDirs</literal>
+                    </td>
+                    <td>
+                        <classname>Set<File></classname>. Can set using anything described in <xref linkend="sec:specifying_multiple_files"/>.
+                    </td>
+                    <td>
+                        <literal>[<replaceable>projectDir</replaceable>/src/<replaceable>name</replaceable>/java]</literal>
+                    </td>
+                    <td>
+                        The source directories containing the Java source files of this source set.
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <literal>resources</literal>
+                    </td>
+                    <td>
+                        <apilink class="org.gradle.api.file.SourceDirectorySet"/> (read-only)
+                    </td>
+                    <td>
+                        Not null
+                    </td>
+                    <td>
+                        The resources of this source set. Contains only resources, and excludes any
+                        <filename>.java</filename> files found in the resource source directories. Other plugins,
+                        such as the Groovy plugin, exclude additional types of files from this collection.
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <literal>resources.srcDirs</literal>
+                    </td>
+                    <td>
+                        <classname>Set<File></classname>. Can set using anything described in <xref linkend="sec:specifying_multiple_files"/>.
+                    </td>
+                    <td>
+                        <literal>[<replaceable>projectDir</replaceable>/src/<replaceable>name</replaceable>/resources]</literal>
+                    </td>
+                    <td>
+                        The source directories containing the resources of this source set.
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <literal>allJava</literal>
+                    </td>
+                    <td>
+                        <apilink class="org.gradle.api.file.FileTree"/> (read-only)
+                    </td>
+                    <td>
+                        <literal>java</literal>
+                    </td>
+                    <td>
+                        All <filename>.java</filename> files of this source set. Some plugins, such as the Groovy plugin,
+                        add additional Java source files to this collection.
+                    </td>
+                </tr>
+                <tr>
+                    <td>
+                        <literal>allSource</literal>
+                    </td>
+                    <td>
+                        <apilink class="org.gradle.api.file.FileTree"/> (read-only)
+                    </td>
+                    <td>
+                        <literal>resources + java</literal>
+                    </td>
+                    <td>
+                        All source files of this source set. This include all resource files and all Java source files.
+                        Some plugins, such as the Groovy plugin, add additional source files to this collection.
+                    </td>
+                </tr>
+            </table>
+        </section>
+
+        <section>
+            <title>Some source set examples</title>
+            <para>Using dependency configurations to define the source set classpath:</para>
+            <sample id="configureSourceSet" dir="userguide/java/sourceSets" title="Defining the classpath of a source set">
+                <sourcefile file="build.gradle" snippet="classpath-using-configurations"/>
+            </sample>
+            <para>Adding a JAR containing the classes of a source set:</para>
+            <sample id="configureSourceSet" dir="userguide/java/sourceSets" title="Assembling a JAR for a source set">
+                <sourcefile file="build.gradle" snippet="jar"/>
+            </sample>
+            <para>Generating Javadoc for a source set:</para>
+            <sample id="configureSourceSet" dir="userguide/java/sourceSets" title="Generating the Javadoc for a source set">
+                <sourcefile file="build.gradle" snippet="javadoc"/>
+            </sample>
+            <para>Adding a test suite to run the tests in a source set:</para>
+            <sample id="configureSourceSet" dir="userguide/java/sourceSets" title="Running tests in a source set">
+                <sourcefile file="build.gradle" snippet="test"/>
+            </sample>
+        </section>
+    </section>
+
+    <section id='sec:javadoc'>
+        <title>Javadoc</title>
+        <para>The <literal>javadoc</literal> task is an instance of <apilink class="org.gradle.api.tasks.javadoc.Javadoc"/>.
+            It supports the core javadoc options and the options of the standard doclet described in the
+            <ulink url='http://java.sun.com/j2se/1.5.0/docs/tooldocs/windows/javadoc.html#referenceguide'>reference documentation</ulink>
+            of the Javadoc executable.
+            For a complete list of supported Javadoc options consult the API documentation of the following classes:
+            <apilink class="org.gradle.external.javadoc.CoreJavadocOptions"/> and <apilink class="org.gradle.external.javadoc.StandardJavadocDocletOptions"/>. 
+        </para>
+        <table>
+            <title>Java plugin - Javadoc properties</title>
+            <thead>
+                <tr>
+                    <td>Task Property</td>
+                    <td>Type</td>
+                    <td>Default Value</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>classpath</literal>
+                </td>
+                <td><apilink class="org.gradle.api.file.FileCollection"/></td>
+                <td><literal>sourceSets.main.classes + sourceSets.main.compileClasspath</literal></td>
+            </tr>
+            <tr>
+                <td><literal>source</literal></td>
+                <td><apilink class="org.gradle.api.file.FileTree"/>. Can set using anything described in <xref linkend="sec:specifying_multiple_files"/>.</td>
+                <td><literal>sourceSets.main.allJava</literal></td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>destinationDir</literal>
+                </td>
+                <td><classname>File</classname></td>
+                <td><filename><replaceable>docsDir</replaceable>/javadoc</filename></td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>title</literal>
+                </td>
+                <td><classname>String</classname></td>
+                <td>The name and version of the project</td>
+            </tr>
+        </table>
+    </section>
+
+    <section id='sec:clean'>
+        <title>Clean</title>
+        <para>The <literal>clean</literal> task is an instance of <apilink class="org.gradle.api.tasks.Delete"/>. It
+            simply removes the directory denoted by its <literal>dir</literal>
+            property.
+        </para>
+        <table>
+            <title>Java plugin - Clean properties</title>
+            <thead>
+                <tr>
+                    <td>Task Property</td>
+                    <td>Type</td>
+                    <td>Default Value</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>dir</literal>
+                </td>
+                <td><classname>File</classname></td>
+                <td><filename><replaceable>buildDir</replaceable></filename></td>
+            </tr>
+        </table>
+    </section>
+
+    <section id='sec:resources'>
+        <title>Resources</title>
+        <para>The Java plugin uses the <apilink class="org.gradle.api.tasks.Copy"/> task for resource handling. It adds an
+            instance for each source set in the project. You can find out more about the copy task in
+            <xref linkend="sec:copying_files"/>.
+        </para>
+        <table>
+            <title>Java plugin - ProcessResources properties</title>
+            <thead>
+                <tr>
+                    <td>Task Property</td>
+                    <td>Type</td>
+                    <td>Default Value</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>srcDirs</literal>
+                </td>
+                <td><classname>Object</classname>. Can set using anything described in <xref linkend="sec:specifying_multiple_files"/>.</td>
+                <td><literal><replaceable>sourceSet</replaceable>.resources</literal></td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>destinationDir</literal>
+                </td>
+                <td><classname>File</classname>. Can set using anything described in <xref linkend="sec:locating_files"/>.</td>
+                <td><literal><replaceable>sourceSet</replaceable>.classesDir</literal></td>
+            </tr>
+        </table>
+    </section>
+
+    <section id='sec:compile'>
+        <title>CompileJava</title>
+        <para>The Java plugin adds a <apilink class="org.gradle.api.tasks.compile.Compile"/> instance for each
+            source set in the project. The compile task delegates to Ant's javac task to do the compile. You can set most
+            of the properties of the Ant javac task.
+        </para>
+        <table>
+            <title>Java plugin - Compile properties</title>
+            <thead>
+                <tr>
+                    <td>Task Property</td>
+                    <td>Type</td>
+                    <td>Default Value</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>classpath</literal>
+                </td>
+                <td><apilink class="org.gradle.api.file.FileCollection"/></td>
+                <td><literal><replaceable>sourceSet</replaceable>.compileClasspath</literal></td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>source</literal>
+                </td>
+                <td><apilink class="org.gradle.api.file.FileTree"/>. Can set using anything described in <xref linkend="sec:specifying_multiple_files"/>.</td>
+                <td><literal><replaceable>sourceSet</replaceable>.java</literal></td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>destinationDir</literal>
+                </td>
+                <td><classname>File</classname>.</td>
+                <td><literal><replaceable>sourceSet</replaceable>.classesDir</literal></td>
+            </tr>
+        </table>
+    </section>
+
+    <section id='sec:java_test'>
+        <title>Test</title>
+        <para>The <literal>test</literal> task is an instance of <apilink class="org.gradle.api.tasks.testing.Test"/>.
+            It automatically detects and executes all unit tests in the <literal>test</literal> source set.
+            It also generates a report once test execution is complete. JUnit and TestNG are both supported.
+            Have a look at <apilink class="org.gradle.api.tasks.testing.Test"/> for the complete API.
+        </para>
+
+        <section>
+            <title>Test execution</title>
+            <para>Tests are executed in a separate isolated JVM. The <apilink class="org.gradle.api.tasks.testing.Test"/> task's
+                API allows you some control over how this happens.
+            </para>
+            <para>There are a number of properties which control how the test process is launched. This includes
+                things such as system properties, JVM arguments, and the Java executable to use. The task also provides a
+                <literal>debug</literal> property, which when set to true, starts the test process in debug mode,
+                suspended and listening on port 5005. This makes it very easy to debug your tests.  You may also enable
+                this using a system property as specified below.
+            </para>
+            <para>You can specify whether or not to execute your tests in parallel. Gradle provides parallel test execution 
+                by running multiple test processes concurrently. Each test process executes only a single test at a time, so you
+                generally don't need to do anything special to your tests to take advantage of this.
+                The <literal>maxParallelForks</literal> property specifies the maximum number of test processes to run
+                at any given time. The default is 1, that is, do not execute the tests in parallel.
+            </para>
+            <para>The test process sets the <literal>org.gradle.test.worker</literal> system property to a unique
+                identifier for that test process, which you can use, for example, in files names or other
+                resource identifiers.
+            </para>
+            <para>You can specify that test processes should be restarted after it has executed a certain number of
+                test classes. This can be a useful alternative to giving your test process a very large
+                heap.
+                The <literal>forkEvery</literal> property specifies the
+                maximum number of test classes to execute in a test process. The default is to execute an unlimited number
+                of tests in each test process.</para>
+            <para>The task has an <literal>ignoreFailures</literal> property to control the behavior when tests fail.
+                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>
+        </section>
+
+        <section>
+            <title>System properties</title>
+            <para>There are two system properties that can affect test execution.  Both of these are based off
+                  of the name of the test task with a suffix.
+            </para>
+            <para>Setting a system property of <replaceable>taskName.single</replaceable> = <replaceable>testNamePattern</replaceable>
+                  will only execute tests that match the specified <replaceable>testNamePattern</replaceable>.
+                  The <replaceable>taskName</replaceable> can be a full multi-project path like ":sub1:sub2:test"
+                  or just the task name.  The <replaceable>testNamePattern</replaceable> will be used to form an include
+                  pattern of "**/testNamePattern*.class".
+                  If no tests with this pattern can be found an exception is thrown. This is to shield you from false security.
+                  If tests of more then one subproject are executed, the pattern is applied to each subproject. An exception
+                  is thrown if no tests can be found for a particular subproject. In such a case you can use the path notation of the
+                  pattern, so that the pattern is applied only to the test task of a specific subproject. Alternatively you can specify the fully
+                  qualified task name to be executed. You can also specify multiple patterns. Examples:
+                  <itemizedlist>
+                      <listitem>
+                          <para><literal>gradle -Dtest.single=ThisUniquelyNamedTest test</literal></para>
+                      </listitem>
+                      <listitem>
+                          <para><literal>gradle -Dtest.single=a/b/ test</literal></para>
+                      </listitem>
+                      <listitem>
+                          <para><literal>gradle -DintegTest.single=*IntegrationTest integTest</literal></para>
+                      </listitem>
+                      <listitem>
+                          <para><literal>gradle -Dtest.single=:proj1:test:Customer build</literal></para>
+                      </listitem>
+                      <listitem>
+                          <para><literal>gradle -DintegTest.single=c/d/ :proj1:integTest</literal></para>
+                      </listitem>
+                  </itemizedlist>
+            </para>
+            <para>Setting a system property of <literal>taskName.debug</literal> will run the tests in debug mode,
+                  suspended and listening on port 5005.  For example:
+                  <literal>gradle test -Dtest.single=ThisUniquelyNamedTest -Dtest.debug</literal>                
+            </para>
+        </section>
+
+        <section>
+            <title>Test detection</title>
+            <para>The <literal>Test</literal> task detects which classes are test classes by inspecting the compiled test classes.
+                By default it scans all <filename>.class</filename> files. You can set custom includes / excludes, only those
+                classes will be scanned. Depending on the test framework used (JUnit / TestNG) the test class detection
+                uses different criteria.
+            </para>
+            <para>
+                When using JUnit, we scan for both JUnit 3 and 4 test classes. If any of the following criteria match, the
+                class is considered to be a JUnit test class:
+                <itemizedlist>
+                    <listitem>
+                        <para>Class or a super class extends <classname>TestCase</classname> or <classname>GroovyTestCase</classname></para>
+                    </listitem>
+                    <listitem>
+                        <para>Class or a super class is annotated with <classname>@RunWith</classname></para>
+                    </listitem>
+                    <listitem>
+                        <para>Class or a super class contain a method annotated with <classname>@Test</classname></para>
+                    </listitem>
+                </itemizedlist>
+            </para>
+            <para>
+                When using TestNG, we scan for methods annotated with <classname>@Test</classname>.
+            </para>
+            <para>
+                Note that abstract classes are not executed. Gradle also scan up the inheritance tree into jar files
+                on the test classpath.
+            </para>
+            <para>
+                In case you don't want to use the test class detection, you can disable it by setting
+                <literal>scanForTestClasses</literal> to false. This will make the test task only use the includes /
+                excludes to find test classes.
+
+                If <literal>scanForTestClasses</literal> is disabled and no include or exclude patterns are specified, the
+                respective defaults are used. For include this is <literal>"**/*Tests.class", "**/*Test.class"</literal>
+                and the for exclude it is <literal>"**/Abstract*.class"</literal>.
+            </para>
+        </section>
+
+        <section>
+            <title>Convention values</title>
+            <table>
+                <title>Java plugin - test properties</title>
+                <thead>
+                    <tr>
+                        <td>Task Property</td>
+                        <td>Type</td>
+                        <td>Default Value</td>
+                    </tr>
+                </thead>
+                <tr>
+                    <td><literal>testClassesDir</literal></td>
+                    <td><classname>File</classname></td>
+                    <td><literal>sourceSets.test.classesDir</literal></td>
+                </tr>
+                <tr>
+                    <td><literal>classpath</literal></td>
+                    <td><apilink class="org.gradle.api.file.FileCollection"/></td>
+                    <td><literal>sourceSets.test.runtimeClasspath</literal></td>
+                </tr>
+                <tr>
+                    <td><literal>testResultsDir</literal></td>
+                    <td><classname>File</classname></td>
+                    <td><literal>testResultsDir</literal></td>
+                </tr>
+                <tr>
+                    <td><literal>testReportDir</literal></td>
+                    <td><classname>File</classname></td>
+                    <td><literal>testReportDir</literal></td>
+                </tr>
+                <tr>
+                    <td><literal>testSrcDirs</literal></td>
+                    <td><classname>List<File></classname></td>
+                    <td><literal>sourceSets.test.java.srcDirs</literal></td>
+                </tr>
+            </table>
+        </section>
+
+    </section>
+
+    <section>
+        <title>Jar</title>
+        <para>The <literal>jar</literal> task creates a JAR file containing the class files and resources of the
+            project. The JAR file is declared as an artifact in the <literal>archives</literal> dependency
+            configuration. This means that the JAR is available in the classpath of a dependent project. If you upload
+            your project into a repository, this JAR is declared as part of the dependency descriptor. You can learn
+            more about how to work with archives in <xref linkend="sec:archives"/> and artifact configurations in
+            <xref linkend="artifact_management"/>.
+        </para>
+        <section id='sub:manifest'>
+            <title>Manifest</title>
+            <para>Each jar or war object has a <literal>manifest</literal>
+                property with a separate instance of <apilink class="org.gradle.api.java.archives.Manifest" lang="java"/>.
+                When the archive is generated, a corresponding <literal>MANIFEST.MF</literal> file is written into the
+                archive.
+            </para>
+            <sample id="manifest" dir="userguide/tutorial/manifest" title="Customization of MANIFEST.MF">
+                <sourcefile file="build.gradle" snippet="add-to-manifest"/>
+            </sample>
+            <para>You can create stand alone instances of a <classname>Manifest</classname>. You can use that for example,
+                to share manifest information between jars.
+            </para>
+            <sample id="manifest" dir="userguide/tutorial/manifest" title="Creating a manifest object.">
+                <sourcefile file="build.gradle" snippet="custom-manifest"/>
+            </sample>
+            <para>You can merge other manifests into any <classname>Manifest</classname>
+                object. The other manifests might
+                be either described by a file path or, like in the example above, by a reference to another <classname>Manifest</classname> object.
+            </para>
+            <sample id="manifest" dir="userguide/tutorial/manifest" title="Separate MANIFEST.MF for a particular archive">
+                <sourcefile file="build.gradle" snippet="merge"/>
+            </sample>
+            <para>Manifest are merged in the order they are declared by the <literal>from</literal> statement. If
+            the based manifest and the merged manifest both define values for the same key, the merged manifest wins by default.
+            You can fully customize the merge behavior behavior by adding <literal>eachEntry</literal> actions in which
+                you have access to a <apilink class="org.gradle.api.java.archives.ManifestMergeDetails"/> instance for each entry
+                of the resulting manifest. The merge is not immediately triggered by the from statement. It is done lazily,
+                either when generating the jar, or by calling <literal>writeTo</literal> or <literal>effectiveManifest</literal></para>
+            <para>You can easily write a manifest to disk.
+            </para>
+            <sample id="manifest" dir="userguide/tutorial/manifest" title="Separate MANIFEST.MF for a particular archive">
+                <sourcefile file="build.gradle" snippet="write"/>
+            </sample>
+        </section>
+        <section id='sub:metainf'>
+            <title>MetaInf</title>
+            <para>The convention object of the Java plugin has a <literal>metaInf</literal> property pointing to a list of
+                <classname>FileSet</classname> objects. With these file sets you can define which files should be in the
+                <filename>META-INF</filename> directory of a JAR or a WAR archive.
+            </para>
+            <programlisting><![CDATA[
+metaInf << new FileSet(someDir)
+]]></programlisting>
+        </section>
+    </section>
+
+    <section id='sec:upload'>
+        <title>Uploading</title>
+        <para>How to upload your archives is described in <xref linkend="artifact_management"/>.
+        </para>
+    </section>
+    
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/javaProjectGenericLayout.xml b/subprojects/gradle-docs/src/docs/userguide/javaProjectGenericLayout.xml
new file mode 100644
index 0000000..8146b4d
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/javaProjectGenericLayout.xml
@@ -0,0 +1,29 @@
+<!--
+  ~ 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.
+  -->
+<tgroup>
+    <tr>
+        <td>
+            <filename>src/<replaceable>sourceSet</replaceable>/java</filename>
+        </td>
+        <td>Java source for the given source set</td>
+    </tr>
+    <tr>
+        <td>
+            <filename>src/<replaceable>sourceSet</replaceable>/resources</filename>
+        </td>
+        <td>Resources for the given source set</td>
+    </tr>
+</tgroup>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/javaProjectMainLayout.xml b/subprojects/gradle-docs/src/docs/userguide/javaProjectMainLayout.xml
new file mode 100644
index 0000000..0e32c44
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/javaProjectMainLayout.xml
@@ -0,0 +1,29 @@
+<!--
+  ~ 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.
+  -->
+<tgroup>
+    <tr>
+        <td>
+            <filename>src/main/java</filename>
+        </td>
+        <td>Production Java source</td>
+    </tr>
+    <tr>
+        <td>
+            <filename>src/main/resources</filename>
+        </td>
+        <td>Production resources</td>
+    </tr>
+</tgroup>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/javaProjectTestLayout.xml b/subprojects/gradle-docs/src/docs/userguide/javaProjectTestLayout.xml
new file mode 100644
index 0000000..4674da9
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/javaProjectTestLayout.xml
@@ -0,0 +1,29 @@
+<!--
+  ~ 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.
+  -->
+<tgroup>
+    <tr>
+        <td>
+            <filename>src/test/java</filename>
+        </td>
+        <td>Test Java source</td>
+    </tr>
+    <tr>
+        <td>
+            <filename>src/test/resources</filename>
+        </td>
+        <td>Test resources</td>
+    </tr>
+</tgroup>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/javaTutorial.xml b/subprojects/gradle-docs/src/docs/userguide/javaTutorial.xml
new file mode 100644
index 0000000..4b93741
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/javaTutorial.xml
@@ -0,0 +1,277 @@
+<!--
+  ~ 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.
+  -->
+<chapter id='tutorial_java_projects' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>Java Quickstart</title>
+
+    <section>
+        <title>The Java plugin</title>
+
+        <para>As we have seen, Gradle is a general-purpose build tool. It can build pretty much anything you care to
+            implement in your build script. Out-of-the-box, however, it doesn't build anything unless you add code to
+            your build script to do so.</para>
+
+        <para>Most Java projects are pretty similar as far as the basics go: you need to compile your Java
+            source files, run some unit tests, and create a JAR file containing your classes. It would be nice if you
+            didn't have to code all this up for every project. Luckily, you don't have to.
+            Gradle solves this problem through the use of <firstterm>plugins</firstterm>. A plugin is an extension to Gradle
+            which configures your project in some way, typically by adding some pre-configured tasks which together do
+            something useful. Gradle ships with a number of plugins, and you can easily write your own and share them with
+            others. One such plugin is the <firstterm>Java plugin</firstterm>. This plugin adds some tasks to your
+            project which will compile and unit test your Java source code, and bundle it into a JAR file.
+        </para>
+
+        <para>The Java plugin is convention based. This means that the plugin defines default values for many aspects of the
+            project, such as where the Java source files are located. If you follow the convention in your project, you
+            generally don't need to do much in your build script to get a useful build. Gradle allows you to customize your
+            project if you don't want to or cannot follow the convention in some way. In fact, because support for Java
+            projects is implemented as a plugin, you don't have to use the plugin at all to build a Java project, if
+            you don't want to.
+        </para>
+
+        <para>We have in-depth coverage with many examples about the Java plugin, dependency management and multi-project
+            builds in later chapters. In this chapter we want to give you an initial idea of how to use the Java plugin to
+            build a Java project.
+        </para>
+    </section>
+
+    <section>
+        <title>A basic Java project</title>
+        <para>Let's look at a simple example. To use the Java plugin, add the following to your build file:</para>
+        <sample id="javaQuickstart" dir="java/quickstart" includeLocation="true" title="Using the Java plugin">
+            <sourcefile file="build.gradle" snippet="use-plugin"/>
+        </sample>
+        <para>This is all you need to define a Java project. This will apply the Java plugin to your project, which adds
+            a number of tasks to your project.</para>
+        <tip>
+            <title>What tasks are available?</title>
+            <para>You can use <userinput>gradle -t</userinput> to list the tasks of a project. This will let you see
+            the tasks that the Java plugin has added to your project.
+        </para></tip>
+
+        <para>Gradle expects to find your production source code under <filename>src/main/java</filename> and your test
+            source code under <filename>src/test/java</filename>. In addition, any files under
+            <filename>src/main/resources</filename> will be included in the JAR file as resources, and any files under
+            <filename>src/test/resources</filename> will be included in the classpath used to run the tests. All output
+            files are created under the <filename>build</filename> directory, with the JAR file ending up in the
+            <filename>build/libs</filename> directory.
+        </para>
+
+        <section>
+            <title>Building the project</title>
+            <para>The Java plugin adds quite a few tasks to your project. However, there are only a handful of tasks
+                that you will need to use to build the project. The most commonly used task is the <literal>build</literal> task,
+                which does a full build of the project. When you run <userinput>gradle build</userinput>, Gradle will
+                compile and test your code, and create a JAR file containing your main classes and resources:</para>
+
+            <sample id="javaQuickstart" dir="java/quickstart" title="Building a Java project">
+                <output args="build"/>
+            </sample>
+
+            <para>Some other useful tasks are:</para>
+            <variablelist>
+                <varlistentry>
+                    <term>clean</term>
+                    <listitem>
+                        <para>Deletes the <filename>build</filename> directory, removing all built files.</para>
+                    </listitem>
+                </varlistentry>
+                <varlistentry>
+                    <term>assemble</term>
+                    <listitem>
+                        <para>Compiles and jars your code, but does not run the unit tests. Other plugins add more
+                            artifacts to this task. For example, if you use the War plugin, this task will also build
+                            the WAR file for your project.
+                        </para>
+                    </listitem>
+                </varlistentry>
+                <varlistentry>
+                    <term>check</term>
+                    <listitem>
+                        <para>Compiles and tests your code. Other plugins add more checks to this task. For example, if
+                            you use the Code-quality plugin, this task will also run Checkstyle against your source
+                            code.
+                        </para>
+                    </listitem>
+                </varlistentry>
+            </variablelist>
+        </section>
+
+        <section>
+            <title>External dependencies</title>
+
+            <para>Usually, a Java project will have some dependencies on external JAR files. To reference these JAR
+                files in the project, you need to tell Gradle where to find them. In Gradle, artifacts such as JAR
+                files, are located in a <firstterm>repository</firstterm>. A repository can be used for fetching the
+                dependencies of a project, or for publishing the artifacts of a project, or both. For this example,
+                we will use the public Maven repository:</para>
+            <sample id="javaQuickstart" dir="java/quickstart" title="Adding Maven repository">
+                <sourcefile file="build.gradle" snippet="repo"/>
+            </sample>
+
+            <para>Let's add some dependencies. Here, we will declare that our production classes have a compile-time
+                dependency on commons collections, and that our test classes have a compile-time dependency on junit:</para>
+            <sample id="javaQuickstart" dir="java/quickstart" title="Adding dependencies">
+                <sourcefile file="build.gradle" snippet="dependencies"/>
+            </sample>
+            <para>You can find out more in <xref linkend="dependency_management"/>.</para>
+        </section>
+
+        <section>
+            <title>Customising the project</title>
+            <para>The Java plugin adds a number of properties to your project. These properties have default values
+                which are usually sufficient to get started. It's easy to change these values if they don't suit. Let's
+                look at this for our sample. Here we will specify the version number for our Java project, along
+                with the Java version our source is written in. We also add some attributes to the JAR manifest.
+            </para>
+            <sample id="javaQuickstart" dir="java/quickstart" title="Customization of MANIFEST.MF">
+                <sourcefile file="build.gradle" snippet="customisation"/>
+            </sample>
+            <tip>
+                <title>What properties are available?</title>
+                <para>You can use <userinput>gradle -r</userinput> to list the properties of a project. This will allow
+                you to see the properties added by the Java plugin, and their default values.</para></tip>
+            <para>The tasks which the Java plugin adds are regular tasks, exactly the same as if they were declared in
+                the build file. This means you can use any of the mechanisms shown in earlier chapters to customise
+                these tasks. For example, you can set the properties of a task, add behaviour to a task, change the
+                dependencies of a task, or replace a task entirely. In our sample, we will configure the
+                <literal>test</literal> task, which is of type <apilink class="org.gradle.api.tasks.testing.Test"/>, to
+                add a system property when the tests are executed: </para>
+            <sample id="javaQuickstart" dir="java/quickstart" title="Adding a test system property">
+                <sourcefile file="build.gradle" snippet="task-customisation"/>
+            </sample>
+        </section>
+
+        <section>
+            <title>Publishing the JAR file</title>
+            <para>Usually the JAR file needs to be published somewhere. To do this, you need to tell Gradle where to
+                publish the JAR file. In Gradle, artifacts such as JAR files are published to repositories. In our
+                sample, we will publish to a local directory. You can also publish to a remote location, or multiple
+                locations.
+            </para>
+            <sample id="javaQuickstart" dir="java/quickstart" title="Publishing the JAR file">
+                <sourcefile file="build.gradle" snippet="upload"/>
+            </sample>
+            <para>To publish the JAR file, run <userinput>gradle uploadArchives</userinput>.</para>
+        </section>
+        
+		<section>
+            <title>Creating an Eclipse project</title>
+            <para>To import your project into Eclipse, you need to add another plugin to your build file:</para>
+        	<sample id="javaQuickstart" dir="java/quickstart" title="Eclipse plugin">
+            	<sourcefile file="build.gradle" snippet="use-eclipse-plugin"/>
+        	</sample>
+        	<para>Now execute <userinput>gradle eclipse</userinput> command to generate Eclipse project files. More on Eclipse
+                task can be found in <xref linkend="eclipse_plugin"/>.
+            </para>
+        </section>
+
+        <section>
+            <title>Summary</title>
+            <para>Here's the complete build file for our sample:</para>
+            <sample id="javaQuickstart" dir="java/quickstart" title="Java example - complete build file">
+                <sourcefile file="build.gradle"/>
+            </sample>
+        </section>
+    </section>
+
+    <section id='sec:examples'>
+        <title>Multi-project Java build</title>
+        <para>Now let's look at a typical multi-project build. Below is the layout for the project:
+        </para>
+        <sample id="javaMultiProject" dir="java/multiproject" includeLocation="true" title="Multi-project build - hierarchical layout">
+            <layout>
+                api
+                services
+                services/webservice
+                shared
+            </layout>
+        </sample>
+        <para>Here we have three projects. Project <literal>api</literal> produces a JAR file which is shipped to the
+            client to provide them a Java client for your XML webservice. Project <literal>webservice</literal> is a
+            webapp which returns XML. Project <literal>shared</literal> contains code used both by <literal>api</literal>
+            and <literal>webservice</literal>.
+        </para>
+
+        <section>
+            <title>Defining a multi-project build</title>
+            <para>To define a multi-project build, you need to create a <firstterm>settings file</firstterm>. The settings
+                file lives in the root directory of the source tree, and specifies which projects to include in the
+                build. It must be called <filename>settings.gradle</filename>. For this example, we are using a simple
+                hierarchical layout. Here is the corresponding settings file:
+            </para>
+            <sample id="javaMultiProject" dir="java/multiproject" title="Multi-project build - settings.gradle file">
+                <sourcefile file="settings.gradle" snippet="include-projects"/>
+            </sample>
+            <para>You can find out more about the settings file in <xref linkend="multi_project_builds"/>.</para>
+        </section>
+
+        <section>
+            <title>Common configuration</title>
+            <para>For most multi-project builds, there is some configuration which is common to all projects.
+                In our sample, we will define this common configuration in the root project, using a technique called
+                <firstterm>configuration injection</firstterm>. Here, the root project is like a container and the
+                <literal>subprojects</literal> method iterates over the elements of this container - the projects in
+                this instance - and injects the specified configuration. This way we can easily define the manifest
+                content for all archives, and some common dependencies:
+            </para>
+            <sample id="javaMultiProject" dir="java/multiproject" title="Multi-project build - common configuration">
+                <sourcefile file="build.gradle" snippet="configuration-injection"/>
+            </sample>
+
+            <para>Notice that our sample applies the Java plugin to each subproject. This means the tasks and
+                configuration properties we have seen in the previous section are available in each subproject.
+                So, you can compile, test, and JAR all the projects by running <userinput>gradle build</userinput> from
+                the root project directory.</para>
+        </section>
+
+        <section>
+            <title>Dependencies between projects</title>
+            <para>You can add dependencies between projects in the same build, so that, for example, the JAR file of one
+                project is used to compile another project. In the <literal>api</literal> build file we will add a dependency
+                on the JAR produced by the <literal>shared</literal> project. Due to this dependency, Gradle will
+                ensure that project <literal>shared</literal> always gets built before project <literal>api</literal>.
+            </para>
+            <sample id="javaMultiProject" dir="java/multiproject" title="Multi-project build - dependencies between projects">
+                <sourcefile file="api/build.gradle" snippet="dependencies"/>
+            </sample>
+            See <xref linkend="disable_dependency_projects"/> for how to disable this functionality.
+        </section>
+
+        <section>
+            <title>Creating a distribution</title>
+            <para>
+                We also add a distribution, that gets shipped to the client:
+            </para>
+            <sample id="javaMultiProject" dir="java/multiproject" title="Multi-project build - distribution file">
+                <sourcefile file="api/build.gradle" snippet="dists"/>
+            </sample>
+        </section>
+    </section>
+
+    <section>
+        <title>Summary</title>
+        <para>In this chapter, you have seen how to do some of the things you commonly need to build a Java based
+            project. This chapter is not exhaustive, and there are many other things you can do with Java projects in
+            Gradle. These are dealt with in later chapters. Also, a lot of the behaviour you have seen in this chapter
+            is configurable. For example, you can change where Gradle looks Java source files, or add extra tasks, or
+            you can change what any task actually does. Again, you will see how this works in later chapters.
+        </para>
+        <para>
+            You can find out more about the Java plugin in <xref linkend="java_plugin"/>, and you can find more sample
+            Java projects in the <filename>samples/java</filename> directory in the Gradle distribution.
+        </para>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/jettyPlugin.xml b/subprojects/gradle-docs/src/docs/userguide/jettyPlugin.xml
new file mode 100644
index 0000000..60d1feb
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/jettyPlugin.xml
@@ -0,0 +1,151 @@
+<!--
+  ~ 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.
+  -->
+<chapter id="jetty_plugin">
+    <title>The Jetty Plugin</title>
+
+    <para>The Jetty plugin extends the War plugin to add tasks which allow you to deploy your web application to a
+        Jetty web container embedded in the build.
+    </para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the Jetty plugin, include in your build script:</para>
+        <sample id="useJettyPlugin" dir="webApplication/quickstart" title="Using the Jetty plugin">
+            <sourcefile file="build.gradle" snippet="use-jetty-plugin"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>Tasks</title>
+        <para>The Jetty plugin defines the following tasks:</para>
+        <table>
+            <title>Jetty plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>jettyRun</literal>
+                </td>
+                <td>
+                    <literal>compile</literal>
+                </td>
+                <td><apilink class="org.gradle.api.plugins.jetty.JettyRun"/></td>
+                <td>Starts a Jetty instance and deploys the exploded web application to it.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>jettyRunWar</literal>
+                </td>
+                <td>
+                    <literal>war</literal>
+                </td>
+                <td><apilink class="org.gradle.api.plugins.jetty.JettyRunWar"/></td>
+                <td>Starts a Jetty instance and deploys the WAR to it.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>jettyStop</literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.plugins.jetty.JettyStop"/></td>
+                <td>Stops the Jetty instance.</td>
+            </tr>
+        </table>
+        <figure>
+            <title>Jetty plugin - tasks</title>
+            <imageobject>
+                <imagedata fileref="img/jettyPluginTasks.png"/>
+            </imageobject>
+        </figure>
+    </section>
+
+    <section>
+        <title>Project layout</title>
+        <para>The Jetty plugin uses the same layout as the War plugin.</para>
+    </section>
+
+    <section>
+        <title>Dependency management</title>
+        <para>The Jetty plugin does not define any dependency configurations.</para>
+    </section>
+
+    <section>
+        <title>Convention properties</title>
+        <para>The Jetty plugin defines the following convention properties:</para>
+        <table>
+            <title>Jetty plugin - properties</title>
+            <thead>
+                <tr>
+                    <td>Property name</td>
+                    <td>Type</td>
+                    <td>Default value</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>httpPort</literal>
+                </td>
+                <td>
+                    <classname>Integer</classname>
+                </td>
+                <td>
+                    <literal>8080</literal>
+                </td>
+                <td>
+                    The TCP port which Jetty should listen for HTTP requests on.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>stopPort</literal>
+                </td>
+                <td>
+                    <classname>Integer</classname>
+                </td>
+                <td>
+                    <literal>null</literal>
+                </td>
+                <td>
+                    The TCP port which Jetty should listen for admin requests on.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>stopKey</literal>
+                </td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td>
+                    <literal>null</literal>
+                </td>
+                <td>
+                    The key to pass to Jetty when requesting it to stop.
+                </td>
+            </tr>
+        </table>
+        <para>These properties are provided by a <apilink class="org.gradle.api.plugins.jetty.JettyPluginConvention"/>
+            convention object.</para>
+    </section>
+
+</chapter>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/logging.xml b/subprojects/gradle-docs/src/docs/userguide/logging.xml
new file mode 100644
index 0000000..75dc1d9
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/logging.xml
@@ -0,0 +1,205 @@
+<!--
+  ~ 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.
+  -->
+<chapter id='logging' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>Logging</title>
+    <para>The log is the main 'UI' of a build tool. If it is too verbose, real warnings and problems are easily hidden
+        by this. On the other hand you need the relevant information for figuring out if things have gone wrong. Gradle
+        defines 6 log levels, as shown in <xref linkend="logLevels"/>. There are two Gradle-specific log levels, in
+        addition to the ones you might normally see. Those levels are <emphasis>QUIET</emphasis> and
+        <emphasis>LIFECYCLE</emphasis>. The latter is the default, and is used to report build progress.
+    </para>
+    <table id="logLevels">
+        <title>Log levels</title>
+        <thead>
+            <tr>
+                <td>Level</td>
+                <td>Used for</td>
+            </tr>
+        </thead>
+        <tr>
+            <td>ERROR</td>
+            <td>Error messages</td>
+        </tr>
+        <tr>
+            <td>QUIET</td>
+            <td>Important information messages</td>
+        </tr>
+        <tr>
+            <td>WARNING</td>
+            <td>Warning messages</td>
+        </tr>
+        <tr>
+            <td>LIFECYCLE</td>
+            <td>Progress information messages</td>
+        </tr>
+        <tr>
+            <td>INFO</td>
+            <td>Information messages</td>
+        </tr>
+        <tr>
+            <td>DEBUG</td>
+            <td>Debug messages</td>
+        </tr>
+    </table>
+    <section id='sec:choosing_a_log_level'>
+        <title>Choosing a log level</title>
+        <para>You can use the command line switches shown in <xref linkend='logLevelCommandLineOptions'/> to choose
+            different log levels. In <xref linkend='stacktraces'/> you find the command line switches which affect
+            stacktrace logging.
+        </para>
+        <table id='logLevelCommandLineOptions'>
+            <title>Log level command-line options</title>
+            <thead>
+                <tr>
+                    <td>Option</td>
+                    <td>Outputs Log Levels</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>no logging options</td>
+                <td>LIFECYCLE and higher</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>-q</literal>
+                </td>
+                <td>QUIET and higher</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>-i</literal>
+                </td>
+                <td>INFO and higher</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>-d</literal>
+                </td>
+                <td>DEBUG and higher (that is, all log messages)</td>
+            </tr>
+
+        </table>
+        <table id='stacktraces'>
+            <title>Stacktrace command-line options</title>
+            <thead>
+                <tr>
+                    <td>Option</td>
+                    <td>Meaning</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>No stacktrace options</td>
+                <td>No stacktraces are printed to the console in case of a build error (e.g. a compile error). Only in
+                    case of internal exceptions will stacktraces be printed. If the loglevel option <literal>-d</literal>
+                    is chosen, truncated stacktraces are always printed.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>-s</literal>
+                </td>
+                <td>Truncated stacktraces are printed. We recommend this over full stacktraces. Groovy full stacktraces
+                    are extremely verbose (Due to the underlying dynamic invocation mechanisms. Yet they usually do not
+                    contain relevant information for what has gone wrong in <emphasis>your</emphasis> code.)
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>-f</literal>
+                </td>
+                <td>The full stacktraces are printed out.</td>
+            </tr>
+
+        </table>
+    </section>
+    <section id='sec:sending_your_own_log_messages'>
+        <title>Writing your own log messages</title>
+        <para>A simple option for logging in your build file is to write messages to standard output. Gradle redirects
+            anything written to standard output to it's logging system at the <literal>QUIET</literal> log level.</para>
+        <sample id="logging_to_stdout" dir="logging/project1" title="Using stdout to write log messages">
+            <sourcefile file="build.gradle" snippet="use-println"/>
+        </sample>
+        <para>Gradle also provides a <literal>logger</literal> property to a build script, which is an instance of
+            <apilink class="org.gradle.api.logging.Logger"/>. This interface extends the SLF4J
+            <classname>Logger</classname> interface and adds a few Gradle specific methods to it. Below is an example
+            of how this is used in the build script:
+        </para>
+        <sample id="logging_ex" dir="logging/project1" title="Writing your own log messages">
+            <sourcefile file="build.gradle" snippet="use-logger"/>
+        </sample>
+        <para>You can also hook into Gradle's logging system from within other classes used in the build (classes from
+            the <filename>buildSrc</filename> directory for example). Simply use an SLF4J logger. You can use this
+            logger the same way as you use the provided logger in the build script.
+        </para>
+        <sample id="logging_with_slf4j" dir="logging/project1" title="Using SLF4J to write log messages">
+            <sourcefile file="build.gradle" snippet="use-slf4j"/>
+        </sample>
+    </section>
+
+    <section id='sec:external_tools'>
+        <title>Logging from external tools and libraries</title>
+        <para>Internally, Gradle uses Ant and Ivy. Both have their own logging system. Gradle redirects their logging
+            output into the Gradle logging system. There is a 1:1 mapping from the Ant/Ivy log levels to the Gradle log
+            levels, except the Ant/Ivy <literal>TRACE</literal> log level, which is mapped to Gradle
+            <literal>DEBUG</literal> log level. This means the default Gradle log level will not show any Ant/Ivy output
+            unless it is an error or a warning.
+        </para>
+        <para>There are many tools out there which still use standard output for logging. By default, Gradle redirects
+            standard output to the <literal>QUIET</literal> log level and standard error to the <literal>ERROR</literal>
+            level. This behavior is configurable. The project object provides a
+            <apilink class="org.gradle.api.logging.LoggingManager"/>, which allows you to change the log levels that
+            standard out or error are redirected to when your build script is evaluated.</para>
+        <sample id="project_stdout_capture" dir="logging/project1" title="Configuring standard output capture">
+            <sourcefile file="build.gradle" snippet="capture-stdout"/>
+        </sample>
+        <para>
+            To change the log level for standard out or error during task execution, tasks also provide a <apilink class="org.gradle.api.logging.LoggingManager"/>.
+        </para>
+        <sample id="task_stdout_capture" dir="logging/project1" title="Configuring standard output capture for a task">
+            <sourcefile file="build.gradle" snippet="task-capture-stdout"/>
+        </sample>
+        <para>Gradle also provides integration with the Java Util Logging, Jakarta Commons Logging and Log4j logging
+            toolkits. Any log messages which your build classes write using these logging toolkits will be redirected to
+            Gradle's logging system.
+        </para>
+    </section>
+
+    <section id="sec:changing_what_gradle_logs">
+        <title>Changing what Gradle logs</title>
+        <para>You can replace much of Gradle's logging UI with your own. You might do this, for example, if you want to
+            customize the UI in some way - to log more or less information, or to change the formatting. You replace
+            the logging using the <apilink class="org.gradle.api.invocation.Gradle" method="useLogger"/> method. This
+            is accessable from a build script, or an init script, or via the embedding API. Below is an example
+            init script which changes how task execution and build completion is logged.
+        </para>
+        <sample id="custom_logging_ui" dir="userguide/initScripts/customLogger" title="Customizing what Gradle logs">
+            <sourcefile file="init.gradle"/>
+            <output args="-I init.gradle build"/>
+        </sample>
+        <para>Your logger can implement any of the listener interfaces listed below.
+            When you register a logger, only the logging for the interfaces that it implements is replaced. Logging
+            for the other interfaces is left untouched.
+            You can find out more about the listener interfaces in <xref linkend="build_lifecycle_events"/>.
+            <itemizedlist>
+                <listitem><para><apilink class="org.gradle.BuildListener"/></para></listitem>
+                <listitem><para><apilink class="org.gradle.api.ProjectEvaluationListener"/></para></listitem>
+                <listitem><para><apilink class="org.gradle.api.execution.TaskExecutionGraphListener"/></para></listitem>
+                <listitem><para><apilink class="org.gradle.api.execution.TaskExecutionListener"/></para></listitem>
+                <listitem><para><apilink class="org.gradle.api.execution.TaskActionListener"/></para></listitem>
+            </itemizedlist>
+        </para>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/mavenPlugin.xml b/subprojects/gradle-docs/src/docs/userguide/mavenPlugin.xml
new file mode 100644
index 0000000..5ddb1d7
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/mavenPlugin.xml
@@ -0,0 +1,372 @@
+<!--
+  ~ 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.
+  -->
+<chapter id="maven_plugin">
+    <title>The Maven Plugin</title>
+    <note>
+        <para>This chapter is a work in progress</para>
+    </note>
+    <para>The Maven plugin adds support for deploying artifacts to Maven repositories.</para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the Maven plugin, include in your build script:</para>
+        <sample id="useMavenPlugin" dir="maven/quickstart" title="Using the Maven plugin">
+            <sourcefile file="build.gradle" snippet="use-plugin"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>Tasks</title>
+        <para>The Maven plugin defines the following tasks:</para>
+        <table>
+            <title>Maven plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>install</literal>
+                </td>
+                <td>
+                    All tasks that build the associated archives.
+                </td>
+                <td><apilink class="org.gradle.api.tasks.Upload"/></td>
+                <td>Installs the associated artifacts to the local Maven cache, including Maven metadata generation.
+                By default the install task is associated with the <literal>archives</literal> configuration. This
+                configuration has by default only the default jar as an element. To learn more about installing to the
+                local repository, see: <xref linkend="sub:installing_to_the_local_repository"/></td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Dependency management</title>
+        <para>The Maven plugin does not define any dependency configurations.</para>
+    </section>
+
+    <section>
+        <title>Convention properties</title>
+        <para>The Maven plugin defines the following convention properties:</para>
+        <table>
+            <title>Maven plugin - properties</title>
+            <thead>
+                <tr>
+                    <td>Property name</td>
+                    <td>Type</td>
+                    <td>Default value</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>pomDirName</literal>
+                </td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td>
+                    <literal>poms</literal>
+                </td>
+                <td>
+                    The path of the directory to write the generated POMs, relative to the build directory.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>pomDir</literal>
+                </td>
+                <td>
+                    <classname>File (read-only)</classname>
+                </td>
+                <td>
+                    <literal><replaceable>buildDir</replaceable>/<replaceable>pomDirName</replaceable></literal>
+                </td>
+                <td>
+                    The directory where the generated POMs are written to.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>conf2ScopeMappings</literal>
+                </td>
+                <td>
+                    <classname><apilink class="org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer"/></classname>
+                </td>
+                <td>
+                    <literal>n/a</literal>
+                </td>
+                <td>
+                    Instructions for mapping Gradle configurations to Maven scopes. See <xref linkend="sub:dependency_mapping"/>.
+                </td>
+            </tr>
+        </table>
+        <para>These properties are provided by a <apilink class="org.gradle.api.plugins.MavenPluginConvention"/>
+            convention object.</para>
+    </section>
+
+    <section>
+        <title>Convention methods</title>
+        <para>The maven plugin provides a factory method for creating a POM. This is useful if you need a POM
+        without the context of uploading to a Maven repo.</para>
+        <sample id="newPom" dir="maven/pomGeneration" title="Creating a stand alone pom.">
+            <sourcefile file="build.gradle" snippet="new-pom"/>
+        </sample>
+        <para>Amongst other things, Gradle supports the same builder syntax as polyglot Maven. To learn more about the Gradle Maven POM object, see
+            <apilink class="org.gradle.api.artifacts.maven.MavenPom"/>. See also: <apilink class="org.gradle.api.plugins.MavenPluginConvention"/>
+        </para>
+    </section>
+
+    <section id='uploading_to_maven_repositories'>
+        <title>Interacting with Maven repositories</title>
+        <section id='sec:introduction'>
+            <title>Introduction</title>
+            <para>With Gradle you can deploy to remote Maven repositories or install to your local Maven repository. This
+                includes all Maven metadata manipulation and works also for Maven snapshots. In fact, Gradle's deployment is
+                100 percent Maven compatible as we use the native Maven Ant tasks under the hood.
+            </para>
+            <para>Deploying to a Maven repository is only half the fun if you don't have a POM. Fortunately Gradle can
+                generate this POM for you using the dependency information it has.
+            </para>
+        </section>
+        <section id='sec:deploying_to_a_maven_repository'>
+            <title>Deploying to a Maven repository</title>
+            <para>Let's assume your project produces just the default jar file. Now you want to deploy this jar file to
+                a remote Maven repository.
+            </para>
+            <sample id="uploadFile" dir="userguide/artifacts/maven" title="Upload of file to remote Maven repository">
+                <sourcefile file="build.gradle" snippet="upload-file"/>
+            </sample>
+            <para>That is all. Calling the
+                <literal>uploadArchives</literal>
+                task will generate the POM and deploys the artifact and the pom to the specified repository.
+            </para>
+            <para>There is some more work to do if you need support for other protocols than <literal>file</literal>. In
+                this case the native Maven code we delegate to needs additional libraries. Which libraries depend on the
+                protocol you need. The available protocols and the corresponding libraries are listed in <xref
+                        linkend='wagonLibs'/> (those libraries have again transitive dependencies which have transitive
+                dependencies).
+                <footnote>
+                    <para>It is planned for a future release to provide out-of-the-box support for this
+                    </para>
+                </footnote>
+                For example to use the ssh protocol you can do:
+            </para>
+            <sample id="uploadWithSsh" dir="userguide/artifacts/maven" title="Upload of file via SSH">
+                <sourcefile file="build.gradle" snippet="upload-with-ssh"/>
+            </sample>
+            <para>There are many configuration options for the Maven deployer. The configuration is done via a Groovy builder.
+                All the elements of this tree are Java beans. To configure the simple attributes you pass a map to the bean
+                elements. To add another bean elements to its parent, you use a closure. In the example above <emphasis>repository</emphasis>
+                and <emphasis>authentication</emphasis> are such bean elements. <xref linkend='deployerConfig'/>
+                lists the available bean elements and a link to the javadoc of the corresponding class. In the javadoc you
+                can see the possible attributes you can set for a particular element.
+            </para>
+            <para>In Maven you can define repositories and optionally snapshot repositories. If no snapshot repository
+                is defined, releases and snapshots are both deployed to the
+                <literal>repository</literal>
+                element. Otherwise snapshots are deployed to the
+                <literal>snapshotRepository</literal>
+                element.
+            </para>
+            <table id='wagonLibs'>
+                <title>Protocol jars for Maven deployment</title>
+                <thead>
+                    <tr>
+                        <td>Protocol</td>
+                        <td>Library</td>
+                    </tr>
+                </thead>
+                <tr>
+                    <td>http</td>
+                    <td>org.apache.maven.wagon:wagon-http:1.0-beta-2</td>
+                </tr>
+                <tr>
+                    <td>ssh</td>
+                    <td>org.apache.maven.wagon:wagon-ssh:1.0-beta-2</td>
+                </tr>
+                <tr>
+                    <td>ssh-external</td>
+                    <td>org.apache.maven.wagon:wagon-ssh-external:1.0-beta-2</td>
+                </tr>
+                <tr>
+                    <td>scp</td>
+                    <td>org.apache.maven.wagon:wagon-scp:1.0-beta-2</td>
+                </tr>
+                <tr>
+                    <td>ftp</td>
+                    <td>org.apache.maven.wagon:wagon-ftp:1.0-beta-2</td>
+                </tr>
+                <tr>
+                    <td>webdav</td>
+                    <td>org.apache.maven.wagon:wagon-webdav-jackrabbit:1.0-beta-6</td>
+                </tr>
+                <tr>
+                    <td>file</td>
+                    <td>-</td>
+                </tr>
+            </table>
+            <table id='deployerConfig'>
+                <title>Configuration elements of the MavenDeployer</title>
+                <thead>
+                    <tr>
+                        <td>Element</td>
+                        <td>Javadoc</td>
+                    </tr>
+                </thead>
+                <tr>
+                    <td>root</td>
+                    <td>
+                        <apilink class="org.gradle.api.artifacts.maven.MavenDeployer"/>
+                    </td>
+                </tr>
+                <tr>
+                    <td>repository</td>
+                    <td>
+                        <ulink url='http://maven.apache.org/ant-tasks/apidocs/org/apache/maven/artifact/ant/RemoteRepository.html'>
+                            org.apache.maven.artifact.ant.RemoteRepository
+                        </ulink>
+                    </td>
+                </tr>
+                <tr>
+                    <td>authentication</td>
+                    <td>
+                        <ulink url='http://maven.apache.org/ant-tasks/apidocs/org/apache/maven/artifact/ant/Authentication.html'>
+                            org.apache.maven.artifact.ant.Authentication
+                        </ulink>
+                    </td>
+                </tr>
+                <tr>
+                    <td>releases</td>
+                    <td>
+                        <ulink url='http://maven.apache.org/ant-tasks/apidocs/org/apache/maven/artifact/ant/RepositoryPolicy.html'>
+                            org.apache.maven.artifact.ant.RepositoryPolicy
+                        </ulink>
+                    </td>
+                </tr>
+                <tr>
+                    <td>snapshots</td>
+                    <td>
+                        <ulink url='http://maven.apache.org/ant-tasks/apidocs/org/apache/maven/artifact/ant/RepositoryPolicy.html'>
+                            org.apache.maven.artifact.ant.RepositoryPolicy
+                        </ulink>
+                    </td>
+                </tr>
+                <tr>
+                    <td>proxy</td>
+                    <td>
+                        <ulink url='http://maven.apache.org/ant-tasks/apidocs/org/apache/maven/artifact/ant/Proxy.html'>
+                            org.apache.maven.artifact.ant.Proxy
+                        </ulink>
+                    </td>
+                </tr>
+                <tr>
+                    <td>snapshotRepository</td>
+                    <td>
+                        <ulink url='http://maven.apache.org/ant-tasks/apidocs/org/apache/maven/artifact/ant/RemoteRepository.html'>
+                            org.apache.maven.artifact.ant.RemoteRepository
+                        </ulink>
+                    </td>
+                </tr>
+
+            </table>
+        </section>
+        <section id='sub:installing_to_the_local_repository'>
+            <title>Installing to the local repository</title>
+            <para>The Maven plugin adds an <literal>install</literal> task to your project. This task depends on all the archives
+                task of the <literal>archives</literal> configuration. It installs those archives to your local Maven repository.
+                If the default location for the local repository is redefined in a Maven <literal>settings.xml</literal>, this is
+                considered by this task.
+            </para>
+        </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.
+                the 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>
+            <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:
+            </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>
+            <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>
+            <sample id="pomBuilder" dir="maven/pomGeneration" title="Modifying auto-generated content">
+                <sourcefile file="build.gradle" snippet="when-configured"/>
+            </sample>
+            <para>If you have more than one artifact to publish, things work a little bit differently. See<xref
+                        linkend="sub:multiple_artifacts_per_project"/>.
+            </para>
+            <para>To customize the settings for the Maven installer (see<xref
+                    linkend='sub:installing_to_the_local_repository'/>), you can do:
+            </para>
+            <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
+                    Maven POM. We think there are many situations where it makes sense to have more than one artifact per
+                    project. In such a case you need to generate multiple POMs. In such a case you have to explicitly declare each artifact
+                    you want to publish to a Maven repository. The <apilink class="org.gradle.api.artifacts.maven.MavenDeployer"/> and the MavenInstaller
+                    both provide an API for this:
+                </para>
+                <sample id="multiplePoms" dir="userguide/artifacts/maven" title="Generation of multiple poms">
+                    <sourcefile file="build.gradle" snippet="multiple-poms"/>
+                </sample>
+                <para>You need to declare a filter for each artifact you want to publish. This filter defines a boolean expression for
+                    which Gradle artifact it accepts. Each filter has a POM associated with it which you can configure.
+                    To learn more about this have a look at <apilink class="org.gradle.api.artifacts.maven.GroovyPomFilterContainer"/>
+                    and its associated classes.
+                </para>
+            </section>
+            <section id='sub:dependency_mapping'>
+                <title>Dependency mapping</title>
+                <para>The Maven plugin configures the default mapping between the Gradle configurations added by the
+                    Java and War plugin and the Maven scopes. Most
+                    of the time you don't need to touch this and you can safely skip this section. The mapping
+                    works like the following. You can map a configuration to one and only one scope. Different
+                    configurations can be mapped to one or different scopes. One can assign also a priority to a particular
+                    configuration-to-scope mapping. Have a look at
+                    <apilink class="org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer"/>
+                    to learn more. To access the mapping configuration you can say:
+                </para>
+                <sample id="mappings" dir="userguide/artifacts/maven" title="Accessing a mapping configuration">
+                    <sourcefile file="build.gradle" snippet="mappings"/>
+                </sample>
+                <para>Gradle exclude rules are converted to Maven excludes if possible. Such a conversion is possible if in
+                    the Gradle exclude rule the group as well as the module name is specified (as Maven needs both in
+                    contrast to Ivy). Right now excludes-per-configuration are not converted to the Maven POM.
+                </para>
+            </section>
+        </section>
+    </section>
+    
+</chapter>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/multiproject.xml b/subprojects/gradle-docs/src/docs/userguide/multiproject.xml
new file mode 100644
index 0000000..bf7e2ff
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/multiproject.xml
@@ -0,0 +1,691 @@
+<!--
+  ~ 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.
+  -->
+<chapter id='multi_project_builds' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>Multi-project Builds</title>
+    <para>The powerful support for multi-project builds is one of Gradle's unique selling points. This topic is also the
+        most intellectually challenging.
+    </para>
+    <section id='sec:cross_project_configuration'>
+        <title>Cross project configuration</title>
+        <para>Let's start with a very simple multi-project build. After all Gradle is a general purpose build tool at
+            its core, so the projects don't have to be java projects. Our first examples are about marine life.
+        </para>
+        <section>
+            <title>Defining common behavior</title>
+            <para>We have the following project tree. This is a multi-project build with a root project
+                <literal>water</literal> and a subproject <literal>bluewhale</literal>.
+            </para>
+            <sample id="multiprojectFirstExample" dir="userguide/multiproject/firstExample/water" includeLocation="true" title="Multi-project tree - water & bluewhale projects">
+                <layout>
+                    build.gradle
+                    settings.gradle
+                    bluewhale
+                </layout>
+                <sourcefile file="settings.gradle"/>
+            </sample>
+            <para>And where is the build script for the <literal>bluewhale</literal> project? In Gradle build scripts are optional.
+                Obviously for a single project build, a project without a build script doesn't make much sense. For
+                multiproject builds the situation is different. Let's look at the build script for the <literal>water</literal> project and
+                execute it:
+            </para>
+            <sample id="multiprojectFirstExample" dir="userguide/multiproject/firstExample/water" title="Build script of water (parent) project">
+                <sourcefile file="build.gradle"/>
+                <output args="-q hello"/>
+            </sample>
+            <para>Gradle allows you to access any project of the multi-project build from any build script. The Project
+                API provides a method called <literal>project()</literal>, which takes a path as an argument and returns
+                the Project object for this path. The capability to configure a project build from any build script we
+                call <firstterm>cross project configuration</firstterm>. Gradle implements this via
+                <firstterm>configuration injection</firstterm>.
+            </para>
+            <para>We are not that happy with the build script of the <literal>water</literal>  project. It is inconvenient to add the task
+                explicitly for every project. We can do better. Let's first add another project called
+                <literal>krill</literal> to our multi-project build.
+            </para>
+            <sample id="multiprojectAddKrill" dir="userguide/multiproject/addKrill/water" includeLocation="true" title="Multi-project tree - water, bluewhale  & krill projects">
+                <layout>
+                    build.gradle
+                    settings.gradle
+                    bluewhale
+                    krill
+                </layout>
+                <sourcefile file="settings.gradle"/>
+            </sample>
+            <para>Now we rewrite the <literal>water</literal> build script and boil it down to a single line.</para>
+            <sample id="multiprojectAddKrill" dir="userguide/multiproject/addKrill/water" title="Water project build script">
+                <sourcefile file="build.gradle"/>
+                <output args="-q hello"/>
+            </sample>
+            <para>Is this cool or is this cool? And how does this work? The Project API provides a property
+                <literal>allprojects</literal>
+                which returns a list with the current project and all its subprojects underneath it. If you call
+                <literal>allprojects</literal>
+                with a closure, the statements of the closure are delegated to the projects associated with <literal>
+                    allprojects</literal>. You could also do an iteration via <literal>allprojects.each</literal>, but
+                that would be more verbose.
+            </para>
+            <para>Other build systems use inheritance as the primary means for defining common behavior. We also offer
+                inheritance for projects as you will see later. But Gradle uses configuration injection as the usual way
+                of defining common behavior. We think it provides a very powerful and flexible way of configuring
+                multiproject builds.
+            </para>
+        </section>
+    </section>
+    <section id='sec:subproject_configuration'>
+        <title>Subproject configuration</title>
+        <para>The Project API also provides a property for accessing the subprojects only.
+        </para>
+        <section>
+            <title>Defining common behavior</title>
+            <sample id="multiprojectUseSubprojects" dir="userguide/multiproject/useSubprojects/water" title="Defining common behaviour of all projects and subprojects">
+                <sourcefile file="build.gradle"/>
+                <output args="-q hello"/>
+            </sample>
+        </section>
+        <section id='sub:adding_specific_behavior'>
+            <title>Adding specific behavior</title>
+            <para>You can add specific behavior on top of the common behavior. Usually we put the project specific
+                behavior in the build script of the project where we want to apply this specific behavior. But as we
+                have already seen, we don't have to do it this way. We could add project specific behavior for the
+                <literal>bluewhale</literal> project like this:
+            </para>
+            <sample id="multiprojectSubprojectsAddFromTop" dir="userguide/multiproject/subprojectsAddFromTop/water" title="Defining specific behaviour for particular project">
+                <sourcefile file="build.gradle"/>
+                <output args="-q hello"/>
+            </sample>
+            <para>As we have said, we usually prefer to put project specific behavior into the build script of this
+                project. Let's refactor and also add some project specific behavior to the <literal>krill</literal>
+                project.
+            </para>
+            <sample id="multiprojectSpreadSpecifics" dir="userguide/multiproject/spreadSpecifics/water" includeLocation="true" title="Defining specific behaviour for project krill">
+                <layout>
+                    build.gradle
+                    settings.gradle
+                    bluewhale
+                    bluewhale/build.gradle
+                    krill
+                    krill/build.gradle
+                </layout>
+                <sourcefile file="settings.gradle"/>
+                <sourcefile file="bluewhale/build.gradle"/>
+                <sourcefile file="krill/build.gradle"/>
+                <sourcefile file="build.gradle"/>
+                <output args="-q hello"/>
+            </sample>
+        </section>
+        <section id='sub:project_filtering'>
+            <title>Project filtering</title>
+            <para>To show more of the power of configuration injection, let's add another project
+                called <literal>tropicalFish</literal> and add more behavior to the build via the build script of the
+                <literal>water</literal> project.
+            </para>
+            <section id='ssub:filtering_by_name'>
+                <title>Filtering by name</title>
+                <sample id="multiprojectAddTropical" dir="userguide/multiproject/addTropical/water" includeLocation="true" title="Adding custom behaviour to some projects (filtered by project name)">
+                    <layout>
+                        build.gradle
+                        settings.gradle
+                        bluewhale
+                        bluewhale/build.gradle
+                        krill
+                        krill/build.gradle
+                        tropicalFish
+                    </layout>
+                    <sourcefile file="settings.gradle"/>
+                    <sourcefile file="build.gradle"/>
+                    <output args="-q hello"/>
+                </sample>
+                <para>The <literal>configure()</literal> method takes a list as an argument and applies the
+                    configuration to the projects in this list.
+                </para>
+            </section>
+            <section id='ssub:filtering_by_properties'>
+                <title>Filtering by properties</title>
+                <para>Using the project name for filtering is one option. Using dynamic project properties is another.
+                </para>
+                <sample id="multiprojectTropicalWithProperties" dir="userguide/multiproject/tropicalWithProperties/water" includeLocation="true" title="Adding custom behaviour to some projects (filtered by project properties)">
+                    <layout>
+                        build.gradle
+                        settings.gradle
+                        bluewhale
+                        bluewhale/build.gradle
+                        krill
+                        krill/build.gradle
+                        tropicalFish
+                        tropicalFish/build.gradle
+                    </layout>
+                    <sourcefile file="settings.gradle"/>
+                    <sourcefile file="bluewhale/build.gradle"/>
+                    <sourcefile file="krill/build.gradle"/>
+                    <sourcefile file="tropicalFish/build.gradle"/>
+                    <sourcefile file="build.gradle"/>
+                    <output args="-q hello"/>
+                </sample>
+                <para>In the build file of the <literal>water</literal> project we use an <literal>afterEvaluate</literal>
+                    notification. This means that the closure we are passing gets evaluated <emphasis>after</emphasis>
+                    the build scripts of the subproject are evaluated. As the property <literal>arctic</literal>
+                    is set in those build scripts, we have to do it this way. You will find more on this topic in
+                    <xref linkend='sec:dependencies_which_dependencies'/>
+                </para>
+            </section>
+        </section>
+    </section>
+    <section id='sec:execution_rules_for_multi_project_builds'>
+        <title>Execution rules for multi-project builds</title>
+        <para>When we have executed the <literal>hello</literal> task from the root project dir things behaved in an
+            intuitive way. All the <literal>hello</literal> tasks of the different projects were executed. Let's switch
+            to the <literal>bluewhale</literal> dir and see what happens if we execute Gradle from there.
+        </para>
+        <sample id="multiprojectSubBuild" dir="userguide/multiproject/tropicalWithProperties/water/bluewhale" title="Running build from subproject">
+           <output args='-q hello'/>
+        </sample>
+        <para>The basic rule behind Gradle's behavior is simple. Gradle looks down the hierarchy, starting with the
+            <emphasis>current dir</emphasis>, for tasks with the name
+            <literal>hello</literal>
+            an executes them. One thing is very important to note. Gradle
+            <emphasis>always</emphasis>
+            evaluates
+            <emphasis>every</emphasis>
+            project of the multi-project build and creates all existing task objects. Then, according to the task name
+            arguments and the current dir, Gradle filters the tasks which should be executed. Because of Gradle's
+            cross project configuration <emphasis>every</emphasis> project has to be evaluated before <emphasis>any</emphasis>
+            task gets executed. We will have a closer look at this in the next section. Let's now have our last marine
+            example. Let's add a task to <literal>bluewhale</literal> and <literal>krill</literal>.
+        </para>
+        <sample id="multiprojectPartialTasks" dir="userguide/multiproject/partialTasks/water" title="Evaluation and execution of projects">
+            <sourcefile file="bluewhale/build.gradle"/>
+            <sourcefile file="krill/build.gradle"/>
+            <output args="-q distanceToIceberg"/>
+        </sample>
+        <para>Here the output without the <literal>-q</literal> option:</para>
+        <sample id="multiprojectPartialTasksNotQuiet" dir="userguide/multiproject/partialTasks/water" title="Evaluation and execution of projects">
+            <output args="distanceToIceberg"/>
+        </sample>
+        <para>The build is executed from the <literal>water</literal> project. Neither <literal>water</literal> nor
+            <literal>tropicalFish</literal> have a task with the name <literal>distanceToIceberg</literal>. Gradle does
+            not care. The simple rule mentioned already above is: Execute all tasks down the hierarchy which have this
+            name. Only complain if there is <emphasis>no</emphasis> such task!
+        </para>
+    </section>
+    <section id='sec:running_partial_build_from_the_root'>
+        <title>Running tasks by their absolute path</title>
+        <para>As we have seen, you can run a multi-project build by entering any subproject dir and execute the build
+            from there. All matching task names of the project hierarchy starting with the current dir are executed. But
+            Gradle also offers to execute tasks by their absolute path (see also <xref linkend='sec:project_and_task_paths'/>):
+        </para>
+        <sample id="multiprojectAbsoluteTaskPaths" dir="userguide/multiproject/tropicalWithProperties/water/tropicalFish" title="Running tasks by their absolute path">
+            <output args="-q :hello :krill:hello hello"/>
+        </sample>
+        <para>The build is executed from the <literal>tropicalFish</literal> project. We execute the <literal>hello</literal>
+            tasks of the <literal>water</literal>, the <literal>krill</literal> and the <literal>tropicalFish</literal>
+            project. The first two tasks are specified by there absolute path, the last task is executed on the name
+            matching mechanism described above.
+        </para>
+    </section>
+    <section id='sec:project_and_task_paths'>
+        <title>Project and task paths</title>
+        <para>A project path has the following pattern: It starts always with a colon, which denotes the root project.
+            The root project is the only project in a path that is not specified by its name. The path
+            <literal>:bluewhale</literal>
+            corresponds to the file system path
+            <literal>water/project</literal>
+            in the case of the example above.
+        </para>
+        <para>The path of a task is simply its project path plus the task name. For example <literal>
+            :bluewhale:hello</literal>. Within a project you can address a task of the same project just by its name.
+            This is interpreted as a relative path.
+        </para>
+        <para>Originally Gradle has used the
+            <literal>'/'</literal>
+            character as a natural path separator. With the introduction of directory tasks (see <xref
+                    linkend='sec:directory_creation'/>) this was no longer possible, as the name of the directory task
+            contains the
+            <literal>'/'</literal>
+            character.
+        </para>
+    </section>
+    <section id='sec:dependencies_which_dependencies'>
+        <title>Dependencies - Which dependencies?</title>
+        <para>The examples from the last section were special, as the projects had no <emphasis>Execution
+            Dependencies</emphasis>. They had only <emphasis>Configuration Dependencies</emphasis>. Here is an example
+            where this is different:
+        </para>
+        <section id='sub:execution_time_dependencies'>
+            <title>Execution dependencies</title>
+            <section id='ssub:dependencies_and_execution_order'>
+                <title>Dependencies and execution order</title>
+                <sample id="multiprojectFirstMessages" dir="userguide/multiproject/dependencies/firstMessages/messages" includeLocation="true" title="Dependencies and execution order">
+                    <layout>
+                        settings.gradle
+                        consumer
+                        consumer/build.gradle
+                        producer
+                        producer/build.gradle
+                    </layout>
+                    <sourcefile file="settings.gradle"/>
+                    <sourcefile file="consumer/build.gradle"/>
+                    <sourcefile file="producer/build.gradle"/>
+                    <output args="-q action"/>
+                </sample>
+                <para>This did not work out. If nothing else is defined, Gradle executes the task in alphanumeric order.
+                    Therefore
+                    <literal>:consumer:action</literal>
+                    is executed before <literal>:producer:action</literal>. Let's try to solve this with a hack and
+                    rename the producer project to <literal>aProducer</literal>.
+                </para>
+                <sample id="multiprojectMessagesHack" dir="userguide/multiproject/dependencies/messagesHack/messages" title="Dependencies and execution order">
+                    <layout>
+                        settings.gradle
+                        aProducer
+                        aProducer/build.gradle
+                        consumer
+                        consumer/build.gradle
+                    </layout>
+                    <sourcefile file="settings.gradle"/>
+                    <sourcefile file="aProducer/build.gradle"/>
+                    <sourcefile file="consumer/build.gradle"/>
+                    <output args="-q action"/>
+                </sample>
+                <para>Now we take the air out of this hack. We simply switch to the <literal>consumer</literal> dir and
+                    execute the build.
+                </para>
+                <sample id="multiprojectMessagesHackBroken" dir="userguide/multiproject/dependencies/messagesHack/messages/consumer" title="Dependencies and execution order">
+                    <output args="-q action"/>
+                </sample>
+                <para>For Gradle the two
+                    <literal>action</literal>
+                    tasks are just not related. If you execute the build from the
+                    <literal>messages</literal>
+                    project Gradle executes them both because they have the same name and they are down the hierarchy.
+                    In the last example only one
+                    <literal>action</literal>
+                    was down the hierarchy and therefore it was the only task that got executed. We need something
+                    better than this hack.
+                </para>
+            </section>
+            <section id='ssub:declaring_dependencies'>
+                <title>Declaring dependencies</title>
+                <sample id="multiprojectMessagesDependencies" dir="userguide/multiproject/dependencies/messagesWithDependencies/messages" includeLocation="true" title="Declaring dependencies">
+                    <layout>
+                        settings.gradle
+                        consumer
+                        consumer/build.gradle
+                        producer
+                        producer/build.gradle
+                    </layout>
+                    <sourcefile file="settings.gradle"/>
+                    <sourcefile file="consumer/build.gradle"/>
+                    <sourcefile file="producer/build.gradle"/>
+                    <output args="-q action"/>
+                </sample>
+                <para>Running this from the <literal>consumer</literal> directory gives:</para>
+                <sample id="multiprojectMessagesDependenciesSubBuild" dir="userguide/multiproject/dependencies/messagesWithDependencies/messages/consumer" title="Declaring dependencies">
+                    <output args="-q action"/>
+                </sample>
+                <para>We have now declared that the
+                    <literal>consumer</literal>
+                    project has an
+                    <emphasis>execution dependency</emphasis>
+                    on the
+                    <literal>producer</literal>
+                    project. For Gradle declaring
+                    <emphasis>execution dependencies</emphasis>
+                    between
+                    <emphasis>projects</emphasis>
+                    is syntactic sugar. Under the hood Gradle creates task dependencies out of them. You can also create
+                    cross project tasks dependencies manually by using the absolute path of the tasks.
+                </para>
+            </section>
+            <section id='ssub:the_nature_of_project_dependencies'>
+                <title>The nature of project dependencies</title>
+                <para>Let's change the naming of our tasks and execute the build.
+                </para>
+                <sample id="multiprojectMessagesDifferentTaskNames" dir="userguide/multiproject/dependencies/messagesDifferentTaskNames/messages" title="Project execution dependencies">
+                    <sourcefile file="consumer/build.gradle"/>
+                    <sourcefile file="producer/build.gradle"/>
+                    <output args="-q consume"/>
+                </sample>
+                <para>Oops. Why does this not work? The
+                    <literal>dependsOn</literal>
+                    command is created for projects with a common lifecycle. Provided you have two Java projects where
+                    one depends on the other. If you trigger a compile for the dependent project you don't want that
+                    <emphasis>all</emphasis>
+                    tasks of the other project get executed. Therefore a
+                    <literal>dependsOn</literal>
+                    creates dependencies between tasks with equal names. To deal with the scenario above you would do
+                    the following:
+                </para>
+                <sample id="multiprojectMessagesTaskDependencies" dir="userguide/multiproject/dependencies/messagesTaskDependencies/messages" title="Cross project task dependencies">
+                    <sourcefile file="consumer/build.gradle"/>
+                    <sourcefile file="producer/build.gradle"/>
+                    <output args="-q consume"/>
+                </sample>
+            </section>
+        </section>
+        <section id='sub:configuration_time_dependencies'>
+            <title>Configuration time dependencies</title>
+            <para>Let's have one more example with our producer-consumer build before we enter
+                <emphasis>Java</emphasis>
+                land. We add a property to the producer project and create now a configuration time dependency from
+                consumer on producer.
+            </para>
+            <sample id="multiprojectMessagesConfigDependenciesBroken" dir="userguide/multiproject/dependencies/messagesConfigDependenciesBroken/messages" title="Configuration time dependencies">
+                <sourcefile file="consumer/build.gradle"/>
+                <sourcefile file="producer/build.gradle"/>
+                <output args="-q consume"/>
+            </sample>
+            <para>The default
+                <emphasis>evaluation</emphasis>
+                order of the projects is alphanumeric (for the same nesting level). Therefore the
+                <literal>consumer</literal>
+                project is evaluated before the
+                <literal>producer</literal>
+                project and the
+                <literal>key</literal>
+                value of the
+                <literal>producer</literal>
+                is set
+                <emphasis>after</emphasis>
+                it is read by the
+                <literal>consumer</literal>
+                project. Gradle offers a solution for this.
+            </para>
+            <sample id="multiprojectMessagesConfigDependencies" dir="userguide/multiproject/dependencies/messagesConfigDependencies/messages" title="Configuration time dependencies - evaluationDependsOn">
+                <sourcefile file="consumer/build.gradle"/>
+                <output args="-q consume"/>
+            </sample>
+            <para>The command
+                <literal>evaluationDependsOn</literal>
+                triggers the evaluation of
+                <literal>producer</literal>
+                <emphasis>before</emphasis>
+                <literal>consumer</literal>
+                is evaluated. The example is a bit contrived for the sake of showing the mechanism. In
+                <emphasis>this</emphasis>
+                case there would be an easier solution by reading the key property at execution time.
+            </para>
+            <sample id="multiprojectMessagesConfigDependenciesAltSolution" dir="userguide/multiproject/dependencies/messagesConfigDependenciesAltSolution/messages" title="Configuration time dependencies">
+                <sourcefile file="consumer/build.gradle"/>
+                <output args="-q consume"/>
+            </sample>
+            <para>Configuration dependencies are very different to execution dependencies. Configuration dependencies
+                are between projects whereas execution dependencies are always resolved to task dependencies. Another
+                difference is that always all projects are configured, even when you start the build from a subproject.
+                The default configuration order is top down, which is usually what is needed.
+            </para>
+            <para>On the same nesting level the configuration order depends on the alphanumeric position. The most
+                common use case is to have multi-project builds that share a common lifecycle (e.g. all projects use the
+                Java plugin). If you declare with
+                <literal>dependsOn</literal>
+                a
+                <emphasis>execution dependency</emphasis>
+                between different projects, the default behavior of this method is to create also a
+                <emphasis>configuration</emphasis>
+                dependency between the two projects. Therefore it is likely that you don't have to define configuration
+                dependencies explicitly.
+            </para>
+        </section>
+        <section id='sub:real_life_examples'>
+            <title>Real life examples</title>
+            <para>Gradle's multi-project features are driven by real life use cases. The first example for describing
+                such a use case, consists of two webapplication projects and a parent project that creates a
+                distribution out of them.
+                <footnote>
+                    <para>The real use case we had, was using <ulink url='http://lucene.apache.org/solr'/>, where you
+                        need a separate war for each index your are accessing. That was one reason why we have created a
+                        distribution of webapps. The Resin servlet container allows us, to let such a distribution point
+                        to a base installation of the servlet container.
+                    </para>
+                </footnote>
+                For the example we use only one build script and do <emphasis>cross project configuration</emphasis>.
+            </para>
+            <sample id="webdist" dir="userguide/multiproject/dependencies/webDist" includeLocation="true" title="Dependencies - real life example - crossproject configuration">
+                <layout>
+                    settings.gradle
+                    build.gradle
+                    date
+                    date/src/main/java/org/gradle/sample/DateServlet.java
+                    hello
+                    hello/src/main/java/org/gradle/sample/HelloServlet.java
+                </layout>
+                <sourcefile file="settings.gradle"/>
+                <sourcefile file="build.gradle"/>
+            </sample>
+            <para>We have an interesting set of dependencies. Obviously the
+                <literal>date</literal>
+                and
+                <literal>hello</literal>
+                task have a
+                <emphasis>configuration</emphasis>
+                dependency on <literal>webDist</literal>, as all the build logic for the webapp projects is injected by
+                <literal>webDist</literal>. The
+                <emphasis>execution</emphasis>
+                dependency is in the other direction, as
+                <literal>webDist</literal>
+                depends on the build artifacts of
+                <literal>date</literal>
+                and <literal>hello</literal>. There is even a third dependency.
+                <literal>webDist</literal>
+                has a
+                <emphasis>configuration</emphasis>
+                dependency on
+                <literal>date</literal>
+                and
+                <literal>hello</literal>
+                because it needs to know the <literal>archivePath</literal>. But it asks for this information at
+                <emphasis>execution time</emphasis>. Therefore we have no circular dependency.
+            </para>
+            <para>Such and other dependency patterns are daily bread in the problem space of multi-project builds. If a
+                build system does not support such patterns, you either can't solve your problem or you need to do ugly
+                hacks which are hard to maintain and massively afflict your productivity as a build master.
+            </para>
+            <para>There is one more thing to note from the current example. We have used the command <literal>
+                dependsOnChildren()</literal>. It is a convenience method and calls the
+                <literal>dependsOn</literal>
+                method of the parent project for every child project (not every sub project). It declares a
+                <literal>execution</literal>
+                dependency of
+                <literal>webDist</literal>
+                on
+                <literal>date</literal>
+                and <literal>hello</literal>.
+            </para>
+            <para>Another use case would be a situation where the subprojects have a configuration
+                <emphasis>and</emphasis>
+                execution dependency on the parent project. This is the case when the parent project does configuration
+                injection into its subprojects, and additionally produces something at execution time that is needed by
+                its child projects (e.g. code generation). In this case the parent project would call the
+                <literal>childrenDependOnMe</literal>
+                method to create an execution dependency for the child projects. We might add an example for this in a
+                future version of the user guide.
+            </para>
+        </section>
+    </section>
+    <section id='sec:project_jar_dependencies'>
+        <title>Project lib dependencies</title>
+        <para>What if one projects needs the jar produced by another project in its compile path? And not just the jar
+            but also the transitive dependencies of this jar? Obviously this is a very common use case for Java
+            multi-project builds. As already mentioned in <xref linkend='sub:project_dependencies'/>, Gradle
+            offers project lib dependencies for this.
+        </para>
+        <sample id="javadependencies_1" dir="userguide/multiproject/dependencies/java" includeLocation="true" title="Project lib dependencies">
+            <layout>
+                settings.gradle
+                build.gradle
+                api
+                api/src/main/java/org/gradle/sample/api/Person.java
+                api/src/main/java/org/gradle/sample/apiImpl/PersonImpl.java
+                services
+                services/personService
+                services/personService/src/main/java/org/gradle/sample/services/PersonService.java
+                services/personService/src/test/java/org/gradle/sample/services/PersonServiceTest.java
+                shared
+                shared/src/main/java/org/gradle/sample/shared/Helper.java
+            </layout>
+        </sample>
+        <para>We have the projects <literal>shared</literal>,
+            <literal>api</literal>
+            and <literal>personService</literal>.
+            <literal>personService</literal>
+            has a lib dependency on the other two projects.
+            <literal>api</literal>
+            has a lib dependency on <literal>shared</literal>.
+            <footnote>
+                <para>
+                    <literal>services</literal>
+                    is also a project, but we use it just as a container. It has no build script and gets nothing
+                    injected by another build script.
+                </para>
+            </footnote>
+        </para>
+        <sample id="javadependencies_2" dir="userguide/multiproject/dependencies/java" title="Project lib dependencies">
+            <sourcefile file="settings.gradle"/>
+            <sourcefile file="build.gradle"/>
+        </sample>
+        <para>All the build logic is in the
+            <literal>build.gradle</literal> of the root project.
+            <footnote>
+                <para>We do this here, as it makes the layout a bit easier. We usually put the project specific stuff
+                    into the buildscript of the respective projects.
+                </para>
+            </footnote>
+            A <emphasis>lib</emphasis>
+            dependency is a special form of an execution dependency. It causes the other project to be build first and
+            adds the jar with the classes of the other project to the classpath. It also add the dependencies of the
+            other project to the classpath. So you can enter the
+            <literal>api</literal>
+            directory and trigger a <userinput>gradle compile</userinput>. First
+            <literal>shared</literal>
+            is built and then
+            <literal>api</literal>
+            is built. Project dependencies enable partial multi-project builds.
+        </para>
+        <para>If you come from Maven land you might be perfectly happy with this. If you come from Ivy land, you might
+            expect some more fine grained control. Gradle offers this to you:
+        </para>
+        <sample id="javaWithCustomConf" dir="userguide/multiproject/dependencies/javaWithCustomConf" title="Fine grained control over dependencies">
+            <sourcefile file="build.gradle"/>
+        </sample>
+        <para>The Java plugin adds per default a jar to your project libraries which contains all the classes. In this
+            example we create an
+            <emphasis>additional</emphasis>
+            library containing only the interfaces of the
+            <literal>api</literal>
+            project. We assign this library to a new <emphasis>dependency configuration</emphasis>. For the person
+            service we declare that the project should be compiled only against the
+            <literal>api</literal>
+            interfaces but tested with all classes from <literal>api</literal>.
+        </para>
+        <section id="disable_dependency_projects">
+            <title>Disable the build of dependency projects.</title>
+            <para>Sometimes you don't want depended on projects to be built when doing a partial build.
+                To disable the build of the depended on projects you can run gradle with the <code>-a</code> option.
+            </para>
+        </section>
+    </section>
+    <section id="sec:multiproject_build_and_test">
+        <title>Multi-Project Building and Testing</title>
+        <para>The <literal>build</literal> task of the Java plugin is typically used to compile, test, and perform
+            code style checks (if the CodeQuality plugin is used) of a single project.  In multi-project builds
+            you may often want to do all of these tasks across a range of projects.  The <literal>buildNeeded</literal>
+            and <literal>buildDependents</literal> tasks can help with this.
+        </para>
+        <para>Let's use the project structure shown in <xref linkend='javadependencies_2'/>.  In this
+            example :services:personservice depends on both :api and :shared.  The :api project also depends on
+            :shared.
+        </para>
+        <para>Assume you are working on a single project, the :api project.  You have been making changes, but
+            have not built the entire project since performing a clean.  You want to build any necessary supporting
+            jars, but only perform code quality and unit tests on the project you have changed.
+            The <literal>build</literal> task does this.
+        </para>
+        <sample id="multitestingBuild" dir="userguide/multiproject/dependencies/java" title="Build and Test Single Project">
+            <output args=":api:build"/>
+        </sample>
+
+        <para>While you are working in a typical development cycle repeatedly building and testing changes to a
+            the :api project (knowing that you are only changing files in this one project), you may not want to
+            even suffer the expense of :shared:compile checking to see what has changed in the :shared project.
+            Adding the <literal>-a</literal> option will cause gradle to use cached jars to resolve any project lib
+            dependencies and not try to re-build the depended on projects.
+        </para>
+        <sample id="multitestingBuildDashA" dir="userguide/multiproject/dependencies/java" title="Partial Build and Test Single Project">
+            <test args=":shared:assemble"/>
+            <output args="-a :api:build"/>
+        </sample>
+
+        <para>If you have just gotten the latest version of source from your version control system which included changes
+            in other projects that :api depends on, you might want to not only build all the projects you depend on,
+            but test them as well. The <literal>buildNeeded</literal> task also tests all the projects from the
+            project lib dependencies of the testRuntime configuration.
+        </para>
+        <sample id="multitestingBuildNeeded" dir="userguide/multiproject/dependencies/java" title="Build and Test Depended On Projects">
+            <output args=":api:buildNeeded"/>
+        </sample>
+
+        <para>You also might want to refactor some part of the :api project that is used in other projects.
+            If you make these types of changes, it is not sufficient to test just the :api,
+            project, you also need to test all projects that depend on the :api project.
+            The <literal>buildDependents</literal> task also tests all the projects that have a project lib dependency
+            (in the testRuntime configuration) on the specified project.
+        </para>
+        <sample id="multitestingBuildDependents" dir="userguide/multiproject/dependencies/java" title="Build and Test Dependent Projects">
+            <output args=":api:buildDependents"/>
+        </sample>
+        <para>Finally, you may want to build and test everything in all projects.  If the root project has declared
+        <literal>dependsOnChildren()</literal> (as this one does), then any task you run against the root project
+        will cause that same named task to be run on all the children.  So you can just run
+        <literal>gradle build</literal> to build and test all projects.
+        </para>
+    </section>
+    <section id='sec:property_and_method_inheritance'>
+        <title>Property and method inheritance</title>
+        <para>Properties and methods declared in a project are inherited to all its subprojects. This is an alternative
+            to configuration injection. But we think that the model of inheritance does not reflect the problem space of
+            multi-project builds very well. In a future edition of this user guide we might write more about this.
+        </para>
+        <para>Method inheritance might be interesting to use as Gradle's
+            <emphasis>Configuration Injection</emphasis>
+            does not support methods yet (but will in a future release.).
+        </para>
+        <para>You might be wondering why we have implemented a feature we obviously don't like that much. One reason is
+            that it is offered by other tools and we want to have the check mark in a feature comparison :). And we like
+            to offer our users a choice.
+        </para>
+    </section>
+    <section>
+        <title>Summary</title>
+        <para>Writing this chapter was pretty exhausting and reading it might have a similar effect. Our final message
+            for this chapter is that multi-project builds with Gradle are usually
+            <emphasis>not</emphasis>
+            difficult. There are six elements you need to remember: <literal>allproject</literal>, <literal>
+            subprojects</literal>, <literal>dependsOn</literal>, <literal>childrenDependOnMe</literal>,
+            <literal>dependsOnChildren</literal>
+            and project lib dependencies.
+            <footnote>
+                <para>So we are well in the range of the
+                    <ulink url='http://en.wikipedia.org/wiki/The_Magical_Number_Seven,_Plus_or_Minus_Two'>7 plus 2
+                        Rule
+                    </ulink>
+                    :)
+                </para>
+            </footnote>
+            With those elements, and keeping in mind that Gradle has a distinct configuration and execution phase, you
+            have already a lot of flexibility. But when you enter steep territory Gradle does not become an obstacle and
+            usually accompanies and carries you to the top of the mountain.
+        </para>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/organizeBuildLogic.xml b/subprojects/gradle-docs/src/docs/userguide/organizeBuildLogic.xml
new file mode 100644
index 0000000..1171a6d
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/organizeBuildLogic.xml
@@ -0,0 +1,193 @@
+<!--
+  ~ 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.
+  -->
+<chapter id='organizing_build_logic' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>Organizing Build Logic</title>
+    <para>Gradle offers a variety of ways to organize your build logic. First of all you can put your build logic
+        directly in the action closure of a task. If a couple of tasks share the same logic you can extract this logic
+        into a method. If multiple projects of a multi-project build share some logic you can define this method in the
+        parent project. If the build logic gets too complex for being properly modeled by methods you want have an OO
+        Model.
+        <footnote>
+            <para>Which might range from a single class to something very complex.
+            </para>
+        </footnote>
+        Gradle makes this very easy. Just drop your classes in a certain directory and Gradle automatically compiles them
+        and puts them in the classpath of your build script.
+    </para>
+    <para>Here is a summary of the ways you can organise your build logic:</para>
+    <itemizedlist>
+        <listitem>
+            <para>POGOs. You can declare and use plain old Groovy objects (POGOs) directly in your build script. The
+                build script is written in Groovy, after all, and Groovy provides you with lots of excellent ways to
+                organize code.
+            </para>
+        </listitem>
+        <listitem>
+            <para><link linkend="sec:inherited_properties_and_methods">Inherited properties and methods</link>. In a
+                multi-project build, sub-projects inherit the properties and methods of their parent project.</para>
+        </listitem>
+        <listitem>
+            <para><link linkend="sec:injected_configuration">Configuration injection</link>. In a
+                multi-project build, a project (usually the root project) can inject properties and methods into another
+                project.</para>
+        </listitem>
+        <listitem>
+            <para><link linkend="sec:build_sources"><filename>buildSrc</filename> project</link>. Drop the source for
+                your build classes into a certain directory and Gradle automatically compiles them and includes them
+                in the classpath of your build script.
+            </para>
+        </listitem>
+        <listitem>
+            <para><link linkend="sec:configuring_using_external_script">Shared scripts</link>. Define common configuration
+                in an external build, and apply the script to multiple projects, possibly across different builds.
+            </para>
+        </listitem>
+        <listitem>
+            <para><link linkend="custom_tasks">Custom tasks</link>. Put your build logic into a custom task, and
+                reuse that task in multiple places.</para>
+        </listitem>
+        <listitem>
+            <para><link linkend="custom_plugins">Custom plugins</link>. Put your build build logic into a custom plugin,
+                and apply that plugin to multiple projects. The plugin must be in the classpath of your build script.
+                You can achieve this either by using <link linkend="sec:build_sources"><filename>build sources</filename></link> or
+                by adding an <link linkend="sec:external_dependencies">external library</link> that contains the plugin. 
+            </para>
+        </listitem>
+        <listitem>
+            <para><link linkend="sec:external_build">Execute an external build</link>. Execute another Gradle build from the
+                current build.</para>
+        </listitem>
+        <listitem>
+            <para><link linkend="sec:external_dependencies">External libraries</link>. Use external libraries directly
+                in your build file.
+            </para>
+        </listitem>
+    </itemizedlist>
+
+    <section id="sec:inherited_properties_and_methods">
+        <title>Inherited properties and methods</title>
+        <para>Any method or property defined in a project build script is also visible to all the sub-projects. You
+            can use this to define common configurations, and to extract build logic into methods which can be reused
+            by the sub-projects.
+        </para>
+        <sample id="inheritedBuildLogic" dir="userguide/organizeBuildLogic/inherited" title="Using inherited properties and methods">
+            <sourcefile file="build.gradle"/>
+            <sourcefile file="child/build.gradle"/>
+            <output args="-q show"/>
+        </sample>
+    </section>
+
+    <section id="sec:injected_configuration">
+        <title>Injected configuration</title>
+        <para>You can use the configuration injection technique discussed in <xref linkend="sec:cross_project_configuration"/>
+            and <xref linkend="sec:subproject_configuration"/> to inject properties and methods into various projects.
+            This is generally a better option than inheritance, for a number of reasons: The injection is
+            explicit in the build script, You can inject different logic into different projects, And you can inject
+            any kind of configuration such as repositories, plug-ins, tasks, and so on.
+            The following sample shows how this works.
+        </para>
+        <sample id="injectedBuildLogic" dir="userguide/organizeBuildLogic/injected" title="Using injected properties and methods">
+            <sourcefile file="build.gradle"/>
+            <sourcefile file="child1/build.gradle"/>
+            <output args="-q show"/>
+        </sample>
+    </section>
+    
+    <section id='sec:build_sources'>
+        <title>Build sources in the <filename>buildSrc</filename> project</title>
+        <para>If you run Gradle, it checks for the existence of a directory called <filename>buildSrc</filename>. Just put
+            your build source code in this directory and stick to the layout convention for a Java/Groovy project (see
+            <xref linkend='javalayout'/>). Gradle then automatically compiles and tests this code and puts it in
+            the classpath of your build script. You don't need to provide any further instruction. This can be a good
+            place to add your custom tasks and plugins.
+        </para>
+        <para>For multi-project builds there can be only one <filename>buildSrc</filename> directory, which has to be
+            in the root project.
+        </para>
+        <para>This is probably good enough for most of the cases. If you need more flexibility, you can provide a
+            <filename>build.gradle</filename> and a <filename>settings.gradle</filename> file in the
+            <filename>buildSrc</filename> directory. If you like, you can even have a multi-project build in there.
+        </para>
+    </section>
+
+    <section id="sec:external_build">
+        <title>Running another Gradle build from a build</title>
+        <para>You can use the <apilink class="org.gradle.api.tasks.GradleBuild"/> task. You can use either of the
+            <literal>dir</literal> or <literal>buildFile</literal> properties to specify which build to execute,
+            and the <literal>tasks</literal> property to specify which tasks to execute.
+        </para>
+        <sample id="nestedBuild" dir="userguide/organizeBuildLogic/nestedBuild" title="Running another build from a build">
+            <sourcefile file="build.gradle" snippet="execute-build"/>
+            <sourcefile file="other.gradle"/>
+            <output args="-q build"/>
+        </sample>
+    </section>
+
+    <section id='sec:external_dependencies'>
+        <title>External dependencies for the build script</title>
+        <para>If your build script needs to use external libraries, you can add them to the script's classpath in the
+            build script itself. You do this using the <literal>buildscript()</literal> method, passing in a closure which
+            declares the build script classpath.
+        </para>
+        <sample id="declareExternalBuildDependency" dir="userguide/organizeBuildLogic/externalDependency" title="Declaring external dependencies for the build script">
+            <sourcefile file="build.gradle" snippet="declare-classpath"/>
+        </sample>
+        <para>The closure passed to the <literal>buildscript()</literal> method configures a
+            <apilink class="org.gradle.api.initialization.dsl.ScriptHandler"/> instance. You declare the build script
+            classpath by adding dependencies to the <literal>classpath</literal> configuration. This is the same way
+            you declare, for example, the Java compilation classpath. You can use any of the dependency types described
+            in <xref linkend='sec:how_to_declare_your_dependencies'/>, except project dependencies.</para>
+        <para>Having declared the build script classpath, you can use the classes in your build script as you would
+            any other classes on the classpath. The following example adds to the previous example, and uses classes
+            from the build script classpath.</para>
+        <sample id="externalBuildDependency" dir="userguide/organizeBuildLogic/externalDependency" title="A build script with external dependencies">
+            <sourcefile file="build.gradle"/>
+            <output args="-q encode"/>
+        </sample>
+        <para>
+            For multi-project builds, the dependencies declared in the a project's build script, are available to the
+            build scripts of all sub-projects.
+        </para>
+    </section>
+
+    <section id='sec:ant_optional_dependencies'>
+        <title>Ant optional dependencies</title>
+        <para>For reasons we don't fully understand yet, external dependencies are not picked up by Ant's optional
+            tasks. But you can easily do it in another way.
+            <footnote>
+                <para>In fact, we think this is anyway the nicer solution. Only if your buildscript and Ant's optional
+                    task need the <emphasis>same</emphasis> library you would have to define it two times. In such a
+                    case it would be nice, if Ant's optional task would automatically pickup the classpath defined
+                    in the <literal>gradesettings</literal>.
+                </para>
+            </footnote>
+        </para>
+        <sample id="buildLogic" dir="userguide/organizeBuildLogic" title="Ant optional dependencies">
+            <sourcefile file="build.gradle"/>
+        </sample>
+        <para>This is also nice example for the usage of client modules. The pom.xml in maven central for the
+        ant-commons-net task does not provide the right information for this use case.</para>
+    </section>
+    <section id='sec:philosophy'>
+        <title>Summary</title>
+        <para>Gradle offers you a variety of ways of organizing your build logic. You can choose what is right for your
+            domain and find the right balance between unnecessary indirections, and avoiding redundancy and a hard to
+            maintain code base. It is our experience that even very complex custom build logic is rarely shared between
+            different builds. Other build tools enforce a separation of this build logic into a separate project. Gradle
+            spares you this unnecessary overhead and indirection.
+        </para>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/osgi.xml b/subprojects/gradle-docs/src/docs/userguide/osgi.xml
new file mode 100644
index 0000000..8ce3203
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/osgi.xml
@@ -0,0 +1,108 @@
+<!--
+  ~ 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.
+  -->
+<chapter id='osgi_plugin' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>The OSGi Plugin</title>
+    <para>The OSGi plugin provides a factory method to create an
+        <apilink class="org.gradle.api.plugins.osgi.OsgiManifest"/> object. <literal>OsgiManifest</literal> extends
+        <apilink class="org.gradle.api.java.archives.Manifest"/>. To learn more
+        about generic manifest handling, see <xref linkend="sub:manifest"/>. If the Java plugins is applied, the OSGi plugin
+        replaces the manifest object of the default jar with an <literal>OsgiManifest</literal> object. The replaced manifest
+        is merged into the new one.
+    </para>
+    <para>The OSGi plugin makes heavy use of Peter Kriens <ulink url='http://www.aqute.biz/Code/Bnd'>BND tool</ulink>.
+    </para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the OSGi plugin, include in your build script:</para>
+        <sample id="useOsgiPlugin" dir="osgi" title="Using the OSGi plugin">
+            <sourcefile file="build.gradle" snippet="use-plugin"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>Implicitly applied plugins</title>
+        <para>Applies the Java base plugin.</para>
+    </section>
+
+    <section>
+        <title>Tasks</title>
+        <para>This plugin does not add any tasks.</para>
+    </section>
+
+    <section>
+        <title>Dependency management</title>
+        <para>TBD</para>
+    </section>
+
+    <section>
+        <title>Convention object</title>
+
+        <para>The OSGi plugin adds the following convention object: <apilink class="org.gradle.api.plugins.osgi.OsgiPluginConvention"/>
+        </para>
+        <section>
+            <title>Convention properties</title>
+
+            <para>The OSGi plugin does not add any convention properties to the project.
+            </para>
+        </section>
+        <section>
+            <title>Convention methods</title>
+
+            <para>The OSGi plugin adds the following methods. For more details, see the javadoc of the convention
+                object.
+            </para>
+            <table>
+                <title>OSGi methods</title>
+                <thead>
+                    <tr>
+                        <td>Method</td>
+                        <td>Return Type</td>
+                        <td>Description</td>
+                    </tr>
+                </thead>
+                <tr>
+                    <td>osgiManifest()</td>
+                    <td>
+                        <apilink class="org.gradle.api.plugins.osgi.OsgiManifest"/>
+                    </td>
+                    <td>Returns an OsgiManifest object.</td>
+                </tr>
+                <tr>
+                    <td>osgiManifest(Closure cl)</td>
+                    <td>
+                        <apilink class="org.gradle.api.plugins.osgi.OsgiManifest"/>
+                    </td>
+                    <td>Returns an OsgiManifest object configured by the closure.</td>
+                </tr>
+            </table>
+        </section>
+    </section>
+    <section>
+        <para>The classes in the classes dir are analyzed regarding there package dependencies and the packages they expose.
+            Based on this the <emphasis>Import-Package</emphasis> and the <emphasis>Export-Package</emphasis> values of the
+            OSGi Manifest are calculated. If the classpath contains jars with an OSGi bundle, the bundle
+            information is used to specify version information for the <emphasis>Import-Package</emphasis>
+            value. Beside the explicit properties of the <literal>OsgiManifest</literal> object you can add instructions.
+        </para>
+        <sample id="osgi" dir="userguide/tutorial/osgi" title="Configuration of OSGi MANIFEST.MF file">
+            <sourcefile file="build.gradle" snippet="configure-jar"/>
+        </sample>
+        <para>The first argument of the instruction call is the key of the property. The other arguments form the value.
+            They are joined by Gradle with the <literal>,</literal> separator. To learn more about the available
+            instructions have a look at the <ulink url='http://www.aqute.biz/Code/Bnd'>BND tool</ulink>.</para>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/overview.xml b/subprojects/gradle-docs/src/docs/userguide/overview.xml
new file mode 100644
index 0000000..32d2cd1
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/overview.xml
@@ -0,0 +1,181 @@
+<!--
+  ~ 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.
+  -->
+<chapter id='overview' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>Overview</title>
+    <section id='sec:special_feature_of_gradle'>
+        <title>Features</title>
+        <para>Here is a list of some of Gradle's features.
+        </para>
+        <variablelist>
+            <varlistentry>
+                <term>Declarative builds and build-by-convention</term>
+                <listitem>
+                    <para>At the heart of Gradle lies a rich extensible Domain Specific Language (DSL) based on Groovy.
+                        Gradle pushes declarative builds to the next level by providing declarative language elements
+                        that you can assemble as you like. Those elements also provide build-by-convention support for
+                        Java, Groovy, OSGi, Web and Scala projects. Even more, this declarative language is extensible.
+                        Add your own new language elements or enhance the existing ones. Thus providing concise,
+                        maintainable and comprehensible builds.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>Language for dependency based programming</term>
+                <listitem>
+                    <para>The declarative language lies on top of a general purpose task graph, which you can fully
+                        leverage in your builds. It provides utmost flexibility to adapt Gradle to your unique needs. 
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>Structure your build</term>
+                <listitem>
+                    <para>The suppleness and richness of Gradle finally allows you to apply common design principles to your build.
+                        For example, it is very easy to compose your build from reusable
+                        pieces of build logic. Inline stuff where unnecessary indirections would be inappropriate. Don't be
+                        forced to tear apart what belongs together (e.g. in your project hierarchy). Thus avoiding smells
+                        like shotgun changes or divergent change that turn your build into a maintenance nightmare.
+                        At last you can create a well structured, easily maintained, comprehensible build.</para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>Deep API</term>
+                <listitem>
+                    <para>From being a pleasure to be used embedded to its many hooks over the whole lifecycle of build
+                        execution, Gradle allows you to monitor and customize its configuration and execution behavior
+                        to its very core.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>Gradle scales</term>
+                <listitem>
+                    <para>Gradle scales very well. It significantly increases your productivity,
+                        from simple single project builds up to huge enterprise multi-project builds. This is true
+                        for structuring the build. With the state-of-art incremental build function, this is also true for
+                        tackling the performance pain many large enterprise builds suffer from.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>Multi-project builds</term>
+                <listitem>
+                    <para>Gradle's support for multi-project build is outstanding. Project dependencies are first class
+                        citizens. We allow you to model the project relationships in a multi-project build as they really
+                        are for your problem domain. Gradle follows your layout not vice versa.
+                    </para>
+                    <para>Gradle provides partial builds. If you build a single subproject Gradle takes care of building
+                        all the subprojects that subproject 
+                        depends on. You can also choose to rebuild the subprojects that depend on a particular subproject.
+                        Together with incremental builds this is a big time saver for larger builds.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>Many ways to manage your dependencies</term>
+                <listitem>
+                    <para>Different teams prefer different ways to manage their external dependencies.
+                        Gradle provides convenient support for any strategy. From transitive dependency 
+                        management with remote maven and ivy repositories to jars or dirs on the local file system.</para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>Gradle is the first build integration tool</term>
+                <listitem>
+                    <para>Ant tasks are first class citizens. Even more interesting, Ant projects are first class citizens as well.
+                        Gradle provides a deep import for any Ant project, turning Ant targets into native Gradle tasks at runtime.
+                        You can depend on them from Gradle, you can enhance them from Gradle, you can even declare dependencies on
+                        Gradle tasks in your build.xml. The same integration is provided for properties, paths, etc ...</para>
+                    <para>Gradle fully supports your existing Maven or Ivy repository infrastructure for publishing and retrieving
+                        dependencies. Gradle also provides a converter for turning a Maven pom.xml into a Gradle script.
+                        Runtime imports of Maven projects will come soon.</para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>Ease of migration</term>
+                <listitem>
+                    <para>Gradle can adapt to any structure you have. Therefore you can always develop your Gradle build
+                        in the same branch where your production build lives and both can evolve in parallel.
+                        We usually recommend to write tests that make sure that the produced artifacts are similar.
+                        That way migration is as less disruptive and as reliable as possible. This is following the best-practices
+                        for refactoring by applying baby steps.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>Groovy</term>
+                <listitem>
+                    <para>Gradle's build scripts are written in Groovy, not XML. But unlike other approaches this is not
+                        for simply exposing the raw scripting power of a dynamic language. That would just lead to a very
+                        difficult to maintain build. The whole design of Gradle is oriented towards being used as a language,
+                        not as a rigid framework. And Groovy is our glue that allows you to tell your individual story with
+                        the abstractions Gradle (or you) provide. Gradle provides some standard stories but they are not
+                        privileged in any form. This is for us a major distinguishing features compared to other declarative
+                        build systems. Our Groovy support is also not just some simple coating sugar layer. The whole Gradle API
+                        is fully groovynized. Only by that using Groovy is the fun and productivity gain it can be.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>The Gradle wrapper</term>
+                <listitem>
+                    <para>The Gradle Wrapper allows you to execute Gradle builds on machines where Gradle is not
+                        installed. This is useful for example for some continuous integration servers. It is also
+                        useful for an open source project to keep the barrier low for building it. The wrapper is also
+                        very interesting for the enterprise. It is a zero administration approach for the client machines.
+                        It also enforces the usage of a particular Gradle version thus minimizing support issues. 
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>Free and open source</term>
+                <listitem>
+                    <para>Gradle is an open source project, and is licensed under the <ulink url="website:license.html">ASL</ulink>.
+                    </para>
+                </listitem>
+            </varlistentry>
+        </variablelist>
+    </section>
+    <section id='sec:why_groovy'>
+        <title>Why Groovy?</title>
+        <para>We think the advantages of an internal DSL (based on a dynamic language) over XML are tremendous in case
+            of <emphasis>build scripts</emphasis>. There are a couple of dynamic languages out there. Why Groovy? The
+            answer lies in the context Gradle is operating in. Although Gradle is a general purpose build tool at its
+            core, its main focus are Java projects.
+            In such projects obviously the team members know Java. We think a build
+            should be as transparent as possible to <emphasis>all</emphasis> team members.
+        </para>
+        <para>You might argue why not using Java then as the language for build scripts. We think this is a valid
+            question. It would have the highest transparency for your team and the lowest learning curve. But due to
+            limitations of Java such a build language would not be as nice, expressive and powerful as it could be.
+            <footnote>
+                <para>At
+                    <ulink url='http://www.defmacro.org/ramblings/lisp.html'/>
+                    you find an interesting article comparing Ant, XML, Java and Lisp. It's funny that the 'if Java had
+                    that syntax' syntax in this article is actually the Groovy syntax.
+                </para>
+            </footnote>
+            Languages like Python, Groovy or Ruby do a much better job here. We have chosen Groovy as it offers by far
+            the greatest transparency for Java people. Its base syntax is the same as Java's as well as its type system,
+            its package structure and other things. Groovy builds a lot on top of that. But on a common ground with Java.
+        </para>
+        <para>For Java teams which share also Python or Ruby knowledge or are happy to learn it, the above arguments
+            don't apply. The Gradle design is well-suited for creating another build script engine in JRuby or Jython.
+            It just doesn't have the highest priority for us at the moment. We happily support any community effort
+            to create additional build script engines.
+        </para>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/plugins.xml b/subprojects/gradle-docs/src/docs/userguide/plugins.xml
new file mode 100644
index 0000000..c775d80
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/plugins.xml
@@ -0,0 +1,147 @@
+<!--
+  ~ 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.
+  -->
+<chapter id='plugins' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>Using Plugins</title>
+    <para>Now we look at <emphasis>how</emphasis> Gradle provides build-by-convention and out of the box functionality.
+        These features are decoupled from the core of Gradle, and are provided via plugins. Although the plugins are
+        decoupled, we would like to point out that the Gradle core plugins are NEVER updated or changed for a
+        particular Gradle distribution. If there is a bug in the compile functionality of Gradle, we will release a new
+        version of Gradle. There is no change of behavior for the lifetime of a given distribution of Gradle.
+    </para>
+    <section id='sec:using_plugins'>
+        <title>Declaring plugins</title>
+        <para>If you want to use the plugin for building a Java project, simply include
+        </para>
+        <sample id="useJavaPlugin" dir="java/quickstart" title="Using a plugin">
+            <sourcefile file="build.gradle" snippet="use-plugin"/>
+        </sample>
+        <para>in your script. That's all. From a technological point of view plugins use just the same
+            operations as you can use from your build scripts. That is, they use the Project and Task API.
+            The Gradle plugins generally use this API to:
+        </para>
+        <itemizedlist>
+            <listitem>
+                <para>Add tasks to the project (e.g. compile, test)
+                </para>
+            </listitem>
+            <listitem>
+                <para>Create dependencies between those tasks to let them execute in the appropriate order.
+                </para>
+            </listitem>
+            <listitem>
+                <para>Add dependency configurations to the project.</para>
+            </listitem>
+            <listitem>
+                <para>Add a so called <firstterm>convention object</firstterm> to the project.
+                </para>
+            </listitem>
+        </itemizedlist>
+        <para>Let's check this out:
+        </para>
+        <sample id="pluginIntro" dir="userguide/tutorial/pluginIntro" title="Applying a plugin by id">
+            <sourcefile file="build.gradle" snippet="apply-by-id"/>
+            <output args="-q show"/>
+        </sample>
+        <para>The Java plugin adds a <literal>compileJava</literal> task and a <literal>processResources</literal> task
+            to the project object which can be accessed by a build script. It has configured the <literal>destinationDir</literal>
+            property of both of these tasks.
+        </para>
+        <para>The <literal>apply()</literal> method either takes a string or a class as an argument. You can write
+        </para>
+        <sample id="pluginIntro" dir="userguide/tutorial/pluginIntro" title="Applying a plugin by type">
+            <sourcefile file="build.gradle" snippet="apply-by-type"/>
+        </sample>
+        <para>Thanks to Gradle's default imports (see <xref linkend='ide_support'/>) you can also write in this case.
+        </para>
+        <sample id="pluginIntro" dir="userguide/tutorial/pluginIntro" title="Applying a plugin by type">
+            <sourcefile file="build.gradle" snippet="apply-by-type-with-import"/>
+        </sample>
+        <para>Any class, which implements the <apilink class="org.gradle.api.Plugin"/> interface, can be used as a
+            plugin. Just pass the class as an argument. You don't need to configure anything else for this.</para>
+        <para>If you want to use your own plugins, you must make sure that they are accessible via the build script
+            classpath (see <xref linkend='organizing_build_logic'/> for more information). To learn more about how
+            to write custom plugins, see <xref linkend='custom_plugins'/>.
+        </para>
+    </section>
+    <section id='sub:more_about_convention_objects'>
+        <title>Using the convention object</title>
+        <para>If you use the Java plugin
+            for example, there are a <literal>compileJava</literal> and a <literal>processResources</literal> task for
+            your production code (the same is true for your test
+            code). The default location for the output of those tasks is the directory <literal>build/classes/main</literal>.
+            What if you want to change this? Let's try:
+        </para>
+        <sample id="pluginConfig" dir="userguide/tutorial/pluginConfig" title="Configuring a plugin task">
+            <sourcefile file="build.gradle"/>
+            <output args="-q show"/>
+        </sample>
+        <para>Setting the <literal>destinationDir</literal>
+            of the <literal>processResources</literal> task had only an effect on the <literal>processResources</literal>
+            task. Maybe this was what you wanted. But what if
+            you want to change the output directory for all tasks? It would be unfortunate if you had to do this for
+            each task separately.
+        </para>
+        <para>Gradle's tasks are usually <firstterm>convention aware</firstterm>. A plugin can add a convention object to
+            your project, and map certain values of this convention object to task properties.
+        </para>
+        <sample id="pluginConvention" dir="userguide/tutorial/pluginConvention" title="Plugin convention object">
+            <sourcefile file="build.gradle"/>
+            <output args="-q show"/>
+        </sample>
+        <para>The Java plugin has added a convention object with a <literal>sourceSets</literal>
+            property, which we use to set the classes directory. Notice that setting this has changed the <literal>destinationDir</literal>
+            property of both the <literal>processResources</literal> and
+            <literal>compileJava</literal> tasks.</para>
+        <para>By setting a task attribute explicitly (as we have done in the first example) you overwrite the convention
+            value for this particular task.
+        </para>
+        <para>Not all of the tasks attributes are mapped to convention object values. It is the decision of the plugin
+            to decide what are the shared properties and then bundle them in a convention object and map them to the
+            tasks.
+        </para>
+        <para>
+            The properties of a convention object can be accessed as project properties. As shown in the following
+            example, you can also access the convention object explicitly.
+        </para>
+        <sample id="pluginAccessConvention" dir="userguide/tutorial/pluginAccessConvention" title="Using the plugin convention object">
+            <sourcefile file="build.gradle"/>
+            <output args="-q show"/>
+        </sample>
+        <para>Every project object has a <apilink class="org.gradle.api.plugins.Convention"/> object which is a
+            container for convention objects contributed
+            by the plugins declared for your project. If you simply access or set a property or access a method in
+            your build script, the project object first looks if this is a property of itself. If not, it delegates
+            the request to its convention object. The convention object checks if any of the plugin convention
+            objects can fulfill the request (first wins and the order is not defined). The plugin convention objects
+            also introduce a namespace.
+        </para>
+        <section id='sub:declaring_plugins_multiple_times'>
+            <title>Declaring plugins multiple times</title>
+            <para>A plugin is only called once for a given project, even if you have multiple
+                <literal>apply()</literal>
+                statements. An additional call after the first call has no effect but doesn't hurt either. This can be
+                important if you use plugins which extend other plugins. For example
+                the Groovy plugin automatically applies the Java plugin. We say the Groovy plugin extends the Java plugin. But you might as well
+                write:
+            </para>
+            <sample id="pluginIntro" dir="userguide/tutorial/pluginIntro" title="Explicit application of an implied plugin">
+                <sourcefile file="build.gradle" snippet="explicit-apply"/>
+            </sample>
+            <para>If you use cross-project configuration in multi-project builds this is a useful feature.
+            </para>
+        </section>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/potentialTraps.xml b/subprojects/gradle-docs/src/docs/userguide/potentialTraps.xml
new file mode 100644
index 0000000..a21bea1
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/potentialTraps.xml
@@ -0,0 +1,50 @@
+<!--
+  ~ 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.
+  -->
+<appendix id='potential_traps' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>Potential Traps</title>
+    <section id='sec:groovy_script_variables'>
+        <title>Groovy script variables</title>
+        <para>For Gradle users it is important to understand how Groovy deals with script variables. Groovy has two types
+            of script variables. One with a local scope and one with a script wide scope.
+        </para>
+        <sample id="scope" dir="userguide/tutorial" title="Variables scope: local and script wide">
+            <sourcefile file="scope.groovy"/>
+            <output args=""/>
+        </sample>
+        <para>Variables which are declared with a type modifier are visible within closures but not visible within
+            methods. This is a heavily discussed behavior in the Groovy community.
+            <footnote>
+                <para>One of those discussions can be found here:
+                    <ulink url='http://www.nabble.com/script-scoping-question-td16034724.html'/>
+                </para>
+            </footnote>
+        </para>
+    </section>
+    <section id='sec:configuration_and_execution_phase'>
+        <title>Configuration and execution phase</title>
+        <para>It is important to keep in mind that Gradle has a distinct configuration and execution phase (see 
+            <xref linkend='build_lifecycle'/>).
+        </para>
+        <sample id="mkdirTrap" dir="userguide/tutorial/mkdirTrap" title="Distinct configuration and execution phase">
+            <sourcefile file="build.gradle"/>
+            <output args="-q compile"/>
+        </sample>
+        <para>As the creation of the directory happens during the configuration phase, the
+            <literal>clean</literal>
+            task removes the directory during the execution phase.
+        </para>
+    </section>
+</appendix>
diff --git a/subprojects/gradle-docs/src/docs/userguide/projectReports.xml b/subprojects/gradle-docs/src/docs/userguide/projectReports.xml
new file mode 100644
index 0000000..a2046f3
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/projectReports.xml
@@ -0,0 +1,161 @@
+<!--
+  ~ 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.
+  -->
+<chapter id="project_reports_plugin" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>The Project Report Plugin</title>
+
+    <note>
+        <para>The Project report plugin is currently a work in progress, and at this stage doesn't do particularly
+            much. We plan to add much more to these reports in future releases of Gradle.
+        </para>
+    </note>
+
+    <para>The Project report plugin adds some tasks to your project which generate reports containing useful
+        information about your build. Those tasks generate exactly the same content as the command line reports triggered
+        by <literal>-t</literal>, <literal>-n</literal>, <literal>-r</literal> (see <xref linkend="sec:obtaining_information_about_your_build"/>).
+        In contrast to the command line reports, the report plugin generates the reports into a file. There is also an
+        aggregating task that depends on all report tasks added by the plugin.
+    </para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the Project report plugin, include in your build script:</para>
+<programlisting language="java">
+apply plugin: 'project-report'
+</programlisting>
+    </section>
+
+    <section>
+        <title>Tasks</title>
+        <para>The project report plugin defines the following tasks:</para>
+        <table>
+            <title>Project report plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>dependencyReport</literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.tasks.diagnostics.DependencyReportTask"/></td>
+                <td>Generates the project dependency report.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>propertyReport</literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.tasks.diagnostics.PropertyReportTask"/></td>
+                <td>Generates the project property report.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>taskReport</literal>
+                </td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.tasks.diagnostics.TaskReportTask"/></td>
+                <td>Generates the project task report.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>projectReport</literal>
+                </td>
+                <td>
+                    <literal>dependencyReport</literal>, <literal>propertyReport</literal>, <literal>taskReport</literal>
+                </td>
+                <td><apilink class="org.gradle.api.Task"/></td>
+                <td>Generates all project reports.</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Project layout</title>
+        <para>The project report plugin does not require any particular project layout.</para>
+    </section>
+
+    <section>
+        <title>Dependency management</title>
+        <para>The project report plugin does not define any dependency configurations.</para>
+    </section>
+
+    <section>
+        <title>Convention properties</title>
+        <para>The project report defines the following convention properties:</para>
+        <table>
+            <title>Project report plugin - convention properties</title>
+            <thead>
+                <tr>
+                    <td>Property name</td>
+                    <td>Type</td>
+                    <td>Default value</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <xi:include href="reportingBasePluginProperties.xml"/>
+            <tr>
+                <td>
+                    <literal>projects</literal>
+                </td>
+                <td>
+                    <classname>Set<Project></classname>
+                </td>
+                <td>
+                    <literal>A one element set with the project the plugin was applied to.</literal>
+                </td>
+                <td>
+                    The projects to generate the reports for.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>projectReportDirName</literal>
+                </td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td>
+                    <literal>project</literal>
+                </td>
+                <td>
+                    The name of the directory to generate the project report into, relative to the reports directory.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>projectReportDir</literal>
+                </td>
+                <td>
+                    <classname>File</classname> (read-only)
+                </td>
+                <td>
+                    <literal><replaceable>reportsDir</replaceable>/<replaceable>projectReportDirName</replaceable></literal>
+                </td>
+                <td>
+                    The directory to generate the project report into.
+                </td>
+            </tr>
+        </table>
+
+        <para>These convention properties are provided by a convention object of type <apilink class="org.gradle.api.plugins.ProjectReportsPluginConvention" lang="groovy"/>.</para>
+    </section>
+    
+</chapter>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/reportingBasePluginProperties.xml b/subprojects/gradle-docs/src/docs/userguide/reportingBasePluginProperties.xml
new file mode 100644
index 0000000..7f7893d
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/reportingBasePluginProperties.xml
@@ -0,0 +1,45 @@
+<!--
+  ~ 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.
+  -->
+<tgroup>
+    <tr>
+        <td>
+            <literal>reportsDirName</literal>
+        </td>
+        <td>
+            <classname>String</classname>
+        </td>
+        <td>
+            <filename>reports</filename>
+        </td>
+        <td>
+            The name of the directory to generate reports into, relative to the build directory.
+        </td>
+    </tr>
+    <tr>
+        <td>
+            <literal>reportsDir</literal>
+        </td>
+        <td>
+            <classname>File</classname> (read-only)
+        </td>
+        <td>
+            <filename><replaceable>buildDir</replaceable>/<replaceable>reportsDirName</replaceable></filename>
+        </td>
+        <td>
+            The directory to generate reports into.
+        </td>
+    </tr>
+</tgroup>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/scalaPlugin.xml b/subprojects/gradle-docs/src/docs/userguide/scalaPlugin.xml
new file mode 100644
index 0000000..97e87eb
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/scalaPlugin.xml
@@ -0,0 +1,261 @@
+<!--
+  ~ 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.
+  -->
+<chapter id="scala_plugin" xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>The Scala Plugin</title>
+
+    <para>The Scala plugin extends the Java plugin to add support for Scala projects. It can deal with Scala-only
+        projects and with mixed Java/Scala projects. It can even deal with Java-only projects.
+        The Scala plugin supports joint compilation of Java and Scala source. This means your project can contain
+        Scala classes which use Java classes, and vice versa.
+    </para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the Scala plugin, include in your build script:</para>
+        <sample id="useScalaPlugin" dir="scala/quickstart" title="Using the Scala plugin">
+            <sourcefile file="build.gradle" snippet="use-plugin"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>Tasks</title>
+        <para>The Scala plugin adds the following tasks to the project.</para>
+        <table>
+            <title>Scala plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td><literal>compileScala</literal></td>
+                <td><literal>compileJava</literal></td>
+                <td><apilink class="org.gradle.api.tasks.scala.ScalaCompile"/></td>
+                <td>Compiles production Scala source files using scalac.</td>
+            </tr>
+            <tr>
+                <td><literal>compileTestScala</literal></td>
+                <td><literal>compileTestJava</literal></td>
+                <td><apilink class="org.gradle.api.tasks.scala.ScalaCompile"/></td>
+                <td>Compiles test Scala source files using scalac.</td>
+            </tr>
+            <tr>
+                <td><literal>compile<replaceable>SourceSet</replaceable>Scala</literal></td>
+                <td><literal>compile<replaceable>SourceSet</replaceable>Java</literal></td>
+                <td><apilink class="org.gradle.api.tasks.scala.ScalaCompile"/></td>
+                <td>Compiles the given source set's Scala source files using scalac.</td>
+            </tr>
+            <tr>
+                <td><literal>scaladoc</literal></td>
+                <td>-</td>
+                <td><apilink class="org.gradle.api.tasks.scala.ScalaDoc"/></td>
+                <td>Generates API documentation for the production Scala source files using scaladoc.</td>
+            </tr>
+        </table>
+        <para>The Scala plugin adds the following dependencies to tasks added by the Java plugin.</para>
+        <table>
+            <title>Scala plugin - additional task dependencies</title>
+            <thead>
+                <td>Task name</td>
+                <td>Depends on</td>
+            </thead>
+            <tr>
+                <td>
+                    <literal>classes</literal>
+                </td>
+                <td>
+                    <literal>compileScala</literal>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>testClasses</literal>
+                </td>
+                <td>
+                    <literal>compileTestScala</literal>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal><replaceable>sourceSet</replaceable>Classes</literal>
+                </td>
+                <td>
+                    <literal>compile<replaceable>SourceSet</replaceable>Scala</literal>
+                </td>
+            </tr>
+        </table>
+        <figure>
+            <title>Scala plugin - tasks</title>
+            <imageobject>
+                <imagedata fileref="img/scalaPluginTasks.png"/>
+            </imageobject>
+        </figure>
+    </section>
+
+    <section>
+        <title>Project layout</title>
+        <para>The Scala plugin assumes the project layout shown below.  All the Scala source directories can contain
+            Scala <emphasis>and</emphasis> Java code. The Java source directories may only contain Java source code.
+            None of these directories need exist or have anything in them. The Scala plugin will compile whatever it
+            finds, and handles anything which is missing.</para>
+        <table id='scalalayout'>
+            <title>Scala plugin - project layout</title>
+            <thead>
+                <tr>
+                    <td>Directory</td>
+                    <td>Meaning</td>
+                </tr>
+            </thead>
+            <xi:include href="javaProjectMainLayout.xml"/>
+            <tr>
+                <td>
+                    <filename>src/main/scala</filename>
+                </td>
+                <td>Production Scala source. May also contain Java source for joint compilation.</td>
+            </tr>
+            <xi:include href="javaProjectTestLayout.xml"/>
+            <tr>
+                <td>
+                    <filename>src/test/scala</filename>
+                </td>
+                <td>Test Scala source. May also contain Java source for joint compilation.</td>
+            </tr>
+            <xi:include href="javaProjectGenericLayout.xml"/>
+            <tr>
+                <td>
+                    <filename>src/<replaceable>sourceSet</replaceable>/scala</filename>
+                </td>
+                <td>Scala source for the given source set. May also contain Java source for joint compilation.</td>
+            </tr>
+        </table>
+
+        <section>
+            <title>Changing the project layout</title>
+            <para>TBD</para>
+            <sample id="customScalaSourceLayout" dir="scala/customizedLayout" title="Custom Scala source layout">
+                <sourcefile file="build.gradle" snippet="define-main"/>
+            </sample>
+        </section>
+
+    </section>
+
+    <section>
+        <title>Dependency Management</title>
+        <para>The Scala plugin adds a <literal>scalaTools</literal> configuration, which it uses to locate the Scala
+            tools, such as scalac, to use. You must specify the version of Scala to use. Below is an example.
+        </para>
+        <sample id="declareScalaTools" dir="scala/quickstart" title="Declaring the Scala version to use">
+            <sourcefile file="build.gradle" snippet="declare-scala-version"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>Convention Properties</title>
+        <para>The Scala plugin does not add any convention properties to the project.</para>
+    </section>
+    
+    <section>
+        <title>Source set properties</title>
+        <para>The Scala plugin adds the following convention properties to each source set in the project. You can
+            use these properties in your build script as though they were properties of the source set object (see
+            <xref linkend="sub:more_about_convention_objects"/>).</para>
+        <table>
+            <title>Scala plugin - source set properties</title>
+            <thead>
+                <tr>
+                    <td>Property name</td>
+                    <td>Type</td>
+                    <td>Default value</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>scala</literal>
+                </td>
+                <td>
+                    <apilink class="org.gradle.api.file.SourceDirectorySet"/> (read-only)
+                </td>
+                <td>
+                    Not null
+                </td>
+                <td>
+                    The Scala source files of this source set. Contains all <filename>.scala</filename> and
+                    <filename>.java</filename> files found in the Scala source directories, and excludes all other
+                    types of files.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>scala.srcDirs</literal>
+                </td>
+                <td>
+                    <classname>Set<File></classname>. Can set using anything described in <xref linkend="sec:specifying_multiple_files"/>.
+                </td>
+                <td>
+                    <literal>[<replaceable>projectDir</replaceable>/src/<replaceable>name</replaceable>/scala]</literal>
+                </td>
+                <td>
+                    The source directories containing the Scala source files of this source set. May also contain
+                    Java source files for joint compilation.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>allScala</literal>
+                </td>
+                <td>
+                    <apilink class="org.gradle.api.file.FileTree"/> (read-only)
+                </td>
+                <td>
+                    Not null
+                </td>
+                <td>
+                    All Scala source files of this source set. Contains only the <filename>.scala</filename> files
+                    found in the Scala source directories.
+                </td>
+            </tr>
+        </table>
+
+        <para>These convention properties are provided by a convention object of type <apilink class="org.gradle.api.tasks.ScalaSourceSet"/>.</para>
+        <para>The Scala plugin also modifies some source set properties:</para>
+        <table>
+            <title>Scala plugin - source set properties</title>
+            <thead>
+                <tr>
+                    <td>Property name</td>
+                    <td>Change</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>allJava</literal>
+                </td>
+                <td>Adds all <filename>.java</filename> files found in the Scala source directories.</td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>allSource</literal>
+                </td>
+                <td>Adds all source files found in the Scala source directories.</td>
+            </tr>
+        </table>
+    </section>
+
+</chapter>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/standardPlugins.xml b/subprojects/gradle-docs/src/docs/userguide/standardPlugins.xml
new file mode 100644
index 0000000..b7c9280
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/standardPlugins.xml
@@ -0,0 +1,246 @@
+<!--
+  ~ 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.
+  -->
+<chapter id="standard_plugins">
+    <title>Standard Gradle plugins</title>
+    <para>There are a number of plugins included in the Gradle distribution. These are listed below.
+    </para>
+    <section>
+        <title>Language plugins</title>
+        <para>These plugins add support for various languages which can be compiled and executed in the JVM.</para>
+        <table>
+            <title>Language plugins</title>
+            <thead>
+                <tr>
+                    <td>Plugin Id</td>
+                    <td>Automatically applies</td>
+                    <td>Works with</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <link linkend='java_plugin'><literal>java</literal></link>
+                </td>
+                <td><literal>java-base</literal></td>
+                <td>-</td>
+                <td>
+                    <para>Adds Java compilation, testing and bundling capabilities to a project. It serves
+                        as the basis for many of the other Gradle plugins. See also <xref linkend="tutorial_java_projects"/>.
+                    </para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend='groovy_plugin'><literal>groovy</literal></link>
+                </td>
+                <td><literal>java</literal>, <literal>groovy-base</literal></td>
+                <td>-</td>
+                <td>
+                    <para>Adds support for building Groovy projects. See also <xref linkend="tutorial_groovy_projects"/>.</para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend="scala_plugin"><literal>scala</literal></link>
+                </td>
+                <td><literal>java</literal>, <literal>scala-base</literal></td>
+                <td>-</td>
+                <td>
+                    <para>Adds support for building Scala projects.</para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend='antlr_plugin'><literal>antlr</literal></link>
+                </td>
+                <td><literal>java</literal></td>
+                <td>-</td>
+                <td>
+                    <para>Adds support for generating parsers using <ulink url="http://www.antlr.org/">Antlr</ulink>.</para>
+                </td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Integration plugins</title>
+        <para>These plugins provide some integration with various build and runtime technologies.</para>
+        <table>
+            <title>Integration plugins</title>
+            <thead>
+                <tr>
+                    <td>Plugin Id</td>
+                    <td>Automatically applies</td>
+                    <td>Works with</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <link linkend='announce_plugin'><literal>announce</literal></link>
+                </td>
+                <td>-</td>
+                <td>-</td>
+                <td>
+                    <para>Publish messages to your favourite platforms, such as Twitter or Growl.</para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend='jetty_plugin'><literal>jetty</literal></link>
+                </td>
+                <td><literal>war</literal></td>
+                <td>-</td>
+                <td>
+                    <para>Deploys your web application to a Jetty web container embedded in the build. See also <xref linkend="web_project_tutorial"/>.
+                    </para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend="maven_plugin"><literal>maven</literal></link>
+                </td>
+                <td>-</td>
+                <td><literal>java</literal>, <literal>war</literal></td>
+                <td>
+                    <para>Adds support for deploying artifacts to Maven repositories.</para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend="osgi_plugin"><literal>osgi</literal></link>
+                </td>
+                <td><literal>java-base</literal></td>
+                <td><literal>java</literal></td>
+                <td><para>Adds support for building OSGi bundles.</para></td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend="war_plugin"><literal>war</literal></link>
+                </td>
+                <td><literal>java</literal></td>
+                <td>-</td>
+                <td>
+                    <para>Adds support for assembling web application WAR files. See also <xref linkend="web_project_tutorial"/>.
+                    </para>
+                </td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Software development plugins</title>
+        <para>These plugins provide help with your software development process.</para>
+        <table>
+            <title>Software development plugins</title>
+            <thead>
+                <tr>
+                    <td>Plugin Id</td>
+                    <td>Automatically applies</td>
+                    <td>Works with</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <link linkend='code_quality_plugin'><literal>code-quality</literal></link>
+                </td>
+                <td><literal>reporting-base</literal></td>
+                <td><literal>java</literal>, <literal>groovy</literal></td>
+                <td>
+                    <para>Performs code quality checks and generate reports from these checks.
+                    </para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend='eclipse_plugin'><literal>eclipse</literal></link>
+                </td>
+                <td>-</td>
+                <td><literal>java</literal>, <literal>groovy</literal>, <literal>scala</literal>, <literal>war</literal></td>
+                <td>
+                    <para>Generates files that are used by <ulink url="http://eclipse.org">Eclipse IDE</ulink>, thus making
+                        it possible to import the project into Eclipse. See also <xref linkend="tutorial_java_projects"/>.
+                    </para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend='idea_plugin'><literal>idea</literal></link>
+                </td>
+                <td>-</td>
+                <td><literal>java</literal></td>
+                <td>
+                    <para>Generates files that are used by <ulink url="http://www.jetbrains.com/idea/index.html">Intellij IDEA IDE</ulink>,
+                        thus making it possible to import the project into IDEA.
+                    </para>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <link linkend='project_reports_plugin'><literal>project-report</literal></link>
+                </td>
+                <td><literal>reporting-base</literal></td>
+                <td>-</td>
+                <td>
+                    <para>Generates reports containing useful information about your Gradle build.
+                    </para>
+                </td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Base plugins</title>
+        <para>These plugins form the basic building blocks which the other plugins are assembled from. They are
+            available for you to use in your build files, and are listed here for completeness. However, be aware that
+            they are not yet considered part of Gradle's public API. As such, these plugins are not documented in the
+            user guide. You might refer to their Javadoc to learn more about them.
+        </para>
+        <table>
+            <title>Base plugins</title>
+            <thead>
+                <tr>
+                    <td>Plugin Id</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>base</td>
+                <td><para>Adds the standard lifecycle tasks to the project, plus some shared convention properties.</para></td>
+            </tr>
+            <tr>
+                <td>java-base</td>
+                <td><para>Adds the source sets concept to the project. Does not add any particular source sets.</para></td>
+            </tr>
+            <tr>
+                <td>groovy-base</td>
+                <td><para>Adds the Groovy source sets concept to the project.</para></td>
+            </tr>
+            <tr>
+                <td>scala-base</td>
+                <td><para>Adds the Scala source sets concept to the project.</para></td>
+            </tr>
+            <tr>
+                <td>reporting-base</td>
+                <td><para>Adds some shared convention properties to the project, relating to report generation.</para></td>
+            </tr>
+        </table>
+    </section>
+    <section>
+        <title>Third party plugins</title>
+        <para>You can find a list of external plugins on the
+            <ulink url="http://gradle.codehaus.org/Plugins">wiki</ulink>.
+        </para>
+    </section>
+</chapter>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/tasks.xml b/subprojects/gradle-docs/src/docs/userguide/tasks.xml
new file mode 100644
index 0000000..e68bfb5
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/tasks.xml
@@ -0,0 +1,261 @@
+<!--
+  ~ 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.
+  -->
+<chapter id='more_about_tasks' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>More about Tasks</title>
+    <para>In the introductory tutorial (<xref linkend='tutorial_using_tasks'/>) you have learned how to
+        create simple tasks. You have also learned how to add additional behavior to these tasks later on. And you have
+        learned how to create dependencies between tasks. This was all about simple tasks. But Gradle takes the concept
+        of tasks further. Gradle supports <firstterm>enhanced tasks</firstterm>, that is, tasks which have their own
+        properties and methods. This is really different to what you are used to with Ant targets. Such enhanced tasks are
+        either provided by you or are provided by Gradle.
+    </para>
+    <section>
+        <title>Defining tasks</title>
+        <para>We have already seen how to define tasks using a keyword style in <xref linkend="tutorial_using_tasks"/>.
+            There are a few variations on this style, which you may need to use in certain situations. For example,
+            the keyword style does not work in expressions.
+        </para>
+        <sample id="defineAsExpression" dir="userguide/tasks/defineAsExpression" title="Defining tasks">
+            <sourcefile file="build.gradle"/>
+        </sample>
+        <para>You can also use strings for the task names:</para>
+        <sample id="defineUsingStringTaskNames" dir="userguide/tasks/defineUsingStringTaskNames" title="Defining tasks - using strings">
+            <sourcefile file="build.gradle"/>
+        </sample>
+        <para>There is an alternative syntax for defining tasks, which you may prefer to use:</para>
+        <sample id="addToTaskContainer" dir="userguide/tasks/addToTaskContainer" title="Defining tasks with alternative syntax">
+            <sourcefile file="build.gradle"/>
+        </sample>
+        <para>Here we add tasks to the <literal>tasks</literal> collection. Have a look at
+            <apilink class="org.gradle.api.tasks.TaskContainer"/> for more variations of the <literal>add()</literal>
+            method.</para>
+    </section>
+    <section>
+        <title>Locating tasks</title>
+        <para>You often need to locate the tasks that you have defined in the build file, for example, to configure them
+            or use them for dependencies. There are a number of ways you can do this. Firstly, each task is available as
+            a property of the project, using the task name as the property name:
+        </para>
+        <sample id="accessAsProperty" dir="userguide/tasks/accessAsProperty" title="Accessing tasks as properties">
+            <sourcefile file="build.gradle"/>
+        </sample>
+        <para>Tasks are also available through the <literal>tasks</literal> collection.</para>
+        <sample id="accessFromTaskContainer" dir="userguide/tasks/accessFromTaskContainer" title="Accessing tasks via tasks collection">
+            <sourcefile file="build.gradle"/>
+        </sample>
+        <para>You can access tasks from any project using the task's path using the <literal>tasks.getByPath()</literal>
+            method. You can call the <literal>getByPath()</literal> method with a task name, or a relative path, or an
+            absolute path.</para>
+        <sample id="accessUsingPath" dir="userguide/tasks/accessUsingPath" title="Accessing tasks by path">
+            <sourcefile file="build.gradle"/>
+            <output args="-q hello"/>
+        </sample>
+        <para>Have a look at <apilink class="org.gradle.api.tasks.TaskContainer"/> for more options for locating tasks.</para>
+    </section>
+    <section id='sec:configuring_tasks'>
+        <title>Configuring tasks</title>
+        <para>As an example, let's look at the <classname>Copy</classname> task provided by Gradle. To create a
+            <classname>Copy</classname> task for your build, you can declare in your build script:
+        </para>
+        <sample id="declareTask" dir="userguide/tasks/configureUsingClosure" title="Creating a copy task">
+            <sourcefile file="build.gradle" snippet="declare-task"/>
+        </sample>
+        <para>This creates a copy task with no default behavior.
+            The task can be configured using its API (see <apilink class="org.gradle.api.tasks.Copy"/>).
+			The following examples show several different ways to achieve the same configuration.
+        </para>
+        <sample id="configureUsingVar" dir="userguide/tasks/configureUsingVar" title="Configuring a task - various ways">
+            <sourcefile file="build.gradle"/>
+        </sample>
+        <para>This is similar to the way we would normally configure objects in Java. You have to repeat the context
+            (<literal>myCopy</literal>) in the configuration statement every time. This is a redundancy and not very
+            nice to read.
+        </para>
+        <para>There is a more convenient way of doing this.
+        </para>
+        <sample id="configureUsingLiterateStyle" dir="userguide/tasks/configureUsingLiterateStyle" title="Configuring a task - fluent interface">
+            <sourcefile file="build.gradle"/>
+        </sample>
+        <para>You might know this approach from the Hibernates Criteria Query API or JMock. Of course the API of a task
+            has to support this. The <literal>from</literal>, <literal>to</literal> and <literal>include</literal>
+            methods all return an object that may be used to chain to additional configuration methods. Gradle's build-in tasks usually
+            support this configuration style.
+        </para>
+        <para>But there is yet another way of configuring a task. It also preserves the context and it is arguably the
+            most readable. It is usually our favorite.
+        </para>
+        <sample id="configureUsingClosure" dir="userguide/tasks/configureUsingClosure" title="Configuring a task - with closure">
+            <sourcefile file="build.gradle"/>
+        </sample>
+        <para>This works for <emphasis>any</emphasis> task. Line 3 of the example is just a shortcut for the
+            <literal>tasks.getByName()</literal> method. It is important to note that if you pass a closure to the
+            <literal>getByName()</literal> method, this closure is applied to <emphasis>configure</emphasis> the task.
+        </para>
+        <para>There is a slightly different ways of doing this.</para>
+        <sample id="configureUsingConfigure" dir="userguide/tasks/configureUsingConfigure" title="Configuring a task - with configure() method">
+            <sourcefile file="build.gradle"/>
+        </sample>
+        <para>Every task has a <literal>configure()</literal> method, which you can pass a closure for configuring the task.
+            Gradle uses this style for configuring objects in many places, not just for tasks.
+        </para>
+        <para>You can also use a configuration closure when you define a task.</para>
+        <sample id="defineAndConfigure" dir="userguide/tasks/defineAndConfigure" title="Defining a task with closure">
+            <sourcefile file="build.gradle" snippet="no-description"/>
+        </sample>
+    </section>
+    <section id='sec:adding_dependencies_to_tasks'>
+        <title>Adding dependencies to a task</title>
+        <para>There are several ways you can define the dependencies of a task. In
+            <xref linkend='sec:task_dependencies'/>
+            you were introduced to defining dependencies using task names. Task names can refer to tasks in the same
+            project as the task, or to tasks in other projects. To refer to a task in another project, you prefix the
+            name of the task with the path of the project it belongs to. Below is an example which adds a dependency
+            from
+            <literal>projectA:taskX</literal>
+            to
+            <literal>projectB:taskY</literal>:
+        </para>
+        <sample id="addDependencyUsingPath" dir="userguide/tasks/addDependencyUsingPath" title="Adding dependency on task from another project">
+            <sourcefile file="build.gradle"/>
+            <output args="-q taskX"/>
+        </sample>
+        <para>Instead of using a task name, you can define a dependency using a
+            <classname>Task</classname> object, as shown in this example:
+        </para>
+        <sample id="addDependencyUsingTask" dir="userguide/tasks/addDependencyUsingTask" title="Adding dependency using task object">
+            <sourcefile file="build.gradle"/>
+            <output args="-q taskX"/>
+        </sample>
+        <para>For more advanced uses, you can define a task dependency using a closure. When evaluated, the closure is
+            passed the task whose dependencies are being calculated. The closure should return a single
+            <classname>Task</classname> or collection of <classname>Task</classname> objects, which are then treated
+            as dependencies of the task. The following example adds a dependency from <literal>taskX</literal>
+            to all the tasks in the project whose name starts with <literal>lib</literal>:
+        </para>
+        <sample id="addDependencyUsingClosure" dir="userguide/tasks/addDependencyUsingClosure" title="Adding dependency using closure">
+            <sourcefile file="build.gradle"/>
+            <output args="-q taskX"/>
+        </sample>
+        <para>For more information about task dependencies, see the <apilink class="org.gradle.api.Task"/> API.</para>
+    </section>
+    <section>
+        <title>Adding a description to a task</title>
+        <para>You can add a description to your task. This description is for example displayed when executing
+            <code>gradle -t</code>.
+        </para>
+        <sample id="describeTask" dir="userguide/tasks/defineAndConfigure" title="Adding a description to a task">
+            <sourcefile file="build.gradle"/>
+        </sample>
+    </section>
+    <section>
+        <title>Replacing tasks</title>
+        <para>Sometimes you want to replace a task. For example if you want to exchange a task added by the Java plugin
+            with a custom task of a different type. You can achieve this with:
+        </para>
+        <sample id="replaceTask" dir="userguide/tutorial/replaceTask" title="Overwriting a task">
+            <sourcefile file="build.gradle"/>
+            <output args="-q copy"/>
+        </sample>
+        <para>Here we replace a task of type <literal>Copy</literal> with a simple task. When creating the simple
+            task, you have to set the <literal>overwrite</literal> property to true. Otherwise Gradle throws an
+            exception, saying that a task with such a name already exists.
+        </para>
+    </section>
+    <section>
+        <title>Skipping tasks</title>
+        <para>Gradle offers multiple ways to skip the execution of a task.</para>
+
+        <section>
+            <title>Using a predicate</title>
+            <para>You can use the <literal>onlyIf()</literal> method to attach a predicate to a task. The task's
+                actions are only executed if the predicate evaluates to true. You implement the predicate as a closure.
+                The closure is passed the task as a parameter, and should return true if the task should execute
+                and false if the task should be skipped. The predicate is evaluated just before the task is due
+                to be executed.
+                </para>
+            <sample id="taskOnlyIf" dir="userguide/tutorial/taskOnlyIf" title="Skipping a task using a predicate">
+                <sourcefile file="build.gradle"/>
+                <output args="hello -PskipHello"/>
+            </sample>
+        </section>
+
+        <section>
+        <title>Using StopExecutionException</title>
+        <para>If the rules for skipping a task can't be expressed with predicate, you can use the
+            <apilink class="org.gradle.api.tasks.StopExecutionException"/>. If this exception is thrown by an action,
+            the further execution of this action as well as the execution of
+            any following action of this task is skipped. The build continues with executing the next task.
+        </para>
+        <sample id="stopExecutionException" dir="userguide/tutorial/stopExecutionException" title="Skipping tasks with StopExecutionException">
+            <sourcefile file="build.gradle"/>
+            <output args="-q myTask"/>
+        </sample>
+        <para>This feature is helpful if you work with tasks provided by Gradle. It allows you to add
+            <emphasis>conditional</emphasis> execution of the built-in actions of such a task.
+            <footnote>
+                <para>You might be wondering why there is neither an import for the
+                    <literal>StopExecutionException</literal>
+                    nor do we access it via its fully qualified name. The reason is, that Gradle adds a set of default imports
+                    to your script. These imports are customizable (see <xref linkend='ide_support'/>).
+                </para>
+            </footnote>
+        </para>
+        </section>
+
+        <section>
+        <title>Enabling and disabling tasks</title>
+        <para>Every task has also an <literal>enabled</literal>
+            flag which defaults to <literal>true</literal>. Setting it to <literal>false</literal> prevents the
+            execution of any of the task's actions.
+        </para>
+        <sample id="disableTask" dir="userguide/tutorial/disableTask" title="Enabling and disabling tasks">
+            <sourcefile file="build.gradle"/>
+            <output args="disableMe"/>
+        </sample>
+        </section>
+    </section>
+    
+    <section>
+        <title>Task rules</title>
+        <para>Sometimes you want to have a task which behavior depends on a large or infinite number value range
+            of parameters. A very nice and expressive way to provide such tasks are task rules: 
+        </para>
+        <sample id="taskRule" dir="userguide/tasks/addRules" title="Task rule">
+            <sourcefile file="build.gradle" snippet="task-rule"/>
+            <output args="-q pingServer1"/>
+        </sample>
+        <para>The String parameter is used as a description for the rule. This description is shown when doing
+            for example <code>gradle -t</code>.
+        </para>
+        <para>Rules not just work for calling tasks from the command line. You can also create dependsOn relations
+            on rule based tasks:
+        </para>
+        <sample id="taskRuleDependsOn" dir="userguide/tasks/addRules" title="Dependency on rule based tasks">
+            <sourcefile file="build.gradle"/>
+            <output args="-q groupPing"/>
+        </sample>
+    </section>
+    <section id='sec:the_idea_behind_gradle_tasks'>
+        <title>Summary</title>
+        <para>If you are coming from Ant, such an enhanced Gradle task as <emphasis>Copy</emphasis> looks like a mixture
+            between an Ant target and an Ant task. And this is actually the case. The separation that Ant does between
+            tasks and targets is not done by Gradle. The simple Gradle tasks are like Ant's targets and the enhanced
+            Gradle tasks also include the Ant task aspects. All of Gradle's tasks share a common API and you can create
+            dependencies between them. Such a task might be nicer to configure than an Ant task.
+            It makes full use of the type system, is more expressive and easier to maintain.
+        </para>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/thisAndThat.xml b/subprojects/gradle-docs/src/docs/userguide/thisAndThat.xml
new file mode 100644
index 0000000..56d57ab
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/thisAndThat.xml
@@ -0,0 +1,176 @@
+<!--
+  ~ 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.
+  -->
+<chapter id='tutorial_this_and_that' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>Tutorial - 'This and That'</title>
+    <section id='sec:directory_creation'>
+        <title>Directory creation</title>
+        <para>There is a common situation, that multiple tasks depend on the existence of a directory. Of course you can
+            deal with this by adding a
+            <literal>mkdir</literal>
+            to the beginning of those tasks. But this is kind of bloated. There is a better solution (works only if the
+            tasks that need the directory have a
+            <emphasis>dependsOn</emphasis>
+            relationship):
+        </para>
+        <sample id="makeDirectory" dir="userguide/tutorial/makeDirectory" title="Directory creation with mkdir">
+            <sourcefile file="build.gradle"/>
+            <output args="-q compile"/>
+        </sample>
+        <para>But Gradle offers you also <emphasis>Directory Tasks</emphasis> to deal with this.</para>
+        <sample id="directoryTask" dir="userguide/tutorial/directoryTask" title="Directory creation with Directory tasks">
+            <sourcefile file="build.gradle"/>
+            <output args="-q otherResources"/>
+        </sample>
+        <para>A <emphasis>Directory Task</emphasis>
+            is a simple task whose name is a relative path to the project dir
+            <footnote>
+                <para>The notation <literal>dir('/somepath')</literal> is a convenience method for
+                    <literal>tasks.add('somepath', type: Directory)</literal>
+                </para>
+            </footnote>
+            . During the execution phase the directory corresponding to this path gets created if it does not exist yet.
+            Another interesting thing to note in this example, is that you can also pass tasks objects to the dependsOn
+            declaration of a task.
+        </para>
+    </section>
+    <section id='sec:gradle_properties_and_system_properties'>
+        <title>Gradle properties and system properties</title>
+        <para>Gradle offers a variety of ways to add properties to your build. With the <option>-D</option> command line
+            option you can pass a system property to the JVM which runs Gradle. The <option>-D</option> option of the
+            <command>gradle</command> command has the same effect as the <option>-D</option> option of the
+            <command>java</command> command.
+        </para>
+        <para>You can also directly add properties to your project objects using properties files. You can place a
+            <filename>gradle.properties</filename> file in the Gradle user home directory (defaults to
+            <filename><replaceable>USER_HOME</replaceable>/.gradle</filename>) or in your project directory. For
+            multi-project builds you can place <filename>gradle.properties</filename> files in any subproject directory.
+            The properties of the <filename>gradle.properties</filename> can be accessed via the project object. The
+            properties file in the user's home directory has precedence over property files in the project directories.
+        </para>
+        <para>You can also add properties directly to your project object via the <option>-P</option>
+            command line option. For more exotic use cases you can even pass properties <emphasis>directly</emphasis>
+            to the project object via system and environment properties. For example if you run a build on a continuous
+            integration server where you have no admin rights for the <emphasis>machine</emphasis>. Your build script
+            needs properties which values should not be seen by others. Therefore you can't use the <option>-P</option>
+            option. In this case you can add an environment property in the project administration section (invisible to
+            normal users).
+            <footnote>
+                <para>
+                    <emphasis>Teamcity</emphasis> or <emphasis>Bamboo</emphasis> are for example CI servers which
+                    offer this functionality.
+                </para>
+            </footnote>
+            If the environment property follows the pattern
+            <literal>ORG_GRADLE_PROJECT_<replaceable>propertyName</replaceable>=somevalue</literal>,
+            <literal>propertyName</literal> is added to your project object. If in the future CI servers support Gradle
+            directly, they might start Gradle via its main method. Therefore we already support the same mechanism for
+            system properties. The only difference is the pattern, which is
+            <literal>org.gradle.project.<replaceable>propertyName</replaceable></literal>.
+        </para>
+        <para>With the <filename>gradle.properties</filename> files you can also set system properties. If a property
+            in such a file has the prefix <literal>systemProp.</literal> the property and its value are added to the 
+            system properties, without the prefix.
+        </para>
+        <sample id="properties" dir="userguide/tutorial/properties" title="Setting properties with a gradle.properties file">
+            <sourcefile file="gradle.properties"/>
+            <sourcefile file="build.gradle"/>
+            <output args="-q -PcommandLineProjectProp=commandLineProjectPropValue -Dorg.gradle.project.systemProjectProp=systemPropertyValue printProps"/>
+        </sample>
+        <section id='sub:checking_for_project_properties'>
+            <title>Checking for project properties</title>
+            <para>You can access a project property in your build script simply by using its name as you would use a
+                variable. In case this property does not exists, an exception is thrown and the build fails. If your
+                build script relies on optional properties the user might set for example in a gradle.properties file,
+                you need to check for existence before you can access them. You can do this by using the method
+                <literal>hasProperty('propertyName')</literal>
+                which returns
+                <literal>true</literal>
+                or <literal>false</literal>.
+            </para>
+        </section>
+    </section>
+    <section id='sec:accessing_the_web_via_a_proxy'>
+        <title>Accessing the web via a proxy</title>
+        <para>Setting a proxy for web access (for example for downloading dependencies) is easy. Gradle does not need to
+            provide special functionality for this. The JVM can be instructed to go via proxy by setting certain system
+            properties. You could set these system properties directly in your build script with <literal>
+                System.properties['proxy.proxyUser'] = 'userid'</literal>. An arguably nicer way is shown in
+            <xref linkend='sec:gradle_properties_and_system_properties'/>. Your gradle.properties file could look like
+            this:
+        </para>
+        <example>
+            <title>Accessing the web via a proxy</title>
+            <para><filename>gradle.properties</filename></para>
+            <programlisting><![CDATA[
+systemProp.http.proxyHost=www.somehost.org
+systemProp.http.proxyPort=8080
+systemProp.http.proxyUser=userid
+systemProp.http.proxyPassword=password
+systemProp.http.nonProxyHosts=*.nonproxyrepos.com|localhost	
+]]></programlisting>
+        </example>
+        <para>We could not find a good overview for all possible proxy settings. One place to look are the constants
+            in a file from the ant project. Here a
+            <ulink url='http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/ant/util/ProxySetup.java?view=markup&pathrev=556977'>
+                link
+            </ulink> to the svn view. The other is a
+            <ulink url='http://java.sun.com/javase/6/docs/technotes/guides/net/properties.html'>
+                Networking Properties page
+            </ulink> from the JDK docs. If anyone knows a better overview please let us know via the mailing list.
+        </para>
+    </section>
+    <section id="sec:configuring_using_external_script">
+        <title>Configuring the project using an external build script</title>
+        <para>You can configure the current project using an external build script. All of the Gradle build language
+            is available in the external script. You can even apply other scripts from the external script.
+        </para>
+        <sample id="configureProjectUsingScript" dir="userguide/tutorial/configureProjectUsingScript" title="Configuring the project using an external build script">
+            <sourcefile file="build.gradle"/>
+            <sourcefile file="other.gradle"/>
+            <output args="-q hello"/>
+        </sample>
+    </section>
+    <section id='sec:configuring_arbitrary_objects'>
+        <title>Configuring arbitrary objects</title>
+        <para>You can configure arbitrary objects in the following very readable way.
+        </para>
+        <sample id="configureObject" dir="userguide/tutorial/configureObject" title="Configuring arbitrary objects">
+            <sourcefile file="build.gradle"/>
+            <output args="-q configure"/>
+        </sample>
+    </section>
+    <section>
+        <title>Configuring arbitrary objects using an external script</title>
+        <para>You can also configure arbitrary objects using an external script.
+        </para>
+        <sample id="configureObjectUsingScript" dir="userguide/tutorial/configureObjectUsingScript" title="Configuring arbitrary objects using a script">
+            <sourcefile file="build.gradle"/>
+            <sourcefile file="other.gradle"/>
+            <output args="-q configure"/>
+        </sample>
+    </section>
+    <section id='sec:caching'>
+        <title>Caching</title>
+        <para>To improve responsiveness Gradle caches all compiled scripts by default. This includes all build scripts,
+            initialization scripts, and other scripts. The first time you run a build for a project, Gradle creates a
+            <filename>.gradle</filename> directory in which it puts the compiled script. The next time you run this
+            build, Gradle uses the compiled script, if the script has not changed since it was compiled.  Otherwise the
+            script gets compiled and the new version is stored in the cache. If you run Gradle with the
+            <option>-C rebuild</option> option, the cached script is discarded and the script is compiled and stored
+            in the cache. This way you can force Gradle to rebuild the cache.
+        </para>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/tutorials.xml b/subprojects/gradle-docs/src/docs/userguide/tutorials.xml
new file mode 100644
index 0000000..9db44f8
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/tutorials.xml
@@ -0,0 +1,72 @@
+<!--
+  ~ 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.
+  -->
+<chapter id="tutorials">
+    <title>Tutorials</title>
+    <section>
+        <title>Getting Started</title>
+        <para>The following tutorials introduce some of the basics of Gradle, to help you get started.</para>
+        <variablelist>
+            <varlistentry>
+                <term>
+                    <xref linkend="installation"/>
+                </term>
+                <listitem>
+                    <para>Describes how to install Gradle.</para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>
+                    <xref linkend="tutorial_using_tasks"/>
+                </term>
+                <listitem>
+                    <para>Introduces the basic build script elements:
+                        <firstterm>projects</firstterm>
+                        and
+                        <firstterm>tasks</firstterm>.
+                    </para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>
+                    <xref linkend="tutorial_java_projects"/>
+                </term>
+                <listitem>
+                    <para>Shows how to start using Gradle's build-by-convention support for Java projects.</para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>
+                    <xref linkend="tutorial_groovy_projects"/>
+                </term>
+                <listitem>
+                    <para>Using Gradle's build-by-convention support for Groovy projects.</para>
+                </listitem>
+            </varlistentry>
+            <varlistentry>
+                <term>
+                    <xref linkend="web_project_tutorial"/>
+                </term>
+                <listitem>
+                    <para>Using Gradle's build-by-convention support for Web applications.</para>
+                </listitem>
+            </varlistentry>
+        </variablelist>
+    </section>
+    <section condition="website">
+        <title>Where to next?</title>
+        <para>There are more tutorials and plenty of reference material in the <ulink url="website:userguide.html">user guide</ulink>.</para>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/userguide.xml b/subprojects/gradle-docs/src/docs/userguide/userguide.xml
new file mode 100644
index 0000000..e75ece4
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/userguide.xml
@@ -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.
+  -->
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+    <bookinfo>
+        <title>Gradle</title>
+        <subtitle>A better way to build</subtitle>
+        <copyright>
+            <year>2007-2010</year>
+            <holder>Hans Dockter, Adam Murdoch</holder>
+        </copyright>
+        <legalnotice>
+            <para>Copies of this document may be made for your own use and for distribution to others, provided that you
+                do not charge any fee for such copies and further provided that each copy contains this Copyright
+                Notice, whether distributed in print or electronically.
+            </para>
+        </legalnotice>
+        <author>
+            <firstname>Hans</firstname>
+            <surname>Dockter</surname>
+        </author>
+        <author>
+            <firstname>Adam</firstname>
+            <surname>Murdoch</surname>
+        </author>
+    </bookinfo>
+    <xi:include href='introduction.xml'/>
+    <xi:include href='overview.xml'/>
+    <xi:include href='tutorials.xml'/>
+    <xi:include href='installation.xml'/>
+    <xi:include href='buildScriptsTutorial.xml'/>
+    <xi:include href='javaTutorial.xml'/>
+    <xi:include href='groovyTutorial.xml'/>
+    <xi:include href='webTutorial.xml'/>
+    <xi:include href='artifactDependenciesTutorial.xml'/>
+    <xi:include href='commandLineTutorial.xml'/>
+    <xi:include href='guiTutorial.xml'/>
+    <xi:include href='thisAndThat.xml'/>
+    <xi:include href='writingBuildScripts.xml'/>
+    <xi:include href='tasks.xml'/>
+    <xi:include href='workingWithFiles.xml'/>
+    <xi:include href='logging.xml'/>
+    <xi:include href='ant.xml'/>
+    <xi:include href='plugins.xml'/>
+    <xi:include href='standardPlugins.xml'/>
+    <xi:include href='javaPlugin.xml'/>
+    <xi:include href='groovyPlugin.xml'/>
+    <xi:include href='scalaPlugin.xml'/>
+    <xi:include href='warPlugin.xml'/>
+    <xi:include href='jettyPlugin.xml'/>
+    <xi:include href='codeQualityPlugin.xml'/>
+    <xi:include href='osgi.xml'/>
+    <xi:include href='eclipsePlugin.xml'/>
+    <xi:include href='ideaPlugin.xml'/>
+    <xi:include href='antlrPlugin.xml'/>
+    <xi:include href='projectReports.xml'/>
+    <xi:include href='announcePlugin.xml'/>
+	<xi:include href='depMngmt.xml'/>
+    <xi:include href='artifactMngmt.xml'/>
+    <xi:include href='mavenPlugin.xml'/>
+    <xi:include href='buildLifecycle.xml'/>
+    <xi:include href='multiproject.xml'/>
+    <xi:include href='customTasks.xml'/>
+    <xi:include href='customPlugins.xml'/>
+    <xi:include href='organizeBuildLogic.xml'/>
+    <xi:include href='initscripts.xml'/>
+    <xi:include href='gradleWrapper.xml'/>
+    <xi:include href='embedding.xml'/>
+    <!-- this is generated -->
+    <xi:include href='../../../build/src/docbook/samplesList.xml'/>
+    <xi:include href='potentialTraps.xml'/>
+    <xi:include href='commandLine.xml'/>
+    <xi:include href='ideSupport.xml'/>
+    <xi:include href='glossary.xml'/>
+</book>
diff --git a/subprojects/gradle-docs/src/docs/userguide/warPlugin.xml b/subprojects/gradle-docs/src/docs/userguide/warPlugin.xml
new file mode 100644
index 0000000..1baada5
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/warPlugin.xml
@@ -0,0 +1,189 @@
+<!--
+  ~ 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.
+  -->
+<chapter id='war_plugin' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>The War Plugin</title>
+    <para>The War plugin extends the Java plugin to add support for assembling web application WAR files.
+        It disables the default JAR archive generation of the Java plugin and adds a default WAR archive task.
+    </para>
+
+    <section>
+        <title>Usage</title>
+        <para>To use the War plugin, include in your build script:</para>
+        <sample id="useWarPlugin" dir="webApplication/quickstart" title="Using the War plugin">
+            <sourcefile file="build.gradle" snippet="use-war-plugin"/>
+        </sample>
+    </section>
+
+    <section>
+        <title>Tasks</title>
+        <para>The War plugin adds the following tasks to the project.</para>
+        <table>
+            <title>War plugin - tasks</title>
+            <thead>
+                <tr>
+                    <td>Task name</td>
+                    <td>Depends on</td>
+                    <td>Type</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>war</literal>
+                </td>
+                <td>
+                    <literal>compile</literal>
+                </td>
+                <td><apilink class="org.gradle.api.tasks.bundling.War" lang="groovy"/></td>
+                <td>Assembles the application WAR file.</td>
+            </tr>
+        </table>
+        <para>The War plugin adds the following dependencies to tasks added by the Java plugin.</para>
+        <table>
+            <title>War plugin - additional task dependencies</title>
+            <thead>
+                <td>Task name</td>
+                <td>Depends on</td>
+            </thead>
+            <tr>
+                <td>assemble</td>
+                <td>war</td>
+            </tr>
+        </table>
+        <figure>
+            <title>War plugin - tasks</title>
+            <imageobject>
+                <imagedata fileref="img/warPluginTasks.png"/>
+            </imageobject>
+        </figure>
+    </section>
+
+    <section>
+        <title>Project layout</title>
+        <table>
+            <title>War plugin - project layout</title>
+            <thead>
+                <tr>
+                    <td>Directory</td>
+                    <td>Meaning</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <filename>src/main/webapp</filename>
+                </td>
+                <td>Web application sources</td>
+            </tr>
+        </table>
+    </section>
+
+    <section>
+        <title>Dependency management</title>
+        <para>The War plugin adds two dependency configurations: <literal>providedCompile</literal> and
+            <literal>providedRuntime</literal>. Those configurations have the same scope as the respective
+            <literal>compile</literal> and <literal>runtime</literal> configurations, except that they are not added to
+            the WAR archive. It is important to note that those <literal>provided</literal> configurations work
+            transitively. Let's say you add <literal>commons-httpclient:commons-httpclient:3.0</literal> to any of the
+            provided configurations. This dependency has a dependency on <literal>commons-codec</literal>.
+            This means neither <literal>httpclient</literal> nor <literal>commons-codec</literal> is added to your
+            WAR, even if <literal>commons-codec</literal> were an explicit dependency of your <literal>compile</literal>
+            configuration. If you don't want this transitive behavior, simply declare your <literal>provided</literal>
+            dependencies like <literal>commons-httpclient:commons-httpclient:3.0 at jar</literal>.
+        </para>
+    </section>
+
+    <section>
+        <title>Convention properties</title>
+        <table>
+            <title>War plugin - directory properties</title>
+            <thead>
+                <tr>
+                    <td>Property name</td>
+                    <td>Type</td>
+                    <td>Default value</td>
+                    <td>Description</td>
+                </tr>
+            </thead>
+            <tr>
+                <td>
+                    <literal>webAppDirName</literal>
+                </td>
+                <td>
+                    <classname>String</classname>
+                </td>
+                <td>
+                    <filename>src/main/webapp</filename>
+                </td>
+                <td>
+                    The name of the web application source directory, relative to the project directory.
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <literal>webAppDir</literal>
+                </td>
+                <td>
+                    <classname>File</classname> (read-only)
+                </td>
+                <td>
+                    <filename><replaceable>projectDir</replaceable>/<replaceable>webAppDirName</replaceable></filename>
+                </td>
+                <td>
+                    The web application source directory.
+                </td>
+            </tr>
+        </table>
+
+        <para>These properties are provided by a <apilink class="org.gradle.api.plugins.WarPluginConvention" lang="groovy"/>
+            convention object.
+        </para>
+    </section>
+
+    <section id='sec:default_settings'>
+        <title>War</title>
+
+        <para>The default behavior of the War task is to copy the content of <literal>src/main/webapp</literal>
+            to the root of the archive. Your <literal>webapp</literal> directory may of course contain a
+            <literal>WEB-INF</literal> sub-directory, which again may contain a <literal>web.xml</literal> file.
+            Your compiled classes are compiled to <literal>WEB-INF/classes</literal>. All the dependencies of the
+            <literal>runtime</literal>
+            <footnote>
+                <para>The
+                    <literal>runtime</literal>
+                    configuration extends the
+                    <literal>compile</literal>
+                    configuration.
+                </para>
+            </footnote>
+            configuration are copied to <literal>WEB-INF/lib</literal>.</para>
+        <para>Have also a look at <apilink class="org.gradle.api.tasks.bundling.War" lang="groovy"/>.</para>
+    </section>
+    <section id='sec:customizing'>
+        <title>Customizing</title>
+        <para>Here is an example with the most important customization options:
+        </para>
+        <sample id="webproject" dir="webApplication/customised" title="Customization of war plugin">
+            <sourcefile file="build.gradle"/>
+        </sample>
+        <para>Of course one can configure the different file-sets with a closure to define excludes and includes.
+        </para>
+        <para>If you want to enable the generation of the default jar archive additional to the war archive just type:
+        </para>
+        <sample id="customisedWebApplication" dir="webApplication/customised" title="Generation of JAR archive in addition to WAR archive">
+            <sourcefile file="build.gradle" snippet="enable-jar"/>
+        </sample>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/webTutorial.xml b/subprojects/gradle-docs/src/docs/userguide/webTutorial.xml
new file mode 100644
index 0000000..f1ac942
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/webTutorial.xml
@@ -0,0 +1,65 @@
+<!--
+  ~ 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.
+  -->
+<chapter id="web_project_tutorial">
+    <title>Web Application Quickstart</title>
+
+    <note>
+        <para>This chapter is a work in progress.</para>
+    </note>
+
+    <para>This chapter introduces some of the Gradle's support for web applications. Gradle provides two plugins for web
+        application developement: the War plugin and the Jetty plugin. The War plugin extends the Java plugin to build a
+        WAR file for your project. The Jetty plugin extends the War plugin to allow you to deploy your web application
+        to an embedded Jetty web container.
+    </para>
+
+    <section>
+        <title>Building a WAR file</title>
+        <para>To build a WAR file, you apply the War plugin to your project:</para>
+        <sample id="webQuickstart" dir="webApplication/quickstart" includeLocation="true" title="War plugin">
+            <sourcefile file="build.gradle" snippet="use-war-plugin"/>
+        </sample>
+        <para>This also applies the Java plugin to your project. Running <userinput>gradle build</userinput> will compile,
+            test and WAR your project. Gradle will look for the source files to include in the WAR file in
+            <filename>src/main/webapp</filename>. Your compiled classes, and their runtime dependencies are also
+            included in the WAR file.
+        </para>
+        <tip><title>Groovy web applications</title><para>You can combine multiple plugins in a single project, so you
+            can use the War and Groovy plugins together to build a Groovy based web application. The appropriate groovy
+            libraries will be added to the WAR file for you.</para></tip>
+    </section>
+
+    <section>
+        <title>Running your web application</title>
+        <para>To run your web application, you apply the Jetty plugin to your project:</para>
+        <sample id="webQuickstart" dir="webApplication/quickstart" title="Running web application with Jetty plugin">
+            <sourcefile file="build.gradle" snippet="use-jetty-plugin"/>
+        </sample>
+        <para>This also applies the War plugin to your project. Running <userinput>gradle jettyRun</userinput> will
+            run your web application in an embedded Jetty web container. Running <userinput>gradle jettyRunWar</userinput>
+            will build and test the WAR file, and then run it in an embedded web container.
+        </para>
+        <para>TODO: which url, configure port, uses source files in place and can edit your files and reload.</para>
+    </section>
+
+    <section>
+        <title>Summary</title>
+        <para>You can find out more about the War plugin in <xref linkend="war_plugin"/> and the Jetty plugin in
+            <xref linkend="jetty_plugin"/>. You can find more sample Java projects in the
+            <filename>samples/webApplication</filename> directory in the Gradle distribution.
+        </para>
+    </section>
+</chapter>
diff --git a/subprojects/gradle-docs/src/docs/userguide/workingWithFiles.xml b/subprojects/gradle-docs/src/docs/userguide/workingWithFiles.xml
new file mode 100644
index 0000000..5420fd4
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/workingWithFiles.xml
@@ -0,0 +1,373 @@
+<!--
+  ~ 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.
+  -->
+<chapter id="working_with_files">
+    <title>Working With Files</title>
+    <para>
+        Most builds work with files. Gradle adds some concepts and APIs to help you achieve this.
+    </para>
+
+    <section id="sec:locating_files">
+        <title>Locating files</title>
+        <para>You can locate a file relative to the project directory using the
+            <apilink class="org.gradle.api.Project" method="file"/> method.
+        </para>
+        <sample id="resolveFile" dir="userguide/files/file" title="Locating files">
+            <sourcefile file="build.gradle"/>
+        </sample>
+        <para>You can pass any object to the <literal>file()</literal> method, and it will attempt to convert the value
+            to an absolute <classname>File</classname> object. Usually, you would pass it a
+            <classname>String</classname> or <classname>File</classname> instance. The supplied object's
+            <literal>toString()</literal> value is used as the file path. If this path is an absolute path, it is used
+            to construct a <classname>File</classname> instance. Otherwise, a <classname>File</classname> instance is
+            constructed by prepending the project directory path to the supplied path. The <literal>file()</literal>
+            method also understands URLs, such as <literal>file:/some/path.xml</literal>.
+        </para>
+        <para>Using this method is a useful way to convert some user provided value into an absolute <classname>File</classname>.
+            It is preferable to using <literal>new File(somePath)</literal>, as <literal>file()</literal> always evaluates
+            the supplied path relative to the project directory, which is fixed, rather than the current working
+            directory, which can change depending on how the user runs Gradle.
+        </para>
+    </section>
+
+    <section id="sec:file_collections">
+        <title>File collections</title>
+
+        <para>
+            A <firstterm>file collection</firstterm> is simply a set of files. It is represented by the
+            <apilink class="org.gradle.api.file.FileCollection"/> interface. Many objects in the Gradle API implement
+            this interface. For example, <link linkend="sub:configurations">dependency configurations</link> implement
+            <literal>FileCollection</literal>.
+        </para>
+
+        <para>
+            One way to obtain a <literal>FileCollection</literal> instance is to use the
+            <apilink class="org.gradle.api.Project" method="files"/> method. You can pass this method any number of
+            objects, which are then converted into a set of <classname>File</classname> objects. The
+            <literal>files()</literal> method accepts any type of object as its parameters. These are evaluated relative
+            to the project directory, as for the <literal>file()</literal> method, described in <xref linkend="sec:locating_files"/>.
+            You can also pass collections, maps and arrays to the <literal>files()</literal> method. These are flattened
+            and the contents converted to <classname>File</classname> instances.
+        </para>
+
+        <sample id="fileCollections" dir="userguide/files/fileCollections" title="Creating a file collection">
+            <sourcefile file="build.gradle" snippet="simple-params"/>
+        </sample>
+
+        <para>A file collection is iterable, and can be converted to a number of other types using the <literal>as</literal>
+            operator. You can also add 2 file collections together using the <literal>+</literal> operator, or subtract one
+            file collection from another using the <literal>-</literal> operator.
+            Here are some examples of what you can do with a file collection.
+        </para>
+        <sample id="fileCollections" dir="userguide/files/fileCollections" title="Using a file collection">
+            <sourcefile file="build.gradle" snippet="usage"/>
+            <test args="-q usage"/>
+        </sample>
+
+        <para>You can also pass the <literal>files()</literal> method a closure or a <classname>Callable</classname>
+            instance. This is called when the contents of the collection are queried, and its return value is converted
+            to a set of <classname>File</classname> instances. The return value can be an object of any of the types
+            supported by the <literal>files()</literal> method. This is a simple way to 'implement' the
+            <classname>FileCollection</classname> interface.
+        </para>
+        <sample id="fileCollections" dir="userguide/files/fileCollections" title="Implementing a file collection">
+            <sourcefile file="build.gradle" snippet="closure"/>
+            <output args="-q list"/>
+        </sample>
+
+        <para>It is important to note that the content of a file collection is evaluated lazily, when it is needed.
+            This means you can, for example, create a <literal>FileCollection</literal> that represents files which
+            will be created in the future by, say, some task.
+        </para>
+
+        <para>The <literal>files()</literal> method also accepts <classname>FileCollection</classname> instances.
+            These are flattened and the contents included in the file collection.
+        </para>
+    </section>
+
+    <section id="sec:file_trees">
+        <title>File trees</title>
+
+        <para>
+            A <firstterm>file tree</firstterm> is a collection of files arranged in a hierarchy. For example, a file tree
+            might represent a directory tree or the contents of a ZIP file. It is represented
+            by the <apilink class="org.gradle.api.file.FileTree"/> interface. The <literal>FileTree</literal> interface
+            extends <literal>FileCollection</literal>, so you can treat a file tree exactly the same way as you would a
+            file collection. Several objects in Gradle implement the <literal>FileTree</literal> interface, such as
+            <link linkend="sec:source_sets">source sets</link>.
+        </para>
+
+        <para>
+            One way to obtain a <literal>FileTree</literal> instance is to use the
+            <apilink class="org.gradle.api.Project" method="fileTree"/> method.
+            This creates a <literal>FileTree</literal> defined with a base directory, and optionally some Ant-style
+            include and exclude patterns.
+        </para>
+
+        <sample id="fileTrees" dir="userguide/files/fileTrees" title="Creating a file tree">
+            <sourcefile file="build.gradle" snippet="define"/>
+        </sample>
+
+        <para>You use a file tree in the same way you use a file collection. You can also visit the contents of the
+            tree, and select a sub-tree using Ant-style patterns:
+        </para>
+        <sample id="fileTrees" dir="userguide/files/fileTrees" title="Using a file tree">
+            <sourcefile file="build.gradle" snippet="use"/>
+        </sample>
+    </section>
+
+    <section id="sec:archive_contents">
+        <title>Using the contents of an archive as a file tree</title>
+
+        <para>You can use the contents of an archive, such as a ZIP or TAR file, as a file tree. You do this using
+            the <apilink class="org.gradle.api.Project" method="zipTree"/> and
+            <apilink class="org.gradle.api.Project" method="tarTree"/> methods. These methods return a <literal>FileTree</literal>
+            instance which you can use like any other file tree or file collection. For example, you can use it to expand
+            the archive by copying the contents, or to merge some archives into another.
+        </para>
+        <sample id="fileTrees" dir="userguide/files/fileTrees" title="Using an archive as a file tree">
+            <sourcefile file="build.gradle" snippet="archive-trees"/>
+        </sample>
+    </section>
+
+    <section id="sec:specifying_multiple_files">
+        <title>Specifying a set of input files</title>
+        <para>Many objects in Gradle have properties which accept a set of input files. For example, the
+            <apilink class="org.gradle.api.tasks.compile.Compile"/> task has a <literal>source</literal> property,
+            which defines the source files to compile. You can set the value of this property using any of the types
+            supported by the <link linkend="sec:file_collections">files()</link> method, which we have seen in above.
+            This means you can set the property using, for example, a <classname>File</classname>, <classname>String</classname>,
+            collection, <classname>FileCollection</classname> or even a closure.
+            Here are some examples:
+        </para>
+        <sample id="inputFiles" dir="userguide/files/inputFiles" title="Specifying a set of files">
+            <sourcefile file="build.gradle" snippet="set-input-files"/>
+        </sample>
+        <para>Usually, there is a method with the same name as the property, which appends to the set of files. Again,
+            this method accepts any of the types supported by the <link linkend="sec:file_collections">files()</link>
+            method.
+        </para>
+        <sample id="inputFiles" dir="userguide/files/inputFiles" title="Specifying a set of files">
+            <sourcefile file="build.gradle" snippet="add-input-files"/>
+        </sample>
+    </section>
+
+    <section id="sec:copying_files">
+        <title>Copying files</title>
+        <para>You can use the <apilink class="org.gradle.api.tasks.Copy"/> task to copy files. The copy task is very flexible, and allows
+            you to, for example, filter the contents of the files as they are copied, and to map the files names.
+        </para>
+        <para>To use the <literal>Copy</literal> task, you must provide a set of source files to copy, and a destination directory to copy
+            the files to. You may also specify how to transform the files as they are copied. You do all this using a
+            <firstterm>copy spec</firstterm>. A copy spec is represented by the <apilink class="org.gradle.api.file.CopySpec"/> interface. The
+            <literal>Copy</literal> task implements this interface.
+            You specify the source files using the <apilink class="org.gradle.api.file.CopySpec" method="from"/>
+            method. To specify the destination directory, you use the <apilink class="org.gradle.api.file.CopySpec" method="into"/>
+            method.
+        </para>
+        <sample id="copy" dir="userguide/files/copy" title="Copying files using the copy task">
+            <sourcefile file="build.gradle" snippet="copy-task"/>
+            <test args="test"/>
+        </sample>
+        <para>The <literal>from()</literal> method accepts any of the arguments that the
+            <link linkend="sec:file_collections">files()</link> method does. When an argument resolves to a directory,
+            everything under that directory (but not the directory itself) is recursively copied into the destination
+            directory. When an argument resolves to a file, that file is copied into the destination directory.
+            When an argument resolves to a non-existing file, that argument is ignored.
+            The <literal>into()</literal> accepts
+            any of the arguments that the <link linkend="sec:locating_files">file()</link> method does. Here is another
+            example:
+        </para>
+        <sample id="copy" dir="userguide/files/copy" title="Specifying copy task source files and destination directory">
+            <sourcefile file="build.gradle" snippet="copy-task-2"/>
+        </sample>
+        <para>You can select the files to copy using Ant-style include or exclude patterns, or using a closure:</para>
+        <sample id="copy" dir="userguide/files/copy" title="Selecting the files to copy">
+            <sourcefile file="build.gradle" snippet="copy-task-with-patterns"/>
+        </sample>
+        <para>You can also use the <apilink class="org.gradle.api.Project" method="copy"/> method to copy files. It works the
+            same way as the task.</para>
+        <sample id="copy" dir="userguide/files/copy" title="Copying files using the copy() method">
+            <sourcefile file="build.gradle" snippet="copy-method"/>
+        </sample>
+        <section>
+            <title>Renaming files</title>
+            <sample id="renameOnCopy" dir="userguide/files/copy" title="Renaming files as they are copied">
+                <sourcefile file="build.gradle" snippet="rename-files"/>
+            </sample>
+        </section>
+        <section>
+            <title>Filtering files</title>
+            <sample id="filterOnCopy" dir="userguide/files/copy" title="Filtering files as they are copied">
+                <sourcefile file="build.gradle" snippet="filter-files"/>
+            </sample>
+        </section>
+        <section>
+            <title>Using the <classname>CopySpec</classname> class</title>
+            <para>Copy specs form a hierarchy. A copy spec inherits its destination path, include patterns, exclude patterns, copy actions,
+                name mappings, filters.</para>
+            <sample id="nestedCopySpecs" dir="userguide/files/copy" title="Nested copy specs">
+                <sourcefile file="build.gradle" snippet="nested-specs"/>
+            </sample>
+        </section>
+    </section>
+
+    <section>
+        <title>Using the <literal>Sync</literal> task</title>
+        <para>The <apilink class="org.gradle.api.tasks.Sync"/> task extends the <literal>Copy</literal> task. When it
+            executes, it copies the source files into the destination directory, and then removes any files from the
+            destination directory which it did not copy.
+        </para>
+    </section>
+
+    <section id="sec:archives">
+        <title>Creating archives</title>
+        <para>
+            A project can have as many as JAR archives as you want. You can also add WAR, ZIP and TAR archives to your project.
+            Archives are created using the various archive tasks:
+            <apilink class="org.gradle.api.tasks.bundling.Zip"/>,
+            <apilink class="org.gradle.api.tasks.bundling.Tar"/>,
+            <apilink class="org.gradle.api.tasks.bundling.Jar" lang="groovy"/>, and
+            <apilink class="org.gradle.api.tasks.bundling.War" lang="groovy"/>.
+            They all work the same way, so let's look at how you create a ZIP file.
+        </para>
+        <sample id="createZip" dir="userguide/files/archives" title="Creating a ZIP archive">
+            <sourcefile file="build.gradle" snippet="zip"/>
+        </sample>
+        <tip>
+            <title>Why are you using the Java plugin?</title>
+            <para>The Java plugin adds a number of default values for the archive tasks. You can use the archive
+                tasks without using the Java plugin, if you like. You will need to provide values for some additional
+                properties.
+            </para>
+        </tip>
+
+        <para>
+            The archive tasks all work exactly the same way as the <literal>Copy</literal> task, and implement the same
+            <classname>CopySpec</classname> interface. As with the <literal>Copy</literal> task, you specify the input
+            files using the <literal>from()</literal> method, and can optionally specify where they end up in the
+            archive using the <literal>into()</literal> method. You can filter the contents of file, rename files, and
+            all the other things you can do with a copy spec.
+        </para>
+
+        <section>
+            <title>Archive naming</title>
+
+            <para>The default name for a generated archive is <filename><replaceable>projectName</replaceable>-<replaceable>version</replaceable>.<replaceable>type</replaceable></filename>
+                For example:
+            </para>
+            <sample id="archiveNaming" dir="userguide/files/archiveNaming" title="Creation of ZIP archive">
+                <sourcefile file="build.gradle"/>
+                <output args="-q myZip"/>
+            </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
+                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>
+
+            <para>There are a number of properties which you can set on an archive task. These are listed below in <xref linkend="archiveTasksNamingProperties"/>.
+                You can, for example, change the name of the archive:
+            </para>
+            <sample id="zipWithCustomName" dir="userguide/tutorial/zipWithCustomName" title="Configuration of archive task - custom archive name">
+                <sourcefile file="build.gradle"/>
+                <output args="-q myZip"/>
+            </sample>
+            <para>You can further customize the archive names:</para>
+            <sample id="zipWithArguments" dir="userguide/tutorial/zipWithArguments" title="Configuration of archive task - appendix & classifier">
+                <sourcefile file="build.gradle"/>
+                <output args="-q myZip"/>
+            </sample>
+
+            <table id="archiveTasksNamingProperties">
+                <title>Archive tasks - naming properties</title>
+                <thead>
+                    <tr>
+                        <td>Property name</td>
+                        <td>Type</td>
+                        <td>Default value</td>
+                        <td>Description</td>
+                    </tr>
+                </thead>
+                <tr>
+                    <td><literal>archiveName</literal></td>
+                    <td><classname>String</classname></td>
+                    <td>
+                        <filename><replaceable>baseName</replaceable>-<replaceable>appendix</replaceable>-<replaceable>version</replaceable>-<replaceable>classifier</replaceable>.<replaceable>extension</replaceable></filename>
+                        <para>If any of these properties is empty the trailing <filename>-</filename> is not added to the name.</para>
+                    </td>
+                    <td>The base file name of the generated archive</td>
+                </tr>
+                <tr>
+                    <td><literal>archivePath</literal></td>
+                    <td><classname>File</classname></td>
+                    <td><filename><replaceable>destinationDir</replaceable>/<replaceable>archiveName</replaceable></filename></td>
+                    <td>The absolute path of the generated archive.</td>
+                </tr>
+                <tr>
+                    <td><literal>destinationDir</literal></td>
+                    <td><classname>File</classname></td>
+                    <td>Depends on the archive type. JARs and WARs are generated into <filename><replaceable>project.buildDir</replaceable>/libraries</filename>.
+                        ZIPs and TARs are generated into <filename><replaceable>project.buildDir</replaceable>/distributions</filename>.
+                    </td>
+                    <td>The directory to generate the archive into</td>
+                </tr>
+                <tr>
+                    <td><literal>baseName</literal></td>
+                    <td><classname>String</classname></td>
+                    <td><filename><replaceable>project.name</replaceable></filename></td>
+                    <td>The base name portion of the archive file name.</td>
+                </tr>
+                <tr>
+                    <td><literal>appendix</literal></td>
+                    <td><classname>String</classname></td>
+                    <td><literal>null</literal></td>
+                    <td>The appendix portion of the archive file name.</td>
+                </tr>
+                <tr>
+                    <td><literal>version</literal></td>
+                    <td><classname>String</classname></td>
+                    <td><filename><replaceable>project.version</replaceable></filename></td>
+                    <td>The version portion of the archive file name.</td>
+                </tr>
+                <tr>
+                    <td><literal>classifier</literal></td>
+                    <td><classname>String</classname></td>
+                    <td><literal>null</literal></td>
+                    <td>The classifier portion of the archive file name,</td>
+                </tr>
+                <tr>
+                    <td><literal>extension</literal></td>
+                    <td><classname>String</classname></td>
+                    <td>Depends on the archive type, and for TAR files, the compression type as well: <filename>zip</filename>, <filename>jar</filename>,
+                        <filename>war</filename>, <filename>tar</filename>, <filename>tgz</filename> or <filename>tbz2</filename>.</td>
+                    <td>The extension of the archive file name.</td>
+                </tr>
+            </table>
+        </section>
+
+        <section>
+            <title>Sharing content between multiple archives</title>
+            <para>Using the <apilink class="org.gradle.api.Project" method="copySpec"/> method to share content between archives.</para>
+        </section>
+
+        <para>Often you will want to publish an archive, so that it is usable from another project. This process is
+            described in <xref linkend="artifact_management"/>
+        </para>
+
+    </section>
+    
+</chapter>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/docs/userguide/writingBuildScripts.xml b/subprojects/gradle-docs/src/docs/userguide/writingBuildScripts.xml
new file mode 100644
index 0000000..24127ff
--- /dev/null
+++ b/subprojects/gradle-docs/src/docs/userguide/writingBuildScripts.xml
@@ -0,0 +1,145 @@
+<!--
+  ~ 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.
+  -->
+<chapter id='writing_build_scripts' xmlns:xi="http://www.w3.org/2001/XInclude">
+    <title>Writing Build Scripts</title>
+    <para>This chapter looks at some of the details of writing a build script.</para>
+
+    <section id='sec:project_api'>
+        <title>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
+            <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>
+        <tip>
+            <title>Getting help writing build scripts</title>
+            <para>Don't forget that your build script is simply Groovy code that drives the Gradle API. And the
+                <apilink class='org.gradle.api.Project'/> interface is your starting point for accessing everything
+                in the Gradle API. So, if you're wondering what 'tags' are available in your build script, you can
+                start with the javadocs for the <classname>Project</classname> interface.
+            </para>
+        </tip>
+        <itemizedlist>
+            <listitem>
+                <para>Any method you call in your build script, which <emphasis>is not defined</emphasis>
+                    in the build script, is delegated to the <classname>Project</classname> object.
+                </para>
+            </listitem>
+            <listitem>
+                <para>Any property you access in your build script, which <emphasis>is not defined</emphasis>
+                    in the build script, is delegated to the <classname>Project</classname> object.
+                </para>
+            </listitem>
+        </itemizedlist>
+        <para>Let's try this out and try to access the <literal>name</literal> property of the
+            <classname>Project</classname> object.
+        </para>
+        <sample id="projectApi" dir="userguide/tutorial/projectApi" title="Accessing property of the Project object">
+            <sourcefile file="build.gradle"/>
+            <output args="-q check"/>
+        </sample>
+        <para>Both <literal>println</literal> statements print out the same property. The first uses auto-delegation to
+            the <classname>Project</classname> object, for properties not defined in the build script. The other
+            statement uses the <literal>project</literal> property available to any build script, which returns the
+            associated <classname>Project</classname> object. Only if you define a property or a method which has the
+            same name as a member of the <classname>Project</classname> object, you need to use the <literal>project</literal>
+            property.
+        </para>
+        <section>
+            <title>Standard project properties</title>
+            <para>The <classname>Project</classname> object provides some standard properties, which are available in
+                your build script. The following table lists a few of the commonly used ones.
+            </para>
+            <table>
+                <title>Project Properties</title>
+                <thead>
+                    <tr>
+                        <td>Name</td>
+                        <td>Type</td>
+                        <td>Default Value</td>
+                    </tr>
+                </thead>
+                <tr>
+                    <td><literal>project</literal></td>
+                    <td><apilink class='org.gradle.api.Project'/></td>
+                    <td>The <classname>Project</classname> instance</td>
+                </tr>
+                <tr>
+                    <td><literal>name</literal></td>
+                    <td><classname>String</classname></td>
+                    <td>The name of the directory containing the build script.</td>
+                </tr>
+                <tr>
+                    <td><literal>path</literal></td>
+                    <td><classname>String</classname></td>
+                    <td>The absolute path of the project.</td>
+                </tr>
+                <tr>
+                    <td><literal>buildFile</literal></td>
+                    <td><classname>File</classname></td>
+                    <td>The build script.</td>
+                </tr>
+                <tr>
+                    <td><literal>projectDir</literal></td>
+                    <td><classname>File</classname></td>
+                    <td>The directory containing the build script.</td>
+                </tr>
+                <tr>
+                    <td><literal>buildDirName</literal></td>
+                    <td><classname>String</classname></td>
+                    <td><filename>build</filename></td>
+                </tr>
+                <tr>
+                    <td><literal>buildDir</literal></td>
+                    <td><classname>File</classname></td>
+                    <td><filename><replaceable>projectDir</replaceable>/build</filename></td>
+                </tr>
+                <tr>
+                    <td><literal>group</literal></td>
+                    <td><classname>Object</classname></td>
+                    <td><literal>unspecified</literal></td>
+                </tr>
+                <tr>
+                    <td><literal>version</literal></td>
+                    <td><classname>Object</classname></td>
+                    <td><literal>unspecified</literal></td>
+                </tr>
+                <tr>
+                    <td><literal>ant</literal></td>
+                    <td><apilink class="org.gradle.api.AntBuilder"/></td>
+                    <td>An <classname>AntBuilder</classname> instance</td>
+                </tr>
+            </table>
+
+            <para>Below is a sample build which demonstrates some of these properties.</para>
+            <sample id="projectCoreProperties" dir="userguide/tutorial/projectCoreProperties" title="Project properties">
+                <layout>
+                    build.gradle
+                    subProject
+                    subProject/build.gradle
+                </layout>
+                <sourcefile file="build.gradle"/>
+                <output args="-q check"/>
+            </sample>
+        </section>
+    </section>
+    <section>
+        <title>Script API</title>
+        <para>When Gradle executes a script, it compiles the script into a class which implements <apilink class="org.gradle.api.Script"/>.</para>
+    </section>
+
+</chapter>
diff --git a/subprojects/gradle-docs/src/samples/announce/build.gradle b/subprojects/gradle-docs/src/samples/announce/build.gradle
new file mode 100644
index 0000000..6b7de53
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/announce/build.gradle
@@ -0,0 +1,23 @@
+// START SNIPPET use-plugin
+apply plugin: 'announce'
+// END SNIPPET use-plugin
+
+//START SNIPPET announce-plugin-conf
+announce {  
+  username = 'myId'
+  password = 'myPassword'
+}
+//END SNIPPET announce-plugin-conf
+
+
+//START SNIPPET announce-usage
+task helloWorld << {  
+    ant.echo(message: "hello") 
+}  
+
+helloWorld.doLast {  
+    announce("Build complete", "notify-send")
+    announce("Build complete", "twitter")
+}
+//END SNIPPET announce-usage
+
diff --git a/subprojects/gradle-docs/src/samples/announce/readme.xml b/subprojects/gradle-docs/src/samples/announce/readme.xml
new file mode 100644
index 0000000..6ae4755
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/announce/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>A project which uses the announce plugin</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/antlr/build.gradle b/subprojects/gradle-docs/src/samples/antlr/build.gradle
new file mode 100644
index 0000000..eedca0a
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/antlr/build.gradle
@@ -0,0 +1,16 @@
+// START SNIPPET use-plugin
+apply plugin: 'antlr'
+// END SNIPPET use-plugin
+
+// START SNIPPET declare-dependency
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    antlr 'antlr:antlr:2.7.7'
+// END SNIPPET declare-dependency
+    testCompile 'junit:junit:4.7'
+// START SNIPPET declare-dependency
+}
+// END SNIPPET declare-dependency
diff --git a/subprojects/gradle-docs/src/samples/antlr/src/main/antlr/org/gradle/Calculator.g b/subprojects/gradle-docs/src/samples/antlr/src/main/antlr/org/gradle/Calculator.g
new file mode 100644
index 0000000..8be161c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/antlr/src/main/antlr/org/gradle/Calculator.g
@@ -0,0 +1,12 @@
+header {
+    package org.gradle;
+}
+
+class CalculatorLexer extends Lexer; 
+
+NUMBER	:	('0'..'9')+;
+PLUS	:	'+';
+
+class CalculatorParser extends Parser;
+
+add	:	NUMBER PLUS NUMBER;
diff --git a/subprojects/gradle-docs/src/samples/antlr/src/test/java/org/gradle/GrammarTest.java b/subprojects/gradle-docs/src/samples/antlr/src/test/java/org/gradle/GrammarTest.java
new file mode 100644
index 0000000..a71ab72
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/antlr/src/test/java/org/gradle/GrammarTest.java
@@ -0,0 +1,14 @@
+package org.gradle;
+
+import java.io.StringReader;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class GrammarTest {
+    @Test
+    public void canUseGeneratedGrammar() throws Exception {
+        CalculatorLexer lexer = new CalculatorLexer(new StringReader("1+2"));
+        CalculatorParser parser = new CalculatorParser(lexer);
+        parser.add();
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/clientModuleDependencies/api/build.gradle b/subprojects/gradle-docs/src/samples/clientModuleDependencies/api/build.gradle
new file mode 100644
index 0000000..e7b22eb
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/clientModuleDependencies/api/build.gradle
@@ -0,0 +1,10 @@
+import org.junit.Assert
+
+dependencies {
+	runtime project(':shared')
+}
+
+task testDeps(dependsOn: configurations.runtime) << {
+	Assert.assertEquals(['commons-lang-2.4.jar', 'commons-io-1.2.jar', 'shared-1.0.jar'] as Set,
+            configurations.runtime.files.collect { it.name } as Set)
+}
diff --git a/subprojects/gradle-docs/src/samples/clientModuleDependencies/build.gradle b/subprojects/gradle-docs/src/samples/clientModuleDependencies/build.gradle
new file mode 100644
index 0000000..0f6aeb1
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/clientModuleDependencies/build.gradle
@@ -0,0 +1,14 @@
+subprojects {
+    buildscript {
+        repositories {
+            mavenCentral()
+        }
+        dependencies {
+            classpath 'junit:junit:4.7'
+        }
+    }
+    apply plugin: 'java'
+    repositories {
+        mavenCentral()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/clientModuleDependencies/settings.gradle b/subprojects/gradle-docs/src/samples/clientModuleDependencies/settings.gradle
new file mode 100644
index 0000000..a2cdd83
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/clientModuleDependencies/settings.gradle
@@ -0,0 +1 @@
+include 'api', 'shared'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/clientModuleDependencies/shared/build.gradle b/subprojects/gradle-docs/src/samples/clientModuleDependencies/shared/build.gradle
new file mode 100644
index 0000000..978c38d
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/clientModuleDependencies/shared/build.gradle
@@ -0,0 +1,13 @@
+import org.junit.Assert
+
+version = '1.0'
+dependencies {
+	runtime module("commons-lang:commons-lang:2.4") {
+	   dependency("commons-io:commons-io:1.2")
+	}
+}
+
+task testDeps << {
+	Assert.assertEquals(['commons-lang-2.4.jar', 'commons-io-1.2.jar'] as Set,
+            configurations.runtime.files.collect { it.name } as Set) 
+}
diff --git a/subprojects/gradle-docs/src/samples/clientModuleDependencies/shared/src/main/java/SomeClass.java b/subprojects/gradle-docs/src/samples/clientModuleDependencies/shared/src/main/java/SomeClass.java
new file mode 100644
index 0000000..341a7e2
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/clientModuleDependencies/shared/src/main/java/SomeClass.java
@@ -0,0 +1 @@
+public class SomeClass {}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/codeQuality/build.gradle b/subprojects/gradle-docs/src/samples/codeQuality/build.gradle
new file mode 100644
index 0000000..e69f10c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/codeQuality/build.gradle
@@ -0,0 +1,13 @@
+// START SNIPPET use-plugin
+apply plugin: 'code-quality'
+// END SNIPPET use-plugin
+apply plugin: 'groovy'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    groovy group: 'org.codehaus.groovy', name: 'groovy', version: '1.7.0'
+    testCompile group: 'junit', name: 'junit', version: '4.7'
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/codeQuality/config/checkstyle/checkstyle.xml b/subprojects/gradle-docs/src/samples/codeQuality/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000..16637f1
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/codeQuality/config/checkstyle/checkstyle.xml
@@ -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.
+  -->
+<!DOCTYPE module PUBLIC
+        "-//Puppy Crawl//DTD Check Configuration 1.2//EN"
+        "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
+<module name="Checker">
+    <module name="TreeWalker">
+        <!-- Naming -->
+        <module name="ClassTypeParameterName"/>
+        <module name="ConstantName"/>
+        <module name="LocalFinalVariableName"/>
+        <module name="LocalVariableName"/>
+        <module name="MemberName"/>
+        <module name="MethodName"/>
+        <module name="MethodTypeParameterName"/>
+        <module name="PackageName">
+            <property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/>
+        </module>
+        <module name="ParameterName"/>
+        <module name="StaticVariableName"/>
+        <module name="TypeName"/>
+    </module>
+</module>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/codeQuality/config/codenarc/codenarc.xml b/subprojects/gradle-docs/src/samples/codeQuality/config/codenarc/codenarc.xml
new file mode 100644
index 0000000..2950082
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/codeQuality/config/codenarc/codenarc.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ 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.
+  -->
+<ruleset xmlns="http://codenarc.org/ruleset/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://codenarc.org/ruleset/1.0 http://codenarc.org/ruleset-schema.xsd"
+         xsi:noNamespaceSchemaLocation="http://codenarc.org/ruleset-schema.xsd">
+    <ruleset-ref path='rulesets/naming.xml'/>
+</ruleset>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/codeQuality/readme.xml b/subprojects/gradle-docs/src/samples/codeQuality/readme.xml
new file mode 100644
index 0000000..8e51f35
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/codeQuality/readme.xml
@@ -0,0 +1,18 @@
+<!--
+  ~ 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.
+  -->
+<sample>
+    <para>A project which uses the code quality plugin.</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/codeQuality/src/main/groovy/org/gradle/sample/GroovyPerson.groovy b/subprojects/gradle-docs/src/samples/codeQuality/src/main/groovy/org/gradle/sample/GroovyPerson.groovy
new file mode 100644
index 0000000..e309893
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/codeQuality/src/main/groovy/org/gradle/sample/GroovyPerson.groovy
@@ -0,0 +1,5 @@
+package org.gradle.sample
+
+class GroovyPerson {
+    def String name
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/codeQuality/src/main/java/org/gradle/sample/Person.java b/subprojects/gradle-docs/src/samples/codeQuality/src/main/java/org/gradle/sample/Person.java
new file mode 100644
index 0000000..5ff1d1a
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/codeQuality/src/main/java/org/gradle/sample/Person.java
@@ -0,0 +1,15 @@
+package org.gradle.sample;
+
+import java.lang.String;
+
+class Person {
+    private String name;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/codeQuality/src/test/groovy/org/gradle/sample/PersonTest.groovy b/subprojects/gradle-docs/src/samples/codeQuality/src/test/groovy/org/gradle/sample/PersonTest.groovy
new file mode 100644
index 0000000..e5c37b6
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/codeQuality/src/test/groovy/org/gradle/sample/PersonTest.groovy
@@ -0,0 +1,13 @@
+package org.gradle.sample
+
+import org.junit.*
+import static org.junit.Assert.*
+
+class PersonTest {
+    @Test
+    def void canCreateAPerson() {
+        Person person = new Person()
+        person.name = 'Barry'
+        assertEquals('Barry', person.name)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/customBuildLanguage/basicEdition/build.gradle b/subprojects/gradle-docs/src/samples/customBuildLanguage/basicEdition/build.gradle
new file mode 100644
index 0000000..60ad497
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customBuildLanguage/basicEdition/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: org.gradle.samples.ProductPlugin
+
+// A custom build element. This defines the product to build
+product {
+    displayName 'Basic Edition'
+    // Which modules to include in this product?
+    module project(':identityManagement')
+    module project(':billing')
+}
diff --git a/subprojects/gradle-docs/src/samples/customBuildLanguage/basicEdition/src/dist/end-user-license-agreement.txt b/subprojects/gradle-docs/src/samples/customBuildLanguage/basicEdition/src/dist/end-user-license-agreement.txt
new file mode 100644
index 0000000..26156b9
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customBuildLanguage/basicEdition/src/dist/end-user-license-agreement.txt
@@ -0,0 +1 @@
+This is the EULA for the Basic Edition
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/customBuildLanguage/billing/build.gradle b/subprojects/gradle-docs/src/samples/customBuildLanguage/billing/build.gradle
new file mode 100644
index 0000000..e8f242d
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customBuildLanguage/billing/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: org.gradle.samples.ProductModulePlugin
+
+dependencies {
+    compile project(':identityManagement')
+    compile 'commons-lang:commons-lang:2.4'
+}
diff --git a/subprojects/gradle-docs/src/samples/customBuildLanguage/build.gradle b/subprojects/gradle-docs/src/samples/customBuildLanguage/build.gradle
new file mode 100644
index 0000000..4c5e97d
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customBuildLanguage/build.gradle
@@ -0,0 +1,4 @@
+
+subprojects {
+    version = '1.0'
+}
diff --git a/subprojects/gradle-docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductDefinition.groovy b/subprojects/gradle-docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductDefinition.groovy
new file mode 100644
index 0000000..bde5d2a
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductDefinition.groovy
@@ -0,0 +1,16 @@
+package org.gradle.samples
+
+import org.gradle.api.Project
+
+class ProductDefinition {
+    String displayName
+    List<Project> modules = []
+
+    def displayName(String name) {
+        displayName = name
+    }
+    
+    def module(Project project) {
+        modules << project
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductModulePlugin.groovy b/subprojects/gradle-docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductModulePlugin.groovy
new file mode 100644
index 0000000..6763bb7
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductModulePlugin.groovy
@@ -0,0 +1,34 @@
+/*
+ * 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.samples
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+
+/**
+ * A plugin which configures a product module project. Each product module is assembled into one or more products.
+ */
+class ProductModulePlugin implements Plugin<Project> {
+    void apply(Project project) {
+        project.configure(project) {
+            apply plugin: 'java'
+            repositories {
+                mavenCentral()
+            }
+            archivesBaseName = "some-company-${name.replaceAll('(\\p{Upper})', '-$1').toLowerCase()}"
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductPlugin.groovy b/subprojects/gradle-docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductPlugin.groovy
new file mode 100644
index 0000000..af7607e
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductPlugin.groovy
@@ -0,0 +1,63 @@
+/*
+ * 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.samples
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.tasks.bundling.Zip
+
+/**
+ * A plugin which configures a product project. Each product is composed of several product modules.
+ */
+class ProductPlugin implements Plugin<Project> {
+    void apply(Project project) {
+        project.configure(project) {
+            apply plugin: 'base'
+            repositories {
+                mavenCentral()
+            }
+
+            def pluginConvention = new ProductPluginConvention()
+            convention.plugins.product = pluginConvention
+            pluginConvention.distSrcDirs << rootProject.file('src/dist')
+            pluginConvention.distSrcDirs << project.file('src/dist')
+
+            configurations {
+                runtime
+            }
+            tasks.add(name: 'dist', type: Zip)
+
+            afterEvaluate {
+                ProductDefinition product = pluginConvention.product
+                product.modules.each {p ->
+                    dependencies { runtime project.project(p.path) }
+                }
+                archivesBaseName = "some-company-${product.displayName.replaceAll('\\s+', '-').toLowerCase()}"
+                dist {
+                    into('lib') {
+                        from configurations.runtime
+                    }
+                    from(pluginConvention.distSrcDirs) {
+                        filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: [
+                                productName: product.displayName,
+                                version: version
+                        ])
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductPluginConvention.groovy b/subprojects/gradle-docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductPluginConvention.groovy
new file mode 100644
index 0000000..f413d92
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customBuildLanguage/buildSrc/src/main/groovy/org/gradle/samples/ProductPluginConvention.groovy
@@ -0,0 +1,12 @@
+package org.gradle.samples
+
+class ProductPluginConvention {
+    ProductDefinition product = new ProductDefinition()
+    List<File> distSrcDirs = []
+
+    def product(Closure cl) {
+        cl.delegate = product
+        cl.resolveStrategy = Closure.DELEGATE_FIRST
+        cl.call()
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/customBuildLanguage/enterpriseEdition/build.gradle b/subprojects/gradle-docs/src/samples/customBuildLanguage/enterpriseEdition/build.gradle
new file mode 100644
index 0000000..4247acb
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customBuildLanguage/enterpriseEdition/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: org.gradle.samples.ProductPlugin
+
+// A custom build element. This defines the product to build
+product {
+    displayName 'Enterprise Edition'
+    // Which modules to include in this product?
+    module project(':identityManagement')
+    module project(':billing')
+    module project(':reporting')
+}
diff --git a/subprojects/gradle-docs/src/samples/customBuildLanguage/enterpriseEdition/src/dist/end-user-license-agreement.txt b/subprojects/gradle-docs/src/samples/customBuildLanguage/enterpriseEdition/src/dist/end-user-license-agreement.txt
new file mode 100644
index 0000000..ea0b1d9
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customBuildLanguage/enterpriseEdition/src/dist/end-user-license-agreement.txt
@@ -0,0 +1 @@
+This is the EULA for the Enterprise Edition
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/customBuildLanguage/identityManagement/build.gradle b/subprojects/gradle-docs/src/samples/customBuildLanguage/identityManagement/build.gradle
new file mode 100644
index 0000000..d35b213
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customBuildLanguage/identityManagement/build.gradle
@@ -0,0 +1 @@
+apply plugin: org.gradle.samples.ProductModulePlugin
diff --git a/subprojects/gradle-docs/src/samples/customBuildLanguage/readme.xml b/subprojects/gradle-docs/src/samples/customBuildLanguage/readme.xml
new file mode 100644
index 0000000..b2bb9ce
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customBuildLanguage/readme.xml
@@ -0,0 +1,36 @@
+<!--
+  ~ 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.
+  -->
+<sample>
+    <para>This sample demonstrates how to add some custom elements to the build DSL. It also demonstrates the use of
+        custom plug-ins to organize build logic.
+    </para>
+
+    <para>The build is composed of 2 types of projects. The first type of project represents a product, and the second
+        represents a product module. Each product includes one or more product modules, and each product module may be
+        included in multiple products. That is, there is a many-to-many relationship between these products and product
+        modules. For each product, the build produces a ZIP containing the runtime classpath for each product module
+        included in the product. The ZIP also contains some product-specific files.
+    </para>
+
+    <para>The custom elements can be seen in the build script for the product projects (for example,
+        <filename>basicEdition/build.gradle</filename>). Notice that the build script uses the
+        <literal>product { }</literal> element. This is a custom element.
+    </para>
+
+    <para>The build scripts of each project contain only declarative elements. The bulk of the work is done by 2
+        custom plug-ins found in <filename>buildSrc/src/main/groovy</filename>.
+    </para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/customBuildLanguage/reporting/build.gradle b/subprojects/gradle-docs/src/samples/customBuildLanguage/reporting/build.gradle
new file mode 100644
index 0000000..de86f33
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customBuildLanguage/reporting/build.gradle
@@ -0,0 +1,7 @@
+apply plugin: org.gradle.samples.ProductModulePlugin
+
+dependencies {
+    compile project(':identityManagement')
+    compile project(':billing')
+    compile 'commons-io:commons-io:1.2'
+}
diff --git a/subprojects/gradle-docs/src/samples/customBuildLanguage/settings.gradle b/subprojects/gradle-docs/src/samples/customBuildLanguage/settings.gradle
new file mode 100644
index 0000000..104015a
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customBuildLanguage/settings.gradle
@@ -0,0 +1,6 @@
+include 'billing'
+include 'identityManagement'
+include 'reporting'
+
+include 'basicEdition'
+include 'enterpriseEdition'
diff --git a/subprojects/gradle-docs/src/samples/customBuildLanguage/src/dist/bin/start.sh b/subprojects/gradle-docs/src/samples/customBuildLanguage/src/dist/bin/start.sh
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/customBuildLanguage/src/dist/readme.txt b/subprojects/gradle-docs/src/samples/customBuildLanguage/src/dist/readme.txt
new file mode 100644
index 0000000..a9cf123
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customBuildLanguage/src/dist/readme.txt
@@ -0,0 +1 @@
+This is the README for @productName@ version @version@
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/customPlugin/build.gradle b/subprojects/gradle-docs/src/samples/customPlugin/build.gradle
new file mode 100644
index 0000000..c717687
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customPlugin/build.gradle
@@ -0,0 +1,15 @@
+// START SNIPPET use-plugin
+apply plugin: 'groovy'
+
+dependencies {
+    compile gradleApi()
+// END SNIPPET use-plugin
+    groovy localGroovy()
+    testCompile 'junit:junit:4.7'
+// START SNIPPET use-plugin
+}
+// END SNIPPET use-plugin
+
+repositories {
+    mavenCentral()
+}
diff --git a/subprojects/gradle-docs/src/samples/customPlugin/readme.xml b/subprojects/gradle-docs/src/samples/customPlugin/readme.xml
new file mode 100644
index 0000000..7ac351d
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customPlugin/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>A project which implements a custom plugin and task.</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/customPlugin/src/main/groovy/org/gradle/GreetingPlugin.groovy b/subprojects/gradle-docs/src/samples/customPlugin/src/main/groovy/org/gradle/GreetingPlugin.groovy
new file mode 100644
index 0000000..dae067c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customPlugin/src/main/groovy/org/gradle/GreetingPlugin.groovy
@@ -0,0 +1,11 @@
+package org.gradle
+
+import org.gradle.api.Project
+import org.gradle.api.Plugin
+
+class GreetingPlugin implements Plugin<Project> {
+
+    void apply(Project target) {
+        target.task('hello', type: GreetingTask)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/customPlugin/src/main/groovy/org/gradle/GreetingTask.groovy b/subprojects/gradle-docs/src/samples/customPlugin/src/main/groovy/org/gradle/GreetingTask.groovy
new file mode 100644
index 0000000..ef470eb
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customPlugin/src/main/groovy/org/gradle/GreetingTask.groovy
@@ -0,0 +1,11 @@
+package org.gradle
+
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.TaskAction
+
+class GreetingTask extends DefaultTask {
+    @TaskAction
+    def greet() {
+        println 'hello from GreetingTask'
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/customPlugin/src/main/resources/META-INF/gradle-plugins/greeting.properties b/subprojects/gradle-docs/src/samples/customPlugin/src/main/resources/META-INF/gradle-plugins/greeting.properties
new file mode 100644
index 0000000..c2985dc
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customPlugin/src/main/resources/META-INF/gradle-plugins/greeting.properties
@@ -0,0 +1 @@
+implementation-class=org.gradle.GreetingPlugin
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/customPlugin/src/test/groovy/org/gradle/GreetingPluginTest.groovy b/subprojects/gradle-docs/src/samples/customPlugin/src/test/groovy/org/gradle/GreetingPluginTest.groovy
new file mode 100644
index 0000000..467d844
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customPlugin/src/test/groovy/org/gradle/GreetingPluginTest.groovy
@@ -0,0 +1,16 @@
+package org.gradle
+
+import org.junit.Test
+import org.gradle.testfixtures.ProjectBuilder
+import org.gradle.api.Project
+import static org.junit.Assert.*
+
+class GreetingPluginTest {
+    @Test
+    public void greeterPluginAddsGreetingTaskToProject() {
+        Project project = ProjectBuilder.builder().build()
+        project.apply plugin: 'greeting'
+
+        assertTrue(project.tasks.hello instanceof GreetingTask)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/customPlugin/src/test/groovy/org/gradle/GreetingTaskTest.groovy b/subprojects/gradle-docs/src/samples/customPlugin/src/test/groovy/org/gradle/GreetingTaskTest.groovy
new file mode 100644
index 0000000..3937322
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/customPlugin/src/test/groovy/org/gradle/GreetingTaskTest.groovy
@@ -0,0 +1,17 @@
+package org.gradle
+
+import org.junit.Test
+import org.gradle.testfixtures.ProjectBuilder
+import org.gradle.api.Project
+import static org.junit.Assert.*
+
+// START SNIPPET test-task
+class GreetingTaskTest {
+    @Test
+    public void canAddTaskToProject() {
+        Project project = ProjectBuilder.builder().build()
+        def task = project.task('greeting', type: GreetingTask)
+        assertTrue(task instanceof GreetingTask)
+    }
+}
+// END SNIPPET test-task
diff --git a/subprojects/gradle-docs/src/samples/dependencies/build.gradle b/subprojects/gradle-docs/src/samples/dependencies/build.gradle
new file mode 100644
index 0000000..eafbe60
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/dependencies/build.gradle
@@ -0,0 +1,177 @@
+import org.apache.ivy.plugins.resolver.FileSystemResolver
+
+group = 'sealife'
+
+project(':atlantic') {
+    apply plugin: 'java'
+    configurations {
+        other
+    }
+    dependencies {
+        other "sea.fish:herring:1.0", files("$rootDir/lib/selfResolving1.jar")
+    }
+}
+
+project(':northSea') {
+    apply plugin: 'java'
+    configurations {
+        other
+    }
+    dependencies {
+        other "sea.fish:squid:1.0", files("$rootDir/lib/selfResolving2.jar"), project(path: ':atlantic', configuration: 'other')
+    }
+}
+
+repositories {
+    add(new FileSystemResolver()) {
+        name = "repo"
+        addArtifactPattern("$rootDir/repo/[organization]/[module]-[revision].[ext]")
+        addIvyPattern("$rootDir/repo/[organization]/ivy-[module]-[revision].xml")
+        checkmodified = true
+    }
+}
+
+configurations {
+    oneDepWithNoTransitives
+    oneDepWithTransitives
+    twoDepsWithNoTransitives
+    twoDepsWithTransitives
+    twoDepsWithOneAsTransitive
+    twoDepsWithVersionConflicts
+    twoDepsWithDifferentDependencyConfigurations
+    depWithMultipleConfigurations
+    extended
+    extending.extendsFrom extended
+    extendingWithDifferentConfiguration.extendsFrom extended
+    selfResolving
+    mixed.extendsFrom selfResolving, twoDepsWithTransitives
+    subprojectAtlantic
+    subprojectNorthSea
+    classifier
+    extendingClassifier.extendsFrom classifier
+    customArtifacts
+}
+
+dependencies {
+    oneDepWithNoTransitives "sea.fish:herring:1.0"
+    oneDepWithTransitives "sea.fish:tuna:1.0"
+    twoDepsWithNoTransitives "sea.fish:herring:1.0", "sea.mammals:seal:1.0"
+    twoDepsWithTransitives "sea.mammals:orca:1.0", "sea.fish:tuna:1.0"
+    twoDepsWithOneAsTransitive "sea.fish:shark:1.0", "sea.fish:tuna:1.0"
+    twoDepsWithVersionConflicts "sea.fish:shark:1.0", "sea.mammals:orca:1.0"
+    twoDepsWithDifferentDependencyConfigurations "sea.fish:shark:1.0", "sea.fish:billfish:1.0"
+    depWithMultipleConfigurations group: 'sea.fish', name: 'tuna', version: '1.0', configuration: 'default'
+    depWithMultipleConfigurations group: 'sea.fish', name: 'tuna', version: '1.0', configuration: 'specialWaters'
+    extended "sea.fish:tuna:1.0"
+    extending "sea.mammals:orca:1.0"
+    extendingWithDifferentConfiguration group: 'sea.fish', name: 'tuna', version: '1.0', configuration: 'specialWaters'
+    extendingWithDifferentConfiguration "sea.mammals:seal:1.0"
+    selfResolving files("$rootDir/lib/selfResolving1.jar"), files("$projectDir/someDir")
+    subprojectAtlantic project(path: ':atlantic', configuration: 'other')
+    subprojectNorthSea project(path: ':northSea', configuration: 'other')
+    classifier "sea.fish:herring:1.0"
+    classifier('sea.mammals:dolphin:1.0') {
+	    artifact {
+	        name = 'dolphin'
+	        type = 'jar'
+	        extension = 'jar'
+	        classifier = 'oceanic'
+        }
+        artifact {
+	        name = 'dolphin'
+	        type = 'jar'
+	        extension = 'jar'
+	        classifier = 'river'
+        }
+	}
+    // A resolve should return both, tuna and custom-tuna. A files filtering on shark or tuna either or.
+    // But Ivy does not return the correct resolve report. In fact if you produce an equivalent ivy.xml, only custom-tuna is returned, not tuna-1.0.jar.
+    // Therefore we don't test the resolve of customArtifacts
+    customArtifacts "sea.fish:shark:1.0"
+    customArtifacts("sea.fish:tuna:1.0") {
+        artifact {
+	        name = 'custom-tuna'
+	        type = 'jar'
+	        extension = 'jar'
+            url = "file:///$rootDir/lib/selfResolving1.jar"
+        }
+    }
+
+}
+
+task test(dependsOn: [configurations.subprojectAtlantic, configurations.subprojectNorthSea]) << {
+    assertCorrectFilesForCompleteConfigurations()
+    assertFilesForDependencySubsets()
+    assertFilesForConfigurationCopies()
+}
+
+def assertCorrectFilesForCompleteConfigurations() {
+    expectedResults = [
+            oneDepWithNoTransitives: ['herring-1.0.jar'] as Set,
+            oneDepWithTransitives: ['tuna-1.0.jar', 'herring-1.0.jar'] as Set,
+            twoDepsWithNoTransitives: ['seal-1.0.jar', 'herring-1.0.jar'] as Set,
+            twoDepsWithTransitives: ['tuna-1.0.jar', 'orca-1.0.jar', 'seal-1.0.jar', 'herring-1.0.jar'] as Set,
+            twoDepsWithOneAsTransitive: ['tuna-1.0.jar', 'shark-1.0.jar', 'seal-2.0.jar', 'herring-1.0.jar'] as Set,
+            twoDepsWithVersionConflicts: ['tuna-1.0.jar', 'shark-1.0.jar', 'seal-2.0.jar', 'orca-1.0.jar', 'herring-1.0.jar'] as Set,
+            twoDepsWithDifferentDependencyConfigurations: ['tuna-1.0.jar', 'shark-1.0.jar', 'seal-2.0.jar', 'billfish-1.0.jar', 'herring-1.0.jar', 'squid-1.0.jar'] as Set,
+            depWithMultipleConfigurations: ['tuna-1.0.jar', 'herring-1.0.jar', 'squid-1.0.jar'] as Set,
+            extendingWithDifferentConfiguration: ['tuna-1.0.jar', 'herring-1.0.jar', 'squid-1.0.jar', 'seal-1.0.jar'] as Set,
+            extending: ['tuna-1.0.jar', 'herring-1.0.jar', 'seal-1.0.jar', 'orca-1.0.jar'] as Set,
+            selfResolving: ['selfResolving1.jar', 'someDir'] as Set,
+            mixed: ['tuna-1.0.jar', 'orca-1.0.jar', 'seal-1.0.jar', 'herring-1.0.jar', 'selfResolving1.jar', 'someDir'] as Set,
+            subprojectAtlantic: ['selfResolving1.jar', 'herring-1.0.jar'] as Set,
+            subprojectNorthSea: ['selfResolving1.jar', 'herring-1.0.jar', 'selfResolving2.jar', 'squid-1.0.jar'] as Set,
+            classifier: ['dolphin-1.0-oceanic.jar', 'dolphin-1.0-river.jar', "herring-1.0.jar"] as Set,
+            extendingClassifier: ['dolphin-1.0-oceanic.jar', 'dolphin-1.0-river.jar', "herring-1.0.jar"] as Set
+    ]
+    expectedResults.each { configurationName, expectedFileNames ->
+        Set resolvedFileNames = configurations[configurationName].files.collect { it.name }
+        assert expectedFileNames == resolvedFileNames
+    }
+}
+
+def assertFilesForDependencySubsets() {
+    assertSubsetFiles(configurations.oneDepWithNoTransitives, { dep -> dep.name == 'herring' }, ['herring-1.0.jar'])
+    assertSubsetFiles(configurations.oneDepWithTransitives, { dep -> dep.name == 'tuna' }, ['tuna-1.0.jar', 'herring-1.0.jar'])
+    assertSubsetFiles(configurations.twoDepsWithNoTransitives, { dep -> dep.name == 'herring' }, ['herring-1.0.jar'])
+    assertSubsetFiles(configurations.twoDepsWithTransitives, { dep -> dep.name == 'tuna' }, ['tuna-1.0.jar', 'herring-1.0.jar'])
+    assertSubsetFiles(configurations.twoDepsWithOneAsTransitive, { dep -> dep.name == 'shark' }, ['tuna-1.0.jar', 'shark-1.0.jar', 'seal-2.0.jar', 'herring-1.0.jar'])
+    assertSubsetFiles(configurations.twoDepsWithOneAsTransitive, { dep -> dep.name == 'tuna' }, ['tuna-1.0.jar', 'herring-1.0.jar'])
+    assertSubsetFiles(configurations.twoDepsWithVersionConflicts, { dep -> dep.name == 'shark' }, ['tuna-1.0.jar', 'shark-1.0.jar', 'seal-2.0.jar', 'herring-1.0.jar'])
+    assertSubsetFiles(configurations.twoDepsWithVersionConflicts, { dep -> dep.name == 'orca' }, ['orca-1.0.jar', 'seal-2.0.jar'])
+    assertSubsetFiles(configurations.twoDepsWithDifferentDependencyConfigurations, { dep -> dep.name == 'shark' }, ['tuna-1.0.jar', 'shark-1.0.jar', 'seal-2.0.jar', 'herring-1.0.jar'])
+    assertSubsetFiles(configurations.twoDepsWithDifferentDependencyConfigurations, { dep -> dep.name == 'billfish' }, ['tuna-1.0.jar', 'billfish-1.0.jar', 'squid-1.0.jar'])
+    assertSubsetFiles(configurations.depWithMultipleConfigurations, { dep -> dep.configuration == 'default' }, ['tuna-1.0.jar', 'herring-1.0.jar'])
+    assertSubsetFiles(configurations.extendingWithDifferentConfiguration, { dep -> dep.name != 'seal' }, ['tuna-1.0.jar', 'herring-1.0.jar', 'squid-1.0.jar'])
+    assertSubsetFiles(configurations.extendingWithDifferentConfiguration, { dep -> dep.name == 'tuna' && dep.configuration == 'default' }, ['tuna-1.0.jar', 'herring-1.0.jar'])
+    assertSubsetFiles(configurations.extending, { dep -> dep.name == 'tuna' }, ['tuna-1.0.jar', 'herring-1.0.jar'])
+    assertSubsetFiles(configurations.selfResolving, { dep -> dep.source.singleFile.name == 'someDir' }, ['someDir'])
+    assertSubsetFiles(configurations.mixed, { dep ->
+        dep instanceof org.gradle.api.artifacts.SelfResolvingDependency || dep.name == 'tuna' }, ['someDir', 'selfResolving1.jar', 'tuna-1.0.jar', 'herring-1.0.jar'])
+    assertSubsetFiles(configurations.classifier, { dep -> dep.name == 'dolphin' }, ['dolphin-1.0-oceanic.jar', 'dolphin-1.0-river.jar'])
+    assertSubsetFiles(configurations.extendingClassifier, { dep -> dep.name == 'dolphin' }, ['dolphin-1.0-oceanic.jar', 'dolphin-1.0-river.jar'])
+}
+
+def assertSubsetFiles(configuration, spec, expectedFileNames) {
+    Set resolvedFileNames = configuration.files(spec).collect { it.name }
+    assert expectedFileNames as Set == resolvedFileNames
+}
+
+def assertFilesForConfigurationCopies() {
+    assertConfigurationCopyFiles(configurations.oneDepWithNoTransitives, { dep -> true }, ['herring-1.0.jar'])
+    assertConfigurationCopyFiles(configurations.oneDepWithTransitives, { dep -> dep.name == 'tuna' }, ['tuna-1.0.jar', 'herring-1.0.jar'])
+    assertConfigurationCopyFiles(configurations.twoDepsWithVersionConflicts, { dep -> dep.name == 'shark' }, ['tuna-1.0.jar', 'shark-1.0.jar', 'seal-2.0.jar', 'herring-1.0.jar'])
+    assertConfigurationCopyFiles(configurations.twoDepsWithVersionConflicts, { dep -> dep.name == 'orca' }, ['orca-1.0.jar', 'seal-1.0.jar'])
+    assertConfigurationCopyFiles(configurations.extending, { dep -> true }, ['seal-1.0.jar', 'orca-1.0.jar'])
+    assertConfigurationCopyFiles(configurations.extending, { dep -> dep.name == 'tuna' }, ['tuna-1.0.jar', 'herring-1.0.jar'], true)
+    assertConfigurationCopyFiles(configurations.selfResolving, { dep -> true }, ['selfResolving1.jar', 'someDir'])
+    assertConfigurationCopyFiles(configurations.selfResolving, { dep -> dep.source.singleFile.name == 'someDir' }, ['someDir'])
+    assertConfigurationCopyFiles(configurations.mixed, { dep ->
+        dep instanceof org.gradle.api.artifacts.SelfResolvingDependency || dep.name == 'tuna' }, ['someDir', 'selfResolving1.jar', 'tuna-1.0.jar', 'herring-1.0.jar'], true)
+}
+
+def assertConfigurationCopyFiles(configuration, spec, expectedFileNames, recursive = false) {
+    String method = recursive ? 'copyRecursive' : 'copy'
+    Set resolvedFileNames = configuration."$method"(spec).collect { it.name }
+    assert expectedFileNames as Set == resolvedFileNames
+}
diff --git a/subprojects/gradle-docs/src/samples/dependencies/repo/sea.fish/ivy-billfish-1.0.xml b/subprojects/gradle-docs/src/samples/dependencies/repo/sea.fish/ivy-billfish-1.0.xml
new file mode 100644
index 0000000..7dd15d3
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/dependencies/repo/sea.fish/ivy-billfish-1.0.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<ivy-module version="1.0">
+    <info
+        organisation="sea.fish"
+        module="billfish"
+        revision="1.0"/>
+    <configurations>
+        <conf name="default"/>
+    </configurations>
+    <dependencies>
+        <dependency org="sea.fish" name="tuna" rev="1.0" conf="default->specialWaters"/>
+    </dependencies>
+</ivy-module>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/dependencies/repo/sea.fish/ivy-shark-1.0.xml b/subprojects/gradle-docs/src/samples/dependencies/repo/sea.fish/ivy-shark-1.0.xml
new file mode 100644
index 0000000..1bfcd8d
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/dependencies/repo/sea.fish/ivy-shark-1.0.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<ivy-module version="1.0">
+    <info
+        organisation="sea.fish"
+        module="shark"
+        revision="1.0"/>
+    <configurations>
+        <conf name="default" extends="runtime"/>
+		<conf name="runtime"/>
+    </configurations>
+    <dependencies>
+        <dependency org="sea.mammals" name="seal" rev="2.0" conf="runtime->unknown(*)"/>
+        <dependency org="sea.fish" name="tuna" rev="1.0" conf="runtime->default"/>
+    </dependencies>
+</ivy-module>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/dependencies/repo/sea.fish/ivy-tuna-1.0.xml b/subprojects/gradle-docs/src/samples/dependencies/repo/sea.fish/ivy-tuna-1.0.xml
new file mode 100644
index 0000000..8dfc0ee
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/dependencies/repo/sea.fish/ivy-tuna-1.0.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<ivy-module version="1.0">
+    <info
+        organisation="sea.fish"
+        module="tuna"
+        revision="1.0"/>
+    <configurations>
+        <conf name="default"/>
+        <conf name="specialWaters"/>
+    </configurations>
+    <dependencies>
+        <dependency org="sea.fish" name="herring" rev="1.0" conf="default->default"/>
+        <dependency org="sea.fish" name="squid" rev="1.0" conf="specialWaters->default"/>
+    </dependencies>
+</ivy-module>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/dependencies/repo/sea.mammals/ivy-orca-1.0.xml b/subprojects/gradle-docs/src/samples/dependencies/repo/sea.mammals/ivy-orca-1.0.xml
new file mode 100644
index 0000000..7f0f277
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/dependencies/repo/sea.mammals/ivy-orca-1.0.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<ivy-module version="1.0">
+    <info
+        organisation="sea.mammals"
+        module="orca"
+        revision="1.0"/>
+    <configurations>
+        <conf name="default"/>
+    </configurations>
+    <dependencies>
+        <dependency org="sea.mammals" name="seal" rev="1.0" conf="default"/>
+    </dependencies>
+</ivy-module>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/dependencies/settings.gradle b/subprojects/gradle-docs/src/samples/dependencies/settings.gradle
new file mode 100644
index 0000000..76ebcde
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/dependencies/settings.gradle
@@ -0,0 +1 @@
+include 'atlantic', 'northSea'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/eclipse/build.gradle b/subprojects/gradle-docs/src/samples/eclipse/build.gradle
new file mode 100644
index 0000000..1f0bb10
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/eclipse/build.gradle
@@ -0,0 +1,36 @@
+apply plugin: 'war'
+// START SNIPPET use-plugin
+apply plugin: 'eclipse'
+// END SNIPPET use-plugin
+// START SNIPPET module-before-configured
+// START SNIPPET module-when-configured
+eclipseClasspath {
+// END SNIPPET module-when-configured
+    beforeConfigured { classpath ->
+        classpath.entries.removeAll { entry -> entry.kind == 'lib' || entry.kind == 'var' }
+    }
+// END SNIPPET module-before-configured
+// START SNIPPET module-when-configured
+    whenConfigured { classpath ->
+        classpath.entries.findAll { entry -> entry.kind == 'lib' }*.exported = false
+    }
+// START SNIPPET module-before-configured
+}
+// END SNIPPET module-before-configured
+// END SNIPPET module-when-configured
+
+// START SNIPPET project-before-configured
+eclipseProject {
+    beforeConfigured { project ->
+        project.natures.clear()
+    }
+}
+// END SNIPPET project-before-configured
+
+// START SNIPPET wtp-with-xml
+eclipseWtp {
+    withXml { xml ->
+        xml.'org.eclipse.wst.commons.project.facet.core'.fixed.find { it. at facet == 'jst.java' }. at facet = 'jst2.java'
+    }
+}
+// END SNIPPET wtp-with-xml
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/gradleUserHome/build.gradle b/subprojects/gradle-docs/src/samples/gradleUserHome/build.gradle
new file mode 100644
index 0000000..baeba05
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/gradleUserHome/build.gradle
@@ -0,0 +1,13 @@
+import org.junit.Assert
+
+task checkGradleUserHomeViaSystemEnv << {
+    Assert.assertEquals(file('customUserHome').absolutePath, gradle.gradleUserHomeDir.absolutePath)
+}
+
+task checkDefaultGradleUserHome<< {
+    Assert.assertEquals(new File(System.properties['user.home'], ".gradle").absolutePath, gradle.gradleUserHomeDir.absolutePath)
+}
+
+task checkSystemPropertyGradleUserHomeHasPrecedence << {
+    Assert.assertEquals(file('systemPropCustomUserHome').absolutePath, gradle.gradleUserHomeDir.absolutePath)
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/customizedLayout/build.gradle b/subprojects/gradle-docs/src/samples/groovy/customizedLayout/build.gradle
new file mode 100644
index 0000000..ef318dc
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/customizedLayout/build.gradle
@@ -0,0 +1,27 @@
+apply plugin: 'groovy'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    groovy group: 'org.codehaus.groovy', name: 'groovy-all', version: '1.7.0'
+    testCompile group: 'junit', name: 'junit', version: '4.7'
+}
+
+// START SNIPPET define-main
+sourceSets {
+    main {
+        groovy {
+            srcDir 'src/groovy'
+        }
+    }
+// END SNIPPET define-main
+    test {
+        groovy {
+            srcDir 'test/groovy'
+        }
+    }
+// START SNIPPET define-main
+}
+// END SNIPPET define-main
diff --git a/subprojects/gradle-docs/src/samples/groovy/customizedLayout/readme.xml b/subprojects/gradle-docs/src/samples/groovy/customizedLayout/readme.xml
new file mode 100644
index 0000000..4ffa845
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/customizedLayout/readme.xml
@@ -0,0 +1,18 @@
+<!--
+  ~ 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.
+  -->
+<sample>
+    <para>Groovy project with a custom source layout</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/customizedLayout/src/groovy/org/gradle/Person.groovy b/subprojects/gradle-docs/src/samples/groovy/customizedLayout/src/groovy/org/gradle/Person.groovy
new file mode 100644
index 0000000..98b99e4
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/customizedLayout/src/groovy/org/gradle/Person.groovy
@@ -0,0 +1,5 @@
+package org.gradle
+
+class Person {
+    String name
+}
diff --git a/subprojects/gradle-docs/src/samples/groovy/customizedLayout/test/groovy/org/gradle/PersonTest.groovy b/subprojects/gradle-docs/src/samples/groovy/customizedLayout/test/groovy/org/gradle/PersonTest.groovy
new file mode 100644
index 0000000..97a4bd3
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/customizedLayout/test/groovy/org/gradle/PersonTest.groovy
@@ -0,0 +1,11 @@
+package org.gradle
+
+import org.junit.Test
+import static org.junit.Assert.*
+
+class PersonTest {
+    @Test public void canConstructAPerson() {
+        Person p = new Person(name: 'name')
+        assertEquals('name', p.name)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/groovy-1.5.6/build.gradle b/subprojects/gradle-docs/src/samples/groovy/groovy-1.5.6/build.gradle
new file mode 100644
index 0000000..e098600
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/groovy-1.5.6/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'groovy'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    groovy 'org.codehaus.groovy:groovy-all:1.5.6'
+    testCompile 'junit:junit:4.7'
+}
diff --git a/subprojects/gradle-docs/src/samples/groovy/groovy-1.5.6/readme.xml b/subprojects/gradle-docs/src/samples/groovy/groovy-1.5.6/readme.xml
new file mode 100644
index 0000000..6373cd6
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/groovy-1.5.6/readme.xml
@@ -0,0 +1,18 @@
+<!--
+  ~ 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.
+  -->
+<sample>
+    <para>Groovy project using Groovy 1.5.6</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/groovy-1.5.6/src/main/groovy/org/gradle/Person.groovy b/subprojects/gradle-docs/src/samples/groovy/groovy-1.5.6/src/main/groovy/org/gradle/Person.groovy
new file mode 100644
index 0000000..98b99e4
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/groovy-1.5.6/src/main/groovy/org/gradle/Person.groovy
@@ -0,0 +1,5 @@
+package org.gradle
+
+class Person {
+    String name
+}
diff --git a/subprojects/gradle-docs/src/samples/groovy/groovy-1.5.6/src/test/groovy/org/gradle/PersonTest.groovy b/subprojects/gradle-docs/src/samples/groovy/groovy-1.5.6/src/test/groovy/org/gradle/PersonTest.groovy
new file mode 100644
index 0000000..8504ae5
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/groovy-1.5.6/src/test/groovy/org/gradle/PersonTest.groovy
@@ -0,0 +1,16 @@
+package org.gradle
+
+import org.codehaus.groovy.runtime.InvokerHelper
+import org.junit.Test
+import static org.junit.Assert.*
+
+class PersonTest {
+    @Test public void canConstructAPerson() {
+        Person p = new Person(name: 'name')
+        assertEquals('name', p.name)
+    }
+
+    @Test public void usingCorrectVersionOfGroovy() {
+        assertEquals('1.5.6', InvokerHelper.version)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/groovy-1.6.7/build.gradle b/subprojects/gradle-docs/src/samples/groovy/groovy-1.6.7/build.gradle
new file mode 100644
index 0000000..d9a3bf4
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/groovy-1.6.7/build.gradle
@@ -0,0 +1,10 @@
+apply plugin: 'groovy'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    groovy 'org.codehaus.groovy:groovy-all:1.6.7'
+    testCompile 'junit:junit:4.7'
+}
diff --git a/subprojects/gradle-docs/src/samples/groovy/groovy-1.6.7/readme.xml b/subprojects/gradle-docs/src/samples/groovy/groovy-1.6.7/readme.xml
new file mode 100644
index 0000000..b00773d
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/groovy-1.6.7/readme.xml
@@ -0,0 +1,18 @@
+<!--
+  ~ 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.
+  -->
+<sample>
+    <para>Groovy project using Groovy 1.6.7</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/groovy-1.6.7/src/main/groovy/org/gradle/Person.groovy b/subprojects/gradle-docs/src/samples/groovy/groovy-1.6.7/src/main/groovy/org/gradle/Person.groovy
new file mode 100644
index 0000000..98b99e4
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/groovy-1.6.7/src/main/groovy/org/gradle/Person.groovy
@@ -0,0 +1,5 @@
+package org.gradle
+
+class Person {
+    String name
+}
diff --git a/subprojects/gradle-docs/src/samples/groovy/groovy-1.6.7/src/test/groovy/org/gradle/PersonTest.groovy b/subprojects/gradle-docs/src/samples/groovy/groovy-1.6.7/src/test/groovy/org/gradle/PersonTest.groovy
new file mode 100644
index 0000000..008adf9
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/groovy-1.6.7/src/test/groovy/org/gradle/PersonTest.groovy
@@ -0,0 +1,31 @@
+/*
+ * 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
+
+import org.codehaus.groovy.runtime.InvokerHelper
+import org.junit.Test
+import static org.junit.Assert.*
+
+class PersonTest {
+    @Test public void canConstructAPerson() {
+        Person p = new Person(name: 'name')
+        assertEquals('name', p.name)
+    }
+
+    @Test public void usingCorrectVersionOfGroovy() {
+        assertEquals('1.6.7', InvokerHelper.version)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/build.gradle b/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/build.gradle
new file mode 100644
index 0000000..aece9bb
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/build.gradle
@@ -0,0 +1,11 @@
+apply plugin: 'groovy'
+version = 1.0
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    groovy group: 'org.codehaus.groovy', name: 'groovy-all', version: '1.7.0'
+    testCompile group: 'junit', name: 'junit', version: '4.7'
+}
diff --git a/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/readme.xml b/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/readme.xml
new file mode 100644
index 0000000..b388565
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/readme.xml
@@ -0,0 +1,18 @@
+<!--
+  ~ 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.
+  -->
+<sample>
+    <para>Project containing a mix of Java and Groovy source</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/src/main/groovy/org/gradle/GroovyPerson.groovy b/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/src/main/groovy/org/gradle/GroovyPerson.groovy
new file mode 100644
index 0000000..6d8f985
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/src/main/groovy/org/gradle/GroovyPerson.groovy
@@ -0,0 +1,5 @@
+package org.gradle
+
+class GroovyPerson implements Person {
+    def String name
+}
diff --git a/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/src/main/groovy/org/gradle/JavaPerson.java b/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/src/main/groovy/org/gradle/JavaPerson.java
new file mode 100644
index 0000000..17228a1
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/src/main/groovy/org/gradle/JavaPerson.java
@@ -0,0 +1,7 @@
+package org.gradle;
+
+public class JavaPerson extends GroovyPerson {
+    public JavaPerson(String name) {
+        setName(name);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/src/main/groovy/org/gradle/PersonList.groovy b/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/src/main/groovy/org/gradle/PersonList.groovy
new file mode 100644
index 0000000..0f54947
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/src/main/groovy/org/gradle/PersonList.groovy
@@ -0,0 +1,7 @@
+package org.gradle
+
+class PersonList {
+    def find(String name) {
+        new JavaPerson(name)
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/src/main/java/org/gradle/Person.java b/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/src/main/java/org/gradle/Person.java
new file mode 100644
index 0000000..94650ea
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/src/main/java/org/gradle/Person.java
@@ -0,0 +1,5 @@
+package org.gradle;
+
+public interface Person {
+    String getName();
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/src/test/groovy/org/gradle/PersonTest.groovy b/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/src/test/groovy/org/gradle/PersonTest.groovy
new file mode 100644
index 0000000..724c798
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/mixedJavaAndGroovy/src/test/groovy/org/gradle/PersonTest.groovy
@@ -0,0 +1,16 @@
+package org.gradle
+
+import org.junit.Test
+import static org.junit.Assert.*
+
+class PersonTest {
+    @Test public void canConstructAJavaPerson() {
+        Person p = new JavaPerson('name')
+        assertEquals('name', p.name)
+    }
+
+    @Test public void canConstructAGroovyPerson() {
+        Person p = new GroovyPerson(name: 'name')
+        assertEquals('name', p.name)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/build.gradle b/subprojects/gradle-docs/src/samples/groovy/multiproject/build.gradle
new file mode 100644
index 0000000..fb3c3f8
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/multiproject/build.gradle
@@ -0,0 +1,5 @@
+subprojects {
+    repositories {
+        mavenCentral()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/buildSrc/src/main/groovy/org/gradle/buildsrc/BuildSrcClass.java b/subprojects/gradle-docs/src/samples/groovy/multiproject/buildSrc/src/main/groovy/org/gradle/buildsrc/BuildSrcClass.java
new file mode 100644
index 0000000..1f61639
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/multiproject/buildSrc/src/main/groovy/org/gradle/buildsrc/BuildSrcClass.java
@@ -0,0 +1,7 @@
+package org.gradle.buildsrc;
+
+import groovy.util.AntBuilder;
+
+public class BuildSrcClass {
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/groovycDetector/build.gradle b/subprojects/gradle-docs/src/samples/groovy/multiproject/groovycDetector/build.gradle
new file mode 100644
index 0000000..a2baec5
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/multiproject/groovycDetector/build.gradle
@@ -0,0 +1,7 @@
+apply plugin: 'java'
+
+version = 'SNAPSHOT'
+
+dependencies {
+    compile 'org.codehaus.groovy:groovy-all:1.6.0'
+}
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/groovycDetector/src/main/java/org/gradle/test/DetectorTransform.java b/subprojects/gradle-docs/src/samples/groovy/multiproject/groovycDetector/src/main/java/org/gradle/test/DetectorTransform.java
new file mode 100644
index 0000000..f1bae89
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/multiproject/groovycDetector/src/main/java/org/gradle/test/DetectorTransform.java
@@ -0,0 +1,48 @@
+
+package org.gradle.test;
+
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ModuleNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.messages.SimpleMessage;
+import org.codehaus.groovy.transform.ASTTransformation;
+import org.codehaus.groovy.transform.GroovyASTTransformation;
+import org.codehaus.groovy.runtime.InvokerHelper;
+
+import java.util.List;
+
+/**
+ * Searches through all classes to be compiled for a field named
+ * "groovycVersion". If such a field is found, a field initializer is added
+ * and the search ends. The initializer sets the field to a String containing
+ * the groovyc version detected (currently "1.6"). Any existing initializer
+ * is overriden. If the field's type is not compatible with String, a compile-
+ * time or runtime error will occur.
+ * Because transforms have only been introduced in Groovy 1.6, this transform
+ * will have no effect on classes compiled with earlier Groovy versions.
+ */
+ at SuppressWarnings("unchecked")
+ at GroovyASTTransformation(phase = CompilePhase.CONVERSION)
+public class DetectorTransform implements ASTTransformation {
+  private static final String VERSION_FIELD_NAME = "groovycVersion";
+
+  public void visit(ASTNode[] nodes, SourceUnit source) {
+    if (nodes.length == 0 || !(nodes[0] instanceof ModuleNode)) {
+      source.getErrorCollector().addError(new SimpleMessage(
+        "internal error in DetectorTransform", source));
+      return;
+    }
+    ModuleNode module = (ModuleNode)nodes[0];
+    for (ClassNode clazz : (List<ClassNode>)module.getClasses()) {
+      FieldNode field = clazz.getField(VERSION_FIELD_NAME);
+      if (field != null) {
+        field.setInitialValueExpression(new ConstantExpression(InvokerHelper.getVersion()));
+        break;
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/groovycDetector/src/main/resources/META-INF/services/org.codehaus.groovy.transform.ASTTransformation b/subprojects/gradle-docs/src/samples/groovy/multiproject/groovycDetector/src/main/resources/META-INF/services/org.codehaus.groovy.transform.ASTTransformation
new file mode 100644
index 0000000..d2049e7
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/multiproject/groovycDetector/src/main/resources/META-INF/services/org.codehaus.groovy.transform.ASTTransformation
@@ -0,0 +1 @@
+org.gradle.test.DetectorTransform
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/readme.xml b/subprojects/gradle-docs/src/samples/groovy/multiproject/readme.xml
new file mode 100644
index 0000000..bb86c70
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/multiproject/readme.xml
@@ -0,0 +1,20 @@
+<!--
+  ~ 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.
+  -->
+<sample>
+    <para>Build made up of multiple Groovy projects. Also demonstrates how to exclude certain source files, and the use
+        of a custom Groovy AST transformation.
+    </para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/settings.gradle b/subprojects/gradle-docs/src/samples/groovy/multiproject/settings.gradle
new file mode 100644
index 0000000..234542a
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/multiproject/settings.gradle
@@ -0,0 +1 @@
+include 'groovycDetector', 'testproject'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/build.gradle b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/build.gradle
new file mode 100644
index 0000000..a3086c8
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/build.gradle
@@ -0,0 +1,36 @@
+apply plugin: 'groovy'
+
+group = 'org.gradle'
+version = '1.0'
+
+dependencies {
+    groovy 'org.codehaus.groovy:groovy-all:1.7.0'
+    compile project(':groovycDetector')
+    testCompile 'junit:junit:4.7'
+}
+
+sourceSets {
+    main {
+        java {
+            exclude '**/ExcludeJava.java'
+        }
+        groovy {
+            exclude '**/ExcludeGroovy.groovy', '**/ExcludeGroovyJava.java'
+        }
+    }
+}
+
+compileGroovy {
+    groovyOptions.fork(memoryMaximumSize: '128M')
+}
+
+jar {
+    manifest.attributes(myprop: 'myvalue')
+    metaInf {
+        from 'src/metaInfFiles'
+    }
+}
+
+test {
+	systemProperties['org.gradle.integtest.buildDir'] = buildDir.absolutePath
+}
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/groovy/org/gradle/ExcludeGroovy.groovy b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/groovy/org/gradle/ExcludeGroovy.groovy
new file mode 100644
index 0000000..e25b80b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/groovy/org/gradle/ExcludeGroovy.groovy
@@ -0,0 +1,3 @@
+class ExcludeGroovy {
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/groovy/org/gradle/ExcludeGroovyJava.java b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/groovy/org/gradle/ExcludeGroovyJava.java
new file mode 100644
index 0000000..f7e753d
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/groovy/org/gradle/ExcludeGroovyJava.java
@@ -0,0 +1,3 @@
+public class ExcludeGroovyJava {
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/groovy/org/gradle/GroovyJavaPerson.java b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/groovy/org/gradle/GroovyJavaPerson.java
new file mode 100644
index 0000000..1200e2c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/groovy/org/gradle/GroovyJavaPerson.java
@@ -0,0 +1,15 @@
+package org.gradle;
+
+import java.util.Properties;
+import java.io.IOException;
+
+/**
+ * @author Hans Dockter
+ */
+public class GroovyJavaPerson {
+    public String readProperty() throws IOException {
+        Properties properties = new Properties();
+        properties.load(getClass().getClassLoader().getResourceAsStream("org/gradle/main.properties"));
+        return properties.getProperty("main");
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/groovy/org/gradle/GroovyPerson.groovy b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/groovy/org/gradle/GroovyPerson.groovy
new file mode 100644
index 0000000..28e1759
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/groovy/org/gradle/GroovyPerson.groovy
@@ -0,0 +1,12 @@
+package org.gradle
+
+/**
+ * @author Hans Dockter
+ */
+class GroovyPerson {
+    String readProperty() throws IOException {
+        Properties properties = new Properties()
+        properties.load(getClass().getClassLoader().getResourceAsStream("org/gradle/main.properties"))
+        properties.properties.main
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/java/org/gradle/ExcludeJava.java b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/java/org/gradle/ExcludeJava.java
new file mode 100644
index 0000000..fbe85d6
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/java/org/gradle/ExcludeJava.java
@@ -0,0 +1,3 @@
+public class ExcludeJava {
+   
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/java/org/gradle/JavaPerson.java b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/java/org/gradle/JavaPerson.java
new file mode 100644
index 0000000..df8099f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/java/org/gradle/JavaPerson.java
@@ -0,0 +1,15 @@
+package org.gradle;
+
+import java.util.Properties;
+import java.io.IOException;
+
+/**
+ * @author Hans Dockter
+ */
+public class JavaPerson {
+    public String readProperty() throws IOException {
+        Properties properties = new Properties();
+        properties.load(getClass().getClassLoader().getResourceAsStream("org/gradle/main.properties"));
+        return properties.getProperty("main");
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/resources/org/gradle/main.properties b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/resources/org/gradle/main.properties
new file mode 100644
index 0000000..8403b54
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/main/resources/org/gradle/main.properties
@@ -0,0 +1 @@
+main=mainValue
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/metaInfFiles/myfile b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/metaInfFiles/myfile
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/test/groovy/org/gradle/GroovyJavaPersonTest.java b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/test/groovy/org/gradle/GroovyJavaPersonTest.java
new file mode 100644
index 0000000..836082a
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/test/groovy/org/gradle/GroovyJavaPersonTest.java
@@ -0,0 +1,15 @@
+package org.gradle;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.File;
+import java.util.Properties;
+
+public class GroovyJavaPersonTest {
+    @Test
+    public void testMarkerFile() throws IOException {
+        new File(System.getProperty("org.gradle.integtest.buildDir") + "/" + getClass().getSimpleName()).createNewFile();
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/test/groovy/org/gradle/GroovyPersonTest.groovy b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/test/groovy/org/gradle/GroovyPersonTest.groovy
new file mode 100644
index 0000000..953ed46
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/test/groovy/org/gradle/GroovyPersonTest.groovy
@@ -0,0 +1,12 @@
+package org.gradle
+
+import org.junit.Test
+import static org.junit.Assert.*
+
+public class GroovyPersonTest {
+    
+    @Test
+    public void testMarkerFile() throws IOException {
+        new File(System.getProperty("org.gradle.integtest.buildDir") + "/" + getClass().getSimpleName()).createNewFile();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/test/groovy/org/gradle/VersionTest.groovy b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/test/groovy/org/gradle/VersionTest.groovy
new file mode 100644
index 0000000..f52b51c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/test/groovy/org/gradle/VersionTest.groovy
@@ -0,0 +1,13 @@
+package org.gradle
+
+import org.junit.Test
+import static org.junit.Assert.*
+
+class GroovycVersionTest {
+  def groovycVersion
+
+  @Test
+  void versionShouldBe1_7_0() {
+    assertEquals("1.7.0", groovycVersion)
+  }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/test/java/org/gradle/JavaPersonTest.java b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/test/java/org/gradle/JavaPersonTest.java
new file mode 100644
index 0000000..c91a852
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/test/java/org/gradle/JavaPersonTest.java
@@ -0,0 +1,27 @@
+package org.gradle;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.File;
+import java.util.Properties;
+
+public class JavaPersonTest {
+    @Test
+    public void testMainProperty() throws IOException {
+        assertEquals("mainValue", new JavaPerson().readProperty());
+    }
+
+    @Test
+    public void testTestProperty() throws IOException {
+        Properties properties = new Properties();
+        properties.load(getClass().getClassLoader().getResourceAsStream("org/gradle/test.properties"));
+        assertEquals("testValue", properties.getProperty("test"));
+    }
+
+    @Test
+    public void testMarkerFile() throws IOException {
+        new File(System.getProperty("org.gradle.integtest.buildDir") + "/" + getClass().getSimpleName()).createNewFile();
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/test/resources/org/gradle/test.properties b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/test/resources/org/gradle/test.properties
new file mode 100644
index 0000000..bb47ed6
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/multiproject/testproject/src/test/resources/org/gradle/test.properties
@@ -0,0 +1 @@
+test=testValue
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/quickstart/build.gradle b/subprojects/gradle-docs/src/samples/groovy/quickstart/build.gradle
new file mode 100644
index 0000000..a257cea
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/quickstart/build.gradle
@@ -0,0 +1,17 @@
+apply plugin: 'eclipse'
+// START SNIPPET use-plugin
+apply plugin: 'groovy'
+// END SNIPPET use-plugin
+
+// START SNIPPET groovy-dependency
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    groovy group: 'org.codehaus.groovy', name: 'groovy', version: '1.7.0'
+// END SNIPPET groovy-dependency
+    testCompile group: 'junit', name: 'junit', version: '4.7'
+// START SNIPPET groovy-dependency
+}
+// END SNIPPET groovy-dependency
diff --git a/subprojects/gradle-docs/src/samples/groovy/quickstart/readme.xml b/subprojects/gradle-docs/src/samples/groovy/quickstart/readme.xml
new file mode 100644
index 0000000..b1be114
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/quickstart/readme.xml
@@ -0,0 +1,18 @@
+<!--
+  ~ 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.
+  -->
+<sample>
+    <para>Groovy quickstart sample</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/quickstart/src/main/groovy/org/gradle/Person.groovy b/subprojects/gradle-docs/src/samples/groovy/quickstart/src/main/groovy/org/gradle/Person.groovy
new file mode 100644
index 0000000..496ec8f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/quickstart/src/main/groovy/org/gradle/Person.groovy
@@ -0,0 +1,16 @@
+package org.gradle
+
+class Person {
+    String name
+
+    def Person() {
+        getClass().getResourceAsStream('/resource.txt').withStream {InputStream str ->
+            name = str.text.trim()
+        }
+        getClass().getResourceAsStream('/script.groovy').withStream {InputStream str ->
+            def shell = new GroovyShell()
+            shell.person = this
+            shell.evaluate(str.text)
+        }
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/groovy/quickstart/src/main/resources/resource.txt b/subprojects/gradle-docs/src/samples/groovy/quickstart/src/main/resources/resource.txt
new file mode 100644
index 0000000..e06963f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/quickstart/src/main/resources/resource.txt
@@ -0,0 +1 @@
+barry
diff --git a/subprojects/gradle-docs/src/samples/groovy/quickstart/src/main/resources/script.groovy b/subprojects/gradle-docs/src/samples/groovy/quickstart/src/main/resources/script.groovy
new file mode 100644
index 0000000..a6b46d7
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/quickstart/src/main/resources/script.groovy
@@ -0,0 +1 @@
+person.name = person.name[0].toUpperCase() + person.name[1..-1]
diff --git a/subprojects/gradle-docs/src/samples/groovy/quickstart/src/test/groovy/org/gradle/PersonTest.groovy b/subprojects/gradle-docs/src/samples/groovy/quickstart/src/test/groovy/org/gradle/PersonTest.groovy
new file mode 100644
index 0000000..9414383
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/quickstart/src/test/groovy/org/gradle/PersonTest.groovy
@@ -0,0 +1,26 @@
+package org.gradle
+
+import org.codehaus.groovy.runtime.InvokerHelper
+import org.junit.Test
+import static org.junit.Assert.*
+
+class PersonTest {
+    @Test public void canConstructAPerson() {
+        Person p = new Person()
+        assertEquals('Barry', p.name)
+    }
+
+    @Test public void canConstructAPersonUsingName() {
+        Person p = new Person(name: 'name')
+        assertEquals('name', p.name)
+    }
+
+    @Test public void usingCorrectVersionOfGroovy() {
+        assertEquals('1.7.0', InvokerHelper.version)
+    }
+    
+    @Test public void testResourcesAreAvailable() {
+        assertNotNull(getClass().getResource('/testResource.txt'))
+        assertNotNull(getClass().getResource('/testScript.groovy'))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/groovy/quickstart/src/test/resources/testResource.txt b/subprojects/gradle-docs/src/samples/groovy/quickstart/src/test/resources/testResource.txt
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/groovy/quickstart/src/test/resources/testScript.groovy b/subprojects/gradle-docs/src/samples/groovy/quickstart/src/test/resources/testScript.groovy
new file mode 100644
index 0000000..b555d70
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/groovy/quickstart/src/test/resources/testScript.groovy
@@ -0,0 +1 @@
+println "this is a script"
diff --git a/subprojects/gradle-docs/src/samples/idea/build.gradle b/subprojects/gradle-docs/src/samples/idea/build.gradle
new file mode 100644
index 0000000..5d45b4b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/idea/build.gradle
@@ -0,0 +1,36 @@
+// START SNIPPET use-plugin
+apply plugin: 'idea'
+// END SNIPPET use-plugin
+// START SNIPPET module-before-configured
+// START SNIPPET module-when-configured
+ideaModule {
+// END SNIPPET module-when-configured
+    beforeConfigured { module ->
+        module.dependencies.clear()
+    }
+// END SNIPPET module-before-configured
+// START SNIPPET module-when-configured
+    whenConfigured { module ->
+        module.dependencies*.exported = true
+    }
+// START SNIPPET module-before-configured
+}
+// END SNIPPET module-before-configured
+// END SNIPPET module-when-configured
+
+// START SNIPPET project-before-configured
+// START SNIPPET project-with-xml
+ideaProject {
+// END SNIPPET project-with-xml
+    beforeConfigured { project ->
+        project.modules.clear()
+    }
+// END SNIPPET project-before-configured
+// START SNIPPET project-with-xml
+    withXml { root ->
+        root.component.find { it. at name == 'VcsDirectoryMappings' }.mapping. at vcs = 'Git'
+    }
+// START SNIPPET project-before-configured
+}
+// END SNIPPET project-before-configured
+// END SNIPPET project-with-xml
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/ivypublish/build.gradle b/subprojects/gradle-docs/src/samples/ivypublish/build.gradle
new file mode 100644
index 0000000..0e6caf9
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/ivypublish/build.gradle
@@ -0,0 +1,88 @@
+
+allprojects {
+    apply plugin: 'java'
+}
+
+version = '1.0'
+group = 'org.gradle.test'
+
+dependencies {
+   compile 'junit:junit:4.7', project(':subproject')
+}
+
+ant {
+    importBuild "$projectDir/build.xml"
+    buildDir = owner.buildDir.toString()
+}
+
+String ivyPattern = '/[module]/[revision]/ivy.xml'
+String artifactPattern = '/[module]/[revision]/[artifact](.[ext])'
+File localPublicationsDir = file("$buildDir/repo")
+
+repositories {
+    add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
+        name = 'repo'
+        validate = false
+        addIvyPattern(localPublicationsDir.absolutePath + ivyPattern)
+        addArtifactPattern(localPublicationsDir.absolutePath + artifactPattern)
+    }
+    mavenCentral()
+}
+
+task sourceJar(type: Jar) {
+    baseName = 'ivypublishSource'
+    from sourceSets.main.java
+    classifier = 'src'
+}
+
+artifacts {
+    archives sourceJar
+}
+
+uploadArchives {
+    uploadDescriptor = true
+    repositories {
+        add project.repositories.repo
+    }
+    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 jar.archivePath.size() == new File(repoDir, 'ivypublish.jar').size()
+        sourceJar.archivePath.size() == new File(repoDir, 'ivypublishSource.jar').size()
+
+        def ns = new groovy.xml.Namespace("http://ant.apache.org/ivy/maven", 'm')
+        def root = new XmlParser().parse(new File(repoDir, 'ivy.xml'))
+        assert root.publications.artifact.find { it. at name == 'ivypublishSource' }.attribute(ns.classifier) == 'src'
+        assert (root.configurations.conf.collect { it. at name } as Set) == ['archives', 'compile', 'default', 'runtime','testCompile', 'testRuntime']  as Set
+        assert root.dependencies.dependency.find { it. at org == 'junit' }.attributes() == [org: 'junit', name: 'junit', rev: '4.7', conf: 'compile->default']
+        assert root.dependencies.dependency.find { it. at org == 'ivypublish' }.attributes() == [org: 'ivypublish', name: 'subproject', rev: 'unspecified',
+                conf: 'compile->default']
+    }
+}
+
+// It would be nice to test if the stuff published by Gradle can be read by a native ivy ant script.
+// The tasks below are supposed to do this. But due to a serious Ivy bugs we can't do it at the
+// moment (e.g. https://issues.apache.org/jira/browse/IVY-1110). As soon as this bug is fixed, we
+// should uncomment the below.
+
+//ivyConfigure.doLast {
+//    def cacheDir = ant.properties['ivy.cache.dir'] + '/org.gradle.test'
+//    println cacheDir
+//    ant.delete(dir: cacheDir, verbose: true)
+//}
+//
+//retrieveFromAnt {
+//    dependsOn uploadArchives
+//    doLast {
+//        File retrieveDir = new File("$buildDir/antRetrieve")
+//        Assert.assertEquals(retrieveDir.listFiles().collect { it.name } as Set,
+//                ["ivypublish.jar", "ivypublishSource.jar"] as Set)
+//        Assert.assertEquals(jar.archivePath.size(), new File(retrieveDir, 'ivypublish.jar').size())
+//        Assert.assertEquals(sourceJar.archivePath.size(), new File(retrieveDir, 'ivypublishSource.jar').size())
+//    }
+//}
+
+
+
+
+
diff --git a/subprojects/gradle-docs/src/samples/ivypublish/build.xml b/subprojects/gradle-docs/src/samples/ivypublish/build.xml
new file mode 100644
index 0000000..216fa7d
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/ivypublish/build.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<project xmlns:ivy="antlib:org.apache.ivy.ant" name="sample">
+    <target name="ivyConfigure">
+         <ivy:configure/>
+    </target>
+    <target name="retrieveFromAnt" depends="ivyConfigure">
+        <ivy:retrieve conf="default" pattern="${buildDir}/antRetrieve/[artifact].[ext]"/>
+    </target>
+</project>
diff --git a/subprojects/gradle-docs/src/samples/ivypublish/ivy.xml b/subprojects/gradle-docs/src/samples/ivypublish/ivy.xml
new file mode 100644
index 0000000..5f91b87
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/ivypublish/ivy.xml
@@ -0,0 +1,6 @@
+<ivy-module version="1.0" xmlns:e="http://ant.apache.org/ivy/extra">
+    <info organisation="org.gradle.test" module="antModule" revision="1.0"/>
+    <dependencies>
+		<dependency org="org.gradle.test" name="ivypublish" rev="1.0" changing="true" conf="default->default"/>
+    </dependencies>
+</ivy-module>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/ivypublish/ivysettings.xml b/subprojects/gradle-docs/src/samples/ivypublish/ivysettings.xml
new file mode 100644
index 0000000..2d26281
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/ivypublish/ivysettings.xml
@@ -0,0 +1,9 @@
+<ivysettings>
+    <settings defaultResolver="main"/>
+    <resolvers>
+        <filesystem name="main" checkmodified="true" validate="true">
+            <ivy pattern="${buildDir}/repo/ivy-[revision].xml"/>
+            <artifact pattern="${buildDir}/repo/[module]-[revision].[ext]"/>
+        </filesystem>
+    </resolvers>
+</ivysettings>
diff --git a/subprojects/gradle-docs/src/samples/ivypublish/settings.gradle b/subprojects/gradle-docs/src/samples/ivypublish/settings.gradle
new file mode 100644
index 0000000..e465e5c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/ivypublish/settings.gradle
@@ -0,0 +1 @@
+include 'subproject'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/ivypublish/src/main/java/org/gradle/SomeClass.java b/subprojects/gradle-docs/src/samples/ivypublish/src/main/java/org/gradle/SomeClass.java
new file mode 100644
index 0000000..4360a7e
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/ivypublish/src/main/java/org/gradle/SomeClass.java
@@ -0,0 +1,4 @@
+package org.gradle;
+
+public class SomeClass {
+}
diff --git a/subprojects/gradle-docs/src/samples/ivypublish/subproject/src/main/java/org/gradle/shared/Person.java b/subprojects/gradle-docs/src/samples/ivypublish/subproject/src/main/java/org/gradle/shared/Person.java
new file mode 100644
index 0000000..c4f58e6
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/ivypublish/subproject/src/main/java/org/gradle/shared/Person.java
@@ -0,0 +1,5 @@
+package org.gradle.shared;
+
+public class Person {
+    private String name;
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/java/base/build.gradle b/subprojects/gradle-docs/src/samples/java/base/build.gradle
new file mode 100644
index 0000000..762aa87
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/base/build.gradle
@@ -0,0 +1,23 @@
+subprojects {
+    apply plugin: 'java-base'
+
+    sourceCompatibility = 1.5
+    version = '1.0'
+
+    repositories {
+        mavenCentral()
+    }
+
+    configurations {
+        compile
+    }
+
+    sourceSets {
+        main {
+            java.srcDir "$projectDir/java"
+            resources.srcDir "$projectDir/java"
+            compileClasspath = configurations.compile
+            runtimeClasspath = compileClasspath + classes
+        }
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/java/base/prod/build.gradle b/subprojects/gradle-docs/src/samples/java/base/prod/build.gradle
new file mode 100644
index 0000000..4c6444e
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/base/prod/build.gradle
@@ -0,0 +1,17 @@
+
+configurations {
+    getByName('default').extendsFrom(compile)
+}
+
+dependencies {
+    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
+}
+
+task jar(type: Jar) {
+    from sourceSets.main.classes
+}
+
+artifacts {
+    archives jar
+}
+
diff --git a/subprojects/gradle-docs/src/samples/java/base/prod/java/org/gradle/Person.java b/subprojects/gradle-docs/src/samples/java/base/prod/java/org/gradle/Person.java
new file mode 100644
index 0000000..8b69988
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/base/prod/java/org/gradle/Person.java
@@ -0,0 +1,16 @@
+package org.gradle;
+
+import org.apache.commons.collections.list.GrowthList;
+
+public class Person {
+    private final String name;
+
+    public Person(String name) {
+        this.name = name;
+        new GrowthList();
+    }
+
+    public String getName() {
+        return name;
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/java/base/readme.xml b/subprojects/gradle-docs/src/samples/java/base/readme.xml
new file mode 100644
index 0000000..194af4a
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/base/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>Java base project</para>    
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/java/base/settings.gradle b/subprojects/gradle-docs/src/samples/java/base/settings.gradle
new file mode 100644
index 0000000..5ef08a5
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/base/settings.gradle
@@ -0,0 +1 @@
+include "prod", "test"
diff --git a/subprojects/gradle-docs/src/samples/java/base/test/build.gradle b/subprojects/gradle-docs/src/samples/java/base/test/build.gradle
new file mode 100644
index 0000000..b253b27
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/base/test/build.gradle
@@ -0,0 +1,11 @@
+dependencies {
+    compile group: 'junit', name: 'junit', version: '4.7', project(':prod')
+}
+
+task test(type: Test) {
+    testClassesDir = sourceSets.main.classesDir
+    classpath = sourceSets.main.runtimeClasspath
+}
+
+check.dependsOn test
+
diff --git a/subprojects/gradle-docs/src/samples/java/base/test/java/org/gradle/PersonTest.java b/subprojects/gradle-docs/src/samples/java/base/test/java/org/gradle/PersonTest.java
new file mode 100644
index 0000000..29fb813
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/base/test/java/org/gradle/PersonTest.java
@@ -0,0 +1,12 @@
+package org.gradle;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class PersonTest {
+    @Test
+    public void canConstructAPersonWithAName() {
+        Person person = new Person("Larry");
+        assertEquals("Larry", person.getName());
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/java/customizedLayout/build.gradle b/subprojects/gradle-docs/src/samples/java/customizedLayout/build.gradle
new file mode 100644
index 0000000..9214ff3
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/customizedLayout/build.gradle
@@ -0,0 +1,32 @@
+apply plugin: 'java'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile group: 'junit', name: 'junit', version: '4.7'
+}
+
+// START SNIPPET define-main
+sourceSets {
+    main {
+        java {
+            srcDir 'src/java'
+        }
+        resources {
+            srcDir 'src/resources'
+        }
+    }
+// END SNIPPET define-main
+    test {
+        java {
+            srcDir 'test/java'
+        }
+        resources {
+            srcDir 'test/resources'
+        }
+    }
+// START SNIPPET define-main
+}
+// END SNIPPET define-main
diff --git a/subprojects/gradle-docs/src/samples/java/customizedLayout/readme.xml b/subprojects/gradle-docs/src/samples/java/customizedLayout/readme.xml
new file mode 100644
index 0000000..336d6e8
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/customizedLayout/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>Java project with a custom source layout</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/java/customizedLayout/src/java/org/gradle/Person.java b/subprojects/gradle-docs/src/samples/java/customizedLayout/src/java/org/gradle/Person.java
new file mode 100644
index 0000000..af969d8
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/customizedLayout/src/java/org/gradle/Person.java
@@ -0,0 +1,13 @@
+package org.gradle;
+
+public class Person {
+    private final String name;
+
+    public Person(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/java/customizedLayout/test/java/org/gradle/PersonTest.java b/subprojects/gradle-docs/src/samples/java/customizedLayout/test/java/org/gradle/PersonTest.java
new file mode 100644
index 0000000..29fb813
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/customizedLayout/test/java/org/gradle/PersonTest.java
@@ -0,0 +1,12 @@
+package org.gradle;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class PersonTest {
+    @Test
+    public void canConstructAPersonWithAName() {
+        Person person = new Person("Larry");
+        assertEquals("Larry", person.getName());
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/api/build.gradle b/subprojects/gradle-docs/src/samples/java/multiproject/api/build.gradle
new file mode 100644
index 0000000..07a79ec
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/api/build.gradle
@@ -0,0 +1,65 @@
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath 'commons-math:commons-math:1.1'
+    }
+}
+
+configurations {
+   spi
+}
+
+// START SNIPPET dependencies
+// START SNIPPET project-dependencies
+dependencies {
+    compile project(':shared')
+// END SNIPPET project-dependencies
+// END SNIPPET dependencies
+    compile module("commons-lang:commons-lang:2.4") {
+        dependency("commons-io:commons-io:1.2")
+    }
+// START SNIPPET dependencies
+// START SNIPPET project-dependencies
+}
+// END SNIPPET dependencies
+// END SNIPPET project-dependencies
+
+// Just a smoke test that using this option does not lead to any exception
+compileJava.options.compilerArgs = ['-Xlint:unchecked']
+
+task spiJar(type: Jar) {
+    appendix = 'spi'
+    from sourceSets.main.classes
+    include 'org/gradle/api/'
+}
+
+artifacts {
+  spi spiJar
+}
+
+// START SNIPPET dists
+task dist(type: Zip) {
+    dependsOn spiJar
+    from 'src/dist'
+    into('libs') {
+        from spiJar.archivePath
+        from configurations.runtime
+    }
+}
+// END SNIPPET dists
+
+// We want to test if commons-math was properly added to the build script classpath
+org.apache.commons.math.fraction.Fraction lhs = new org.apache.commons.math.fraction.Fraction(1, 3);
+org.gradle.buildsrc.BuildSrcClass bsc = new org.gradle.buildsrc.BuildSrcClass()
+
+task checkProjectDependency(dependsOn: project(':shared').jar) << {
+    File cachedSharedJarDir = new File(gradle.gradleUserHomeDir, "cache/multiproject/shared/jars")
+    copy {
+        from project(':shared').jar.archivePath
+        into cachedSharedJarDir
+    }
+    File sharedJar = configurations.compile.files.find { File file -> file.name.startsWith('shared')}
+    assert sharedJar.absolutePath == project(':shared').jar.archivePath.absolutePath
+}
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/api/src/dist/README.txt b/subprojects/gradle-docs/src/samples/java/multiproject/api/src/dist/README.txt
new file mode 100644
index 0000000..df5033f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/api/src/dist/README.txt
@@ -0,0 +1 @@
+this is the README
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/api/src/main/java/org/gradle/api/PersonList.java b/subprojects/gradle-docs/src/samples/java/multiproject/api/src/main/java/org/gradle/api/PersonList.java
new file mode 100644
index 0000000..c07030f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/api/src/main/java/org/gradle/api/PersonList.java
@@ -0,0 +1,21 @@
+package org.gradle.api;
+
+import java.util.ArrayList;
+import org.gradle.shared.Person;
+import org.gradle.apiImpl.Impl;
+
+
+public class PersonList {
+    private ArrayList<Person> persons = new ArrayList<Person>();
+
+    public void doSomethingWithImpl() {
+        org.apache.commons.lang.builder.ToStringBuilder stringBuilder;
+        try {
+             Class.forName("org.apache.commons.io.FileUtils");
+        } catch (ClassNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+        new Impl().implMethod();
+    }
+
+}
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/api/src/main/java/org/gradle/api/package.html b/subprojects/gradle-docs/src/samples/java/multiproject/api/src/main/java/org/gradle/api/package.html
new file mode 100644
index 0000000..2df31a4
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/api/src/main/java/org/gradle/api/package.html
@@ -0,0 +1,3 @@
+<html>
+    <body><p>These are the API classes</p></body>
+</html>
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/api/src/main/java/org/gradle/apiImpl/Impl.java b/subprojects/gradle-docs/src/samples/java/multiproject/api/src/main/java/org/gradle/apiImpl/Impl.java
new file mode 100644
index 0000000..4529ec6
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/api/src/main/java/org/gradle/apiImpl/Impl.java
@@ -0,0 +1,10 @@
+package org.gradle.apiImpl;
+
+
+public class Impl {
+
+    public void implMethod() {
+        double a = 4.0 * 4;
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/build.gradle b/subprojects/gradle-docs/src/samples/java/multiproject/build.gradle
new file mode 100644
index 0000000..55391fe
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/build.gradle
@@ -0,0 +1,20 @@
+// START SNIPPET configuration-injection
+subprojects {
+    apply plugin: 'java'
+    apply plugin: 'eclipse'
+
+    repositories {
+       mavenCentral()
+    }
+
+    dependencies {
+        testCompile 'junit:junit:4.7'
+    }
+
+    version = '1.0'
+
+    jar {
+        manifest.attributes provider: 'gradle'
+    }
+}
+// END SNIPPET configuration-injection
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/buildSrc/build.gradle b/subprojects/gradle-docs/src/samples/java/multiproject/buildSrc/build.gradle
new file mode 100644
index 0000000..f8baabe
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/buildSrc/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: 'java'
+
+repositories {
+    mavenCentral()
+}
+
+// START SNIPPET gradle-api-dependencies
+dependencies {
+    compile gradleApi()
+// END SNIPPET gradle-api-dependencies
+    testCompile group: 'junit', name: 'junit', version: '4.7'
+// START SNIPPET gradle-api-dependencies
+}
+// END SNIPPET gradle-api-dependencies
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/buildSrc/src/main/java/org/gradle/buildsrc/BuildSrcClass.java b/subprojects/gradle-docs/src/samples/java/multiproject/buildSrc/src/main/java/org/gradle/buildsrc/BuildSrcClass.java
new file mode 100644
index 0000000..1f61639
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/buildSrc/src/main/java/org/gradle/buildsrc/BuildSrcClass.java
@@ -0,0 +1,7 @@
+package org.gradle.buildsrc;
+
+import groovy.util.AntBuilder;
+
+public class BuildSrcClass {
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/buildSrc/src/test/java/org/gradle/buildsrc/BuildSrcClassTest.java b/subprojects/gradle-docs/src/samples/java/multiproject/buildSrc/src/test/java/org/gradle/buildsrc/BuildSrcClassTest.java
new file mode 100644
index 0000000..98380e5
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/buildSrc/src/test/java/org/gradle/buildsrc/BuildSrcClassTest.java
@@ -0,0 +1,8 @@
+package org.gradle.buildsrc;
+
+public class BuildSrcClassTest {
+    @org.junit.Test
+    public void canConstructBuildSrcClass() {
+        new BuildSrcClass();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/readme.xml b/subprojects/gradle-docs/src/samples/java/multiproject/readme.xml
new file mode 100644
index 0000000..6855fb1
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/readme.xml
@@ -0,0 +1,8 @@
+<sample>
+    <para>This sample demonstrates how an application can be composed using multiple Java projects.</para>
+
+    <para>This build creates a client-server application which is distributed as 2 archives. First, there is a client
+        ZIP which includes an API JAR, which a 3rd party application would compile against, and a client runtime. Then,
+        there is a server WAR which provides a web service.
+    </para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/services/shared/build.gradle b/subprojects/gradle-docs/src/samples/java/multiproject/services/shared/build.gradle
new file mode 100644
index 0000000..c5f5805
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/services/shared/build.gradle
@@ -0,0 +1,3 @@
+dependencies {
+    compile project(':shared')
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/services/shared/src/main/java/org/gradle/services/shared/TestTest.java b/subprojects/gradle-docs/src/samples/java/multiproject/services/shared/src/main/java/org/gradle/services/shared/TestTest.java
new file mode 100644
index 0000000..14b3df4
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/services/shared/src/main/java/org/gradle/services/shared/TestTest.java
@@ -0,0 +1,12 @@
+package org.gradle.services.shared;
+
+import org.gradle.shared.Person;
+
+public class TestTest {
+    private String name;
+
+    public void method() {
+        new Person("someName");
+    }
+
+}
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/services/webservice/build.gradle b/subprojects/gradle-docs/src/samples/java/multiproject/services/webservice/build.gradle
new file mode 100644
index 0000000..c7737cd
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/services/webservice/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'war'
+
+version = '2.5'
+
+// START SNIPPET dependency-configurations
+dependencies {
+// END SNIPPET dependency-configurations    
+    compile project(':shared'), 'commons-collections:commons-collections:3.2 at jar', 'commons-io:commons-io:1.2', 'commons-lang:commons-lang:2.4 at jar'
+// START SNIPPET dependency-configurations
+    compile project(path: ':api', configuration: 'spi')
+// END SNIPPET dependency-configurations
+    runtime project(':api')
+// START SNIPPET dependency-configurations
+}
+// END SNIPPET dependency-configurations
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/services/webservice/src/main/java/org/gradle/webservice/TestTest.java b/subprojects/gradle-docs/src/samples/java/multiproject/services/webservice/src/main/java/org/gradle/webservice/TestTest.java
new file mode 100644
index 0000000..66d7263
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/services/webservice/src/main/java/org/gradle/webservice/TestTest.java
@@ -0,0 +1,19 @@
+package org.gradle.webservice;
+
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.collections.list.GrowthList;
+import org.gradle.shared.Person;
+import org.gradle.api.PersonList;
+
+public class TestTest {
+    private String name;
+
+    public void method() {
+        FilenameUtils.separatorsToUnix("my/unix/filename");
+        ToStringBuilder.reflectionToString(new Person("name"));
+        new GrowthList();
+        new PersonList().doSomethingWithImpl(); // compile with api-spi, runtime with api
+    }
+
+}
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/services/webservice/src/test/java/org/gradle/webservice/TestTestTest.java b/subprojects/gradle-docs/src/samples/java/multiproject/services/webservice/src/test/java/org/gradle/webservice/TestTestTest.java
new file mode 100644
index 0000000..320d999
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/services/webservice/src/test/java/org/gradle/webservice/TestTestTest.java
@@ -0,0 +1,16 @@
+package org.gradle.webservice;
+
+import junit.framework.TestCase;
+
+/**
+ * @author Hans Dockter
+ */
+public class TestTestTest extends TestCase {
+    public void testClasspath() {
+        new TestTest().method();
+    }
+
+    public void testApiCompileClasspath() {
+        new org.gradle.api.PersonList();
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/settings.gradle b/subprojects/gradle-docs/src/samples/java/multiproject/settings.gradle
new file mode 100644
index 0000000..aab1858
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/settings.gradle
@@ -0,0 +1,4 @@
+
+// START SNIPPET include-projects
+include "shared", "api", "services:webservice", "services:shared"
+// END SNIPPET include-projects
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/shared/src/main/java/org/gradle/shared/Person.java b/subprojects/gradle-docs/src/samples/java/multiproject/shared/src/main/java/org/gradle/shared/Person.java
new file mode 100644
index 0000000..98a77b0
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/shared/src/main/java/org/gradle/shared/Person.java
@@ -0,0 +1,26 @@
+package org.gradle.shared;
+
+import java.util.Properties;
+import java.io.IOException;
+
+public class Person {
+    private String name;
+
+    public Person(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String readProperty() throws IOException {
+        Properties properties = new Properties();
+        properties.load(getClass().getClassLoader().getResourceAsStream("org/gradle/shared/main.properties"));
+        return properties.getProperty("main");
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/shared/src/main/java/org/gradle/shared/package-info.java b/subprojects/gradle-docs/src/samples/java/multiproject/shared/src/main/java/org/gradle/shared/package-info.java
new file mode 100644
index 0000000..90fcecc
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/shared/src/main/java/org/gradle/shared/package-info.java
@@ -0,0 +1,5 @@
+
+/**
+ * These are the shared classes.
+ */
+package org.gradle.shared;
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/shared/src/main/resources/org/gradle/shared/main.properties b/subprojects/gradle-docs/src/samples/java/multiproject/shared/src/main/resources/org/gradle/shared/main.properties
new file mode 100644
index 0000000..8403b54
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/shared/src/main/resources/org/gradle/shared/main.properties
@@ -0,0 +1 @@
+main=mainValue
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/shared/src/test/java/org/gradle/shared/PersonTest.java b/subprojects/gradle-docs/src/samples/java/multiproject/shared/src/test/java/org/gradle/shared/PersonTest.java
new file mode 100644
index 0000000..bec0dc1
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/shared/src/test/java/org/gradle/shared/PersonTest.java
@@ -0,0 +1,25 @@
+package org.gradle.shared;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.util.Properties;
+
+public class PersonTest extends TestCase {
+    public void testTest() {
+        Person person = new Person("testname1");
+        assertEquals("testname1", person.getName());
+        person.setName("testname2");
+        assertEquals("testname2", person.getName());
+    }
+
+    public void testMainProperty() throws IOException {
+        assertEquals("mainValue", new Person("test").readProperty());
+    }
+
+    public void testTestProperty() throws IOException {
+        Properties properties = new Properties();
+        properties.load(getClass().getClassLoader().getResourceAsStream("org/gradle/shared/test.properties"));
+        assertEquals("testValue", properties.getProperty("test"));
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/java/multiproject/shared/src/test/resources/org/gradle/shared/test.properties b/subprojects/gradle-docs/src/samples/java/multiproject/shared/src/test/resources/org/gradle/shared/test.properties
new file mode 100644
index 0000000..bb47ed6
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/multiproject/shared/src/test/resources/org/gradle/shared/test.properties
@@ -0,0 +1 @@
+test=testValue
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/java/onlyif/build.gradle b/subprojects/gradle-docs/src/samples/java/onlyif/build.gradle
new file mode 100644
index 0000000..a5b141e
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/onlyif/build.gradle
@@ -0,0 +1,16 @@
+apply plugin: 'java'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile group: 'junit', name: 'junit', version: '4.7'
+}
+
+test {
+    systemProperties['property'] = 'value'
+    onlyIf { task ->
+        compileJava.didWork
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/java/onlyif/src/main/java/org/gradle/Person.java b/subprojects/gradle-docs/src/samples/java/onlyif/src/main/java/org/gradle/Person.java
new file mode 100644
index 0000000..af969d8
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/onlyif/src/main/java/org/gradle/Person.java
@@ -0,0 +1,13 @@
+package org.gradle;
+
+public class Person {
+    private final String name;
+
+    public Person(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/java/onlyif/src/test/java/org/gradle/PersonTest.java b/subprojects/gradle-docs/src/samples/java/onlyif/src/test/java/org/gradle/PersonTest.java
new file mode 100644
index 0000000..29fb813
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/onlyif/src/test/java/org/gradle/PersonTest.java
@@ -0,0 +1,12 @@
+package org.gradle;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class PersonTest {
+    @Test
+    public void canConstructAPersonWithAName() {
+        Person person = new Person("Larry");
+        assertEquals("Larry", person.getName());
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/java/quickstart/build.gradle b/subprojects/gradle-docs/src/samples/java/quickstart/build.gradle
new file mode 100644
index 0000000..8e274b9
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/quickstart/build.gradle
@@ -0,0 +1,43 @@
+// START SNIPPET use-plugin
+apply plugin: 'java'
+// END SNIPPET use-plugin
+// START SNIPPET use-eclipse-plugin
+apply plugin: 'eclipse'
+// END SNIPPET use-eclipse-plugin
+
+// START SNIPPET customisation
+sourceCompatibility = 1.5
+version = '1.0'
+jar {
+    manifest {
+        attributes 'Implementation-Title': 'Gradle Quickstart', 'Implementation-Version': version
+    }
+}
+// END SNIPPET customisation
+
+// START SNIPPET repo
+repositories {
+    mavenCentral()
+}
+// END SNIPPET repo
+
+// START SNIPPET dependencies
+dependencies {
+    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
+    testCompile group: 'junit', name: 'junit', version: '4.+'
+}
+// END SNIPPET dependencies
+
+// START SNIPPET task-customisation
+test {
+    systemProperties 'property': 'value'
+}
+// END SNIPPET task-customisation
+
+// START SNIPPET upload
+uploadArchives {
+    repositories {
+       flatDir(dirs: file('repos'))
+    }
+}
+// END SNIPPET upload
diff --git a/subprojects/gradle-docs/src/samples/java/quickstart/readme.xml b/subprojects/gradle-docs/src/samples/java/quickstart/readme.xml
new file mode 100644
index 0000000..199ba3f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/quickstart/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>Java quickstart project</para>    
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/java/quickstart/src/main/java/org/gradle/Person.java b/subprojects/gradle-docs/src/samples/java/quickstart/src/main/java/org/gradle/Person.java
new file mode 100644
index 0000000..8b69988
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/quickstart/src/main/java/org/gradle/Person.java
@@ -0,0 +1,16 @@
+package org.gradle;
+
+import org.apache.commons.collections.list.GrowthList;
+
+public class Person {
+    private final String name;
+
+    public Person(String name) {
+        this.name = name;
+        new GrowthList();
+    }
+
+    public String getName() {
+        return name;
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/java/quickstart/src/test/java/org/gradle/PersonTest.java b/subprojects/gradle-docs/src/samples/java/quickstart/src/test/java/org/gradle/PersonTest.java
new file mode 100644
index 0000000..29fb813
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/quickstart/src/test/java/org/gradle/PersonTest.java
@@ -0,0 +1,12 @@
+package org.gradle;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class PersonTest {
+    @Test
+    public void canConstructAPersonWithAName() {
+        Person person = new Person("Larry");
+        assertEquals("Larry", person.getName());
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/java/testListener/build.gradle b/subprojects/gradle-docs/src/samples/java/testListener/build.gradle
new file mode 100644
index 0000000..c3e49ba
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/testListener/build.gradle
@@ -0,0 +1,54 @@
+import static org.junit.Assert.*
+
+apply plugin: 'java'
+repositories.mavenCentral()
+dependencies.testCompile 'junit:junit:4.+'
+test.stopAtFailuresOrErrors = false
+
+def events = []
+// START SNIPPET testListenerImpl
+class MyListener implements org.gradle.api.tasks.testing.TestListener
+{
+    def events
+
+    public void beforeSuite(TestDescriptor suite) {
+        recordAndOutput('suiteStarting: '+suite.getName())
+    }
+
+    public void afterSuite(TestDescriptor suite, TestResult result) {
+        recordAndOutput 'suiteFinished: '+suite.getName()
+    }
+
+    public void beforeTest(TestDescriptor test) {
+        recordAndOutput 'testStarting: '+test.getName()
+    }
+
+    public void afterTest(TestDescriptor test, TestResult result) {
+        recordAndOutput 'testFinished: '+test.getName()+', result: '+result.getResultType()
+    }
+
+    private void recordAndOutput(String msg) {
+        events << msg
+        println '  '+msg
+    }
+}
+// END SNIPPET testListenerImpl
+
+// START SNIPPET testListenerRegister
+gradle.addListener(new MyListener(events: events))
+// END SNIPPET testListenerRegister
+
+test.doLast {
+    def expectedEvents = []
+    expectedEvents << 'suiteStarting: DoNothingTest'
+    expectedEvents << 'testStarting: doNothing(DoNothingTest)'
+    expectedEvents << 'testFinished: doNothing(DoNothingTest), result: SUCCESS'
+    expectedEvents << 'testStarting: doNothingButFail(DoNothingTest)'
+    expectedEvents << 'testFinished: doNothingButFail(DoNothingTest), result: FAILURE'
+    expectedEvents << 'testStarting: doNothingButError(DoNothingTest)'
+    expectedEvents << 'testFinished: doNothingButError(DoNothingTest), result: FAILURE'
+    expectedEvents << 'suiteFinished: DoNothingTest'
+
+    assertEquals(expectedEvents, events)
+}
+
diff --git a/subprojects/gradle-docs/src/samples/java/testListener/src/test/java/org/gradle/DoNothingTest.java b/subprojects/gradle-docs/src/samples/java/testListener/src/test/java/org/gradle/DoNothingTest.java
new file mode 100644
index 0000000..d115a2b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/testListener/src/test/java/org/gradle/DoNothingTest.java
@@ -0,0 +1,18 @@
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class DoNothingTest {
+    @Test
+    public void doNothing() {
+    }
+
+    @Test
+    public void doNothingButFail() {
+       fail("I always fail");
+    }
+
+    @Test
+    public void doNothingButError() {
+       throw new RuntimeException("I always throw exceptions");
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/java/withIntegrationTests/build.gradle b/subprojects/gradle-docs/src/samples/java/withIntegrationTests/build.gradle
new file mode 100644
index 0000000..b8b7285
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/withIntegrationTests/build.gradle
@@ -0,0 +1,36 @@
+apply plugin: 'java'
+
+repositories {
+    mavenCentral()
+}
+
+configurations {
+    integrationTestCompile {
+        extendsFrom testCompile
+    }
+    integrationTestRuntime {
+        extendsFrom integrationTestCompile, testRuntime
+    }
+}
+
+dependencies {
+    testCompile group: 'junit', name: 'junit', version: '4.7'
+    integrationTestCompile group: 'commons-collections', name: 'commons-collections', version: '3.2'
+}
+
+sourceSets {
+    integrationTest {
+        java.srcDir file('src/integration-test/java')
+        resources.srcDir file('src/integration-test/resources')
+        compileClasspath = sourceSets.main.classes + sourceSets.test.classes + configurations.integrationTestCompile
+        runtimeClasspath = classes + compileClasspath + configurations.integrationTestRuntime
+    }
+}
+
+task integrationTest(type: Test, dependsOn: jar) {
+    testClassesDir = sourceSets.integrationTest.classesDir
+    classpath = sourceSets.integrationTest.runtimeClasspath
+    systemProperties['jar.path'] = jar.archivePath
+}
+
+build.dependsOn integrationTest
diff --git a/subprojects/gradle-docs/src/samples/java/withIntegrationTests/readme.xml b/subprojects/gradle-docs/src/samples/java/withIntegrationTests/readme.xml
new file mode 100644
index 0000000..86bdde5
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/withIntegrationTests/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>This sample demonstrates how to use a source set to add an integration test suite to a Java project.</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/java/withIntegrationTests/src/integration-test/java/org/gradle/PersonIntegrationTest.java b/subprojects/gradle-docs/src/samples/java/withIntegrationTests/src/integration-test/java/org/gradle/PersonIntegrationTest.java
new file mode 100644
index 0000000..ecdeb3f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/withIntegrationTests/src/integration-test/java/org/gradle/PersonIntegrationTest.java
@@ -0,0 +1,30 @@
+package org.gradle;
+
+import org.junit.Test;
+import java.io.File;
+import java.util.List;
+import java.util.Properties;
+import static org.junit.Assert.*;
+import org.apache.commons.collections.list.GrowthList;
+
+public class PersonIntegrationTest {
+    @Test
+    public void canConstructAPersonWithAName() {
+        // check our classpath
+        List list = new GrowthList();
+
+        // check Jar exists
+        File jarFile = new File(System.getProperty("jar.path"));
+        assertTrue(jarFile.isFile());
+
+        Person person = PersonTestFixture.create("Larry");
+        assertEquals("Larry", person.getName());
+    }
+    
+    @Test
+    public void resourcesAreAvailableInClasspath() throws Exception {
+        Properties properties = new Properties();
+        properties.load(getClass().getResourceAsStream("inttest.properties"));
+        assertEquals("value", properties.getProperty("int.test.prop"));
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/java/withIntegrationTests/src/integration-test/resources/org/gradle/inttest.properties b/subprojects/gradle-docs/src/samples/java/withIntegrationTests/src/integration-test/resources/org/gradle/inttest.properties
new file mode 100644
index 0000000..710ba9f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/withIntegrationTests/src/integration-test/resources/org/gradle/inttest.properties
@@ -0,0 +1 @@
+int.test.prop=value
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/java/withIntegrationTests/src/main/java/org/gradle/Person.java b/subprojects/gradle-docs/src/samples/java/withIntegrationTests/src/main/java/org/gradle/Person.java
new file mode 100644
index 0000000..af969d8
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/withIntegrationTests/src/main/java/org/gradle/Person.java
@@ -0,0 +1,13 @@
+package org.gradle;
+
+public class Person {
+    private final String name;
+
+    public Person(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/java/withIntegrationTests/src/test/java/org/gradle/PersonTestFixture.java b/subprojects/gradle-docs/src/samples/java/withIntegrationTests/src/test/java/org/gradle/PersonTestFixture.java
new file mode 100644
index 0000000..43a1f2c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/java/withIntegrationTests/src/test/java/org/gradle/PersonTestFixture.java
@@ -0,0 +1,10 @@
+package org.gradle;
+
+import static org.junit.Assert.*;
+
+public class PersonTestFixture {
+    public static Person create(String name) {
+        assertNotNull(name);
+        return new Person(name);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/logging/build.gradle b/subprojects/gradle-docs/src/samples/logging/build.gradle
new file mode 100644
index 0000000..5b8b5c9
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/logging/build.gradle
@@ -0,0 +1 @@
+apply from: 'external.gradle'
diff --git a/subprojects/gradle-docs/src/samples/logging/buildSrc/build.gradle b/subprojects/gradle-docs/src/samples/logging/buildSrc/build.gradle
new file mode 100644
index 0000000..65d1a05
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/logging/buildSrc/build.gradle
@@ -0,0 +1,22 @@
+println 'buildSrc quiet'
+logger.info 'buildSrc info'
+
+task classes << {}
+
+convention.plugins.java = new ProjectInfo(project: project);
+
+class ProjectInfo implements org.gradle.api.internal.plugins.EmbeddableJavaProject {
+    def Project project
+
+    Collection<String> getRebuildTasks() {
+        return ['classes']
+    }
+
+    Collection<String> getBuildTasks() {
+        return ['classes']
+    }
+
+    FileCollection getRuntimeClasspath() {
+        return project.files()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/logging/external.gradle b/subprojects/gradle-docs/src/samples/logging/external.gradle
new file mode 100644
index 0000000..c0f53f0
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/logging/external.gradle
@@ -0,0 +1,9 @@
+println 'external QUIET message'
+logging.captureStandardOutput LogLevel.INFO
+println 'external INFO message'
+
+System.err.println 'external ERROR error message'
+logging.captureStandardError LogLevel.LIFECYCLE
+System.err.println 'external LIFECYCLE error message'
+
+logger.lifecycle 'external LIFECYCLE log message'
diff --git a/subprojects/gradle-docs/src/samples/logging/init.gradle b/subprojects/gradle-docs/src/samples/logging/init.gradle
new file mode 100644
index 0000000..3569a94
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/logging/init.gradle
@@ -0,0 +1,46 @@
+
+println 'init QUIET out'
+logging.captureStandardOutput LogLevel.INFO
+println 'init INFO out'
+
+System.err.println 'init ERROR err'
+logging.captureStandardError LogLevel.INFO
+System.err.println 'init INFO err'
+
+logger.lifecycle('init lifecycle log')
+logger.info('init info log')
+
+useLogger(new CustomLogger())
+
+class CustomLogger extends BuildAdapter implements BuildListener, ProjectEvaluationListener, TaskExecutionListener, TaskActionListener {
+    def logger = Logging.getLogger('init-script')
+
+    public void buildFinished(BuildResult result) {
+        logger.info("LOGGER: build finished")
+        println 'init callback quiet out'
+    }
+
+    public void beforeEvaluate(Project project) {
+        logger.lifecycle("LOGGER: evaluating $project.path")
+    }
+
+    public void afterEvaluate(Project project, ProjectState state) {
+        logger.info('LOGGER: evaluated project')
+    }
+
+    public void beforeExecute(Task task) {
+        logger.lifecycle("LOGGER: executing $task.path")
+    }
+
+    public void afterExecute(Task task, TaskState state) {
+        logger.info('LOGGER: executed task')
+    }
+
+    public void beforeActions(Task task) {
+        logger.info('LOGGER: task starting work')
+    }
+
+    public void afterActions(Task task) {
+        logger.info('LOGGER: task completed work')
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/logging/nestedBuild/build.gradle b/subprojects/gradle-docs/src/samples/logging/nestedBuild/build.gradle
new file mode 100644
index 0000000..fe0b481
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/logging/nestedBuild/build.gradle
@@ -0,0 +1,6 @@
+println 'nestedBuild quiet'
+logger.info 'nestedBuild info'
+
+task log << {
+    println 'nestedBuild task quiet'
+}
diff --git a/subprojects/gradle-docs/src/samples/logging/nestedBuild/buildSrc/build.gradle b/subprojects/gradle-docs/src/samples/logging/nestedBuild/buildSrc/build.gradle
new file mode 100644
index 0000000..4ff86c7
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/logging/nestedBuild/buildSrc/build.gradle
@@ -0,0 +1,22 @@
+println 'nestedBuild/buildSrc quiet'
+logger.info 'nestedBuild/buildSrc info'
+
+task classes << { }
+
+convention.plugins.java = new ProjectInfo(project: project);
+
+class ProjectInfo implements org.gradle.api.internal.plugins.EmbeddableJavaProject {
+    def Project project
+
+    Collection<String> getRebuildTasks() {
+        return ['classes']
+    }
+
+    Collection<String> getBuildTasks() {
+        return ['classes']
+    }
+
+    FileCollection getRuntimeClasspath() {
+        return project.files()
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/logging/project1/build.gradle b/subprojects/gradle-docs/src/samples/logging/project1/build.gradle
new file mode 100644
index 0000000..790dea5
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/logging/project1/build.gradle
@@ -0,0 +1,75 @@
+// START SNIPPET use-logger
+logger.quiet('An info log message which is always logged.')
+logger.error('An error log message.')
+logger.warn('A warning log message.')
+logger.lifecycle('A lifecycle info log message.')
+logger.info('An info log message.')
+logger.debug('A debug log message.')
+logger.trace('A trace log message.')
+// END SNIPPET use-logger
+
+// START SNIPPET use-println
+println 'A message which is logged at QUIET level'
+// END SNIPPET use-println
+
+// START SNIPPET capture-stdout
+logging.captureStandardOutput LogLevel.INFO
+println 'A message which is logged at INFO level'
+// END SNIPPET capture-stdout
+
+System.err.println 'An error message which is logged at ERROR level'
+logging.captureStandardError LogLevel.LIFECYCLE
+System.err.println 'An error message which is logged at LIFECYCLE level'
+
+task logLifecycle {
+    logging.captureStandardOutput LogLevel.LIFECYCLE
+    logging.captureStandardError LogLevel.WARN
+    doFirst {
+        println('A task message which is logged at LIFECYCLE level')
+        System.err.println('A task error message which is logged at WARN level')
+    }
+}
+
+// START SNIPPET task-capture-stdout
+task logInfo {
+    logging.captureStandardOutput LogLevel.INFO
+    doFirst {
+        println 'A task message which is logged at INFO level'
+    }
+}
+// END SNIPPET task-capture-stdout
+
+task nestedBuildLog << {
+    def startParam = project.gradle.startParameter.newBuild()
+    startParam.currentDir = rootProject.file('nestedBuild')
+    startParam.taskNames = ['log']
+    GradleLauncher.newInstance(startParam).run().rethrowFailure()
+}
+
+task log(dependsOn: [logInfo, logLifecycle, nestedBuildLog]) << {
+    println('A task message which is logged at QUIET level')
+}
+
+// warn is the default log level for echo
+ant.echo('A warn message logged from Ant')
+ant.echo('An error message logged from Ant', level: org.apache.tools.ant.types.LogLevel.ERR)
+ant.echo('An info message logged from Ant', level: org.apache.tools.ant.types.LogLevel.INFO)
+ant.echo('A debug message logged from Ant', level: org.apache.tools.ant.types.LogLevel.DEBUG)
+
+// START SNIPPET use-slf4j
+org.slf4j.Logger slf4jLogger = org.slf4j.LoggerFactory.getLogger('some-logger')
+slf4jLogger.info('An info log message logged using SLF4j')
+// END SNIPPET use-slf4j
+
+org.apache.commons.logging.Log jclLogger = org.apache.commons.logging.LogFactory.getLog('some-logger')
+jclLogger.info('An info log message logged using JCL')
+
+org.apache.log4j.Logger log4jLogger = org.apache.log4j.Logger.getLogger('some-logger')
+log4jLogger.info('An info log message logged using Log4j')
+
+java.util.logging.Logger julLogger = java.util.logging.Logger.getLogger('some-logger')
+julLogger.severe('A severe log message logged using JUL')
+julLogger.warning('A warning log message logged using JUL')
+julLogger.info('An info log message logged using JUL')
+julLogger.config('A config log message logged using JUL')
+julLogger.fine('A fine log message logged using JUL')
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/logging/project2/build.gradle b/subprojects/gradle-docs/src/samples/logging/project2/build.gradle
new file mode 100644
index 0000000..aac6f2a
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/logging/project2/build.gradle
@@ -0,0 +1,14 @@
+
+buildscript {
+    println('quietProject2ScriptClassPathOut')
+    logging.captureStandardOutput LogLevel.INFO
+    logger.info('infoProject2ScriptClassPathOut')
+}
+
+dependsOn(':project1')
+// stdout capture config injected
+println('infoProject2Out')
+
+gradle.taskGraph.whenReady {
+    println 'quietProject2CallbackOut'
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/logging/settings.gradle b/subprojects/gradle-docs/src/samples/logging/settings.gradle
new file mode 100644
index 0000000..249146c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/logging/settings.gradle
@@ -0,0 +1,8 @@
+include 'project1', 'project2'
+
+println 'settings quiet out'
+logging.captureStandardOutput(LogLevel.INFO)
+println 'settings info out'
+
+logger.lifecycle('settings lifecycle log')
+logger.info('settings info log')
diff --git a/subprojects/gradle-docs/src/samples/maven/pomGeneration/build.gradle b/subprojects/gradle-docs/src/samples/maven/pomGeneration/build.gradle
new file mode 100644
index 0000000..a32b669
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/maven/pomGeneration/build.gradle
@@ -0,0 +1,105 @@
+import org.apache.maven.settings.Settings
+
+apply plugin: 'war'
+// START SNIPPET use-plugin
+apply plugin: 'maven'
+// END SNIPPET use-plugin
+
+group = 'gradle'
+version = '1.0'
+archivesBaseName = 'mywar'
+buildDirName = 'target'
+
+repositories {
+    flatDir(dirs: "$projectDir/lib")
+}
+
+dependencies {
+    compile("group1:compile:1.0") {
+        exclude(group: 'excludeGroup', module: 'excludeArtifact')
+    }
+    providedCompile "group2:providedCompile:1.0 at jar"
+    runtime "group3:runtime:1.0"
+    providedRuntime("group4:providedRuntime:1.0 at zip") {
+        artifact {
+            name = 'providedRuntime-util'
+            type = 'war'
+        }
+    }
+    testCompile "group5:testCompile:1.0"
+    testRuntime "group6:testRuntime:1.0"
+}
+
+// Include a javadoc zip
+
+task javadocZip(type: Zip, dependsOn: javadoc) {
+    classifier = 'javadoc'
+    from javadoc.destinationDir
+}
+
+artifacts {
+    archives javadocZip
+}
+
+// Configure the release and snapshot repositories
+
+def deployer = null
+uploadArchives {
+    repositories {
+        deployer = mavenDeployer {
+            repository(url: "file://localhost/$projectDir/pomRepo/")
+            snapshotRepository(url: "file://localhost/$projectDir/snapshotRepo/")
+        }
+    }
+}
+
+// Customize the contents of the pom
+
+installer = install.repositories.mavenInstaller
+
+if (hasProperty('customVersion')) {
+    [installer, deployer]*.pom*.version = customVersion
+    installer.pom.project {
+        groupId 'installGroup'
+    }
+    deployer.pom.groupId = 'deployGroup'
+}
+
+// START SNIPPET new-pom
+task writeNewPom << {
+    pom {
+        project {
+            inceptionYear '2008'
+            licenses {
+                license {
+                    name 'The Apache Software License, Version 2.0'
+                    url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+                    distribution 'repo'
+                }
+            }
+        }
+    }.writeTo("$buildDir/newpom.xml")
+}
+// END SNIPPET new-pom
+
+// START SNIPPET when-configured
+[installer, deployer]*.pom*.whenConfigured {pom ->
+    pom.dependencies.find {dep -> dep.groupId == 'group3' && dep.artifactId == 'runtime' }.optional = true
+}
+// END SNIPPET when-configured
+
+task writeDeployerPom(dependsOn: uploadArchives) << {
+    deployer.pom.writeTo("$buildDir/deployerpom.xml")
+}
+
+// For our integration tests
+
+install.doLast {
+    Settings settings = installer.settings
+    new File(buildDir, "localRepoPath.txt").write(settings.localRepository)
+}
+
+clean {
+    delete 'pomRepo'
+    delete 'snapshotRepo'
+}
diff --git a/subprojects/gradle-docs/src/samples/maven/pomGeneration/readme.xml b/subprojects/gradle-docs/src/samples/maven/pomGeneration/readme.xml
new file mode 100644
index 0000000..61fd97b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/maven/pomGeneration/readme.xml
@@ -0,0 +1,6 @@
+<sample>
+    <para>Demonstrates how to deploy and install to a Maven repository. Also demonstrates how to deploy a javadoc JAR
+        along with the main JAR, how to customize the contents of the generated POM, and how to deploy snapshots and
+        releases to different repositories.
+    </para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/maven/pomGeneration/src/main/java/org/MyClass.java b/subprojects/gradle-docs/src/samples/maven/pomGeneration/src/main/java/org/MyClass.java
new file mode 100644
index 0000000..81d5a85
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/maven/pomGeneration/src/main/java/org/MyClass.java
@@ -0,0 +1,5 @@
+package org;
+
+public class MyClass {
+    
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/maven/pomGeneration/src/main/webapp/WEB-INF/web.xml b/subprojects/gradle-docs/src/samples/maven/pomGeneration/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/maven/quickstart/build.gradle b/subprojects/gradle-docs/src/samples/maven/quickstart/build.gradle
new file mode 100644
index 0000000..e610edf
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/maven/quickstart/build.gradle
@@ -0,0 +1,30 @@
+import org.apache.maven.settings.Settings
+
+apply plugin: 'java'
+// START SNIPPET use-plugin
+apply plugin: 'maven'
+// END SNIPPET use-plugin
+
+group = 'gradle'
+version = '1.0'
+
+// Configure the repository
+
+uploadArchives {
+    repositories {
+        mavenDeployer {
+            repository(url: "file://localhost/$projectDir/pomRepo/")
+        }
+    }
+}
+
+// For our integration tests
+
+install.doLast {
+    Settings settings = repositories.mavenInstaller.settings
+    new File(buildDir, "localRepoPath.txt").write(settings.localRepository)
+}
+
+clean {
+    delete 'pomRepo'
+}
diff --git a/subprojects/gradle-docs/src/samples/maven/quickstart/readme.xml b/subprojects/gradle-docs/src/samples/maven/quickstart/readme.xml
new file mode 100644
index 0000000..7be29d6
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/maven/quickstart/readme.xml
@@ -0,0 +1,18 @@
+<!--
+  ~ 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.
+  -->
+<sample>
+    <para>Demonstrates how to deploy and install artifacts to a Maven repository.</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/maven/quickstart/src/main/java/org/MyClass.java b/subprojects/gradle-docs/src/samples/maven/quickstart/src/main/java/org/MyClass.java
new file mode 100644
index 0000000..769119b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/maven/quickstart/src/main/java/org/MyClass.java
@@ -0,0 +1,20 @@
+/*
+ * 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;
+
+public class MyClass {
+    
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/mavenRepo/build.gradle b/subprojects/gradle-docs/src/samples/mavenRepo/build.gradle
new file mode 100644
index 0000000..0bc7130
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/mavenRepo/build.gradle
@@ -0,0 +1,103 @@
+
+sillyexceptions = 'sillyexceptions'
+repotest = 'repotest'
+
+/*
+ * 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.
+ */
+
+/*
+ * gradle_sourceforge:
+ * - repotest
+ * -- repotest
+ * --- 1.0
+ * ---- repotest-1.0.pom (-> testdep-1.0)
+ *
+ * - repotest
+ * -- classifier
+ * --- 1.0
+ * ---- classifier-1.0.pom (-> classifier-dep-1.0)
+ * ---- classifier-1.0-jdk14.jar
+ * ---- classifier-1.0-jdk15.jar
+ *
+ * - repotest
+ * -- classifier-dep
+ * --- 1.0
+ * ---- classifier-dep-1.0.pom
+ * ---- classifier-dep-1.0.jar
+ *
+ * gradle_sourceforge2
+ * - repotest
+ * -- repotest
+ * --- 1.0
+ * ---- repotest-1.0.jar
+ *
+ * - testdep
+ * -- testdep
+ * --- 1.0
+ * ---- testdep-1.0.pom
+ * ---- testdep-1.0.jar
+ *
+ * - testdep2
+ * -- testdep2
+ * --- 1.0
+ * ---- testdep2-1.0.jar
+ * ---- testdep2-1.0.pom
+ *
+ * - jaronly
+ * -- jaronly
+ * --- 1.0
+ * ---- jaronly-1.0.jar
+ *
+ * Maven Repo:
+ *
+ * - sillyexceptions
+ * -- sillyexceptions
+ * --- 1.0.1
+ * ---- sillyexceptions-1.0.1.jar
+ * ---- sillyexceptions-1.0.1.pom
+ *
+ * Transitive Dependencies
+ *
+ * repotest -> testdep
+ * testdep -> testdep2
+ */
+
+configurations {
+    test
+}
+
+repositories {
+    mavenRepo(urls: ['http://gradle.sourceforge.net/repository/', 'http://gradle.sourceforge.net/otherrepo/']).allownomd = false
+    mavenRepo(urls: 'http://gradle.sourceforge.net/otherrepo/')
+    mavenCentral()
+}
+
+dependencies {
+    test "$sillyexceptions:$sillyexceptions:1.0.1 at jar", "$repotest:$repotest:1.0", "$repotest:classifier:1.0:jdk15", "jaronly:jaronly:1.0"
+}
+
+task retrieve << {
+    delete buildDir
+    delete new File(gradle.gradleUserHomeDir, "$ResolverContainer.DEFAULT_CACHE_DIR_NAME/$sillyexceptions")
+    delete new File(gradle.gradleUserHomeDir, "$ResolverContainer.DEFAULT_CACHE_DIR_NAME/$repotest")
+    delete new File(gradle.gradleUserHomeDir, "$ResolverContainer.DEFAULT_CACHE_DIR_NAME/testdep")
+    delete new File(gradle.gradleUserHomeDir, "$ResolverContainer.DEFAULT_CACHE_DIR_NAME/testdep2")
+    delete new File(gradle.gradleUserHomeDir, "$ResolverContainer.DEFAULT_CACHE_DIR_NAME/jaronly")
+    copy {
+        from configurations.test
+        into buildDir
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/osgi/build.gradle b/subprojects/gradle-docs/src/samples/osgi/build.gradle
new file mode 100644
index 0000000..35fba11
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/osgi/build.gradle
@@ -0,0 +1,27 @@
+group = 'gradle_tooling'
+version = '1.0'
+
+apply plugin: 'groovy'
+// START SNIPPET use-plugin
+apply plugin: 'osgi'
+// END SNIPPET use-plugin
+
+repositories {
+    mavenCentral()
+    mavenRepo(urls: 'http://repository.jboss.org/maven2/')
+}
+
+dependencies {
+    groovy group: 'org.codehaus.groovy', name: 'groovy-all', version: '1.7.0'
+    compile group: 'org.eclipse', name: 'osgi', version: '3.4.3.R34x_v20081215-1030'
+}
+
+jar {
+    manifest {
+        version = '1.0'
+        name = 'Example Gradle Activator'
+        instruction 'Bundle-Activator', 'org.gradle.GradleActivator'
+        instruction 'Import-Package', '*'
+        instruction 'Export-Package', '*'
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/osgi/readme.xml b/subprojects/gradle-docs/src/samples/osgi/readme.xml
new file mode 100644
index 0000000..dfe5ccf
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/osgi/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>A project which builds an OSGi bundle</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/osgi/src/main/groovy/org/gradle/GradleActivator.groovy b/subprojects/gradle-docs/src/samples/osgi/src/main/groovy/org/gradle/GradleActivator.groovy
new file mode 100644
index 0000000..a5116ff
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/osgi/src/main/groovy/org/gradle/GradleActivator.groovy
@@ -0,0 +1,19 @@
+package org.gradle
+
+import org.osgi.framework.BundleActivator
+import org.osgi.framework.BundleContext
+
+/**
+* A sample OSGi Activator written in Groovy.
+*
+* @author Hamlet D'Arcy
+*/ 
+public class GradleActivator implements BundleActivator {
+
+    public void start(BundleContext context) {
+        println "Hello from a Groovy Gradle Activator"
+    }
+
+    public void stop(BundleContext context) { 
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/scala/customizedLayout/build.gradle b/subprojects/gradle-docs/src/samples/scala/customizedLayout/build.gradle
new file mode 100644
index 0000000..f644cfa
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/scala/customizedLayout/build.gradle
@@ -0,0 +1,30 @@
+apply plugin: 'scala'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    scalaTools 'org.scala-lang:scala-compiler:2.7.7'
+    scalaTools 'org.scala-lang:scala-library:2.7.7'
+
+    compile 'org.scala-lang:scala-library:2.7.7'
+    testCompile group: 'junit', name: 'junit', version: '4.7'
+}
+
+// START SNIPPET define-main
+sourceSets {
+    main {
+        scala {
+            srcDir 'src/scala'
+        }
+    }
+// END SNIPPET define-main
+    test {
+        scala {
+            srcDir 'test/scala'
+        }
+    }
+// START SNIPPET define-main
+}
+// END SNIPPET define-main
diff --git a/subprojects/gradle-docs/src/samples/scala/customizedLayout/readme.xml b/subprojects/gradle-docs/src/samples/scala/customizedLayout/readme.xml
new file mode 100644
index 0000000..beca7c4
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/scala/customizedLayout/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>Scala project with a custom source layout</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/scala/customizedLayout/src/scala/org/gradle/sample/api/Person.scala b/subprojects/gradle-docs/src/samples/scala/customizedLayout/src/scala/org/gradle/sample/api/Person.scala
new file mode 100644
index 0000000..5effb67
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/scala/customizedLayout/src/scala/org/gradle/sample/api/Person.scala
@@ -0,0 +1,9 @@
+package org.gradle.sample.api
+
+/**
+ * Defines the interface for a person.
+ */
+abstract trait Person
+{
+  def names: List[String]
+}
diff --git a/subprojects/gradle-docs/src/samples/scala/customizedLayout/src/scala/org/gradle/sample/impl/PersonImpl.scala b/subprojects/gradle-docs/src/samples/scala/customizedLayout/src/scala/org/gradle/sample/impl/PersonImpl.scala
new file mode 100644
index 0000000..a9813f5
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/scala/customizedLayout/src/scala/org/gradle/sample/impl/PersonImpl.scala
@@ -0,0 +1,10 @@
+package org.gradle.sample.impl
+
+import org.gradle.sample.api.Person
+
+/**
+ * Immutable implementation of {@link Person}.
+ */
+class PersonImpl(val names: List[String]) extends Person
+{
+}
diff --git a/subprojects/gradle-docs/src/samples/scala/customizedLayout/test/scala/org/gradle/sample/impl/PersonImplTest.scala b/subprojects/gradle-docs/src/samples/scala/customizedLayout/test/scala/org/gradle/sample/impl/PersonImplTest.scala
new file mode 100644
index 0000000..250e376
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/scala/customizedLayout/test/scala/org/gradle/sample/impl/PersonImplTest.scala
@@ -0,0 +1,14 @@
+package org.gradle.sample.impl
+
+import _root_.junit.framework.TestCase
+import _root_.org.gradle.sample.api.Person
+
+class PersonImplTest extends TestCase {
+
+  // FIXME: use a Scala test framework to run a test
+  def testCanCreatePersonImpl(): Unit = {
+    def person: Person = new PersonImpl(List("bob", "smith"))
+    person
+  }
+
+}
diff --git a/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/build.gradle b/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/build.gradle
new file mode 100644
index 0000000..e6afb98
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'scala'
+
+version = 1.0
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    scalaTools 'org.scala-lang:scala-compiler:2.7.7'
+    scalaTools 'org.scala-lang:scala-library:2.7.7'
+
+    compile 'org.scala-lang:scala-library:2.7.7'
+    testCompile group: 'junit', name: 'junit', version: '4.7'
+}
diff --git a/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/readme.xml b/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/readme.xml
new file mode 100644
index 0000000..995686c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>A project containing a mix of Java and Scala source.</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/src/main/java/org/gradle/sample/Person.java b/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/src/main/java/org/gradle/sample/Person.java
new file mode 100644
index 0000000..1d99d7e
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/src/main/java/org/gradle/sample/Person.java
@@ -0,0 +1,5 @@
+package org.gradle.sample;
+
+public interface Person {
+    String getName();
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/src/main/scala/org/gradle/sample/impl/JavaPerson.java b/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/src/main/scala/org/gradle/sample/impl/JavaPerson.java
new file mode 100644
index 0000000..ebfc9bc
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/src/main/scala/org/gradle/sample/impl/JavaPerson.java
@@ -0,0 +1,7 @@
+package org.gradle.sample.impl;
+
+public class JavaPerson extends PersonImpl {
+    public JavaPerson(String name) {
+        super(name);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/src/main/scala/org/gradle/sample/impl/PersonImpl.scala b/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/src/main/scala/org/gradle/sample/impl/PersonImpl.scala
new file mode 100644
index 0000000..f99fa7b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/src/main/scala/org/gradle/sample/impl/PersonImpl.scala
@@ -0,0 +1,11 @@
+package org.gradle.sample.impl
+
+import org.gradle.sample.Person
+
+/**
+ * Immutable implementation of {@link Person}.
+ */
+class PersonImpl(val name: String) extends Person
+{
+    def getName() = name
+}
diff --git a/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/src/main/scala/org/gradle/sample/impl/PersonList.scala b/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/src/main/scala/org/gradle/sample/impl/PersonList.scala
new file mode 100644
index 0000000..8c70a69
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/src/main/scala/org/gradle/sample/impl/PersonList.scala
@@ -0,0 +1,9 @@
+package org.gradle.sample.impl
+
+import org.gradle.sample.Person
+
+class PersonList {
+    def find(name: String): Person = {
+        new JavaPerson(name)
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/src/test/scala/org/gradle/sample/PersonTest.scala b/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/src/test/scala/org/gradle/sample/PersonTest.scala
new file mode 100644
index 0000000..7806f5d
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/scala/mixedJavaAndScala/src/test/scala/org/gradle/sample/PersonTest.scala
@@ -0,0 +1,20 @@
+package org.gradle.sample
+
+import _root_.junit.framework.TestCase
+import _root_.org.gradle.sample.impl.PersonImpl
+import _root_.org.gradle.sample.impl.JavaPerson
+
+class PersonTest extends TestCase {
+
+  // FIXME: use a Scala test framework to run a test
+  def testCanCreateScalaPersonImpl(): Unit = {
+    def person: Person = new PersonImpl("bob smith")
+    person
+  }
+
+  def testCanCreateJavaPersonImpl(): Unit = {
+    def person: Person = new JavaPerson("bob smith")
+    person
+  }
+
+}
diff --git a/subprojects/gradle-docs/src/samples/scala/quickstart/build.gradle b/subprojects/gradle-docs/src/samples/scala/quickstart/build.gradle
new file mode 100644
index 0000000..a18fb25
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/scala/quickstart/build.gradle
@@ -0,0 +1,24 @@
+apply plugin: 'eclipse'
+// START SNIPPET use-plugin
+apply plugin: 'scala'
+// END SNIPPET use-plugin
+
+// START SNIPPET declare-scala-version
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    // Libraries needed to run the scala tools
+    scalaTools 'org.scala-lang:scala-compiler:2.7.7'
+    scalaTools 'org.scala-lang:scala-library:2.7.7'
+
+    // Libraries needed for scala api
+    compile 'org.scala-lang:scala-library:2.7.7'
+}
+// END SNIPPET declare-scala-version
+
+dependencies {
+    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
+    testCompile group: 'junit', name: 'junit', version: '4.7'
+}
diff --git a/subprojects/gradle-docs/src/samples/scala/quickstart/readme.xml b/subprojects/gradle-docs/src/samples/scala/quickstart/readme.xml
new file mode 100644
index 0000000..36983f7
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/scala/quickstart/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>Scala quickstart project</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/scala/quickstart/src/main/scala/org/gradle/sample/api/Person.scala b/subprojects/gradle-docs/src/samples/scala/quickstart/src/main/scala/org/gradle/sample/api/Person.scala
new file mode 100644
index 0000000..5effb67
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/scala/quickstart/src/main/scala/org/gradle/sample/api/Person.scala
@@ -0,0 +1,9 @@
+package org.gradle.sample.api
+
+/**
+ * Defines the interface for a person.
+ */
+abstract trait Person
+{
+  def names: List[String]
+}
diff --git a/subprojects/gradle-docs/src/samples/scala/quickstart/src/main/scala/org/gradle/sample/impl/PersonImpl.scala b/subprojects/gradle-docs/src/samples/scala/quickstart/src/main/scala/org/gradle/sample/impl/PersonImpl.scala
new file mode 100644
index 0000000..c6743e9
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/scala/quickstart/src/main/scala/org/gradle/sample/impl/PersonImpl.scala
@@ -0,0 +1,12 @@
+package org.gradle.sample.impl
+
+import org.gradle.sample.api.Person
+import org.apache.commons.collections.list.GrowthList;
+
+/**
+ * Immutable implementation of {@link Person}.
+ */
+class PersonImpl(val names: List[String]) extends Person
+{
+  private val importedList = new GrowthList();
+}
diff --git a/subprojects/gradle-docs/src/samples/scala/quickstart/src/test/scala/org/gradle/sample/impl/PersonImplTest.scala b/subprojects/gradle-docs/src/samples/scala/quickstart/src/test/scala/org/gradle/sample/impl/PersonImplTest.scala
new file mode 100644
index 0000000..250e376
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/scala/quickstart/src/test/scala/org/gradle/sample/impl/PersonImplTest.scala
@@ -0,0 +1,14 @@
+package org.gradle.sample.impl
+
+import _root_.junit.framework.TestCase
+import _root_.org.gradle.sample.api.Person
+
+class PersonImplTest extends TestCase {
+
+  // FIXME: use a Scala test framework to run a test
+  def testCanCreatePersonImpl(): Unit = {
+    def person: Person = new PersonImpl(List("bob", "smith"))
+    person
+  }
+
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/groovy-jdk15-failing/build.gradle b/subprojects/gradle-docs/src/samples/testng/groovy-jdk15-failing/build.gradle
new file mode 100644
index 0000000..5592f0b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/groovy-jdk15-failing/build.gradle
@@ -0,0 +1,17 @@
+apply plugin: 'groovy'
+
+sourceCompatibility=1.5
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+	groovy "org.codehaus.groovy:groovy-all:1.7.0"
+
+    testCompile 'org.testng:testng:5.8:jdk15'
+}
+
+test {
+   useTestNG() 
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/groovy-jdk15-failing/src/main/groovy/org/gradle/Ok.groovy b/subprojects/gradle-docs/src/samples/testng/groovy-jdk15-failing/src/main/groovy/org/gradle/Ok.groovy
new file mode 100644
index 0000000..2748554
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/groovy-jdk15-failing/src/main/groovy/org/gradle/Ok.groovy
@@ -0,0 +1,4 @@
+package org.gradle;
+public class Ok{
+ String test = "dummy";
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/groovy-jdk15-failing/src/test/groovy/org/gradle/BadTest.groovy b/subprojects/gradle-docs/src/samples/testng/groovy-jdk15-failing/src/test/groovy/org/gradle/BadTest.groovy
new file mode 100644
index 0000000..ba7c845
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/groovy-jdk15-failing/src/test/groovy/org/gradle/BadTest.groovy
@@ -0,0 +1,5 @@
+package org.gradle;
+public class BadTest {
+   @org.testng.annotations.Test
+   public void failingTest() { throw new IllegalArgumentException('broken'); }
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/groovy-jdk15-passing/build.gradle b/subprojects/gradle-docs/src/samples/testng/groovy-jdk15-passing/build.gradle
new file mode 100644
index 0000000..5592f0b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/groovy-jdk15-passing/build.gradle
@@ -0,0 +1,17 @@
+apply plugin: 'groovy'
+
+sourceCompatibility=1.5
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+	groovy "org.codehaus.groovy:groovy-all:1.7.0"
+
+    testCompile 'org.testng:testng:5.8:jdk15'
+}
+
+test {
+   useTestNG() 
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/groovy-jdk15-passing/src/main/groovy/org/gradle/Ok.groovy b/subprojects/gradle-docs/src/samples/testng/groovy-jdk15-passing/src/main/groovy/org/gradle/Ok.groovy
new file mode 100644
index 0000000..2748554
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/groovy-jdk15-passing/src/main/groovy/org/gradle/Ok.groovy
@@ -0,0 +1,4 @@
+package org.gradle;
+public class Ok{
+ String test = "dummy";
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/groovy-jdk15-passing/src/test/groovy/org/gradle/OkTest.groovy b/subprojects/gradle-docs/src/samples/testng/groovy-jdk15-passing/src/test/groovy/org/gradle/OkTest.groovy
new file mode 100644
index 0000000..53aa719
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/groovy-jdk15-passing/src/test/groovy/org/gradle/OkTest.groovy
@@ -0,0 +1,5 @@
+package org.gradle;
+public class OkTest {
+   @org.testng.annotations.Test
+   public void passingTest() { }
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk14-failing/build.gradle b/subprojects/gradle-docs/src/samples/testng/java-jdk14-failing/build.gradle
new file mode 100644
index 0000000..483abf0
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk14-failing/build.gradle
@@ -0,0 +1,16 @@
+apply plugin: 'java'
+
+sourceCompatibility=1.4
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile 'org.testng:testng:5.8:jdk14'
+}
+
+test {
+    useTestNG()
+    scanForTestClasses = false
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk14-failing/src/main/java/org/gradle/Ok.java b/subprojects/gradle-docs/src/samples/testng/java-jdk14-failing/src/main/java/org/gradle/Ok.java
new file mode 100644
index 0000000..2748554
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk14-failing/src/main/java/org/gradle/Ok.java
@@ -0,0 +1,4 @@
+package org.gradle;
+public class Ok{
+ String test = "dummy";
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk14-failing/src/test/java/org/gradle/BadTest.java b/subprojects/gradle-docs/src/samples/testng/java-jdk14-failing/src/test/java/org/gradle/BadTest.java
new file mode 100644
index 0000000..a131c33
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk14-failing/src/test/java/org/gradle/BadTest.java
@@ -0,0 +1,7 @@
+package org.gradle;
+public class BadTest {
+   /** 
+    * @testng.test 
+    */
+   public void failingTest() { throw new IllegalArgumentException("broken"); }
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk14-passing/build.gradle b/subprojects/gradle-docs/src/samples/testng/java-jdk14-passing/build.gradle
new file mode 100644
index 0000000..483abf0
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk14-passing/build.gradle
@@ -0,0 +1,16 @@
+apply plugin: 'java'
+
+sourceCompatibility=1.4
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile 'org.testng:testng:5.8:jdk14'
+}
+
+test {
+    useTestNG()
+    scanForTestClasses = false
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk14-passing/src/main/java/org/gradle/Ok.java b/subprojects/gradle-docs/src/samples/testng/java-jdk14-passing/src/main/java/org/gradle/Ok.java
new file mode 100644
index 0000000..2748554
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk14-passing/src/main/java/org/gradle/Ok.java
@@ -0,0 +1,4 @@
+package org.gradle;
+public class Ok{
+ String test = "dummy";
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk14-passing/src/test/java/org/gradle/OkTest.java b/subprojects/gradle-docs/src/samples/testng/java-jdk14-passing/src/test/java/org/gradle/OkTest.java
new file mode 100644
index 0000000..706badc
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk14-passing/src/test/java/org/gradle/OkTest.java
@@ -0,0 +1,7 @@
+package org.gradle;
+public class OkTest {
+   /** 
+    * @testng.test 
+    */
+   public void passingTest() { }
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk15-failing/build.gradle b/subprojects/gradle-docs/src/samples/testng/java-jdk15-failing/build.gradle
new file mode 100644
index 0000000..2647d76
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk15-failing/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'java'
+
+sourceCompatibility=1.5
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile 'org.testng:testng:5.8:jdk15'
+}
+
+test {
+   useTestNG()
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk15-failing/src/main/java/org/gradle/Ok.java b/subprojects/gradle-docs/src/samples/testng/java-jdk15-failing/src/main/java/org/gradle/Ok.java
new file mode 100644
index 0000000..2748554
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk15-failing/src/main/java/org/gradle/Ok.java
@@ -0,0 +1,4 @@
+package org.gradle;
+public class Ok{
+ String test = "dummy";
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk15-failing/src/test/java/org/gradle/BadTest.java b/subprojects/gradle-docs/src/samples/testng/java-jdk15-failing/src/test/java/org/gradle/BadTest.java
new file mode 100644
index 0000000..e9db4e6
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk15-failing/src/test/java/org/gradle/BadTest.java
@@ -0,0 +1,5 @@
+package org.gradle;
+public class BadTest {
+   @org.testng.annotations.Test
+   public void failingTest() { throw new IllegalArgumentException("broken"); }
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk15-failing/src/test/java/org/gradle/BrokenAfterSuite.java b/subprojects/gradle-docs/src/samples/testng/java-jdk15-failing/src/test/java/org/gradle/BrokenAfterSuite.java
new file mode 100644
index 0000000..8a70095
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk15-failing/src/test/java/org/gradle/BrokenAfterSuite.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+import org.testng.annotations.AfterSuite;
+
+public class BrokenAfterSuite {
+    @AfterSuite
+    public void cleanup() {
+        throw new RuntimeException("broken");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk15-failing/src/test/java/org/gradle/TestWithBrokenSetup.java b/subprojects/gradle-docs/src/samples/testng/java-jdk15-failing/src/test/java/org/gradle/TestWithBrokenSetup.java
new file mode 100644
index 0000000..b476c46
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk15-failing/src/test/java/org/gradle/TestWithBrokenSetup.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+public class TestWithBrokenSetup {
+    @BeforeMethod
+    public void setup() {
+        throw new RuntimeException("broken");
+    }
+
+    @Test
+    public void okTest() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing-no-report/build.gradle b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing-no-report/build.gradle
new file mode 100644
index 0000000..b7a1abe
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing-no-report/build.gradle
@@ -0,0 +1,16 @@
+apply plugin: 'java'
+
+sourceCompatibility=1.5
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile 'org.testng:testng:5.8:jdk15'
+}
+
+test {
+    useTestNG() 
+	disableTestReport()
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing-no-report/src/main/java/org/gradle/Ok.java b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing-no-report/src/main/java/org/gradle/Ok.java
new file mode 100644
index 0000000..2748554
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing-no-report/src/main/java/org/gradle/Ok.java
@@ -0,0 +1,4 @@
+package org.gradle;
+public class Ok{
+ String test = "dummy";
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing-no-report/src/test/java/org/gradle/OkTest.java b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing-no-report/src/test/java/org/gradle/OkTest.java
new file mode 100644
index 0000000..53aa719
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing-no-report/src/test/java/org/gradle/OkTest.java
@@ -0,0 +1,5 @@
+package org.gradle;
+public class OkTest {
+   @org.testng.annotations.Test
+   public void passingTest() { }
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/build.gradle b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/build.gradle
new file mode 100644
index 0000000..f5493ed
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/build.gradle
@@ -0,0 +1,15 @@
+apply plugin: 'java'
+
+sourceCompatibility=1.5
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile 'org.testng:testng:5.8:jdk15'
+}
+
+test {
+   useTestNG() 
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/main/java/org/gradle/Ok.java b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/main/java/org/gradle/Ok.java
new file mode 100644
index 0000000..2748554
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/main/java/org/gradle/Ok.java
@@ -0,0 +1,4 @@
+package org.gradle;
+public class Ok{
+ String test = "dummy";
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/AbstractTest.java b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/AbstractTest.java
new file mode 100644
index 0000000..0c0884c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/AbstractTest.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+import org.testng.annotations.Test;
+
+ at Test
+public abstract class AbstractTest {
+    public void ok() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/ConcreteTest.java b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/ConcreteTest.java
new file mode 100644
index 0000000..0d67e88
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/ConcreteTest.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;
+
+public class ConcreteTest extends AbstractTest {
+    public void alsoOk() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/OkTest.java b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/OkTest.java
new file mode 100644
index 0000000..8ad2001
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/OkTest.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+public class OkTest {
+    @org.testng.annotations.Test
+    public void passingTest() {
+    }
+
+    @org.testng.annotations.Test(expectedExceptions = RuntimeException.class)
+    public void expectedFailTest() {
+        throw new RuntimeException("broken");
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/SuiteCleanup.java b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/SuiteCleanup.java
new file mode 100644
index 0000000..7c35acf
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/SuiteCleanup.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+import org.testng.annotations.AfterSuite;
+
+public class SuiteCleanup {
+    @AfterSuite
+    public void cleanupSuite() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/SuiteSetup.java b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/SuiteSetup.java
new file mode 100644
index 0000000..bcc5387
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/SuiteSetup.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+import org.testng.annotations.BeforeSuite;
+
+public class SuiteSetup {
+    @BeforeSuite
+    public void setupSuite() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/TestCleanup.java b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/TestCleanup.java
new file mode 100644
index 0000000..12add6e
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/TestCleanup.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+import org.testng.annotations.AfterTest;
+
+public class TestCleanup {
+    @AfterTest
+    public void cleanupTest() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/TestSetup.java b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/TestSetup.java
new file mode 100644
index 0000000..7c7502c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/java-jdk15-passing/src/test/java/org/gradle/TestSetup.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+import org.testng.annotations.BeforeTest;
+
+public class TestSetup {
+    @BeforeTest
+    public void setupTest() {
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/testng/suitexmlbuilder/build.gradle b/subprojects/gradle-docs/src/samples/testng/suitexmlbuilder/build.gradle
new file mode 100644
index 0000000..f4c07b0
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/suitexmlbuilder/build.gradle
@@ -0,0 +1,25 @@
+apply plugin: 'java'
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    testCompile 'org.testng:testng:5.8:jdk15'
+}
+
+test {
+	useTestNG() {
+        suiteXmlBuilder().suite(name: 'testing-testng') {
+            test (name : 'testing-testng', annotations : 'JDK', verbose:'1') {
+                classes([:]) {
+                    'class' (name: 'org.gradle.testng.UserImplTest') {
+                        methods([:]) {
+                            include(name: 'testOkFirstName')
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/suitexmlbuilder/src/main/java/org/gradle/testng/User.java b/subprojects/gradle-docs/src/samples/testng/suitexmlbuilder/src/main/java/org/gradle/testng/User.java
new file mode 100644
index 0000000..06503c7
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/suitexmlbuilder/src/main/java/org/gradle/testng/User.java
@@ -0,0 +1,11 @@
+package org.gradle.testng;
+
+/**
+ * @author Tom Eyckmans
+ */
+public interface User
+{
+    String getFirstName();
+
+    String getLastName();
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/suitexmlbuilder/src/main/java/org/gradle/testng/UserImpl.java b/subprojects/gradle-docs/src/samples/testng/suitexmlbuilder/src/main/java/org/gradle/testng/UserImpl.java
new file mode 100644
index 0000000..eb7bd74
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/suitexmlbuilder/src/main/java/org/gradle/testng/UserImpl.java
@@ -0,0 +1,26 @@
+package org.gradle.testng;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class UserImpl implements User
+{
+    private final String firstName;
+    private final String lastName;
+
+    public UserImpl(String firstName, String lastName)
+    {
+        this.firstName = firstName;
+        this.lastName = lastName;
+    }
+
+    public String getFirstName()
+    {
+        return firstName;
+    }
+
+    public String getLastName()
+    {
+        return lastName;
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/testng/suitexmlbuilder/src/test/java/org/gradle/testng/UserImplTest.java b/subprojects/gradle-docs/src/samples/testng/suitexmlbuilder/src/test/java/org/gradle/testng/UserImplTest.java
new file mode 100644
index 0000000..99deeb8
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/testng/suitexmlbuilder/src/test/java/org/gradle/testng/UserImplTest.java
@@ -0,0 +1,41 @@
+package org.gradle.testng;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.testng.annotations.AfterMethod;
+import static org.testng.Assert.*;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class UserImplTest
+{
+    private UserImpl user;
+
+    private final String okFirstName = "Tom";
+    private final String okLastName = "Eyckmans";
+
+    @BeforeMethod
+    public void setUp() throws Exception
+    {
+        user = new UserImpl(okFirstName, okLastName);
+    }
+
+    @Test
+    public void testOkFirstName()
+    {
+        assertEquals(user.getFirstName(), okFirstName);
+    }
+
+    @Test
+    public void testOkLastName()
+    {
+        assertEquals(user.getLastName(), okLastName);
+    }
+
+    @AfterMethod
+    public void tearDown() throws Exception
+    {
+        user = null;
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/ant/addBehaviourToAntTarget/build.gradle b/subprojects/gradle-docs/src/samples/userguide/ant/addBehaviourToAntTarget/build.gradle
new file mode 100644
index 0000000..83eb4bb
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/ant/addBehaviourToAntTarget/build.gradle
@@ -0,0 +1,5 @@
+ant.importBuild 'build.xml'
+
+hello << {
+    println 'Hello, from Gradle'
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/ant/addBehaviourToAntTarget/build.xml b/subprojects/gradle-docs/src/samples/userguide/ant/addBehaviourToAntTarget/build.xml
new file mode 100644
index 0000000..d48889b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/ant/addBehaviourToAntTarget/build.xml
@@ -0,0 +1,5 @@
+<project>
+    <target name="hello">
+        <echo>Hello, from Ant</echo>
+    </target>
+</project>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/ant/dependsOnAntTarget/build.gradle b/subprojects/gradle-docs/src/samples/userguide/ant/dependsOnAntTarget/build.gradle
new file mode 100644
index 0000000..25047e0
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/ant/dependsOnAntTarget/build.gradle
@@ -0,0 +1,5 @@
+ant.importBuild 'build.xml'
+
+task intro(dependsOn: hello) << {
+    println 'Hello, from Gradle'
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/ant/dependsOnAntTarget/build.xml b/subprojects/gradle-docs/src/samples/userguide/ant/dependsOnAntTarget/build.xml
new file mode 100644
index 0000000..d48889b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/ant/dependsOnAntTarget/build.xml
@@ -0,0 +1,5 @@
+<project>
+    <target name="hello">
+        <echo>Hello, from Ant</echo>
+    </target>
+</project>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/ant/dependsOnTask/build.gradle b/subprojects/gradle-docs/src/samples/userguide/ant/dependsOnTask/build.gradle
new file mode 100644
index 0000000..a6f7649
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/ant/dependsOnTask/build.gradle
@@ -0,0 +1,5 @@
+ant.importBuild 'build.xml'
+
+task intro << {
+    println 'Hello, from Gradle'
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/ant/dependsOnTask/build.xml b/subprojects/gradle-docs/src/samples/userguide/ant/dependsOnTask/build.xml
new file mode 100644
index 0000000..f11e33f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/ant/dependsOnTask/build.xml
@@ -0,0 +1,5 @@
+<project>
+    <target name="hello" depends="intro">
+        <echo>Hello, from Ant</echo>
+    </target>
+</project>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/ant/hello/build.gradle b/subprojects/gradle-docs/src/samples/userguide/ant/hello/build.gradle
new file mode 100644
index 0000000..17c03ab
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/ant/hello/build.gradle
@@ -0,0 +1 @@
+ant.importBuild 'build.xml'
diff --git a/subprojects/gradle-docs/src/samples/userguide/ant/hello/build.xml b/subprojects/gradle-docs/src/samples/userguide/ant/hello/build.xml
new file mode 100644
index 0000000..d48889b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/ant/hello/build.xml
@@ -0,0 +1,5 @@
+<project>
+    <target name="hello">
+        <echo>Hello, from Ant</echo>
+    </target>
+</project>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/ant/properties/build.gradle b/subprojects/gradle-docs/src/samples/userguide/ant/properties/build.gradle
new file mode 100644
index 0000000..6ae2950
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/ant/properties/build.gradle
@@ -0,0 +1,25 @@
+//START SNIPPET set-property
+ant.buildDir = buildDir
+ant.properties.buildDir = buildDir
+ant.properties['buildDir'] = buildDir
+ant.property(name: 'buildDir', location: buildDir)
+//END SNIPPET set-property
+
+ant.importBuild 'build.xml'
+
+//START SNIPPET get-property
+println ant.antProp
+println ant.properties.antProp
+println ant.properties['antProp']
+//END SNIPPET get-property
+
+//START SNIPPET set-reference
+ant.path(id: 'classpath', location: 'libs')
+ant.references.classpath = ant.path(location: 'libs')
+ant.references['classpath'] = ant.path(location: 'libs')
+//END SNIPPET set-reference
+
+//START SNIPPET get-reference
+println ant.references.antPath
+println ant.references['antPath']
+//END SNIPPET get-reference
diff --git a/subprojects/gradle-docs/src/samples/userguide/ant/properties/build.xml b/subprojects/gradle-docs/src/samples/userguide/ant/properties/build.xml
new file mode 100644
index 0000000..c83409e
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/ant/properties/build.xml
@@ -0,0 +1,17 @@
+<project>
+<!-- START SNIPPET set-property -->
+<echo>buildDir = ${buildDir}</echo>
+<!-- END SNIPPET set-property -->
+
+<!-- START SNIPPET get-property -->
+<property name="antProp" value="a property defined in an Ant build"/>
+<!-- END SNIPPET get-property -->
+
+<!-- START SNIPPET set-reference -->
+<path refid="classpath"/>
+<!-- END SNIPPET set-reference -->
+
+<!-- START SNIPPET get-reference -->
+<path id="antPath" location="libs"/>
+<!-- END SNIPPET get-reference -->
+</project>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/ant/taskWithNestedElements/build.gradle b/subprojects/gradle-docs/src/samples/userguide/ant/taskWithNestedElements/build.gradle
new file mode 100644
index 0000000..e3d182f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/ant/taskWithNestedElements/build.gradle
@@ -0,0 +1,8 @@
+task zip << {
+    ant.zip(destfile: 'archive.zip') {
+        fileset(dir: 'src') {
+            include(name: '**.xml')
+            exclude(name: '**.java')
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/ant/taskWithNestedElements/src/test.xml b/subprojects/gradle-docs/src/samples/userguide/ant/taskWithNestedElements/src/test.xml
new file mode 100644
index 0000000..0386e3e
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/ant/taskWithNestedElements/src/test.xml
@@ -0,0 +1 @@
+<someXmlElement/>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/ant/taskWithNestedText/build.gradle b/subprojects/gradle-docs/src/samples/userguide/ant/taskWithNestedText/build.gradle
new file mode 100644
index 0000000..71b720d
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/ant/taskWithNestedText/build.gradle
@@ -0,0 +1,3 @@
+task hello << {
+    ant.echo('hello from Ant')
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/ant/useAntTask/build.gradle b/subprojects/gradle-docs/src/samples/userguide/ant/useAntTask/build.gradle
new file mode 100644
index 0000000..b92b208
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/ant/useAntTask/build.gradle
@@ -0,0 +1,4 @@
+task hello << {
+    String greeting = 'hello from Ant'
+    ant.echo(message: greeting)
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/ant/useAntType/build.gradle b/subprojects/gradle-docs/src/samples/userguide/ant/useAntType/build.gradle
new file mode 100644
index 0000000..b268e7b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/ant/useAntType/build.gradle
@@ -0,0 +1,8 @@
+task list << {
+    def path = ant.path {
+        fileset(dir: 'libs', includes: '*.jar')
+    }
+    path.list().each {
+        println it
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/ant/useExternalAntTask/build.gradle b/subprojects/gradle-docs/src/samples/userguide/ant/useExternalAntTask/build.gradle
new file mode 100644
index 0000000..1d09c14
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/ant/useExternalAntTask/build.gradle
@@ -0,0 +1,10 @@
+task check << {
+    ant.taskdef(resource: 'checkstyletask.properties') {
+        classpath {
+            fileset(dir: 'libs', include: '*.jar')
+        }
+    }
+    ant.checkstyle(config: 'checkstyle.xml') {
+        fileset(dir: 'src')
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/ant/useExternalAntTaskWithConfig/build.gradle b/subprojects/gradle-docs/src/samples/userguide/ant/useExternalAntTaskWithConfig/build.gradle
new file mode 100644
index 0000000..31ff223
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/ant/useExternalAntTaskWithConfig/build.gradle
@@ -0,0 +1,23 @@
+repositories {
+    mavenCentral()
+}
+
+// START SNIPPET define-classpath
+configurations {
+    pmd
+}
+
+dependencies {
+    pmd group: 'pmd', name: 'pmd', version: '4.2.5'
+}
+// END SNIPPET define-classpath
+
+// START SNIPPET use-classpath
+task check << {
+    ant.taskdef(name: 'pmd', classname: 'net.sourceforge.pmd.ant.PMDTask', classpath: configurations.pmd.asPath)
+    ant.pmd(shortFilenames: 'true', failonruleviolation: 'true', rulesetfiles: file('pmd-rules.xml').toURI().toString()) {
+        formatter(type: 'text', toConsole: 'true')
+        fileset(dir: 'src')
+    }
+}
+// END SNIPPET use-classpath
diff --git a/subprojects/gradle-docs/src/samples/userguide/ant/useExternalAntTaskWithConfig/pmd-rules.xml b/subprojects/gradle-docs/src/samples/userguide/ant/useExternalAntTaskWithConfig/pmd-rules.xml
new file mode 100644
index 0000000..43d57af
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/ant/useExternalAntTaskWithConfig/pmd-rules.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<ruleset name="Custom ruleset"
+    xmlns="http://pmd.sf.net/ruleset/1.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd"
+    xsi:noNamespaceSchemaLocation="http://pmd.sf.net/ruleset_xml_schema.xsd">
+  <description>
+  This ruleset checks my code for bad stuff
+  </description>
+</ruleset>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/ant/useExternalAntTaskWithConfig/src/Source.java b/subprojects/gradle-docs/src/samples/userguide/ant/useExternalAntTaskWithConfig/src/Source.java
new file mode 100644
index 0000000..616ce35
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/ant/useExternalAntTaskWithConfig/src/Source.java
@@ -0,0 +1,3 @@
+public class Source {
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/artifacts/configurationHandling/build.gradle b/subprojects/gradle-docs/src/samples/userguide/artifacts/configurationHandling/build.gradle
new file mode 100644
index 0000000..525520b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/artifacts/configurationHandling/build.gradle
@@ -0,0 +1,73 @@
+import org.apache.ivy.plugins.resolver.FileSystemResolver
+
+repositories {
+    add(new FileSystemResolver()) {
+        name = "repo"
+        addArtifactPattern("$projectDir/repo/[organization]/[module]-[revision].[ext]")
+        addIvyPattern("$projectDir/repo/[organization]/ivy-[module]-[revision].xml")
+        checkmodified = true
+    }
+}
+
+//START SNIPPET setup
+configurations {
+    sealife
+    alllife.extendsFrom sealife
+}
+
+dependencies {
+    sealife "sea.mammals:orca:1.0", "sea.fish:shark:1.0", "sea.fish:tuna:1.0"
+    alllife "air.birds:albatros:1.0"
+}
+//END SNIPPET setup
+
+//START SNIPPET dependencies
+task dependencies << {
+    configurations.alllife.dependencies.each { dep -> println dep.name }
+    println()
+    configurations.alllife.allDependencies.each { dep -> println dep.name }
+    println()
+    configurations.alllife.allDependencies.findAll { dep -> dep.name != 'orca' }.each { dep -> println dep.name }
+}
+//END SNIPPET dependencies
+
+//START SNIPPET allFiles
+task allFiles << {
+    configurations.sealife.files.each { file ->
+        println file.name
+    }
+}
+//END SNIPPET allFiles
+
+//START SNIPPET files
+task files << {
+    configurations.sealife.files { dep -> dep.name == 'orca' }.each { file ->
+        println file.name
+    }
+}
+//END SNIPPET files
+
+
+//START SNIPPET copy
+task copy << {
+    configurations.alllife.copyRecursive { dep -> dep.name != 'orca' }.allDependencies.each { dep ->
+        println dep.name
+    }
+    println()
+    configurations.alllife.copy().allDependencies.each { dep ->
+        println dep.name
+    }
+}
+//END SNIPPET copy
+
+//START SNIPPET copyVsFiles
+task copyVsFiles << {
+    configurations.sealife.copyRecursive { dep -> dep.name == 'orca' }.each { file ->
+        println file.name
+    }
+    println()
+    configurations.sealife.files { dep -> dep.name == 'orca' }.each { file ->
+        println file.name
+    }
+}
+//END SNIPPET copyVsFiles
diff --git a/subprojects/gradle-docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.fish/ivy-shark-1.0.xml b/subprojects/gradle-docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.fish/ivy-shark-1.0.xml
new file mode 100644
index 0000000..7f9d066
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.fish/ivy-shark-1.0.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<ivy-module version="1.0">
+    <info
+        organisation="sea.fish"
+        module="shark"
+        revision="1.0"/>
+    <configurations>
+        <conf name="default"/>
+    </configurations>
+    <dependencies>
+        <dependency org="sea.mammals" name="seal" rev="2.0" conf="default"/>
+        <dependency org="sea.fish" name="tuna" rev="1.0" conf="default->default"/>
+    </dependencies>
+</ivy-module>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.fish/ivy-tuna-1.0.xml b/subprojects/gradle-docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.fish/ivy-tuna-1.0.xml
new file mode 100644
index 0000000..772be12
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.fish/ivy-tuna-1.0.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<ivy-module version="1.0">
+    <info
+        organisation="sea.fish"
+        module="tuna"
+        revision="1.0"/>
+    <configurations>
+        <conf name="default"/>
+    </configurations>
+    <dependencies>
+        <dependency org="sea.fish" name="herring" rev="1.0" conf="default"/>
+    </dependencies>
+</ivy-module>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.mammals/ivy-orca-1.0.xml b/subprojects/gradle-docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.mammals/ivy-orca-1.0.xml
new file mode 100644
index 0000000..7f0f277
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/artifacts/configurationHandling/repo/sea.mammals/ivy-orca-1.0.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<ivy-module version="1.0">
+    <info
+        organisation="sea.mammals"
+        module="orca"
+        revision="1.0"/>
+    <configurations>
+        <conf name="default"/>
+    </configurations>
+    <dependencies>
+        <dependency org="sea.mammals" name="seal" rev="1.0" conf="default"/>
+    </dependencies>
+</ivy-module>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/artifacts/defineConfiguration/build.gradle b/subprojects/gradle-docs/src/samples/userguide/artifacts/defineConfiguration/build.gradle
new file mode 100644
index 0000000..cc15d61
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/artifacts/defineConfiguration/build.gradle
@@ -0,0 +1,25 @@
+// START SNIPPET define-configuration
+configurations {
+    compile
+}
+// END SNIPPET define-configuration
+
+// START SNIPPET lookup-configuration
+println configurations.compile.name
+println configurations['compile'].name
+// END SNIPPET lookup-configuration
+
+// START SNIPPET configure-configuration
+configurations {
+    compile {
+        description = 'compile classpath'
+        transitive = true
+    }
+    runtime {
+        extendsFrom compile
+    }
+}
+configurations.compile {
+    description = 'compile classpath'
+}
+// END SNIPPET configure-configuration
diff --git a/subprojects/gradle-docs/src/samples/userguide/artifacts/defineRepository/build.gradle b/subprojects/gradle-docs/src/samples/userguide/artifacts/defineRepository/build.gradle
new file mode 100644
index 0000000..8c3666d
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/artifacts/defineRepository/build.gradle
@@ -0,0 +1,58 @@
+//START SNIPPET maven-central
+repositories {
+    mavenCentral()
+}
+//END SNIPPET maven-central
+
+//START SNIPPET maven-password-protected-repo
+org.apache.ivy.util.url.CredentialsStore.INSTANCE.addCredentials("REALM", "HOST", "USER", "PASSWORD"); 
+//END SNIPPET maven-password-protected-repo
+
+//START SNIPPET maven-central-jar-repo
+repositories {
+    mavenCentral name: 'single-jar-repo', urls: "http://repo.mycompany.com/jars"
+    mavenCentral name: 'multi-jar-repos', urls: ["http://repo.mycompany.com/jars1", "http://repo.mycompany.com/jars2"]
+}
+//END SNIPPET maven-central-jar-repo
+
+//START SNIPPET maven-like-repo
+repositories {
+    mavenRepo urls: "http://repo.mycompany.com/maven2"
+}
+//END SNIPPET maven-like-repo
+
+//START SNIPPET maven-like-repo-with-jar-repo
+repositories {
+    mavenRepo urls: ["http://repo2.mycompany.com/maven2", "http://repo.mycompany.com/jars"]
+}
+//END SNIPPET maven-like-repo-with-jar-repo
+
+//START SNIPPET flat-dir
+//START SNIPPET flat-dir-multi
+repositories {
+    flatDir name: 'localRepository', dirs: 'lib'
+//END SNIPPET flat-dir
+    flatDir dirs: ['lib1', 'lib2']
+//START SNIPPET flat-dir
+}
+//END SNIPPET flat-dir
+//END SNIPPET flat-dir-multi
+
+
+task lookup << {
+    //START SNIPPET lookup-resolver
+    println repositories.localRepository.name
+    println repositories['localRepository'].name
+    //END SNIPPET lookup-resolver
+}
+
+//START SNIPPET configure-resolver
+repositories {
+    localRepository {
+        addArtifactPattern(file('lib').absolutePath + '/[name]/[revision]/[name]-[revision].[ext]')
+    }
+}
+repositories.localRepository {
+    addArtifactPattern(file('lib').absolutePath + '/[name]/[revision]/[name]-[revision].[ext]')
+}
+//END SNIPPET configure-resolver
diff --git a/subprojects/gradle-docs/src/samples/userguide/artifacts/excludesAndClassifiers/build.gradle b/subprojects/gradle-docs/src/samples/userguide/artifacts/excludesAndClassifiers/build.gradle
new file mode 100644
index 0000000..bbf2d90
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/artifacts/excludesAndClassifiers/build.gradle
@@ -0,0 +1,45 @@
+apply plugin: 'java'
+
+// START SNIPPET file-system-resolver
+repositories {
+    add(new org.apache.ivy.plugins.resolver.FileSystemResolver()) {
+        name = 'repo'
+        addIvyPattern "$projectDir/repo/[organisation]/[module]-ivy-[revision].xml"
+        addArtifactPattern "$projectDir/repo/[organisation]/[module]-[revision](-[classifier]).[ext]"
+        descriptor = 'optional'
+        checkmodified = true
+    }
+}
+// END SNIPPET file-system-resolver
+
+// START SNIPPET exclude-dependencies
+configurations {
+// END SNIPPET exclude-dependencies
+    otherConf
+// START SNIPPET exclude-dependencies
+    compile.exclude module: 'commons'
+    all*.exclude group: 'org.gradle.test.excludes', module: 'reports'
+}
+
+dependencies {
+	compile("org.gradle.test.excludes:api:1.0") {
+	    exclude module: 'shared'
+	}
+// END SNIPPET exclude-dependencies
+    runtime 'org.gradle.test.excludes:other-api:1.0'
+// START SNIPPET classifier
+	compile "org.gradle.test.classifiers:service:1.0:jdk15 at jar"
+    otherConf group: 'org.gradle.test.classifiers', name: 'service', version: '1.0', classifier: 'jdk14'
+// END SNIPPET classifier
+// START SNIPPET exclude-dependencies
+}
+// END SNIPPET exclude-dependencies
+
+task resolveCompile << {
+	println configurations.compile.resolve()
+	println configurations.otherConf.resolve()
+}
+
+task resolveRuntime << {
+	println configurations.runtime.resolve()
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/artifacts/excludesAndClassifiers/repo/org.gradle.test.excludes/api-ivy-1.0.xml b/subprojects/gradle-docs/src/samples/userguide/artifacts/excludesAndClassifiers/repo/org.gradle.test.excludes/api-ivy-1.0.xml
new file mode 100644
index 0000000..cb58566
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/artifacts/excludesAndClassifiers/repo/org.gradle.test.excludes/api-ivy-1.0.xml
@@ -0,0 +1,8 @@
+<ivy-module version="2.0">
+    <info organisation="org.gradle.test.excludes" module="api" revision="1.0"/>
+    <dependencies>
+        <dependency org="org.gradle.test.excludes" name="commons" rev="1.0"/>
+        <dependency org="org.gradle.test.excludes" name="reports" rev="1.0"/>
+        <dependency org="org.gradle.test.excludes" name="shared" rev="1.0"/>
+    </dependencies>
+</ivy-module>
diff --git a/subprojects/gradle-docs/src/samples/userguide/artifacts/excludesAndClassifiers/repo/org.gradle.test.excludes/other-api-ivy-1.0.xml b/subprojects/gradle-docs/src/samples/userguide/artifacts/excludesAndClassifiers/repo/org.gradle.test.excludes/other-api-ivy-1.0.xml
new file mode 100644
index 0000000..535b072
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/artifacts/excludesAndClassifiers/repo/org.gradle.test.excludes/other-api-ivy-1.0.xml
@@ -0,0 +1,6 @@
+<ivy-module version="2.0">
+    <info organisation="org.gradle.test.excludes" module="other-api" revision="1.0"/>
+    <dependencies>
+        <dependency org="org.gradle.test.excludes" name="shared" rev="1.0"/>
+    </dependencies>
+</ivy-module>
diff --git a/subprojects/gradle-docs/src/samples/userguide/artifacts/externalDependencies/build.gradle b/subprojects/gradle-docs/src/samples/userguide/artifacts/externalDependencies/build.gradle
new file mode 100644
index 0000000..0b1440b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/artifacts/externalDependencies/build.gradle
@@ -0,0 +1,92 @@
+repositories {
+    mavenCentral()
+}
+
+//START SNIPPET define-dependency
+configurations {
+    compile
+}
+
+dependencies {
+    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
+}
+//END SNIPPET define-dependency
+
+//START SNIPPET use-configuration
+task listJars << {
+    configurations.compile.each { File file -> println file.name }
+}
+//END SNIPPET use-configuration
+
+configurations {
+    runtime
+}
+
+//START SNIPPET module-dependencies
+dependencies {
+    runtime group: 'org.springframework', name: 'spring-core', version: '2.5'
+    runtime 'org.springframework:spring-core:2.5', 'org.springframework:spring-aop:2.5'
+    runtime(
+        [group: 'org.springframework', name: 'spring-core', version: '2.5'],
+        [group: 'org.springframework', name: 'spring-aop', version: '2.5']
+    )
+    runtime('org.hibernate:hibernate:3.0.5') {
+        transitive = true
+    }
+    runtime group: 'org.hibernate', name: 'hibernate', version: '3.0.5', transitive: true
+    runtime(group: 'org.hibernate', name: 'hibernate', version: '3.0.5') {
+        transitive = true
+    }
+}
+//END SNIPPET module-dependencies
+
+//START SNIPPET dependencies-with-empty-attributes
+dependencies {
+    runtime ":junit:4.4", ":testng"
+    runtime name: 'testng' 
+}
+//END SNIPPET dependencies-with-empty-attributes
+
+//START SNIPPET dependency-configurations
+dependencies {
+    runtime group: 'org.somegroup', name: 'somedependency', version: '1.0', configuration: 'someConfiguration'
+}
+//END SNIPPET dependency-configurations
+
+
+//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'
+}
+//END SNIPPET artifact-only
+
+//START SNIPPET client-modules
+dependencies {
+    runtime module("org.codehaus.groovy:groovy-all:1.7.0") {
+        dependency("commons-cli:commons-cli:1.0") {
+            transitive = false
+        }
+        module(group: 'org.apache.ant', name: 'ant', version: '1.7.0') {
+            dependencies "org.apache.ant:ant-launcher:1.7.0 at jar", "org.apache.ant:ant-junit:1.7.0"
+        }
+    }
+}
+//END SNIPPET client-modules
+
+//START SNIPPET file-dependencies
+dependencies {
+    runtime files('libs/a.jar', 'libs/b.jar')
+    runtime fileTree(dir: 'libs', include: '*.jar')
+}
+//END SNIPPET file-dependencies
+
+//START SNIPPET list-grouping
+List groovy = ["org.codehaus.groovy:groovy-all:1.7.0 at jar",
+               "commons-cli:commons-cli:1.0 at jar",
+               "org.apache.ant:ant:1.7.0 at jar"]
+List hibernate = ['org.hibernate:hibernate:3.0.5 at jar', 'somegroup:someorg:1.0 at jar']
+dependencies {
+	runtime groovy, hibernate
+}
+//END SNIPPET list-grouping
diff --git a/subprojects/gradle-docs/src/samples/userguide/artifacts/generatedFileDependencies/build.gradle b/subprojects/gradle-docs/src/samples/userguide/artifacts/generatedFileDependencies/build.gradle
new file mode 100644
index 0000000..1b754ff
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/artifacts/generatedFileDependencies/build.gradle
@@ -0,0 +1,19 @@
+configurations {
+    compile
+}
+
+// START SNIPPET generated-file-dependencies
+dependencies {
+    compile files("$buildDir/classes") {
+        builtBy 'compile'
+    }
+}
+
+task compile << {
+    println 'compiling classes'
+}
+
+task list(dependsOn: configurations.compile) << {
+    println "classpath = ${configurations.compile.collect {File file -> file.name}}"
+}
+// END SNIPPET generated-file-dependencies
diff --git a/subprojects/gradle-docs/src/samples/userguide/artifacts/maven/build.gradle b/subprojects/gradle-docs/src/samples/userguide/artifacts/maven/build.gradle
new file mode 100644
index 0000000..f7206ee
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/artifacts/maven/build.gradle
@@ -0,0 +1,83 @@
+apply plugin: 'java'
+//START SNIPPET upload-file
+apply plugin: 'maven'
+
+//START SNIPPET builder
+//START SNIPPET customize-pom
+//START SNIPPET multiple-poms
+uploadArchives {
+    repositories.mavenDeployer {
+        repository(url: "file://localhost/tmp/myRepo/")
+//END SNIPPET upload-file
+//END SNIPPET multiple-poms
+//END SNIPPET builder
+        pom.version = '1.0Maven'
+        pom.artifactId = 'myMavenName'
+//END SNIPPET customize-pom
+//START SNIPPET builder
+        pom.project {
+            licenses {
+                license {
+                    name 'The Apache Software License, Version 2.0'
+                    url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+                    distribution 'repo'
+                }
+            }
+        }
+//END SNIPPET builder
+//START SNIPPET multiple-poms
+        addFilter('api') {artifact, file ->
+            artifact.name == 'api'
+        }
+        addFilter('service') {artifact, file ->
+            artifact.name == 'service'
+        }
+        pom('api').version = 'mySpecialMavenVersion'
+//START SNIPPET customize-pom
+//START SNIPPET upload-file
+//START SNIPPET builder
+    }
+}
+//END SNIPPET customize-pom
+//END SNIPPET multiple-poms
+//END SNIPPET upload-file
+//END SNIPPET builder
+
+//START SNIPPET upload-with-ssh
+configurations {
+    deployerJars
+}
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    deployerJars "org.apache.maven.wagon:wagon-ssh:1.0-beta-2"
+}
+
+uploadArchives {
+    repositories.mavenDeployer {
+        name = 'sshDeployer' // optional
+        configuration = configurations.deployerJars
+        repository(url: "scp://repos.mycompany.com/releases") {
+            authentication(userName: "me", password: "myPassword")
+        }
+    }
+}
+//END SNIPPET upload-with-ssh
+
+//START SNIPPET customize-installer
+configure(install.repositories.mavenInstaller) {
+    pom.project {
+        version '1.0Maven'
+        artifactId 'myName'
+    }
+}
+//END SNIPPET customize-installer
+
+//START SNIPPET mappings
+task mappings << {
+    println conf2ScopeMappings.mappings
+}
+//END SNIPPET mappings
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/artifacts/uploading/build.gradle b/subprojects/gradle-docs/src/samples/userguide/artifacts/uploading/build.gradle
new file mode 100644
index 0000000..de34000
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/artifacts/uploading/build.gradle
@@ -0,0 +1,28 @@
+apply plugin: 'java'
+
+//START SNIPPET assign-artifact
+task myJar(type: Jar)
+
+artifacts {
+    archives myJar
+}
+//END SNIPPET assign-artifact
+
+//START SNIPPET uploading
+repositories {
+    flatDir(name: 'fileRepo', dirs: "$projectDir/repo")
+}
+
+uploadArchives {
+    uploadDescriptor = false
+    repositories {
+        add project.repositories.fileRepo
+        add(new org.apache.ivy.plugins.resolver.SshResolver()) {
+            name = 'sshRepo'
+            user = 'username'
+            userPassword = 'pw'
+            host = "http://repo.mycompany.com"
+        }
+    }
+}
+//END SNIPPET uploading
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/basic/build.gradle b/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/basic/build.gradle
new file mode 100644
index 0000000..44ee863
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/basic/build.gradle
@@ -0,0 +1,9 @@
+println 'This is executed during the configuration phase.'
+
+task configured {
+    println 'This is also executed during the configuration phase.'
+}
+
+task test << {
+    println 'This is executed during the execution phase.'
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/basic/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/basic/settings.gradle
new file mode 100644
index 0000000..84172a8
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/basic/settings.gradle
@@ -0,0 +1 @@
+println 'This is executed during the initialization phase.'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/buildProjectEvaluateEvents/build.gradle b/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/buildProjectEvaluateEvents/build.gradle
new file mode 100644
index 0000000..bebf53c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/buildProjectEvaluateEvents/build.gradle
@@ -0,0 +1,11 @@
+// START SNIPPET evaluate-events
+gradle.afterProject {project, projectState ->
+    if (projectState.failure) {
+        println "Evaluation of $project FAILED"
+    } else {
+        println "Evaluation of $project succeeded"
+    }
+}
+// END SNIPPET evaluate-events
+
+task test
diff --git a/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/buildProjectEvaluateEvents/projectB.gradle b/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/buildProjectEvaluateEvents/projectB.gradle
new file mode 100644
index 0000000..e009d10
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/buildProjectEvaluateEvents/projectB.gradle
@@ -0,0 +1 @@
+throw new RuntimeException('broken')
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/buildProjectEvaluateEvents/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/buildProjectEvaluateEvents/settings.gradle
new file mode 100644
index 0000000..d75d4b8
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/buildProjectEvaluateEvents/settings.gradle
@@ -0,0 +1,3 @@
+include 'projectA', 'projectB'
+
+project(':projectB').buildFileName = '../projectB.gradle'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/projectEvaluateEvents/build.gradle b/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/projectEvaluateEvents/build.gradle
new file mode 100644
index 0000000..977b632
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/projectEvaluateEvents/build.gradle
@@ -0,0 +1,16 @@
+// START SNIPPET after-evaluate
+allprojects {
+    afterEvaluate { project ->
+        if (project.hasTests) {
+            println "Adding test task to $project"
+            project.task('test') << {
+                println "Running tests for $project"
+            }
+        }
+    }
+}
+// END SNIPPET after-evaluate
+
+allprojects {
+    hasTests = false
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/projectEvaluateEvents/projectA.gradle b/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/projectEvaluateEvents/projectA.gradle
new file mode 100644
index 0000000..db657cc
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/projectEvaluateEvents/projectA.gradle
@@ -0,0 +1 @@
+hasTests = true
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/projectEvaluateEvents/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/projectEvaluateEvents/settings.gradle
new file mode 100644
index 0000000..12ee626
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/projectEvaluateEvents/settings.gradle
@@ -0,0 +1,3 @@
+include 'projectA', 'projectB'
+
+project(':projectA').buildFileName = '../projectA.gradle'
diff --git a/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/taskCreationEvents/build.gradle b/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/taskCreationEvents/build.gradle
new file mode 100644
index 0000000..09281cd
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/taskCreationEvents/build.gradle
@@ -0,0 +1,7 @@
+tasks.whenTaskAdded { task ->
+    task.srcDir = 'src/main/java'
+}
+
+task a
+
+println "source dir is $a.srcDir"
diff --git a/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/taskExecutionEvents/build.gradle b/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/taskExecutionEvents/build.gradle
new file mode 100644
index 0000000..f8b085d
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/buildlifecycle/taskExecutionEvents/build.gradle
@@ -0,0 +1,18 @@
+task ok
+
+task broken(dependsOn: ok) << {
+    throw new RuntimeException('broken')
+}
+
+gradle.taskGraph.beforeTask { Task task ->
+    println "executing $task ..."
+}
+
+gradle.taskGraph.afterTask { Task task, TaskState state ->
+    if (state.failure) {
+        println "FAILED"
+    }
+    else {
+        println "done"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/files/archiveNaming/build.gradle b/subprojects/gradle-docs/src/samples/userguide/files/archiveNaming/build.gradle
new file mode 100644
index 0000000..a9b12d7
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/files/archiveNaming/build.gradle
@@ -0,0 +1,11 @@
+apply plugin: 'java'
+
+version = 1.0
+
+task myZip(type: Zip) {
+    from 'somedir'
+}
+
+println myZip.archiveName
+println relativePath(myZip.destinationDir)
+println relativePath(myZip.archivePath)
diff --git a/subprojects/gradle-docs/src/samples/userguide/files/archiveNaming/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/files/archiveNaming/settings.gradle
new file mode 100644
index 0000000..ee8b300
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/files/archiveNaming/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'zipProject'
diff --git a/subprojects/gradle-docs/src/samples/userguide/files/archiveNaming/somedir/file.txt b/subprojects/gradle-docs/src/samples/userguide/files/archiveNaming/somedir/file.txt
new file mode 100644
index 0000000..4a4155f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/files/archiveNaming/somedir/file.txt
@@ -0,0 +1,2 @@
+something to copy
+
diff --git a/subprojects/gradle-docs/src/samples/userguide/files/archives/build.gradle b/subprojects/gradle-docs/src/samples/userguide/files/archives/build.gradle
new file mode 100644
index 0000000..4d922ee
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/files/archives/build.gradle
@@ -0,0 +1,21 @@
+// START SNIPPET zip
+apply plugin: 'java'
+
+task zip(type: Zip) {
+    from 'src/dist'
+    into('libs') {
+        from configurations.runtime
+    }
+}
+// END SNIPPET zip
+
+// START SNIPPET tar
+apply plugin: 'java'
+
+task tar(type: Tar) {
+    from 'src/dist'
+    into('libs') {
+        from configurations.runtime
+    }
+}
+// END SNIPPET tar
diff --git a/subprojects/gradle-docs/src/samples/userguide/files/copy/build.gradle b/subprojects/gradle-docs/src/samples/userguide/files/copy/build.gradle
new file mode 100644
index 0000000..85c0815
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/files/copy/build.gradle
@@ -0,0 +1,97 @@
+// START SNIPPET copy-task
+task copyTask(type: Copy) {
+    from 'src/main/webapp'
+    into 'build/explodedWar'
+}
+// END SNIPPET copy-task
+
+// START SNIPPET copy-task-with-patterns
+task copyTaskWithPatterns(type: Copy) {
+    from 'src/main/webapp'
+    into 'build/explodedWar'
+    include '**/*.html'
+    include '**/*.jsp'
+    exclude { details -> details.file.name.endsWith('.html') && details.file.text.contains('staging') }
+}
+// END SNIPPET copy-task-with-patterns
+
+// START SNIPPET copy-task-2
+task anotherCopyTask(type: Copy) {
+    // Copy everything under src/main/webapp
+    from 'src/main/webapp'
+    // Copy a single file
+    from 'src/staging/index.html'
+    // Copy the contents of a Zip file
+    from zipTree('src/main/assets.zip')
+    // Determine the destination directory later
+    into { getDestDir() }
+}
+// END SNIPPET copy-task-2
+
+def getDestDir() {
+    file('some-dir')
+}
+
+// START SNIPPET copy-method
+task copyMethod << {
+    copy {
+        from 'src/main/webapp'
+        into 'build/explodedWar'
+        include '**/*.html'
+        include '**/*.jsp'
+    }
+}
+// END SNIPPET copy-method
+
+configurations { runtime }
+
+// START SNIPPET nested-specs
+task nestedSpecs(type: Copy) {
+    into 'build/explodedWar'
+    exclude '**/*staging*'
+    from('src/dist') {
+        include '**/*.html'
+    }
+    into('libs') {
+        from configurations.runtime
+    }
+}
+// END SNIPPET nested-specs
+
+// START SNIPPET rename-files
+task rename(type: Copy) {
+    from 'src/main/webapp'
+    into 'build/explodedWar'
+    // Use a closure to map the file name
+    rename { String fileName ->
+        fileName.replace('-staging-', '')
+    }
+    // Use a regular expression to map the file name
+    rename '(.+)-staging-(.+)', '$1$2'
+    rename(/(.+)-staging-(.+)/, '$1$2')
+}
+// END SNIPPET rename-files
+
+// START SNIPPET filter-files
+import org.apache.tools.ant.filters.FixCrLfFilter
+import org.apache.tools.ant.filters.ReplaceTokens
+
+task filter(type: Copy) {
+    from 'src/main/webapp'
+    into 'build/explodedWar'
+    // Substitute property references in files
+    expand(copyright: '2009', version: '2.3.1')
+    expand(project.properties)
+    // Use some of the filters provided by Ant
+    filter(FixCrLfFilter)
+    filter(ReplaceTokens, tokens: [copyright: '2009', version: '2.3.1'])
+    // Use a closure to filter each line
+    filter { String line ->
+        "[$line]"
+    }
+}
+// END SNIPPET filter-files
+
+task test {
+    dependsOn tasks.findAll { it.name != 'test' }
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/files/file/build.gradle b/subprojects/gradle-docs/src/samples/userguide/files/file/build.gradle
new file mode 100644
index 0000000..3473162
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/files/file/build.gradle
@@ -0,0 +1,8 @@
+// Using a relative path
+File configFile = file('src/config.xml')
+
+// Using an absolute path
+configFile = file(configFile.absolutePath)
+
+// Using a File object with a relative path
+configFile = file(new File('src/config.xml'))
diff --git a/subprojects/gradle-docs/src/samples/userguide/files/fileCollections/build.gradle b/subprojects/gradle-docs/src/samples/userguide/files/fileCollections/build.gradle
new file mode 100644
index 0000000..719a352
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/files/fileCollections/build.gradle
@@ -0,0 +1,51 @@
+// START SNIPPET simple-params
+FileCollection collection = files('src/file1.txt', new File('src/file2.txt'), ['src/file3.txt', 'src/file4.txt'])
+// END SNIPPET simple-params
+
+file('src').mkdirs()
+file('src/dir1').mkdirs()
+file('src/file1.txt').mkdirs()
+file('src2').mkdirs()
+file('src2/dir1').mkdirs()
+file('src2/dir2').mkdirs()
+
+// START SNIPPET closure
+task list << {
+    File srcDir
+
+    // Create a file collection using a closure
+    collection = files { srcDir.listFiles() }
+
+    srcDir = file('src')
+    println "Contents of $srcDir.name"
+    collection.collect { relativePath(it) }.sort().each { println it }
+
+    srcDir = file('src2')
+    println "Contents of $srcDir.name"
+    collection.collect { relativePath(it) }.sort().each { println it }
+}
+// END SNIPPET closure
+
+task usage << {
+collection = files('src/file1.txt')
+
+//START SNIPPET usage
+// Iterate over the files in the collection
+collection.each {File file ->
+    println file.name
+}
+
+// Convert the collection to various types
+Set set = collection.files
+Set set2 = collection as Set
+List list = collection as List
+String path = collection.asPath
+File file = collection.singleFile
+File file2 = collection as File
+
+// Add and subtract collections
+def union = collection + files('src/file3.txt')
+def different = collection - files('src/file3.txt')
+
+//END SNIPPET usage
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/files/fileTrees/build.gradle b/subprojects/gradle-docs/src/samples/userguide/files/fileTrees/build.gradle
new file mode 100644
index 0000000..6bea9af
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/files/fileTrees/build.gradle
@@ -0,0 +1,50 @@
+// START SNIPPET define
+// Create a file tree with a base directory
+FileTree tree = fileTree(dir: 'src/main')
+
+// Add include and exclude patterns to the tree
+tree.include '**/*.java'
+tree.exclude '**/Abstract*'
+
+// Create a tree using path
+tree = fileTree('src').include('**/*.java')
+
+// Create a tree using closure
+tree = fileTree {
+    from 'src'
+    include '**/*.java'
+}
+
+// Create a tree using a map
+tree = fileTree(dir: 'src', include: '**/*.java')
+tree = fileTree(dir: 'src', includes: ['**/*.java', '**/*.xml'])
+tree = fileTree(dir: 'src', include: '**/*.java', exclude: '**/*test*/**')
+// END SNIPPET define
+
+// START SNIPPET use
+// Iterate over the contents of a tree
+tree.each {File file ->
+    println file
+}
+
+// Filter a tree
+FileTree filtered = tree.matching {
+    include 'org/gradle/api/**'
+}
+
+// Add trees together
+FileTree sum = tree + fileTree(dir: 'src/test')
+
+// Visit the elements of the tree
+tree.visit {element ->
+    println "$element.relativePath => $element.file"
+}
+// END SNIPPET use
+
+// START SNIPPET archive-trees
+// Create a ZIP file tree using path
+FileTree zip = zipTree('someFile.zip')
+
+// Create a TAR file tree using path
+FileTree tar = tarTree('someFile.tar')
+// END SNIPPET archive-trees
diff --git a/subprojects/gradle-docs/src/samples/userguide/files/inputFiles/build.gradle b/subprojects/gradle-docs/src/samples/userguide/files/inputFiles/build.gradle
new file mode 100644
index 0000000..ce7481d
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/files/inputFiles/build.gradle
@@ -0,0 +1,44 @@
+task compile(type: Compile)
+
+// START SNIPPET set-input-files
+// Use a File object to specify the source directory
+compile {
+    source = file('src/main/java')
+}
+
+// Use a String path to specify the source directory
+compile {
+    source = 'src/main/java'
+}
+
+// Use a collection to specify multiple source directories
+compile {
+    source = ['src/main/java', '../shared/java']
+}
+
+// Use a FileCollection (or FileTree in this case) to specify the source files
+compile {
+    source = fileTree(dir: 'src/main/java').matching { include 'org/gradle/api/**' }
+}
+
+// Using a closure to specify the source files.
+compile {
+    source = {
+        // Use the contents of each zip file in the src dir
+        file('src').listFiles().findAll {it.name.endsWith('.zip')}.collect { zipTree(it) }
+    }
+}
+// END SNIPPET set-input-files
+
+// START SNIPPET add-input-files
+compile {
+    // Add some source directories use String paths
+    source 'src/main/java', 'src/main/groovy'
+
+    // Add a source directory using a File object
+    source file('../shared/java')
+
+    // Add some source directories using a closure
+    source { file('src/test/').listFiles() }
+}
+// END SNIPPET add-input-files
diff --git a/subprojects/gradle-docs/src/samples/userguide/initScripts/customLogger/build.gradle b/subprojects/gradle-docs/src/samples/userguide/initScripts/customLogger/build.gradle
new file mode 100644
index 0000000..981de33
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/initScripts/customLogger/build.gradle
@@ -0,0 +1,10 @@
+task compile << {
+    println "compiling source"
+}
+task testCompile(dependsOn: compile) << {
+    println "compiling test source"
+}
+task test(dependsOn: [compile, testCompile]) << {
+    println "running unit tests"
+}
+task build(dependsOn: [test])
diff --git a/subprojects/gradle-docs/src/samples/userguide/initScripts/customLogger/init.gradle b/subprojects/gradle-docs/src/samples/userguide/initScripts/customLogger/init.gradle
new file mode 100644
index 0000000..bbf0bce
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/initScripts/customLogger/init.gradle
@@ -0,0 +1,16 @@
+useLogger(new CustomEventLogger())
+
+class CustomEventLogger extends BuildAdapter implements BuildListener, TaskExecutionListener {
+
+    public void beforeExecute(Task task) {
+        println "[$task.name]"
+    }
+
+    public void afterExecute(Task task, TaskState state) {
+        println()
+    }
+    
+    public void buildFinished(BuildResult result) {
+        println 'build completed'
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/initScripts/externalDependency/build.gradle b/subprojects/gradle-docs/src/samples/userguide/initScripts/externalDependency/build.gradle
new file mode 100644
index 0000000..1a237e3
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/initScripts/externalDependency/build.gradle
@@ -0,0 +1 @@
+task doNothing
diff --git a/subprojects/gradle-docs/src/samples/userguide/initScripts/externalDependency/init.gradle b/subprojects/gradle-docs/src/samples/userguide/initScripts/externalDependency/init.gradle
new file mode 100644
index 0000000..93ab8f5
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/initScripts/externalDependency/init.gradle
@@ -0,0 +1,14 @@
+import org.apache.commons.math.fraction.Fraction
+
+// START SNIPPET declare-classpath
+initscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath group: 'org.apache.commons', name: 'commons-math', version: '2.0'
+    }
+}
+// END SNIPPET declare-classpath
+
+println Fraction.ONE_FIFTH.multiply(2)
diff --git a/subprojects/gradle-docs/src/samples/userguide/java/sourceSets/build.gradle b/subprojects/gradle-docs/src/samples/userguide/java/sourceSets/build.gradle
new file mode 100644
index 0000000..572c800
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/java/sourceSets/build.gradle
@@ -0,0 +1,63 @@
+apply plugin: 'java'
+
+// START SNIPPET access-source-set
+// Various ways to access the main source set
+println sourceSets.main.classesDir
+println sourceSets['main'].classesDir
+sourceSets {
+    println main.classesDir
+}
+sourceSets {
+    main {
+        println classesDir
+    }
+}
+
+// Iterate over the source sets
+sourceSets.each {SourceSet set ->
+    println set.name
+}
+// END SNIPPET access-source-set
+
+// START SNIPPET define-source-set
+sourceSets {
+    intTest
+}
+// END SNIPPET define-source-set
+
+repositories {
+    mavenCentral()
+}
+
+// START SNIPPET classpath-using-configurations
+configurations {
+    intTestCompile { extendsFrom compile }
+    intTestRuntime { extendsFrom intTestCompile, runtime }
+}
+
+sourceSets {
+    intTest {
+        compileClasspath = sourceSets.main.classes + configurations.intTestCompile
+        runtimeClasspath = classes + sourceSets.main.classes + configurations.intTestRuntime
+    }
+}
+// END SNIPPET classpath-using-configurations
+
+// START SNIPPET jar
+task intTestJar(type: Jar) {
+    from sourceSets.intTest.classes
+}
+// END SNIPPET jar
+
+// START SNIPPET javadoc
+task intTestJavadoc(type: Javadoc) {
+    source sourceSets.intTest.allJava
+}
+// END SNIPPET javadoc
+
+// START SNIPPET test
+task intTest(type: Test) {
+    testClassesDir = sourceSets.intTest.classesDir
+    classpath = sourceSets.intTest.runtimeClasspath
+}
+// END SNIPPET test
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/addKrill/water/bluewhale/.gitignore b/subprojects/gradle-docs/src/samples/userguide/multiproject/addKrill/water/bluewhale/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/addKrill/water/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/addKrill/water/build.gradle
new file mode 100644
index 0000000..92d6118
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/addKrill/water/build.gradle
@@ -0,0 +1,3 @@
+allprojects {
+    task hello << { task -> println "I'm $task.project.name" }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/addKrill/water/krill/.gitignore b/subprojects/gradle-docs/src/samples/userguide/multiproject/addKrill/water/krill/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/addKrill/water/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/addKrill/water/settings.gradle
new file mode 100644
index 0000000..a978eb9
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/addKrill/water/settings.gradle
@@ -0,0 +1 @@
+include 'bluewhale', 'krill'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/addSpecifics/water/bluewhale/.gitignore b/subprojects/gradle-docs/src/samples/userguide/multiproject/addSpecifics/water/bluewhale/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/addSpecifics/water/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/addSpecifics/water/build.gradle
new file mode 100644
index 0000000..5693fc4
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/addSpecifics/water/build.gradle
@@ -0,0 +1,7 @@
+allprojects {
+    task hello << {task -> println "I'm $task.project.name" }
+}
+subprojects {
+    hello << {println "- I depend on water"}
+}
+project(':bluewhale').hello << { println "- I'm the largest animal that has ever lived on this planet." }
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/addSpecifics/water/krill/.gitignore b/subprojects/gradle-docs/src/samples/userguide/multiproject/addSpecifics/water/krill/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/addSpecifics/water/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/addSpecifics/water/settings.gradle
new file mode 100644
index 0000000..a978eb9
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/addSpecifics/water/settings.gradle
@@ -0,0 +1 @@
+include 'bluewhale', 'krill'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/addTropical/water/bluewhale/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/addTropical/water/bluewhale/build.gradle
new file mode 100644
index 0000000..e1e1d27
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/addTropical/water/bluewhale/build.gradle
@@ -0,0 +1 @@
+hello.doLast { println "- I'm the largest animal that has ever lived on this planet." }
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/addTropical/water/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/addTropical/water/build.gradle
new file mode 100644
index 0000000..e1aaf02
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/addTropical/water/build.gradle
@@ -0,0 +1,9 @@
+allprojects {
+    task hello << {task -> println "I'm $task.project.name" }
+}
+subprojects {
+    hello << {println "- I depend on water"}
+}
+configure(subprojects.findAll {it.name != 'tropicalFish'}) {
+    hello << {println '- I love to spend time in the arctic waters.'}
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/addTropical/water/krill/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/addTropical/water/krill/build.gradle
new file mode 100644
index 0000000..94d3a16
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/addTropical/water/krill/build.gradle
@@ -0,0 +1 @@
+hello.doLast { println "- The weight of my species in summer is twice as heavy as all human beings." }
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/addTropical/water/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/addTropical/water/settings.gradle
new file mode 100644
index 0000000..3bd4359
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/addTropical/water/settings.gradle
@@ -0,0 +1 @@
+include 'bluewhale', 'krill', 'tropicalFish'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/addTropical/water/tropicalFish/.gitignore b/subprojects/gradle-docs/src/samples/userguide/multiproject/addTropical/water/tropicalFish/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/customLayout/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/customLayout/settings.gradle
new file mode 100644
index 0000000..382c587
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/customLayout/settings.gradle
@@ -0,0 +1,12 @@
+include 'projectA', 'projectB'
+
+// START SNIPPET lookup-project
+println rootProject.name
+println project(':projectA').name
+// END SNIPPET lookup-project
+
+// START SNIPPET change-project
+rootProject.name = 'main'
+project(':projectA').projectDir = new File(settingsDir, '../my-project-a')
+project(':projectA').buildFileName = 'projectA.gradle'
+// END SNIPPET change-project
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/firstMessages/messages/consumer/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/firstMessages/messages/consumer/build.gradle
new file mode 100644
index 0000000..b552cba
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/firstMessages/messages/consumer/build.gradle
@@ -0,0 +1,4 @@
+task action << {
+    println("Consuming message: " +
+            (rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null'))
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/firstMessages/messages/producer/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/firstMessages/messages/producer/build.gradle
new file mode 100644
index 0000000..340ca3c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/firstMessages/messages/producer/build.gradle
@@ -0,0 +1,4 @@
+task action << {
+    println "Producing message:"
+    rootProject.producerMessage = 'Watch the order of execution.'
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/firstMessages/messages/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/firstMessages/messages/settings.gradle
new file mode 100644
index 0000000..055860f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/firstMessages/messages/settings.gradle
@@ -0,0 +1 @@
+include 'consumer', 'producer'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/api/src/main/java/org/gradle/sample/api/Person.java b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/api/src/main/java/org/gradle/sample/api/Person.java
new file mode 100644
index 0000000..82f57e3
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/api/src/main/java/org/gradle/sample/api/Person.java
@@ -0,0 +1,6 @@
+package org.gradle.sample.api;
+
+public interface Person {
+    String getFirstname();
+    String getSurname();
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/api/src/main/java/org/gradle/sample/apiImpl/PersonImpl.java b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/api/src/main/java/org/gradle/sample/apiImpl/PersonImpl.java
new file mode 100644
index 0000000..ae09884
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/api/src/main/java/org/gradle/sample/apiImpl/PersonImpl.java
@@ -0,0 +1,34 @@
+package org.gradle.sample.apiImpl;
+
+import org.gradle.sample.api.Person;
+import org.gradle.sample.shared.Helper;
+
+public class PersonImpl implements Person {
+    private String firstname;
+    private String surname;
+
+    public PersonImpl(String surname, String firstname) {
+        this.surname = surname;
+        this.firstname = firstname;
+    }
+
+    public String getFirstname() {
+        return firstname;
+    }
+
+    public void setFirstname(String firstname) {
+        this.firstname = firstname;
+    }
+
+    public String getSurname() {
+        return surname;
+    }
+
+    public void setSurname(String surname) {
+        this.surname = surname;
+    }
+
+    public String toString() {
+        return Helper.prettyPrint(firstname + " " + surname);
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/build.gradle
new file mode 100644
index 0000000..9783816
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/build.gradle
@@ -0,0 +1,25 @@
+subprojects {
+    repositories {
+        mavenCentral()
+    }
+    apply plugin: 'java'
+    group = 'org.gradle.sample'
+    version = '1.0'
+}
+
+project(':api') {
+    dependencies {
+        compile project(':shared')
+    }
+}
+
+project(':services:personService') {
+    dependencies {
+        compile project(':shared'), project(':api')
+        testCompile "junit:junit:3.8.2"
+    }
+}
+
+dependsOnChildren()
+
+
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/services/personService/src/main/java/org/gradle/sample/services/PersonService.java b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/services/personService/src/main/java/org/gradle/sample/services/PersonService.java
new file mode 100644
index 0000000..9c17e22
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/services/personService/src/main/java/org/gradle/sample/services/PersonService.java
@@ -0,0 +1,14 @@
+package org.gradle.api.services;
+
+import org.gradle.sample.api.Person;
+import org.gradle.sample.shared.Helper;
+
+public class PersonService {
+    boolean checkPerson(Person person) {
+        System.out.println(Helper.prettyPrint("Checking"));
+        if (person.getFirstname().length() < 2) {
+            return false;
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/services/personService/src/test/java/org/gradle/sample/services/PersonServiceTest.java b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/services/personService/src/test/java/org/gradle/sample/services/PersonServiceTest.java
new file mode 100644
index 0000000..73e9b59
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/services/personService/src/test/java/org/gradle/sample/services/PersonServiceTest.java
@@ -0,0 +1,11 @@
+package org.gradle.api.services;
+
+import junit.framework.TestCase;
+import org.gradle.sample.apiImpl.PersonImpl;
+
+public class PersonServiceTest extends TestCase {
+    public void testFindPerson() {
+        PersonImpl testPerson = new PersonImpl("Build", "Master");
+        assertTrue(new PersonService().checkPerson(testPerson));
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/settings.gradle
new file mode 100644
index 0000000..df9138b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/settings.gradle
@@ -0,0 +1 @@
+include 'api', 'shared', 'services:personService'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/shared/src/main/java/org/gradle/sample/shared/Helper.java b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/shared/src/main/java/org/gradle/sample/shared/Helper.java
new file mode 100644
index 0000000..6613217
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/java/shared/src/main/java/org/gradle/sample/shared/Helper.java
@@ -0,0 +1,8 @@
+package org.gradle.sample.shared;
+
+public class Helper {
+    public static String prettyPrint(String text) {
+       return "*** " + text + " ***";
+    }
+}
+
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/api/src/main/java/org/gradle/sample/api/Person.java b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/api/src/main/java/org/gradle/sample/api/Person.java
new file mode 100644
index 0000000..82f57e3
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/api/src/main/java/org/gradle/sample/api/Person.java
@@ -0,0 +1,6 @@
+package org.gradle.sample.api;
+
+public interface Person {
+    String getFirstname();
+    String getSurname();
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/api/src/main/java/org/gradle/sample/apiImpl/PersonImpl.java b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/api/src/main/java/org/gradle/sample/apiImpl/PersonImpl.java
new file mode 100644
index 0000000..ae09884
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/api/src/main/java/org/gradle/sample/apiImpl/PersonImpl.java
@@ -0,0 +1,34 @@
+package org.gradle.sample.apiImpl;
+
+import org.gradle.sample.api.Person;
+import org.gradle.sample.shared.Helper;
+
+public class PersonImpl implements Person {
+    private String firstname;
+    private String surname;
+
+    public PersonImpl(String surname, String firstname) {
+        this.surname = surname;
+        this.firstname = firstname;
+    }
+
+    public String getFirstname() {
+        return firstname;
+    }
+
+    public void setFirstname(String firstname) {
+        this.firstname = firstname;
+    }
+
+    public String getSurname() {
+        return surname;
+    }
+
+    public void setSurname(String surname) {
+        this.surname = surname;
+    }
+
+    public String toString() {
+        return Helper.prettyPrint(firstname + " " + surname);
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/build.gradle
new file mode 100644
index 0000000..78b4076
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/build.gradle
@@ -0,0 +1,31 @@
+subprojects {
+    apply plugin: 'java'
+    group = 'org.gradle.sample'
+    version = '1.0'
+}
+
+project(':api') {
+    configurations {
+        spi
+    }
+    dependencies {
+        compile project(':shared')
+    }
+    task spiJar(type: Jar) {
+        baseName = 'api-spi'
+        dependsOn classes
+        from sourceSets.main.classes
+        include('org/gradle/sample/api/**')
+    }
+    artifacts {
+        spi spiJar
+    }
+}
+
+project(':services:personService') {
+    dependencies {
+        compile project(':shared')
+        compile project(path: ':api', configuration: 'spi')
+        testCompile "junit:junit:3.8.2", project(':api')
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/services/personService/src/main/java/org/gradle/sample/services/PersonService.java b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/services/personService/src/main/java/org/gradle/sample/services/PersonService.java
new file mode 100644
index 0000000..9c17e22
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/services/personService/src/main/java/org/gradle/sample/services/PersonService.java
@@ -0,0 +1,14 @@
+package org.gradle.api.services;
+
+import org.gradle.sample.api.Person;
+import org.gradle.sample.shared.Helper;
+
+public class PersonService {
+    boolean checkPerson(Person person) {
+        System.out.println(Helper.prettyPrint("Checking"));
+        if (person.getFirstname().length() < 2) {
+            return false;
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/services/personService/src/test/java/org/gradle/sample/services/PersonServiceTest.java b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/services/personService/src/test/java/org/gradle/sample/services/PersonServiceTest.java
new file mode 100644
index 0000000..73e9b59
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/services/personService/src/test/java/org/gradle/sample/services/PersonServiceTest.java
@@ -0,0 +1,11 @@
+package org.gradle.api.services;
+
+import junit.framework.TestCase;
+import org.gradle.sample.apiImpl.PersonImpl;
+
+public class PersonServiceTest extends TestCase {
+    public void testFindPerson() {
+        PersonImpl testPerson = new PersonImpl("Build", "Master");
+        assertTrue(new PersonService().checkPerson(testPerson));
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/settings.gradle
new file mode 100644
index 0000000..df9138b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/settings.gradle
@@ -0,0 +1 @@
+include 'api', 'shared', 'services:personService'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/shared/src/main/java/org/gradle/sample/shared/Helper.java b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/shared/src/main/java/org/gradle/sample/shared/Helper.java
new file mode 100644
index 0000000..6613217
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/javaWithCustomConf/shared/src/main/java/org/gradle/sample/shared/Helper.java
@@ -0,0 +1,8 @@
+package org.gradle.sample.shared;
+
+public class Helper {
+    public static String prettyPrint(String text) {
+       return "*** " + text + " ***";
+    }
+}
+
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependencies/messages/consumer/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependencies/messages/consumer/build.gradle
new file mode 100644
index 0000000..9ca9261
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependencies/messages/consumer/build.gradle
@@ -0,0 +1,7 @@
+evaluationDependsOn(':producer')
+
+message = rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null'
+
+task consume << {
+    println("Consuming message: " + message)
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependencies/messages/producer/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependencies/messages/producer/build.gradle
new file mode 100644
index 0000000..41c5818
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependencies/messages/producer/build.gradle
@@ -0,0 +1 @@
+rootProject.producerMessage = 'Watch the order of evaluation.'
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependencies/messages/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependencies/messages/settings.gradle
new file mode 100644
index 0000000..055860f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependencies/messages/settings.gradle
@@ -0,0 +1 @@
+include 'consumer', 'producer'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesAltSolution/messages/consumer/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesAltSolution/messages/consumer/build.gradle
new file mode 100644
index 0000000..d36e857
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesAltSolution/messages/consumer/build.gradle
@@ -0,0 +1,4 @@
+task consume << {
+    println("Consuming message: " +
+            (rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null'))
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesAltSolution/messages/producer/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesAltSolution/messages/producer/build.gradle
new file mode 100644
index 0000000..4cbb964
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesAltSolution/messages/producer/build.gradle
@@ -0,0 +1 @@
+rootProject.producerMessage = 'Watch the order of evaluation.'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesAltSolution/messages/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesAltSolution/messages/settings.gradle
new file mode 100644
index 0000000..055860f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesAltSolution/messages/settings.gradle
@@ -0,0 +1 @@
+include 'consumer', 'producer'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesBroken/messages/consumer/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesBroken/messages/consumer/build.gradle
new file mode 100644
index 0000000..d1ba74e
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesBroken/messages/consumer/build.gradle
@@ -0,0 +1,5 @@
+message = rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null'
+
+task consume << {
+    println("Consuming message: " + message)
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesBroken/messages/producer/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesBroken/messages/producer/build.gradle
new file mode 100644
index 0000000..4cbb964
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesBroken/messages/producer/build.gradle
@@ -0,0 +1 @@
+rootProject.producerMessage = 'Watch the order of evaluation.'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesBroken/messages/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesBroken/messages/settings.gradle
new file mode 100644
index 0000000..055860f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesConfigDependenciesBroken/messages/settings.gradle
@@ -0,0 +1 @@
+include 'consumer', 'producer'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesDifferentTaskNames/messages/consumer/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesDifferentTaskNames/messages/consumer/build.gradle
new file mode 100644
index 0000000..5505c5f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesDifferentTaskNames/messages/consumer/build.gradle
@@ -0,0 +1,6 @@
+dependsOn(':producer')
+
+task consume << {
+    println("Consuming message: " +
+            (rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null'))
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesDifferentTaskNames/messages/producer/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesDifferentTaskNames/messages/producer/build.gradle
new file mode 100644
index 0000000..49a8f5a
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesDifferentTaskNames/messages/producer/build.gradle
@@ -0,0 +1,4 @@
+task produce << {
+    println "Producing message:"
+    rootProject.producerMessage = 'Watch the order of execution.'
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesDifferentTaskNames/messages/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesDifferentTaskNames/messages/settings.gradle
new file mode 100644
index 0000000..055860f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesDifferentTaskNames/messages/settings.gradle
@@ -0,0 +1 @@
+include 'consumer', 'producer'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesHack/messages/aProducer/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesHack/messages/aProducer/build.gradle
new file mode 100644
index 0000000..340ca3c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesHack/messages/aProducer/build.gradle
@@ -0,0 +1,4 @@
+task action << {
+    println "Producing message:"
+    rootProject.producerMessage = 'Watch the order of execution.'
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesHack/messages/consumer/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesHack/messages/consumer/build.gradle
new file mode 100644
index 0000000..b552cba
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesHack/messages/consumer/build.gradle
@@ -0,0 +1,4 @@
+task action << {
+    println("Consuming message: " +
+            (rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null'))
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesHack/messages/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesHack/messages/settings.gradle
new file mode 100644
index 0000000..b80f025
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesHack/messages/settings.gradle
@@ -0,0 +1 @@
+include 'consumer', 'aProducer'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesTaskDependencies/messages/consumer/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesTaskDependencies/messages/consumer/build.gradle
new file mode 100644
index 0000000..8abffac
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesTaskDependencies/messages/consumer/build.gradle
@@ -0,0 +1,4 @@
+task consume(dependsOn: ':producer:produce') << {
+    println("Consuming message: " +
+            (rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null'))
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesTaskDependencies/messages/producer/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesTaskDependencies/messages/producer/build.gradle
new file mode 100644
index 0000000..49a8f5a
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesTaskDependencies/messages/producer/build.gradle
@@ -0,0 +1,4 @@
+task produce << {
+    println "Producing message:"
+    rootProject.producerMessage = 'Watch the order of execution.'
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesTaskDependencies/messages/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesTaskDependencies/messages/settings.gradle
new file mode 100644
index 0000000..055860f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesTaskDependencies/messages/settings.gradle
@@ -0,0 +1 @@
+include 'consumer', 'producer'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesWithDependencies/messages/consumer/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesWithDependencies/messages/consumer/build.gradle
new file mode 100644
index 0000000..85ebec4
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesWithDependencies/messages/consumer/build.gradle
@@ -0,0 +1,6 @@
+dependsOn(':producer')
+
+task action << {
+    println("Consuming message: " +
+            (rootProject.hasProperty('producerMessage') ? rootProject.producerMessage : 'null'))
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesWithDependencies/messages/producer/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesWithDependencies/messages/producer/build.gradle
new file mode 100644
index 0000000..340ca3c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesWithDependencies/messages/producer/build.gradle
@@ -0,0 +1,4 @@
+task action << {
+    println "Producing message:"
+    rootProject.producerMessage = 'Watch the order of execution.'
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesWithDependencies/messages/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesWithDependencies/messages/settings.gradle
new file mode 100644
index 0000000..055860f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/messagesWithDependencies/messages/settings.gradle
@@ -0,0 +1 @@
+include 'consumer', 'producer'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/webDist/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/webDist/build.gradle
new file mode 100644
index 0000000..d2bc499
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/webDist/build.gradle
@@ -0,0 +1,29 @@
+dependsOnChildren()
+
+allprojects {
+    apply plugin: 'java'
+    group = 'org.gradle.sample'
+    version = '1.0'
+}
+
+subprojects {
+    apply plugin: 'war'
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        compile "javax.servlet:servlet-api:2.5"
+    }
+}
+
+task explodedDist(dependsOn: assemble) << {
+    File explodedDist = mkdir(buildDir, 'explodedDist')
+    subprojects.each {project ->
+        project.tasks.withType(Jar).each {archiveTask ->
+            copy {
+                from archiveTask.archivePath
+                into explodedDist
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/webDist/date/src/main/java/org/gradle/sample/DateServlet.java b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/webDist/date/src/main/java/org/gradle/sample/DateServlet.java
new file mode 100644
index 0000000..1bc5ccc
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/webDist/date/src/main/java/org/gradle/sample/DateServlet.java
@@ -0,0 +1,12 @@
+package org.gradle.sample;
+
+import java.io.*;
+import java.util.*;
+import javax.servlet.http.*;
+import javax.servlet.*;
+
+public class DateServlet extends HttpServlet {
+    public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
+        res.getWriter().println("Date: " + new Date());
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/webDist/date/src/main/webapp/web.xml b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/webDist/date/src/main/webapp/web.xml
new file mode 100644
index 0000000..cd18039
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/webDist/date/src/main/webapp/web.xml
@@ -0,0 +1,13 @@
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee" version="2.4"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http:/java.sun.com/dtd/web-app_2_3.dtd">
+  <servlet>
+    <servlet-name>date</servlet-name>
+    <servlet-class>org.gradle.sample.DateServlet</servlet-class>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>date</servlet-name>
+    <url-pattern>/date</url-pattern>
+  </servlet-mapping>
+</web-app>
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/webDist/hello/src/main/java/org/gradle/sample/HelloServlet.java b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/webDist/hello/src/main/java/org/gradle/sample/HelloServlet.java
new file mode 100644
index 0000000..674a984
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/webDist/hello/src/main/java/org/gradle/sample/HelloServlet.java
@@ -0,0 +1,12 @@
+package org.gradle.sample;
+
+import java.io.*;
+import java.util.*;
+import javax.servlet.http.*;
+import javax.servlet.*;
+
+public class HelloServlet extends HttpServlet {
+    public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
+        res.getWriter().println("Hello world!");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/webDist/hello/src/main/webapp/web.xml b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/webDist/hello/src/main/webapp/web.xml
new file mode 100644
index 0000000..a7f6fc4
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/webDist/hello/src/main/webapp/web.xml
@@ -0,0 +1,13 @@
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee" version="2.4"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http:/java.sun.com/dtd/web-app_2_3.dtd">
+  <servlet>
+    <servlet-name>hello</servlet-name>
+    <servlet-class>org.gradle.sample.HelloServlet</servlet-class>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>hello</servlet-name>
+    <url-pattern>/hello</url-pattern>
+  </servlet-mapping>
+</web-app>
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/webDist/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/webDist/settings.gradle
new file mode 100644
index 0000000..303766e
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/dependencies/webDist/settings.gradle
@@ -0,0 +1 @@
+include 'date', 'hello'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/firstExample/water/bluewhale/.gitignore b/subprojects/gradle-docs/src/samples/userguide/multiproject/firstExample/water/bluewhale/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/firstExample/water/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/firstExample/water/build.gradle
new file mode 100644
index 0000000..7404d27
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/firstExample/water/build.gradle
@@ -0,0 +1,5 @@
+Closure cl = { task -> println "I'm $task.project.name" }
+task hello << cl
+project(':bluewhale') {
+    task hello << cl
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/firstExample/water/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/firstExample/water/settings.gradle
new file mode 100644
index 0000000..b911396
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/firstExample/water/settings.gradle
@@ -0,0 +1 @@
+include 'bluewhale'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/flat/dolphin/.gitignore b/subprojects/gradle-docs/src/samples/userguide/multiproject/flat/dolphin/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/flat/master/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/flat/master/build.gradle
new file mode 100644
index 0000000..2d9093c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/flat/master/build.gradle
@@ -0,0 +1,3 @@
+allprojects {
+    task hello << {task -> println "I'm $task.project.name" }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/flat/master/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/flat/master/settings.gradle
new file mode 100644
index 0000000..0cef583
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/flat/master/settings.gradle
@@ -0,0 +1 @@
+includeFlat('dolphin', 'shark')
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/flat/shark/.gitignore b/subprojects/gradle-docs/src/samples/userguide/multiproject/flat/shark/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/flatWithNoDefaultMaster/dolphin/.gitignore b/subprojects/gradle-docs/src/samples/userguide/multiproject/flatWithNoDefaultMaster/dolphin/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/flatWithNoDefaultMaster/shark/.gitignore b/subprojects/gradle-docs/src/samples/userguide/multiproject/flatWithNoDefaultMaster/shark/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/flatWithNoDefaultMaster/water/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/flatWithNoDefaultMaster/water/build.gradle
new file mode 100644
index 0000000..2d9093c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/flatWithNoDefaultMaster/water/build.gradle
@@ -0,0 +1,3 @@
+allprojects {
+    task hello << {task -> println "I'm $task.project.name" }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/flatWithNoDefaultMaster/water/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/flatWithNoDefaultMaster/water/settings.gradle
new file mode 100644
index 0000000..0cef583
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/flatWithNoDefaultMaster/water/settings.gradle
@@ -0,0 +1 @@
+includeFlat('dolphin', 'shark')
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/partialTasks/water/bluewhale/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/partialTasks/water/bluewhale/build.gradle
new file mode 100644
index 0000000..2b3696c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/partialTasks/water/bluewhale/build.gradle
@@ -0,0 +1,6 @@
+arctic = true
+hello << { println "- I'm the largest animal that has ever lived on this planet." }
+
+task distanceToIceberg << {
+    println '20 nautical miles'
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/partialTasks/water/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/partialTasks/water/build.gradle
new file mode 100644
index 0000000..690332b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/partialTasks/water/build.gradle
@@ -0,0 +1,14 @@
+import org.gradle.api.Project
+
+allprojects {
+    task hello << {task -> println "I'm $task.project.name" }
+}
+subprojects {
+    hello {
+        doLast {println "- I depend on water"}
+        afterEvaluate { Project project ->
+            if (project.arctic) { doLast { println '- I love to spend time in the arctic waters.' }}
+        }
+    }
+}
+
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/partialTasks/water/krill/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/partialTasks/water/krill/build.gradle
new file mode 100644
index 0000000..05de0ba
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/partialTasks/water/krill/build.gradle
@@ -0,0 +1,6 @@
+arctic = true
+hello << { println "- The weight of my species in summer is twice as heavy as all human beings." }
+
+task distanceToIceberg << {
+    println '5 nautical miles'
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/partialTasks/water/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/partialTasks/water/settings.gradle
new file mode 100644
index 0000000..3bd4359
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/partialTasks/water/settings.gradle
@@ -0,0 +1 @@
+include 'bluewhale', 'krill', 'tropicalFish'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/partialTasks/water/tropicalFish/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/partialTasks/water/tropicalFish/build.gradle
new file mode 100644
index 0000000..45f5a66
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/partialTasks/water/tropicalFish/build.gradle
@@ -0,0 +1 @@
+arctic = false
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/spreadSpecifics/water/bluewhale/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/spreadSpecifics/water/bluewhale/build.gradle
new file mode 100644
index 0000000..d9ee47e
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/spreadSpecifics/water/bluewhale/build.gradle
@@ -0,0 +1 @@
+hello.doLast { println "- I'm the largest animal that has ever lived on this planet." }
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/spreadSpecifics/water/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/spreadSpecifics/water/build.gradle
new file mode 100644
index 0000000..316a0ac
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/spreadSpecifics/water/build.gradle
@@ -0,0 +1,6 @@
+allprojects {
+    task hello << {task -> println "I'm $task.project.name" }
+}
+subprojects {
+    hello << {println "- I depend on water"}
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/spreadSpecifics/water/krill/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/spreadSpecifics/water/krill/build.gradle
new file mode 100644
index 0000000..be7e903
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/spreadSpecifics/water/krill/build.gradle
@@ -0,0 +1,3 @@
+hello.doLast {
+    println "- The weight of my species in summer is twice as heavy as all human beings."
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/spreadSpecifics/water/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/spreadSpecifics/water/settings.gradle
new file mode 100644
index 0000000..a978eb9
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/spreadSpecifics/water/settings.gradle
@@ -0,0 +1 @@
+include 'bluewhale', 'krill'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/standardLayouts/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/standardLayouts/settings.gradle
new file mode 100644
index 0000000..e898abc
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/standardLayouts/settings.gradle
@@ -0,0 +1,7 @@
+//START SNIPPET hierarchical-layout
+include 'project1', 'project2', 'project2:child1'
+//END SNIPPET hierarchical-layout
+
+//START SNIPPET flat-layout
+includeFlat 'project3', 'project4'
+//END SNIPPET flat-layout
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/subprojectsAddFromTop/water/bluewhale/.gitignore b/subprojects/gradle-docs/src/samples/userguide/multiproject/subprojectsAddFromTop/water/bluewhale/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/subprojectsAddFromTop/water/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/subprojectsAddFromTop/water/build.gradle
new file mode 100644
index 0000000..e9bb67a
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/subprojectsAddFromTop/water/build.gradle
@@ -0,0 +1,9 @@
+allprojects {
+    task hello << {task -> println "I'm $task.project.name" }
+}
+subprojects {
+    hello << {println "- I depend on water"}
+}
+project(':bluewhale').hello << {
+    println "- I'm the largest animal that has ever lived on this planet."
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/subprojectsAddFromTop/water/krill/.gitignore b/subprojects/gradle-docs/src/samples/userguide/multiproject/subprojectsAddFromTop/water/krill/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/subprojectsAddFromTop/water/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/subprojectsAddFromTop/water/settings.gradle
new file mode 100644
index 0000000..a978eb9
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/subprojectsAddFromTop/water/settings.gradle
@@ -0,0 +1 @@
+include 'bluewhale', 'krill'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/tropicalWithProperties/water/bluewhale/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/tropicalWithProperties/water/bluewhale/build.gradle
new file mode 100644
index 0000000..d922c8f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/tropicalWithProperties/water/bluewhale/build.gradle
@@ -0,0 +1,2 @@
+arctic = true
+hello.doLast { println "- I'm the largest animal that has ever lived on this planet." }
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/tropicalWithProperties/water/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/tropicalWithProperties/water/build.gradle
new file mode 100644
index 0000000..a83852d
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/tropicalWithProperties/water/build.gradle
@@ -0,0 +1,13 @@
+allprojects {
+    task hello << {task -> println "I'm $task.project.name" }
+}
+subprojects {
+    hello {
+        doLast {println "- I depend on water"}
+        afterEvaluate { Project project ->
+            if (project.arctic) { doLast {
+                println '- I love to spend time in the arctic waters.' }
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/tropicalWithProperties/water/krill/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/tropicalWithProperties/water/krill/build.gradle
new file mode 100644
index 0000000..9d4fbfb
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/tropicalWithProperties/water/krill/build.gradle
@@ -0,0 +1,4 @@
+arctic = true
+hello.doLast {
+    println "- The weight of my species in summer is twice as heavy as all human beings."
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/tropicalWithProperties/water/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/tropicalWithProperties/water/settings.gradle
new file mode 100644
index 0000000..3bd4359
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/tropicalWithProperties/water/settings.gradle
@@ -0,0 +1 @@
+include 'bluewhale', 'krill', 'tropicalFish'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/tropicalWithProperties/water/tropicalFish/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/tropicalWithProperties/water/tropicalFish/build.gradle
new file mode 100644
index 0000000..45f5a66
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/tropicalWithProperties/water/tropicalFish/build.gradle
@@ -0,0 +1 @@
+arctic = false
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/useSubprojects/water/bluewhale/.gitignore b/subprojects/gradle-docs/src/samples/userguide/multiproject/useSubprojects/water/bluewhale/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/useSubprojects/water/build.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/useSubprojects/water/build.gradle
new file mode 100644
index 0000000..316a0ac
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/useSubprojects/water/build.gradle
@@ -0,0 +1,6 @@
+allprojects {
+    task hello << {task -> println "I'm $task.project.name" }
+}
+subprojects {
+    hello << {println "- I depend on water"}
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/useSubprojects/water/krill/.gitignore b/subprojects/gradle-docs/src/samples/userguide/multiproject/useSubprojects/water/krill/.gitignore
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/userguide/multiproject/useSubprojects/water/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/multiproject/useSubprojects/water/settings.gradle
new file mode 100644
index 0000000..a978eb9
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/multiproject/useSubprojects/water/settings.gradle
@@ -0,0 +1 @@
+include 'bluewhale', 'krill'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/build.gradle b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/build.gradle
new file mode 100644
index 0000000..275dbaa
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/build.gradle
@@ -0,0 +1,22 @@
+configurations {
+    ftpAntTask
+}
+
+dependencies {
+    ftpAntTask("org.apache.ant:ant-commons-net:1.7.0") {
+        module("commons-net:commons-net:1.4.1") {
+            dependencies "oro:oro:2.0.8:jar"
+        }
+    }
+}
+
+task ftp << {
+    ant {
+        taskdef(name: 'ftp',
+                classname: 'org.apache.tools.ant.taskdefs.optional.net.FTP',
+                classpath: configurations.ftpAntTask.asPath)
+        ftp(server: "ftp.apache.org", userid: "anonymous", password: "me at myorg.com") {
+            fileset(dir: "htdocs/manual")
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/customPlugin/build.gradle b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/customPlugin/build.gradle
new file mode 100644
index 0000000..f290fca
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/customPlugin/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: GreetingPlugin
+
+class GreetingPlugin implements Plugin<Project> {
+    def void apply(Project project) {
+        project.task('hello') << {
+            println "Hello from the GreetingPlugin"
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/customPluginWithAdvancedConvention/build.gradle b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/customPluginWithAdvancedConvention/build.gradle
new file mode 100644
index 0000000..7abec1e
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/customPluginWithAdvancedConvention/build.gradle
@@ -0,0 +1,25 @@
+apply plugin: GreetingPlugin
+
+greet {
+    message = 'Hi from Gradle' 
+}
+
+class GreetingPlugin implements Plugin<Project> {
+    def void apply(Project project) {
+
+        project.convention.plugins.greet = new GreetingPluginConvention()
+        project.task('hello') << {
+            println project.convention.plugins.greet.message
+        }
+    }
+}
+
+class GreetingPluginConvention {
+    String message
+
+    def greet(Closure closure) {
+        closure.delegate = this
+        closure() 
+    }
+}
+
diff --git a/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/customPluginWithConvention/build.gradle b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/customPluginWithConvention/build.gradle
new file mode 100644
index 0000000..db9e9a2
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/customPluginWithConvention/build.gradle
@@ -0,0 +1,16 @@
+apply plugin: GreetingPlugin
+
+greeting = 'Hi from Gradle'
+
+class GreetingPlugin implements Plugin<Project> {
+    def void apply(Project project) {
+        project.convention.plugins.greet = new GreetingPluginConvention()
+        project.task('hello') << {
+            println project.convention.plugins.greet.greeting
+        }
+    }
+}
+
+class GreetingPluginConvention {
+    def String greeting = 'Hello from GreetingPlugin'
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/externalDependency/build.gradle b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/externalDependency/build.gradle
new file mode 100644
index 0000000..cb5731a
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/externalDependency/build.gradle
@@ -0,0 +1,17 @@
+import org.apache.commons.codec.binary.Base64
+
+// START SNIPPET declare-classpath
+buildscript {
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        classpath group: 'commons-codec', name: 'commons-codec', version: '1.2'
+    }
+}
+// END SNIPPET declare-classpath
+
+task encode << {
+    def byte[] encodedString = new Base64().encode('hello world\n' as byte[])
+    println new String(encodedString)
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/inherited/build.gradle b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/inherited/build.gradle
new file mode 100644
index 0000000..99e47c8
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/inherited/build.gradle
@@ -0,0 +1,5 @@
+srcDirName = 'src/java'
+
+def getSrcDir(project) {
+    return project.file(srcDirName)
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/inherited/child/build.gradle b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/inherited/child/build.gradle
new file mode 100644
index 0000000..0482ba9
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/inherited/child/build.gradle
@@ -0,0 +1,8 @@
+task show << {
+    // Use inherited property
+    println 'srcDirName: ' + srcDirName
+
+    // Use inherited method
+    File srcDir = getSrcDir(project)
+    println 'srcDir: ' + rootProject.relativePath(srcDir)
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/inherited/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/inherited/settings.gradle
new file mode 100644
index 0000000..70656a3
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/inherited/settings.gradle
@@ -0,0 +1 @@
+include 'child'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/injected/build.gradle b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/injected/build.gradle
new file mode 100644
index 0000000..23b538a
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/injected/build.gradle
@@ -0,0 +1,18 @@
+subprojects {
+    // Inject a property and method
+    srcDirName = 'src/java'
+    srcDir = { file(srcDirName) }
+
+    // Inject a task
+    task show << {
+        println 'project: ' + project.path
+        println 'srcDirName: ' + srcDirName
+        File srcDir = srcDir()
+        println 'srcDir: ' + rootProject.relativePath(srcDir)
+    }
+}
+
+// Inject special case configuration into a particular project
+project(':child2') {
+    srcDirName = "$srcDirName/legacy"
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/injected/child1/build.gradle b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/injected/child1/build.gradle
new file mode 100644
index 0000000..71019db
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/injected/child1/build.gradle
@@ -0,0 +1,3 @@
+// Use injected property and method. Here, we override the injected value
+srcDirName = 'java'
+def dir = srcDir()
diff --git a/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/injected/child2/build.gradle b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/injected/child2/build.gradle
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/injected/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/injected/settings.gradle
new file mode 100644
index 0000000..8be37a5
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/injected/settings.gradle
@@ -0,0 +1 @@
+include 'child1', 'child2'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/nestedBuild/build.gradle b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/nestedBuild/build.gradle
new file mode 100644
index 0000000..d84600d
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/nestedBuild/build.gradle
@@ -0,0 +1,9 @@
+// START SNIPPET execute-build
+task build(type: GradleBuild) {
+    buildFile = 'other.gradle'
+    tasks = ['hello']
+// END SNIPPET execute-build
+    startParameter.searchUpwards = false
+// START SNIPPET execute-build
+}
+// END SNIPPET execute-build
diff --git a/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/nestedBuild/other.gradle b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/nestedBuild/other.gradle
new file mode 100644
index 0000000..f7af689
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/organizeBuildLogic/nestedBuild/other.gradle
@@ -0,0 +1,3 @@
+task hello << {
+    println "hello from the other build."
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tasks/accessAsProperty/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tasks/accessAsProperty/build.gradle
new file mode 100644
index 0000000..308b15c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tasks/accessAsProperty/build.gradle
@@ -0,0 +1,4 @@
+task hello
+
+println hello.name
+println project.hello.name
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tasks/accessFromTaskContainer/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tasks/accessFromTaskContainer/build.gradle
new file mode 100644
index 0000000..502aa86
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tasks/accessFromTaskContainer/build.gradle
@@ -0,0 +1,4 @@
+task hello
+
+println tasks.hello.name
+println tasks['hello'].name
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tasks/accessUsingPath/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tasks/accessUsingPath/build.gradle
new file mode 100644
index 0000000..b800b62
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tasks/accessUsingPath/build.gradle
@@ -0,0 +1,10 @@
+project(':projectA') {
+    task hello
+}
+
+task hello
+
+println tasks.getByPath('hello').path
+println tasks.getByPath(':hello').path
+println tasks.getByPath('projectA:hello').path
+println tasks.getByPath(':projectA:hello').path
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tasks/accessUsingPath/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/tasks/accessUsingPath/settings.gradle
new file mode 100644
index 0000000..1f2d0b7
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tasks/accessUsingPath/settings.gradle
@@ -0,0 +1 @@
+include 'projectA'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tasks/addDependencyUsingClosure/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tasks/addDependencyUsingClosure/build.gradle
new file mode 100644
index 0000000..44b1cc4
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tasks/addDependencyUsingClosure/build.gradle
@@ -0,0 +1,19 @@
+task taskX << {
+    println 'taskX'
+}
+
+taskX.dependsOn {
+    tasks.findAll { task -> task.name.startsWith('lib') }
+}
+
+task lib1 << {
+    println 'lib1'
+}
+
+task lib2 << {
+    println 'lib2'
+}
+
+task notALib << {
+    println 'notALib'
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/tasks/addDependencyUsingPath/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tasks/addDependencyUsingPath/build.gradle
new file mode 100644
index 0000000..8318e85
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tasks/addDependencyUsingPath/build.gradle
@@ -0,0 +1,11 @@
+project('projectA') {
+    task taskX(dependsOn: ':projectB:taskY') << {
+        println 'taskX'
+    }
+}
+
+project('projectB') {
+    task taskY << {
+        println 'taskY'
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tasks/addDependencyUsingPath/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/tasks/addDependencyUsingPath/settings.gradle
new file mode 100644
index 0000000..ee6b6cd
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tasks/addDependencyUsingPath/settings.gradle
@@ -0,0 +1 @@
+include 'projectA', 'projectB'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tasks/addDependencyUsingTask/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tasks/addDependencyUsingTask/build.gradle
new file mode 100644
index 0000000..a3f5cb8
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tasks/addDependencyUsingTask/build.gradle
@@ -0,0 +1,9 @@
+task taskX << {
+    println 'taskX'
+}
+
+task taskY << {
+    println 'taskY'
+}
+
+taskX.dependsOn taskY
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tasks/addRules/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tasks/addRules/build.gradle
new file mode 100644
index 0000000..7af5744
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tasks/addRules/build.gradle
@@ -0,0 +1,13 @@
+// START SNIPPET task-rule
+tasks.addRule("Pattern: ping<ID>") { String taskName ->
+    if (taskName.startsWith("ping")) {
+        task(taskName) << {
+            println "Pinging: " + (taskName - 'ping')
+        }
+    }
+}
+// END SNIPPET task-rule
+
+task groupPing {
+    dependsOn pingServer1, pingServer2
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tasks/addToTaskContainer/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tasks/addToTaskContainer/build.gradle
new file mode 100644
index 0000000..e8e6a5e
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tasks/addToTaskContainer/build.gradle
@@ -0,0 +1,8 @@
+tasks.add(name: 'hello') << {
+    println "hello"
+}
+
+tasks.add(name: 'copy', type: Copy) {
+    from(file('srcDir'))
+    into(buildDir)
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/tasks/configureUsingClosure/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tasks/configureUsingClosure/build.gradle
new file mode 100644
index 0000000..edf6c73
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tasks/configureUsingClosure/build.gradle
@@ -0,0 +1,9 @@
+// START SNIPPET declare-task
+task myCopy(type: Copy)
+// END SNIPPET declare-task
+
+myCopy {
+   from 'resources'
+   into 'target'
+   include('**/*.txt', '**/*.xml', '**/*.properties')
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/tasks/configureUsingConfigure/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tasks/configureUsingConfigure/build.gradle
new file mode 100644
index 0000000..cbf1a53
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tasks/configureUsingConfigure/build.gradle
@@ -0,0 +1,7 @@
+task myCopy(type: Copy)
+
+myCopy.configure {
+   from('source')
+   into('target')
+   include('**/*.txt', '**/*.xml', '**/*.properties')
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/tasks/configureUsingLiterateStyle/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tasks/configureUsingLiterateStyle/build.gradle
new file mode 100644
index 0000000..b713f2e
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tasks/configureUsingLiterateStyle/build.gradle
@@ -0,0 +1,4 @@
+task(myCopy, type: Copy)
+    .from('resources')
+    .into('target')
+    .include('**/*.txt', '**/*.xml', '**/*.properties')
diff --git a/subprojects/gradle-docs/src/samples/userguide/tasks/configureUsingVar/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tasks/configureUsingVar/build.gradle
new file mode 100644
index 0000000..b0d2752
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tasks/configureUsingVar/build.gradle
@@ -0,0 +1,4 @@
+Copy myCopy = task(myCopy, type: Copy)
+myCopy.from 'resources'
+myCopy.into 'target'
+myCopy.include('**/*.txt', '**/*.xml', '**/*.properties')
diff --git a/subprojects/gradle-docs/src/samples/userguide/tasks/customTask/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tasks/customTask/build.gradle
new file mode 100644
index 0000000..4e2e5f3
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tasks/customTask/build.gradle
@@ -0,0 +1,15 @@
+
+// START SNIPPET add-action
+task hello(type: GreetingTask)
+
+// START SNIPPET define-task
+class GreetingTask extends DefaultTask {
+// END SNIPPET define-task
+    @TaskAction
+    def greet() {
+        println 'hello from GreetingTask'
+    }
+// START SNIPPET define-task
+}
+// END SNIPPET define-task
+// END SNIPPET add-action
diff --git a/subprojects/gradle-docs/src/samples/userguide/tasks/customTaskWithProperty/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tasks/customTaskWithProperty/build.gradle
new file mode 100644
index 0000000..70ae299
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tasks/customTaskWithProperty/build.gradle
@@ -0,0 +1,18 @@
+// START SNIPPET add-property
+// Use the default greeting
+task hello(type: GreetingTask)
+
+// Customize the greeting
+task greeting(type: GreetingTask) {
+    greeting = 'greetings from GreetingTask'
+}
+
+class GreetingTask extends DefaultTask {
+    def String greeting = 'hello from GreetingTask'
+
+    @TaskAction
+    def greet() {
+        println greeting
+    }
+}
+// END SNIPPET add-property
diff --git a/subprojects/gradle-docs/src/samples/userguide/tasks/defineAndConfigure/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tasks/defineAndConfigure/build.gradle
new file mode 100644
index 0000000..5a6919a
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tasks/defineAndConfigure/build.gradle
@@ -0,0 +1,10 @@
+// START SNIPPET no-description
+task copy(type: Copy) {
+// END SNIPPET no-description
+   description = 'Copies the resource directory to the target directory.'
+// START SNIPPET no-description
+   from 'resources'
+   into 'target'
+   include('**/*.txt', '**/*.xml', '**/*.properties')
+}
+// END SNIPPET no-description
diff --git a/subprojects/gradle-docs/src/samples/userguide/tasks/defineAsExpression/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tasks/defineAsExpression/build.gradle
new file mode 100644
index 0000000..fef1e9f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tasks/defineAsExpression/build.gradle
@@ -0,0 +1,8 @@
+task(hello) << {
+    println "hello"
+}
+
+task(copy, type: Copy) {
+    from(file('srcDir'))
+    into(buildDir)
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tasks/defineUsingStringTaskNames/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tasks/defineUsingStringTaskNames/build.gradle
new file mode 100644
index 0000000..e161e6a
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tasks/defineUsingStringTaskNames/build.gradle
@@ -0,0 +1,9 @@
+task('hello') <<
+{
+    println "hello"
+}
+
+task('copy', type: Copy) {
+    from(file('srcDir'))
+    into(buildDir)
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/announce/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/announce/build.gradle
new file mode 100644
index 0000000..0b2a542
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/announce/build.gradle
@@ -0,0 +1,11 @@
+apply {
+  plugin 'announce'
+  plugin 'java'
+}
+
+// START SNIPPET full-example
+announce {
+  announceTwitterUsername = 'AnnounceExampleTwitterAccount'
+  announceTwitterPassword = 'your-password'
+}
+// END SNIPPET full-example
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/antChecksum/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/antChecksum/build.gradle
new file mode 100644
index 0000000..0be3cef
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/antChecksum/build.gradle
@@ -0,0 +1,10 @@
+task checksum << {
+    def files = file('../antChecksumFiles').listFiles().sort()
+    files.each { File file ->
+        if (file.isFile()) {
+            ant.checksum(file: file, property: file.name)
+            println "$file.name Checksum: ${ant.properties[file.name]}"
+        }
+    }
+}
+        
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/antChecksumFiles/agile_manifesto.html b/subprojects/gradle-docs/src/samples/userguide/tutorial/antChecksumFiles/agile_manifesto.html
new file mode 100644
index 0000000..59f8430
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/antChecksumFiles/agile_manifesto.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+<title>Manifesto for Agile Software Development</title>
+<META name="description" content="We are uncovering better ways of developing software by doing it and helping others do it. These are our values and principles.">
+<META name="keywords" content="agilemanifesto agilealliance alliance manifesto agile programming, agile development, extreme programming, XP, software project management, iterative development, collaborative development, software engineering best practices, software development best practices">
+</head>
+<body background=background.jpg>
+<center>
+<br><br><br><br>
+
+<h1>Manifesto for Agile Software Development</h1>
+<br><br><br>
+
+<p>
+
+<font size="+2">
+We are uncovering better ways of developing <br>
+software by doing it and helping others do it. <br>
+Through this work we have come to value:
+</font>
+
+<p>
+<font size="+3">Individuals and interactions </font><font size="+2">over processes and tools</font> <br>
+<font size="+3">Working software </font><font size="+2">over comprehensive documentation</font> <br>
+
+<font size="+3">Customer collaboration </font><font size="+2">over contract negotiation</font> <br>
+<font size="+3">Responding to change </font><font size="+2">over following a plan</font> <br>
+
+<p>
+<font size="+2">
+That is, while there is value in the items on <br>
+the right, we value the items on the left more.
+</font>
+<br><br><br><br>
+
+<table cellpadding=15><tr align=center valign=top>
+<td><font size="+2">
+Kent Beck<br>
+Mike Beedle<br>
+Arie van Bennekum<br>
+Alistair Cockburn<br>
+Ward Cunningham<br>
+Martin Fowler<br>
+</font></td>
+
+<td><font size="+2">
+
+James Grenning<br>
+Jim Highsmith<br>
+Andrew Hunt<br>
+Ron Jeffries<br>
+Jon Kern<br>
+Brian Marick<br>
+</font></td>
+
+<td><font size="+2">
+Robert C. Martin<br>
+
+Steve Mellor<br>
+Ken Schwaber<br>
+Jeff Sutherland<br>
+Dave Thomas<br>
+</font></td>
+</tr></table>
+<br><br><br>
+
+<font size=1 color=gray>
+© 2001, the above authors<br>
+this declaration may be freely copied in any form, <br>
+
+but only in its entirety through this notice.
+</font>
+<br><br>
+
+</center>
+</body>
+</html>
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/antChecksumFiles/agile_principles.html b/subprojects/gradle-docs/src/samples/userguide/tutorial/antChecksumFiles/agile_principles.html
new file mode 100644
index 0000000..0aa281d
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/antChecksumFiles/agile_principles.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<HTML>
+<HEAD>
+<title>Principles behind the Agile Manifesto</title>
+<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=windows-1252">
+</HEAD>
+<BODY>
+
+<br><br><br><br>
+<center><h1>Principles behind the Agile Manifesto</h1></center>
+
+<br><br>
+<center>
+<font size="+2">
+<i>We follow these principles:</i>
+
+</font>
+
+<p><font size="+2">
+Our highest priority is to satisfy the customer<br>
+through early and continuous delivery<br>
+of valuable software.
+</font></p>
+
+<p><font size="+2">
+Welcome changing requirements, even late in <br>
+development. Agile processes harness change for <br>
+the customer's competitive advantage.
+</font></p>
+
+<p><font size="+2">
+Deliver working software frequently, from a <br>
+couple of weeks to a couple of months, with a <br>
+preference to the shorter timescale.
+</font></p>
+
+<p><font size="+2">
+Business people and developers must work <br>
+together daily throughout the project.
+</font></p>
+
+<p><font size="+2">
+Build projects around motivated individuals. <br>
+
+Give them the environment and support they need, <br>
+and trust them to get the job done.
+</font></p>
+
+<p><font size="+2">
+The most efficient and effective method of <br>
+conveying information to and within a development <br>
+team is face-to-face conversation.
+</font></p>
+
+<p><font size="+2">
+Working software is the primary measure of progress.
+</font></p>
+
+<p><font size="+2">
+Agile processes promote sustainable development. <br>
+The sponsors, developers, and users should be able <br>
+to maintain a constant pace indefinitely.
+</font></p>
+
+<p><font size="+2">
+Continuous attention to technical excellence <br>
+and good design enhances agility.
+</font></p>
+
+<p><font size="+2">
+Simplicity--the art of maximizing the amount <br>
+
+of work not done--is essential.
+</font></p>
+
+<p><font size="+2">
+The best architectures, requirements, and designs <br>
+emerge from self-organizing teams.
+</font></p>
+
+<p><font size="+2">
+At regular intervals, the team reflects on how <br>
+to become more effective, then tunes and adjusts <br>
+its behavior accordingly.
+</font></p>
+<br><br><br>
+</center>
+</BODY>
+</HTML>
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/antChecksumFiles/dylan_thomas.txt b/subprojects/gradle-docs/src/samples/userguide/tutorial/antChecksumFiles/dylan_thomas.txt
new file mode 100644
index 0000000..c8210fd
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/antChecksumFiles/dylan_thomas.txt
@@ -0,0 +1,9 @@
+And death shall have no dominion
+Dead men naked they shall be one
+With the man in the wind and the west moon
+When their bones are picked clean and the clean bones gone
+They shall have stars at elbow and foot
+Though they go mad they shall be sane
+Though they sink through the sea they shall rise again
+Though lovers be lost love shall not
+And death shall have no dominion.
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/antChecksumWithMethod/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/antChecksumWithMethod/build.gradle
new file mode 100644
index 0000000..e435c4b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/antChecksumWithMethod/build.gradle
@@ -0,0 +1,17 @@
+task checksum << {
+    fileList('../antChecksumFiles').each {File file ->
+        ant.checksum(file: file, property: "cs_$file.name")
+        println "$file.name Checksum: ${ant.properties["cs_$file.name"]}"
+    }
+}
+
+task length << {
+    fileList('../antChecksumFiles').each {File file ->
+        ant.length(file: file, property: "lt_$file.name")
+        println "$file.name Length: ${ant.properties["lt_$file.name"]}"
+    }
+}
+
+File[] fileList(String dir) {
+    file(dir).listFiles({file -> file.isFile() } as FileFilter).sort()
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/archiveContent/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/archiveContent/build.gradle
new file mode 100644
index 0000000..01fa3a3
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/archiveContent/build.gradle
@@ -0,0 +1,36 @@
+apply plugin: 'java'
+
+// START SNIPPET file-set
+task zipWithFileSet(type: Zip) {
+    from ('contentDir') {
+        include('**/*.txt')
+        exclude('**/*.gif')
+    }
+}
+// END SNIPPET file-set
+
+// START SNIPPET files
+task zipWithFiles(type: Zip) {
+    from 'path_to_file1', 'path_to_file2'
+}
+// END SNIPPET files
+
+// START SNIPPET zip-file-set
+task zipWithZipFileSet(type: Zip) {
+    from('contentDir') {
+        include '**/*.txt'
+        exclude '**/*.gif'
+        into 'myprefix'
+    }
+}
+// END SNIPPET zip-file-set
+
+// START SNIPPET tar-file-set
+task tarWithFileSet(type: Tar) {
+    from('contentDir') {
+        include('**/*.txt')
+        exclude('**/*.gif')
+        uid = 'myuid'
+    }
+}
+// END SNIPPET tar-file-set
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/configByDag/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/configByDag/build.gradle
new file mode 100644
index 0000000..a20ce99
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/configByDag/build.gradle
@@ -0,0 +1,14 @@
+gradle.taskGraph.whenReady {taskGraph ->
+    if (taskGraph.hasTask(':release')) {
+        version = '1.0'
+    } else {
+        version = '1.0-SNAPSHOT'
+    }
+}
+
+task distribution << {
+    println "We build the zip with version=$version"
+}
+task release(dependsOn: 'distribution') << {
+    println 'We release now'
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/configureObject/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/configureObject/build.gradle
new file mode 100644
index 0000000..e5e63b3
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/configureObject/build.gradle
@@ -0,0 +1,8 @@
+task configure << {
+    pos = configure(new java.text.FieldPosition(10)) {
+        beginIndex = 1
+        endIndex = 5
+    }
+    println pos.beginIndex
+    println pos.endIndex
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/configureObjectUsingScript/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/configureObjectUsingScript/build.gradle
new file mode 100644
index 0000000..5bd4856
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/configureObjectUsingScript/build.gradle
@@ -0,0 +1,7 @@
+task configure << {
+    pos = new java.text.FieldPosition(10)
+    // Apply the script
+    apply from: 'other.gradle', to: pos
+    println pos.beginIndex
+    println pos.endIndex
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/configureObjectUsingScript/other.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/configureObjectUsingScript/other.gradle
new file mode 100644
index 0000000..4159bf9
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/configureObjectUsingScript/other.gradle
@@ -0,0 +1,2 @@
+beginIndex = 1;
+endIndex = 5;
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/configureProjectUsingScript/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/configureProjectUsingScript/build.gradle
new file mode 100644
index 0000000..3dd2e6d
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/configureProjectUsingScript/build.gradle
@@ -0,0 +1 @@
+apply from: 'other.gradle'
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/configureProjectUsingScript/other.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/configureProjectUsingScript/other.gradle
new file mode 100644
index 0000000..23bc833
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/configureProjectUsingScript/other.gradle
@@ -0,0 +1,4 @@
+println "configuring $project"
+task hello << {
+    println 'hello from other script'
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/count/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/count/build.gradle
new file mode 100644
index 0000000..06afefa
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/count/build.gradle
@@ -0,0 +1,3 @@
+task count << {
+    4.times { print "$it " }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/defaultTasks/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/defaultTasks/build.gradle
new file mode 100644
index 0000000..ed9053b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/defaultTasks/build.gradle
@@ -0,0 +1,13 @@
+defaultTasks 'clean', 'run'
+
+task clean << {
+    println 'Default Cleaning!'
+}
+
+task run << {
+    println 'Default Running!'
+}
+
+task other << {
+    println "I'm not a default task!"
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/directoryTask/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/directoryTask/build.gradle
new file mode 100644
index 0000000..0cd8c19
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/directoryTask/build.gradle
@@ -0,0 +1,10 @@
+classes = dir('build/classes')
+task resources(dependsOn: classes) << {
+    // do something
+}
+task otherResources(dependsOn: classes) << {
+    if (classes.dir.isDirectory()) {
+        println 'The class directory exists. I can operate'
+    }
+    // do something
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/disableTask/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/disableTask/build.gradle
new file mode 100644
index 0000000..01b6be3
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/disableTask/build.gradle
@@ -0,0 +1,4 @@
+task disableMe << {
+    println 'This should not be printed if the task is disabled.'
+}
+disableMe.enabled = false
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/dynamic/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/dynamic/build.gradle
new file mode 100644
index 0000000..91a2646
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/dynamic/build.gradle
@@ -0,0 +1,5 @@
+4.times { counter ->
+    task "task$counter" << {
+        println "I'm task number $counter"
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/dynamicDepends/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/dynamicDepends/build.gradle
new file mode 100644
index 0000000..209b501
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/dynamicDepends/build.gradle
@@ -0,0 +1,6 @@
+4.times { counter ->
+    task "task$counter" << {
+        println "I'm task number $counter"
+    }
+}
+task0.dependsOn task2, task3
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/dynamicProperties/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/dynamicProperties/build.gradle
new file mode 100644
index 0000000..b536f07
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/dynamicProperties/build.gradle
@@ -0,0 +1,6 @@
+task myTask
+myTask.myProperty = 'myCustomPropValue'
+
+task showProps << {
+    println myTask.myProperty
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/excludeTasks/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/excludeTasks/build.gradle
new file mode 100644
index 0000000..b78679a
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/excludeTasks/build.gradle
@@ -0,0 +1,15 @@
+task compile << {
+    println 'compiling source'
+}
+
+task compileTest(dependsOn: compile) << {
+    println 'compiling unit tests'
+}
+
+task test(dependsOn: [compile, compileTest]) << {
+    println 'running unit tests'
+}
+
+task dist(dependsOn: [compile, test]) << {
+    println 'building the distribution'
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/groovyWithFlatDir/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/groovyWithFlatDir/build.gradle
new file mode 100644
index 0000000..e24e9fc
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/groovyWithFlatDir/build.gradle
@@ -0,0 +1,18 @@
+apply plugin: 'groovy'
+
+// START SNIPPET groovy-dependency
+repositories {
+    flatDir(dirs: file('lib'))
+}
+
+dependencies {
+    groovy module(':groovy:1.6.0') {
+        dependency('asm:asm-all:2.2.3')
+        dependency('antlr:antlr:2.7.7')
+        dependency('commons-cli:commons-cli:1.2')
+        module(':ant:1.7.0') {
+            dependencies(':ant-junit:1.7.0:jar', ':ant-launcher:1.7.0')
+        }
+    }
+}
+// END SNIPPET groovy-dependency
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/hello/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/hello/build.gradle
new file mode 100644
index 0000000..0536759
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/hello/build.gradle
@@ -0,0 +1,5 @@
+task hello {
+    doLast {
+        println 'Hello world!'
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/helloEnhanced/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/helloEnhanced/build.gradle
new file mode 100644
index 0000000..54b6a08
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/helloEnhanced/build.gradle
@@ -0,0 +1,12 @@
+task hello << {
+    println 'Hello Earth'
+}
+hello.doFirst {
+    println 'Hello Venus'
+}
+hello.doLast {
+    println 'Hello Mars'
+}
+hello << {
+    println 'Hello Jupiter'
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/helloShortcut/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/helloShortcut/build.gradle
new file mode 100644
index 0000000..45618b0
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/helloShortcut/build.gradle
@@ -0,0 +1,3 @@
+task hello << {
+    println 'Hello world!'
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/helloWithShortCut/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/helloWithShortCut/build.gradle
new file mode 100644
index 0000000..9c3c23b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/helloWithShortCut/build.gradle
@@ -0,0 +1,6 @@
+task hello << {
+    println 'Hello world!'
+}
+hello.doLast {
+    println "Greetings from the $hello.name task."
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/intro/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/intro/build.gradle
new file mode 100644
index 0000000..e603dee
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/intro/build.gradle
@@ -0,0 +1,6 @@
+task hello << {
+    println 'Hello world!'
+}
+task intro(dependsOn: hello) << {
+    println "I'm Gradle"
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/lazyDependsOn/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/lazyDependsOn/build.gradle
new file mode 100644
index 0000000..f014308
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/lazyDependsOn/build.gradle
@@ -0,0 +1,6 @@
+task taskX(dependsOn: 'taskY') << {
+    println 'taskX'
+}
+task taskY << {
+    println 'taskY'
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/makeDirectory/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/makeDirectory/build.gradle
new file mode 100644
index 0000000..e52c087
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/makeDirectory/build.gradle
@@ -0,0 +1,11 @@
+classesDir = new File('build/classes')
+task resources << {
+    classesDir.mkdirs()
+    // do something
+}
+task compile(dependsOn: 'resources') << {
+    if (classesDir.isDirectory()) {
+        println 'The class directory exists. I can operate'
+    }
+    // do something
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/manifest/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/manifest/build.gradle
new file mode 100644
index 0000000..d72eb0f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/manifest/build.gradle
@@ -0,0 +1,44 @@
+apply plugin: 'java'
+version = '1.0'
+
+// START SNIPPET add-to-manifest
+jar {
+    manifest {
+        attributes("Implementation-Title": "Gradle", "Implementation-Version": version)
+    }
+}
+// END SNIPPET add-to-manifest
+
+// START SNIPPET custom-manifest
+sharedManifest = manifest {
+    attributes("Implementation-Title": "Gradle", "Implementation-Version": version)
+}
+task fooJar(type: Jar) {
+    manifest = project.manifest {
+        from sharedManifest
+    }
+}
+// END SNIPPET custom-manifest
+
+// START SNIPPET merge
+task barJar(type: Jar) {
+    manifest {
+        attributes key1: 'value1'
+        from sharedManifest, 'src/config/basemanifest.txt'
+        from('src/config/javabasemanifest.txt', 'src/config/libbasemanifest.txt') {
+            eachEntry { details ->
+                if (details.baseValue != details.mergeValue) {
+                    details.value = baseValue
+                }
+                if (details.key == 'foo') {
+                    details.exclude()
+                }
+            }
+        }
+    }
+}
+// END SNIPPET merge
+
+// START SNIPPET write
+jar.manifest.writeTo("$buildDir/mymanifest.mf")
+// END SNIPPET write
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/mkdirTrap/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/mkdirTrap/build.gradle
new file mode 100644
index 0000000..547ec97
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/mkdirTrap/build.gradle
@@ -0,0 +1,12 @@
+classesDir = file('build/classes')
+classesDir.mkdirs()
+task clean(type: Delete) {
+    delete 'build'
+}
+task compile(dependsOn: 'clean') << {
+    if (!classesDir.isDirectory()) {
+        println 'The class directory does not exist. I can not operate'
+        // do something
+    }
+    // do something
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/osgi/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/osgi/build.gradle
new file mode 100644
index 0000000..bcfbdcf
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/osgi/build.gradle
@@ -0,0 +1,23 @@
+apply {
+   plugin 'osgi'
+   plugin 'java'
+}
+
+// START SNIPPET configure-jar
+jar {
+    manifest { // the manifest of the default jar is of type OsgiManifest
+        name = 'overwrittenSpecialOsgiName'
+        instruction 'Private-Package',
+                'org.mycomp.package1',
+                'org.mycomp.package2'
+        instruction 'Bundle-Vendor', 'MyCompany'
+        instruction 'Bundle-Description', 'Platform2: Metrics 2 Measures Framework'
+        instruction 'Bundle-DocURL', 'http://www.mycompany.com'
+    }
+}
+task fooJar(type: Jar) {
+    manifest = osgiManifest {
+        instruction 'Bundle-Vendor', 'MyCompany'    
+    }
+}
+// END SNIPPET configure-jar
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/pluginAccessConvention/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/pluginAccessConvention/build.gradle
new file mode 100644
index 0000000..083f92c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/pluginAccessConvention/build.gradle
@@ -0,0 +1,11 @@
+apply plugin: 'java'
+
+task show << {
+    // Access the convention property as a project property
+    println relativePath(sourceSets.main.classesDir)
+    println relativePath(project.sourceSets.main.classesDir)
+
+    // Access the convention property via the convention object
+    println relativePath(project.convention.sourceSets.main.classesDir)
+    println relativePath(project.convention.plugins.java.sourceSets.main.classesDir)
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/pluginConfig/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/pluginConfig/build.gradle
new file mode 100644
index 0000000..40df7b1
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/pluginConfig/build.gradle
@@ -0,0 +1,7 @@
+apply plugin: 'java'
+
+task show << {
+    processResources.destinationDir = new File(buildDir, 'output')
+    println relativePath(processResources.destinationDir)
+    println relativePath(compileJava.destinationDir)
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/pluginConvention/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/pluginConvention/build.gradle
new file mode 100644
index 0000000..e6aa5b9
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/pluginConvention/build.gradle
@@ -0,0 +1,7 @@
+apply plugin: 'java'
+
+task show << {
+    sourceSets.main.classesDir = new File(buildDir, 'output')
+    println relativePath(processResources.destinationDir)
+    println relativePath(compileJava.destinationDir)
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/pluginIntro/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/pluginIntro/build.gradle
new file mode 100644
index 0000000..b8ce76e
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/pluginIntro/build.gradle
@@ -0,0 +1,18 @@
+// START SNIPPET apply-by-id
+apply plugin: 'java'
+
+task show << {
+    println relativePath(compileJava.destinationDir)
+    println relativePath(processResources.destinationDir)
+}
+// END SNIPPET apply-by-id
+// START SNIPPET apply-by-type
+apply plugin: org.gradle.api.plugins.JavaPlugin
+// END SNIPPET apply-by-type
+// START SNIPPET apply-by-type-with-import
+apply plugin: JavaPlugin
+// END SNIPPET apply-by-type-with-import
+// START SNIPPET explicit-apply
+apply plugin: 'java'
+apply plugin: 'groovy'
+// END SNIPPET explicit-apply
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/projectApi/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/projectApi/build.gradle
new file mode 100644
index 0000000..d598d2f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/projectApi/build.gradle
@@ -0,0 +1,5 @@
+println name
+println project.name
+
+task check << {
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/projectCoreProperties/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/projectCoreProperties/build.gradle
new file mode 100644
index 0000000..de24787
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/projectCoreProperties/build.gradle
@@ -0,0 +1,9 @@
+task check << {
+    allprojects {
+        println "project path $path"
+        println "  project name = $name"
+        println "  project dir = '${rootProject.relativePath(projectDir)}'"
+        println "  build file = '${rootProject.relativePath(buildFile)}'"
+        println "  build dir = '${rootProject.relativePath(buildDir)}'"
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/projectCoreProperties/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/projectCoreProperties/settings.gradle
new file mode 100644
index 0000000..79dcf99
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/projectCoreProperties/settings.gradle
@@ -0,0 +1 @@
+include 'subProject'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/projectCoreProperties/subProject/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/projectCoreProperties/subProject/build.gradle
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/projectReports/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/projectReports/build.gradle
new file mode 100644
index 0000000..2d77a0c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/projectReports/build.gradle
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+defaultTasks 'dists'
+
+allprojects {
+    task clean {
+        description = "Deletes the build directory ($buildDir.name)"
+        group = 'build'
+    }
+}
+
+task libs {
+    dependsOn { subprojects*.libs }
+    description = 'Builds the JAR'
+}
+
+// START SNIPPET add-task-to-report
+task dists {
+// END SNIPPET add-task-to-report
+    dependsOn libs
+// START SNIPPET add-task-to-report
+    description = 'Builds the distribution'
+    group = 'build'
+}
+// END SNIPPET add-task-to-report
+
+subprojects {
+    configurations {
+        compile
+    }
+    repositories {
+        mavenCentral()
+    }
+    dependencies {
+        if (project.name == 'webapp') {
+        	compile "commons-io:commons-io:1.2"
+		} else {
+			compile "junit:junit:4.7"
+		}
+    }
+    task libs {
+        if (project.name == 'webapp') {
+            dependsOn ':api:libs' 
+        }
+        description = 'Builds the JAR'
+        group = 'build'
+    }
+    tasks.addRule(new TestRule())
+}
+
+class TestRule implements Rule {
+    public String getDescription() {
+        'build<ConfigurationName>: builds the artifacts of the given configuration'
+    }
+
+    public void apply(String taskName) {
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/projectReports/settings.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/projectReports/settings.gradle
new file mode 100644
index 0000000..c42e832
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/projectReports/settings.gradle
@@ -0,0 +1,6 @@
+include 'api', 'webapp'
+
+rootProject.children.each {
+    it.buildFile.parentFile.mkdirs()
+    it.buildFile.text = ''
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/properties/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/properties/build.gradle
new file mode 100644
index 0000000..d240d83
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/properties/build.gradle
@@ -0,0 +1,7 @@
+task printProps << {
+    println commandLineProjectProp
+    println gradlePropertiesProp
+    println systemProjectProp
+    println envProjectProp
+    println System.properties['system']
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/properties/gradle.properties b/subprojects/gradle-docs/src/samples/userguide/tutorial/properties/gradle.properties
new file mode 100644
index 0000000..0083447
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/properties/gradle.properties
@@ -0,0 +1,4 @@
+gradlePropertiesProp=gradlePropertiesValue
+systemPropertiesProp=shouldBeOverWrittenBySystemProp
+envPropertiesProp=shouldBeOverWrittenByEnvProp
+systemProp.system=systemValue
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/replaceTask/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/replaceTask/build.gradle
new file mode 100644
index 0000000..12e0633
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/replaceTask/build.gradle
@@ -0,0 +1,5 @@
+task copy(type: Copy)
+
+task copy(overwrite: true) << {
+    println('I am the new one.')
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/scope.groovy b/subprojects/gradle-docs/src/samples/userguide/tutorial/scope.groovy
new file mode 100644
index 0000000..cd665a0
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/scope.groovy
@@ -0,0 +1,22 @@
+String localScope1 = 'localScope1'
+def localScope2 = 'localScope2'
+scriptScope = 'scriptScope'
+
+println localScope1
+println localScope2
+println scriptScope
+
+closure = {
+    println localScope1
+    println localScope2
+    println scriptScope
+}
+
+def method() {
+    try {localScope1} catch(MissingPropertyException e) {println 'localScope1NotAvailable' }
+    try {localScope2} catch(MissingPropertyException e) {println 'localScope2NotAvailable' }
+    println scriptScope
+}
+
+closure.call()
+method()
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/selectProject/subdir/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/selectProject/subdir/build.gradle
new file mode 100644
index 0000000..02f89d1
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/selectProject/subdir/build.gradle
@@ -0,0 +1,3 @@
+task hello << {
+    println "using build file '$buildFile.name' in '$buildFile.parentFile.name'."
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/selectProject/subdir/myproject.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/selectProject/subdir/myproject.gradle
new file mode 100644
index 0000000..02f89d1
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/selectProject/subdir/myproject.gradle
@@ -0,0 +1,3 @@
+task hello << {
+    println "using build file '$buildFile.name' in '$buildFile.parentFile.name'."
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/stopExecutionException/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/stopExecutionException/build.gradle
new file mode 100644
index 0000000..8823ebf
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/stopExecutionException/build.gradle
@@ -0,0 +1,11 @@
+task compile << {
+    println 'We are doing the compile.'
+}
+
+compile.doFirst {
+    // Here you would put arbitrary conditions in real life. But we use this as an integration test, so we want defined behavior.
+    if (true) { throw new StopExecutionException() }
+}
+task myTask(dependsOn: 'compile') << {
+   println 'I am not affected'
+}
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/taskOnlyIf/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/taskOnlyIf/build.gradle
new file mode 100644
index 0000000..ac1a6b4
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/taskOnlyIf/build.gradle
@@ -0,0 +1,5 @@
+task hello << {
+    println 'hello world'
+}
+
+hello.onlyIf { !project.hasProperty('skipHello') }
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/upper/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/upper/build.gradle
new file mode 100644
index 0000000..778a1a2
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/upper/build.gradle
@@ -0,0 +1,5 @@
+task upper << {
+    String someString = 'mY_nAmE'
+    println "Original: " + someString 
+    println "Upper case: " + someString.toUpperCase()
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/zipWithArguments/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/zipWithArguments/build.gradle
new file mode 100644
index 0000000..aa6e7f9
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/zipWithArguments/build.gradle
@@ -0,0 +1,11 @@
+apply plugin: 'java'
+archivesBaseName = 'gradle'
+version = 1.0
+
+task myZip(type: Zip) {
+    appendix = 'wrapper'
+    classifier = 'src'
+    from 'somedir'
+}
+
+println myZip.archiveName
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/zipWithArguments/somedir/file.txt b/subprojects/gradle-docs/src/samples/userguide/tutorial/zipWithArguments/somedir/file.txt
new file mode 100644
index 0000000..4a4155f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/zipWithArguments/somedir/file.txt
@@ -0,0 +1,2 @@
+something to copy
+
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/zipWithCustomName/build.gradle b/subprojects/gradle-docs/src/samples/userguide/tutorial/zipWithCustomName/build.gradle
new file mode 100644
index 0000000..10fc6d6
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/zipWithCustomName/build.gradle
@@ -0,0 +1,9 @@
+apply plugin: 'java'
+version = 1.0
+
+task myZip(type: Zip) {
+    from 'somedir'
+    baseName = 'customName'
+}
+
+println myZip.archiveName
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguide/tutorial/zipWithCustomName/somedir/file.txt b/subprojects/gradle-docs/src/samples/userguide/tutorial/zipWithCustomName/somedir/file.txt
new file mode 100644
index 0000000..4a4155f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/tutorial/zipWithCustomName/somedir/file.txt
@@ -0,0 +1,2 @@
+something to copy
+
diff --git a/subprojects/gradle-docs/src/samples/userguide/wrapper/build.gradle b/subprojects/gradle-docs/src/samples/userguide/wrapper/build.gradle
new file mode 100644
index 0000000..4997f48
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguide/wrapper/build.gradle
@@ -0,0 +1,8 @@
+//START SNIPPET wrapper-simple
+task wrapper(type: Wrapper) {
+    gradleVersion = '0.6'
+//END SNIPPET wrapper-simple
+    jarPath = 'wrapper'
+//START SNIPPET wrapper-simple
+}
+//END SNIPPET wrapper-simple
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/abbreviateCamelCaseTaskName.out b/subprojects/gradle-docs/src/samples/userguideOutput/abbreviateCamelCaseTaskName.out
new file mode 100644
index 0000000..0130ca3
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/abbreviateCamelCaseTaskName.out
@@ -0,0 +1,8 @@
+:compile
+compiling source
+:compileTest
+compiling unit tests
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/abbreviateTaskName.out b/subprojects/gradle-docs/src/samples/userguideOutput/abbreviateTaskName.out
new file mode 100644
index 0000000..dc36bda
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/abbreviateTaskName.out
@@ -0,0 +1,12 @@
+:compile
+compiling source
+:compileTest
+compiling unit tests
+:test
+running unit tests
+:dist
+building the distribution
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/accessUsingPath.out b/subprojects/gradle-docs/src/samples/userguideOutput/accessUsingPath.out
new file mode 100644
index 0000000..cf9531e
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/accessUsingPath.out
@@ -0,0 +1,4 @@
+:hello
+:hello
+:projectA:hello
+:projectA:hello
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/addBehaviourToAntTarget.out b/subprojects/gradle-docs/src/samples/userguideOutput/addBehaviourToAntTarget.out
new file mode 100644
index 0000000..ce008d9
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/addBehaviourToAntTarget.out
@@ -0,0 +1,7 @@
+:hello
+[ant:echo] Hello, from Ant
+Hello, from Gradle
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/addDependencyUsingClosure.out b/subprojects/gradle-docs/src/samples/userguideOutput/addDependencyUsingClosure.out
new file mode 100644
index 0000000..98ea647
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/addDependencyUsingClosure.out
@@ -0,0 +1,3 @@
+lib1
+lib2
+taskX
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/addDependencyUsingPath.out b/subprojects/gradle-docs/src/samples/userguideOutput/addDependencyUsingPath.out
new file mode 100644
index 0000000..e2f4145
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/addDependencyUsingPath.out
@@ -0,0 +1,2 @@
+taskY
+taskX
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/addDependencyUsingTask.out b/subprojects/gradle-docs/src/samples/userguideOutput/addDependencyUsingTask.out
new file mode 100644
index 0000000..e2f4145
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/addDependencyUsingTask.out
@@ -0,0 +1,2 @@
+taskY
+taskX
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/antChecksum.out b/subprojects/gradle-docs/src/samples/userguideOutput/antChecksum.out
new file mode 100644
index 0000000..c496307
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/antChecksum.out
@@ -0,0 +1,3 @@
+agile_manifesto.html Checksum: 2dd24e01676046d8dedc2009a1a8f563
+agile_principles.html Checksum: 659d204c8c7ccb5d633de0b0d26cd104
+dylan_thomas.txt Checksum: 91040ca1cefcbfdc8016b1b3e51f23d3
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/antChecksumWithMethod.out b/subprojects/gradle-docs/src/samples/userguideOutput/antChecksumWithMethod.out
new file mode 100644
index 0000000..c496307
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/antChecksumWithMethod.out
@@ -0,0 +1,3 @@
+agile_manifesto.html Checksum: 2dd24e01676046d8dedc2009a1a8f563
+agile_principles.html Checksum: 659d204c8c7ccb5d633de0b0d26cd104
+dylan_thomas.txt Checksum: 91040ca1cefcbfdc8016b1b3e51f23d3
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/antHello.out b/subprojects/gradle-docs/src/samples/userguideOutput/antHello.out
new file mode 100644
index 0000000..f9ef717
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/antHello.out
@@ -0,0 +1,6 @@
+:hello
+[ant:echo] Hello, from Ant
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/archiveNaming.out b/subprojects/gradle-docs/src/samples/userguideOutput/archiveNaming.out
new file mode 100644
index 0000000..e0000e1
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/archiveNaming.out
@@ -0,0 +1,3 @@
+zipProject-1.0.zip
+build/distributions
+build/distributions/zipProject-1.0.zip
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/buildProjectEvaluateEvents.out b/subprojects/gradle-docs/src/samples/userguideOutput/buildProjectEvaluateEvents.out
new file mode 100644
index 0000000..44c5ebd
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/buildProjectEvaluateEvents.out
@@ -0,0 +1,3 @@
+Evaluation of root project 'buildProjectEvaluateEvents' succeeded
+Evaluation of project ':projectA' succeeded
+Evaluation of project ':projectB' FAILED
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/buildlifecycle.out b/subprojects/gradle-docs/src/samples/userguideOutput/buildlifecycle.out
new file mode 100644
index 0000000..98c19f3
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/buildlifecycle.out
@@ -0,0 +1,9 @@
+This is executed during the initialization phase.
+This is executed during the configuration phase.
+This is also executed during the configuration phase.
+:test
+This is executed during the execution phase.
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/configByDag.out b/subprojects/gradle-docs/src/samples/userguideOutput/configByDag.out
new file mode 100644
index 0000000..c89a41b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/configByDag.out
@@ -0,0 +1,2 @@
+We build the zip with version=1.0
+We release now
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/configByDagNoRelease.out b/subprojects/gradle-docs/src/samples/userguideOutput/configByDagNoRelease.out
new file mode 100644
index 0000000..26072b7
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/configByDagNoRelease.out
@@ -0,0 +1 @@
+We build the zip with version=1.0-SNAPSHOT
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/configurationHandlingAllFiles.out b/subprojects/gradle-docs/src/samples/userguideOutput/configurationHandlingAllFiles.out
new file mode 100644
index 0000000..2096352
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/configurationHandlingAllFiles.out
@@ -0,0 +1,5 @@
+orca-1.0.jar
+seal-2.0.jar
+shark-1.0.jar
+tuna-1.0.jar
+herring-1.0.jar
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/configurationHandlingCopy.out b/subprojects/gradle-docs/src/samples/userguideOutput/configurationHandlingCopy.out
new file mode 100644
index 0000000..ebabd66
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/configurationHandlingCopy.out
@@ -0,0 +1,5 @@
+albatros
+shark
+tuna
+
+albatros
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/configurationHandlingCopyVsFiles.out b/subprojects/gradle-docs/src/samples/userguideOutput/configurationHandlingCopyVsFiles.out
new file mode 100644
index 0000000..2e8b038
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/configurationHandlingCopyVsFiles.out
@@ -0,0 +1,5 @@
+orca-1.0.jar
+seal-1.0.jar
+
+orca-1.0.jar
+seal-2.0.jar
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/configurationHandlingDependencies.out b/subprojects/gradle-docs/src/samples/userguideOutput/configurationHandlingDependencies.out
new file mode 100644
index 0000000..4cdb766
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/configurationHandlingDependencies.out
@@ -0,0 +1,10 @@
+albatros
+
+albatros
+orca
+shark
+tuna
+
+albatros
+shark
+tuna
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/configurationHandlingFiles.out b/subprojects/gradle-docs/src/samples/userguideOutput/configurationHandlingFiles.out
new file mode 100644
index 0000000..aa5efcd
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/configurationHandlingFiles.out
@@ -0,0 +1,2 @@
+orca-1.0.jar
+seal-2.0.jar
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/configureObject.out b/subprojects/gradle-docs/src/samples/userguideOutput/configureObject.out
new file mode 100644
index 0000000..26b37d0
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/configureObject.out
@@ -0,0 +1,2 @@
+1
+5
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/configureObjectUsingScript.out b/subprojects/gradle-docs/src/samples/userguideOutput/configureObjectUsingScript.out
new file mode 100644
index 0000000..26b37d0
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/configureObjectUsingScript.out
@@ -0,0 +1,2 @@
+1
+5
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/configureProjectUsingScript.out b/subprojects/gradle-docs/src/samples/userguideOutput/configureProjectUsingScript.out
new file mode 100644
index 0000000..cc565a5
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/configureProjectUsingScript.out
@@ -0,0 +1,2 @@
+configuring root project 'configureProjectUsingScript'
+hello from other script
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/count.out b/subprojects/gradle-docs/src/samples/userguideOutput/count.out
new file mode 100644
index 0000000..d7ef7d6
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/count.out
@@ -0,0 +1 @@
+0 1 2 3 
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/customPlugin.out b/subprojects/gradle-docs/src/samples/userguideOutput/customPlugin.out
new file mode 100644
index 0000000..6c70859
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/customPlugin.out
@@ -0,0 +1 @@
+Hello from the GreetingPlugin
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/customPluginWithAdvancedConvention.out b/subprojects/gradle-docs/src/samples/userguideOutput/customPluginWithAdvancedConvention.out
new file mode 100644
index 0000000..87fce72
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/customPluginWithAdvancedConvention.out
@@ -0,0 +1 @@
+Hi from Gradle
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/customPluginWithConvention.out b/subprojects/gradle-docs/src/samples/userguideOutput/customPluginWithConvention.out
new file mode 100644
index 0000000..87fce72
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/customPluginWithConvention.out
@@ -0,0 +1 @@
+Hi from Gradle
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/customTaskWithAction.out b/subprojects/gradle-docs/src/samples/userguideOutput/customTaskWithAction.out
new file mode 100644
index 0000000..bebe0b3
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/customTaskWithAction.out
@@ -0,0 +1 @@
+hello from GreetingTask
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/customTaskWithProperty.out b/subprojects/gradle-docs/src/samples/userguideOutput/customTaskWithProperty.out
new file mode 100644
index 0000000..74ea8ec
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/customTaskWithProperty.out
@@ -0,0 +1,2 @@
+hello from GreetingTask
+greetings from GreetingTask
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/custom_logging_ui.out b/subprojects/gradle-docs/src/samples/userguideOutput/custom_logging_ui.out
new file mode 100644
index 0000000..bc100ab
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/custom_logging_ui.out
@@ -0,0 +1,12 @@
+[compile]
+compiling source
+
+[testCompile]
+compiling test source
+
+[test]
+running unit tests
+
+[build]
+
+build completed
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/defaultTasks.out b/subprojects/gradle-docs/src/samples/userguideOutput/defaultTasks.out
new file mode 100644
index 0000000..612e3cf
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/defaultTasks.out
@@ -0,0 +1,2 @@
+Default Cleaning!
+Default Running!
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/dependencyListReport.out b/subprojects/gradle-docs/src/samples/userguideOutput/dependencyListReport.out
new file mode 100644
index 0000000..6a99a79
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/dependencyListReport.out
@@ -0,0 +1,17 @@
+
+------------------------------------------------------------
+Root Project
+------------------------------------------------------------
+No configurations
+
+------------------------------------------------------------
+Project :api
+------------------------------------------------------------
+compile
+|-----junit:junit:4.7:default
+
+------------------------------------------------------------
+Project :webapp
+------------------------------------------------------------
+compile
+|-----commons-io:commons-io:1.2:default
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/dependsOnAntTarget.out b/subprojects/gradle-docs/src/samples/userguideOutput/dependsOnAntTarget.out
new file mode 100644
index 0000000..41bd5d2
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/dependsOnAntTarget.out
@@ -0,0 +1,8 @@
+:hello
+[ant:echo] Hello, from Ant
+:intro
+Hello, from Gradle
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/dependsOnTask.out b/subprojects/gradle-docs/src/samples/userguideOutput/dependsOnTask.out
new file mode 100644
index 0000000..4c9f42b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/dependsOnTask.out
@@ -0,0 +1,8 @@
+:intro
+Hello, from Gradle
+:hello
+[ant:echo] Hello, from Ant
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/directoryTask.out b/subprojects/gradle-docs/src/samples/userguideOutput/directoryTask.out
new file mode 100644
index 0000000..67dcb8f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/directoryTask.out
@@ -0,0 +1 @@
+The class directory exists. I can operate
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/disableTask.out b/subprojects/gradle-docs/src/samples/userguideOutput/disableTask.out
new file mode 100644
index 0000000..28312e8
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/disableTask.out
@@ -0,0 +1,5 @@
+:disableMe SKIPPED
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/dynamic.out b/subprojects/gradle-docs/src/samples/userguideOutput/dynamic.out
new file mode 100644
index 0000000..39b8ea5
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/dynamic.out
@@ -0,0 +1 @@
+I'm task number 1
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/dynamicDepends.out b/subprojects/gradle-docs/src/samples/userguideOutput/dynamicDepends.out
new file mode 100644
index 0000000..b5a3258
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/dynamicDepends.out
@@ -0,0 +1,3 @@
+I'm task number 2
+I'm task number 3
+I'm task number 0
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/dynamicProperties.out b/subprojects/gradle-docs/src/samples/userguideOutput/dynamicProperties.out
new file mode 100644
index 0000000..32b8a4d
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/dynamicProperties.out
@@ -0,0 +1 @@
+myCustomPropValue
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/excludeTask.out b/subprojects/gradle-docs/src/samples/userguideOutput/excludeTask.out
new file mode 100644
index 0000000..5d815ef
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/excludeTask.out
@@ -0,0 +1,8 @@
+:compile
+compiling source
+:dist
+building the distribution
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/externalBuildDependency.out b/subprojects/gradle-docs/src/samples/userguideOutput/externalBuildDependency.out
new file mode 100644
index 0000000..cdc5b6e
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/externalBuildDependency.out
@@ -0,0 +1 @@
+aGVsbG8gd29ybGQK
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/externalDependencies.out b/subprojects/gradle-docs/src/samples/userguideOutput/externalDependencies.out
new file mode 100644
index 0000000..443a8a4
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/externalDependencies.out
@@ -0,0 +1 @@
+commons-collections-3.2.jar
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/externalInitDependency.out b/subprojects/gradle-docs/src/samples/userguideOutput/externalInitDependency.out
new file mode 100644
index 0000000..580bec6
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/externalInitDependency.out
@@ -0,0 +1 @@
+2 / 5
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/fileCollections.out b/subprojects/gradle-docs/src/samples/userguideOutput/fileCollections.out
new file mode 100644
index 0000000..6c98cea
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/fileCollections.out
@@ -0,0 +1,6 @@
+Contents of src
+src/dir1
+src/file1.txt
+Contents of src2
+src2/dir1
+src2/dir2
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/generatedFileDependencies.out b/subprojects/gradle-docs/src/samples/userguideOutput/generatedFileDependencies.out
new file mode 100644
index 0000000..23904ed
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/generatedFileDependencies.out
@@ -0,0 +1,2 @@
+compiling classes
+classpath = [classes]
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/hello.out b/subprojects/gradle-docs/src/samples/userguideOutput/hello.out
new file mode 100644
index 0000000..cd08755
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/hello.out
@@ -0,0 +1 @@
+Hello world!
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/helloEnhanced.out b/subprojects/gradle-docs/src/samples/userguideOutput/helloEnhanced.out
new file mode 100644
index 0000000..a65599e
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/helloEnhanced.out
@@ -0,0 +1,4 @@
+Hello Venus
+Hello Earth
+Hello Mars
+Hello Jupiter
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/helloWithShortCut.out b/subprojects/gradle-docs/src/samples/userguideOutput/helloWithShortCut.out
new file mode 100644
index 0000000..bd6b4a6
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/helloWithShortCut.out
@@ -0,0 +1,2 @@
+Hello world!
+Greetings from the hello task.
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/inheritedBuildLogic.out b/subprojects/gradle-docs/src/samples/userguideOutput/inheritedBuildLogic.out
new file mode 100644
index 0000000..b6282cf
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/inheritedBuildLogic.out
@@ -0,0 +1,2 @@
+srcDirName: src/java
+srcDir: child/src/java
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/injectedBuildLogic.out b/subprojects/gradle-docs/src/samples/userguideOutput/injectedBuildLogic.out
new file mode 100644
index 0000000..af8154b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/injectedBuildLogic.out
@@ -0,0 +1,6 @@
+project: :child1
+srcDirName: java
+srcDir: child1/java
+project: :child2
+srcDirName: src/java/legacy
+srcDir: child2/src/java/legacy
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/intro.out b/subprojects/gradle-docs/src/samples/userguideOutput/intro.out
new file mode 100644
index 0000000..bf6564b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/intro.out
@@ -0,0 +1,2 @@
+Hello world!
+I'm Gradle
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/javaQuickstart.out b/subprojects/gradle-docs/src/samples/userguideOutput/javaQuickstart.out
new file mode 100644
index 0000000..78b7985
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/javaQuickstart.out
@@ -0,0 +1,15 @@
+:compileJava
+:processResources
+:classes
+:jar
+:assemble
+:compileTestJava
+:processTestResources
+:testClasses
+:test
+:check
+:build
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/lazyDependsOn.out b/subprojects/gradle-docs/src/samples/userguideOutput/lazyDependsOn.out
new file mode 100644
index 0000000..e2f4145
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/lazyDependsOn.out
@@ -0,0 +1,2 @@
+taskY
+taskX
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/makeDirectory.out b/subprojects/gradle-docs/src/samples/userguideOutput/makeDirectory.out
new file mode 100644
index 0000000..67dcb8f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/makeDirectory.out
@@ -0,0 +1 @@
+The class directory exists. I can operate
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/mkdirTrap.out b/subprojects/gradle-docs/src/samples/userguideOutput/mkdirTrap.out
new file mode 100644
index 0000000..8218f44
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/mkdirTrap.out
@@ -0,0 +1 @@
+The class directory does not exist. I can not operate
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multipleTasksFromCommandLine.out b/subprojects/gradle-docs/src/samples/userguideOutput/multipleTasksFromCommandLine.out
new file mode 100644
index 0000000..dc36bda
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multipleTasksFromCommandLine.out
@@ -0,0 +1,12 @@
+:compile
+compiling source
+:compileTest
+compiling unit tests
+:test
+running unit tests
+:dist
+building the distribution
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectAbsoluteTaskPaths.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectAbsoluteTaskPaths.out
new file mode 100644
index 0000000..b4cc5b7
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectAbsoluteTaskPaths.out
@@ -0,0 +1,7 @@
+I'm water
+I'm krill
+- I depend on water
+- The weight of my species in summer is twice as heavy as all human beings.
+- I love to spend time in the arctic waters.
+I'm tropicalFish
+- I depend on water
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectAddKrill.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectAddKrill.out
new file mode 100644
index 0000000..cd13a50
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectAddKrill.out
@@ -0,0 +1,3 @@
+I'm water
+I'm bluewhale
+I'm krill
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectAddTropical.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectAddTropical.out
new file mode 100644
index 0000000..eab5507
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectAddTropical.out
@@ -0,0 +1,11 @@
+I'm water
+I'm bluewhale
+- I depend on water
+- I love to spend time in the arctic waters.
+- I'm the largest animal that has ever lived on this planet.
+I'm krill
+- I depend on water
+- I love to spend time in the arctic waters.
+- The weight of my species in summer is twice as heavy as all human beings.
+I'm tropicalFish
+- I depend on water
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectFirstExample.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectFirstExample.out
new file mode 100644
index 0000000..369f657
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectFirstExample.out
@@ -0,0 +1,2 @@
+I'm water
+I'm bluewhale
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectFirstMessages.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectFirstMessages.out
new file mode 100644
index 0000000..8f3a6a1
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectFirstMessages.out
@@ -0,0 +1,2 @@
+Consuming message: null
+Producing message:
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectFlat.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectFlat.out
new file mode 100644
index 0000000..57f83cf
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectFlat.out
@@ -0,0 +1,3 @@
+I'm master
+I'm dolphin
+I'm shark
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectFlatPartial.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectFlatPartial.out
new file mode 100644
index 0000000..7ce5f49
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectFlatPartial.out
@@ -0,0 +1 @@
+I'm shark
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectFlatPartialWithNoDefaultMaster.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectFlatPartialWithNoDefaultMaster.out
new file mode 100644
index 0000000..7ce5f49
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectFlatPartialWithNoDefaultMaster.out
@@ -0,0 +1 @@
+I'm shark
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesConfigDependencies.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesConfigDependencies.out
new file mode 100644
index 0000000..b087c25
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesConfigDependencies.out
@@ -0,0 +1 @@
+Consuming message: Watch the order of evaluation.
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesConfigDependenciesAltSolution.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesConfigDependenciesAltSolution.out
new file mode 100644
index 0000000..b087c25
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesConfigDependenciesAltSolution.out
@@ -0,0 +1 @@
+Consuming message: Watch the order of evaluation.
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesConfigDependenciesBroken.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesConfigDependenciesBroken.out
new file mode 100644
index 0000000..5a4e9b1
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesConfigDependenciesBroken.out
@@ -0,0 +1 @@
+Consuming message: null
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesDependencies.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesDependencies.out
new file mode 100644
index 0000000..58abfbd
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesDependencies.out
@@ -0,0 +1,2 @@
+Producing message:
+Consuming message: Watch the order of execution.
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesDependenciesSubBuild.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesDependenciesSubBuild.out
new file mode 100644
index 0000000..58abfbd
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesDependenciesSubBuild.out
@@ -0,0 +1,2 @@
+Producing message:
+Consuming message: Watch the order of execution.
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesDifferentTaskNames.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesDifferentTaskNames.out
new file mode 100644
index 0000000..5a4e9b1
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesDifferentTaskNames.out
@@ -0,0 +1 @@
+Consuming message: null
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesHack.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesHack.out
new file mode 100644
index 0000000..58abfbd
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesHack.out
@@ -0,0 +1,2 @@
+Producing message:
+Consuming message: Watch the order of execution.
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesHackBroken.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesHackBroken.out
new file mode 100644
index 0000000..5a4e9b1
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesHackBroken.out
@@ -0,0 +1 @@
+Consuming message: null
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesTaskDependencies.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesTaskDependencies.out
new file mode 100644
index 0000000..58abfbd
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectMessagesTaskDependencies.out
@@ -0,0 +1,2 @@
+Producing message:
+Consuming message: Watch the order of execution.
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectPartialTasks.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectPartialTasks.out
new file mode 100644
index 0000000..b09da5b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectPartialTasks.out
@@ -0,0 +1,2 @@
+20 nautical miles
+5 nautical miles
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectPartialTasksNotQuiet.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectPartialTasksNotQuiet.out
new file mode 100644
index 0000000..4c40976
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectPartialTasksNotQuiet.out
@@ -0,0 +1,8 @@
+:bluewhale:distanceToIceberg
+20 nautical miles
+:krill:distanceToIceberg
+5 nautical miles
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectSpreadSpecifics.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectSpreadSpecifics.out
new file mode 100644
index 0000000..686d7c5
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectSpreadSpecifics.out
@@ -0,0 +1,7 @@
+I'm water
+I'm bluewhale
+- I depend on water
+- I'm the largest animal that has ever lived on this planet.
+I'm krill
+- I depend on water
+- The weight of my species in summer is twice as heavy as all human beings.
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectSubBuild.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectSubBuild.out
new file mode 100644
index 0000000..23b3ae3
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectSubBuild.out
@@ -0,0 +1,4 @@
+I'm bluewhale
+- I depend on water
+- I'm the largest animal that has ever lived on this planet.
+- I love to spend time in the arctic waters.
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectSubprojectsAddFromTop.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectSubprojectsAddFromTop.out
new file mode 100644
index 0000000..d65e547
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectSubprojectsAddFromTop.out
@@ -0,0 +1,6 @@
+I'm water
+I'm bluewhale
+- I depend on water
+- I'm the largest animal that has ever lived on this planet.
+I'm krill
+- I depend on water
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectTropicalWithProperties.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectTropicalWithProperties.out
new file mode 100644
index 0000000..41a0e02
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectTropicalWithProperties.out
@@ -0,0 +1,11 @@
+I'm water
+I'm bluewhale
+- I depend on water
+- I'm the largest animal that has ever lived on this planet.
+- I love to spend time in the arctic waters.
+I'm krill
+- I depend on water
+- The weight of my species in summer is twice as heavy as all human beings.
+- I love to spend time in the arctic waters.
+I'm tropicalFish
+- I depend on water
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectUseSubprojects.out b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectUseSubprojects.out
new file mode 100644
index 0000000..9f425de
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multiprojectUseSubprojects.out
@@ -0,0 +1,5 @@
+I'm water
+I'm bluewhale
+- I depend on water
+I'm krill
+- I depend on water
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multitestingBuild.out b/subprojects/gradle-docs/src/samples/userguideOutput/multitestingBuild.out
new file mode 100644
index 0000000..cf6509b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multitestingBuild.out
@@ -0,0 +1,19 @@
+:shared:compileJava
+:shared:processResources
+:shared:classes
+:shared:jar
+:api:compileJava
+:api:processResources
+:api:classes
+:api:jar
+:api:assemble
+:api:compileTestJava
+:api:processTestResources
+:api:testClasses
+:api:test
+:api:check
+:api:build
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multitestingBuildDashA.out b/subprojects/gradle-docs/src/samples/userguideOutput/multitestingBuildDashA.out
new file mode 100644
index 0000000..f15dca6
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multitestingBuildDashA.out
@@ -0,0 +1,15 @@
+:api:compileJava
+:api:processResources
+:api:classes
+:api:jar
+:api:assemble
+:api:compileTestJava
+:api:processTestResources
+:api:testClasses
+:api:test
+:api:check
+:api:build
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multitestingBuildDependents.out b/subprojects/gradle-docs/src/samples/userguideOutput/multitestingBuildDependents.out
new file mode 100644
index 0000000..5d47300
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multitestingBuildDependents.out
@@ -0,0 +1,31 @@
+:shared:compileJava
+:shared:processResources
+:shared:classes
+:shared:jar
+:api:compileJava
+:api:processResources
+:api:classes
+:api:jar
+:api:assemble
+:api:compileTestJava
+:api:processTestResources
+:api:testClasses
+:api:test
+:api:check
+:api:build
+:services:personService:compileJava
+:services:personService:processResources
+:services:personService:classes
+:services:personService:jar
+:services:personService:assemble
+:services:personService:compileTestJava
+:services:personService:processTestResources
+:services:personService:testClasses
+:services:personService:test
+:services:personService:check
+:services:personService:build
+:api:buildDependents
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/multitestingBuildNeeded.out b/subprojects/gradle-docs/src/samples/userguideOutput/multitestingBuildNeeded.out
new file mode 100644
index 0000000..e4347d3
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/multitestingBuildNeeded.out
@@ -0,0 +1,27 @@
+:shared:compileJava
+:shared:processResources
+:shared:classes
+:shared:jar
+:api:compileJava
+:api:processResources
+:api:classes
+:api:jar
+:api:assemble
+:api:compileTestJava
+:api:processTestResources
+:api:testClasses
+:api:test
+:api:check
+:api:build
+:shared:assemble
+:shared:compileTestJava
+:shared:processTestResources
+:shared:testClasses
+:shared:test
+:shared:check
+:shared:build
+:api:buildNeeded
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/nestedBuild.out b/subprojects/gradle-docs/src/samples/userguideOutput/nestedBuild.out
new file mode 100644
index 0000000..71a940c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/nestedBuild.out
@@ -0,0 +1 @@
+hello from the other build.
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/pluginAccessConvention.out b/subprojects/gradle-docs/src/samples/userguideOutput/pluginAccessConvention.out
new file mode 100644
index 0000000..f14f2a5
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/pluginAccessConvention.out
@@ -0,0 +1,4 @@
+build/classes/main
+build/classes/main
+build/classes/main
+build/classes/main
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/pluginConfig.out b/subprojects/gradle-docs/src/samples/userguideOutput/pluginConfig.out
new file mode 100644
index 0000000..9d22cbc
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/pluginConfig.out
@@ -0,0 +1,2 @@
+build/output
+build/classes/main
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/pluginConvention.out b/subprojects/gradle-docs/src/samples/userguideOutput/pluginConvention.out
new file mode 100644
index 0000000..6daa1e0
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/pluginConvention.out
@@ -0,0 +1,2 @@
+build/output
+build/output
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/pluginIntro.out b/subprojects/gradle-docs/src/samples/userguideOutput/pluginIntro.out
new file mode 100644
index 0000000..ce14304
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/pluginIntro.out
@@ -0,0 +1,2 @@
+build/classes/main
+build/classes/main
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/projectApi.out b/subprojects/gradle-docs/src/samples/userguideOutput/projectApi.out
new file mode 100644
index 0000000..8953670
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/projectApi.out
@@ -0,0 +1,2 @@
+projectApi
+projectApi
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/projectCoreProperties.out b/subprojects/gradle-docs/src/samples/userguideOutput/projectCoreProperties.out
new file mode 100644
index 0000000..aee5606
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/projectCoreProperties.out
@@ -0,0 +1,10 @@
+project path :
+  project name = projectCoreProperties
+  project dir = '.'
+  build file = 'build.gradle'
+  build dir = 'build'
+project path :subProject
+  project name = subProject
+  project dir = 'subProject'
+  build file = 'subProject/build.gradle'
+  build dir = 'subProject/build'
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/projectEvaluateEvents.out b/subprojects/gradle-docs/src/samples/userguideOutput/projectEvaluateEvents.out
new file mode 100644
index 0000000..1c4a35c
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/projectEvaluateEvents.out
@@ -0,0 +1,2 @@
+Adding test task to project ':projectA'
+Running tests for project ':projectA'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/properties.out b/subprojects/gradle-docs/src/samples/userguideOutput/properties.out
new file mode 100644
index 0000000..485fcc0
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/properties.out
@@ -0,0 +1,5 @@
+commandLineProjectPropValue
+gradlePropertiesValue
+systemPropertyValue
+envPropertyValue
+systemValue
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/propertyListReport.out b/subprojects/gradle-docs/src/samples/userguideOutput/propertyListReport.out
new file mode 100644
index 0000000..fe4ea49
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/propertyListReport.out
@@ -0,0 +1,15 @@
+
+------------------------------------------------------------
+Project :api
+------------------------------------------------------------
+additionalProperties: {}
+all: [task ':api:clean', task ':api:libs']
+allprojects: [project ':api']
+ant: org.gradle.api.internal.project.DefaultAntBuilder at 12345
+antBuilderFactory: org.gradle.api.internal.project.DefaultAntBuilderFactory at 12345
+artifacts: org.gradle.api.internal.artifacts.dsl.DefaultArtifactHandler at 12345
+asDynamicObject: org.gradle.api.internal.DynamicObjectHelper at 12345
+asMap: {clean=task ':api:clean', libs=task ':api:libs'}
+buildDir: /home/user/gradle/samples/userguide/tutorial/projectReports/api/build
+buildDirName: build
+buildFile: /home/user/gradle/samples/userguide/tutorial/projectReports/api/build.gradle
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/replaceTask.out b/subprojects/gradle-docs/src/samples/userguideOutput/replaceTask.out
new file mode 100644
index 0000000..39c80e0
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/replaceTask.out
@@ -0,0 +1 @@
+I am the new one.
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/scope.out b/subprojects/gradle-docs/src/samples/userguideOutput/scope.out
new file mode 100644
index 0000000..dcb9915
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/scope.out
@@ -0,0 +1,9 @@
+localScope1
+localScope2
+scriptScope
+localScope1
+localScope2
+scriptScope
+localScope1NotAvailable
+localScope2NotAvailable
+scriptScope
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/selectProjectUsingBuildFile.out b/subprojects/gradle-docs/src/samples/userguideOutput/selectProjectUsingBuildFile.out
new file mode 100644
index 0000000..9ecf5e1
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/selectProjectUsingBuildFile.out
@@ -0,0 +1 @@
+using build file 'myproject.gradle' in 'subdir'.
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/selectProjectUsingProjectDir.out b/subprojects/gradle-docs/src/samples/userguideOutput/selectProjectUsingProjectDir.out
new file mode 100644
index 0000000..1ea848e
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/selectProjectUsingProjectDir.out
@@ -0,0 +1 @@
+using build file 'build.gradle' in 'subdir'.
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/stopExecutionException.out b/subprojects/gradle-docs/src/samples/userguideOutput/stopExecutionException.out
new file mode 100644
index 0000000..781d43a
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/stopExecutionException.out
@@ -0,0 +1 @@
+I am not affected
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/taskCreationEvents.out b/subprojects/gradle-docs/src/samples/userguideOutput/taskCreationEvents.out
new file mode 100644
index 0000000..88eddf9
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/taskCreationEvents.out
@@ -0,0 +1 @@
+source dir is src/main/java
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/taskExecutionEvents.out b/subprojects/gradle-docs/src/samples/userguideOutput/taskExecutionEvents.out
new file mode 100644
index 0000000..7fd3f1b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/taskExecutionEvents.out
@@ -0,0 +1,4 @@
+executing task ':ok' ...
+done
+executing task ':broken' ...
+FAILED
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/taskListAllReport.out b/subprojects/gradle-docs/src/samples/userguideOutput/taskListAllReport.out
new file mode 100644
index 0000000..f769fd8
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/taskListAllReport.out
@@ -0,0 +1,11 @@
+
+------------------------------------------------------------
+Root Project
+------------------------------------------------------------
+Default tasks: dists
+
+Build tasks
+-----------
+:clean - Deletes the build directory (build)
+:dists - Builds the distribution [:api:libs, :webapp:libs]
+    :libs - Builds the JAR
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/taskListReport.out b/subprojects/gradle-docs/src/samples/userguideOutput/taskListReport.out
new file mode 100644
index 0000000..59f4b71
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/taskListReport.out
@@ -0,0 +1,10 @@
+
+------------------------------------------------------------
+Root Project
+------------------------------------------------------------
+Default tasks: dists
+
+Build tasks
+-----------
+:clean - Deletes the build directory (build)
+:dists - Builds the distribution
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/taskOnlyIf.out b/subprojects/gradle-docs/src/samples/userguideOutput/taskOnlyIf.out
new file mode 100644
index 0000000..12fe752
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/taskOnlyIf.out
@@ -0,0 +1,5 @@
+:hello SKIPPED
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/taskRule.out b/subprojects/gradle-docs/src/samples/userguideOutput/taskRule.out
new file mode 100644
index 0000000..c31d109
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/taskRule.out
@@ -0,0 +1 @@
+Pinging: Server1
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/taskRuleDependsOn.out b/subprojects/gradle-docs/src/samples/userguideOutput/taskRuleDependsOn.out
new file mode 100644
index 0000000..3bce976
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/taskRuleDependsOn.out
@@ -0,0 +1,2 @@
+Pinging: Server1
+Pinging: Server2
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/taskWithNestedText.out b/subprojects/gradle-docs/src/samples/userguideOutput/taskWithNestedText.out
new file mode 100644
index 0000000..cdde2b6
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/taskWithNestedText.out
@@ -0,0 +1,6 @@
+:hello
+[ant:echo] hello from Ant
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/upper.out b/subprojects/gradle-docs/src/samples/userguideOutput/upper.out
new file mode 100644
index 0000000..2290527
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/upper.out
@@ -0,0 +1,2 @@
+Original: mY_nAmE
+Upper case: MY_NAME
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/useAntTask.out b/subprojects/gradle-docs/src/samples/userguideOutput/useAntTask.out
new file mode 100644
index 0000000..cdde2b6
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/useAntTask.out
@@ -0,0 +1,6 @@
+:hello
+[ant:echo] hello from Ant
+
+BUILD SUCCESSFUL
+
+Total time: 1 secs
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/zipWithArguments.out b/subprojects/gradle-docs/src/samples/userguideOutput/zipWithArguments.out
new file mode 100644
index 0000000..0333bec
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/zipWithArguments.out
@@ -0,0 +1 @@
+gradle-wrapper-1.0-src.zip
diff --git a/subprojects/gradle-docs/src/samples/userguideOutput/zipWithCustomName.out b/subprojects/gradle-docs/src/samples/userguideOutput/zipWithCustomName.out
new file mode 100644
index 0000000..cc214fb
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/userguideOutput/zipWithCustomName.out
@@ -0,0 +1 @@
+customName-1.0.zip
diff --git a/subprojects/gradle-docs/src/samples/water/bluewhale/build.gradle b/subprojects/gradle-docs/src/samples/water/bluewhale/build.gradle
new file mode 100644
index 0000000..d546c8f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/water/bluewhale/build.gradle
@@ -0,0 +1,7 @@
+
+    dependsOn(':krill')
+
+    hello.doLast {
+        println "I'm the largets animal which has ever lived on this planet!"
+    }
+    
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/water/build.gradle b/subprojects/gradle-docs/src/samples/water/build.gradle
new file mode 100644
index 0000000..e157bde
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/water/build.gradle
@@ -0,0 +1,16 @@
+childrenDependOnMe()
+
+allprojects {
+    task hello << {Task task ->
+        println "Hello, I'm $task.project.name"
+    }
+}
+
+subprojects*.hello*.doLast {
+    println 'I love water.'
+}
+
+hello << {
+    println 'As you all know, I cover three quarters of this planet!'
+}
+    
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/water/krill/build.gradle b/subprojects/gradle-docs/src/samples/water/krill/build.gradle
new file mode 100644
index 0000000..d1c7d4f
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/water/krill/build.gradle
@@ -0,0 +1,7 @@
+
+    dependsOn(':phytoplankton')
+
+    hello.doLast {
+        println "The weight of my species in summer is twice as heavy as all human beings!"
+    }
+    
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/water/phytoplankton/build.gradle b/subprojects/gradle-docs/src/samples/water/phytoplankton/build.gradle
new file mode 100644
index 0000000..ec57a7d
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/water/phytoplankton/build.gradle
@@ -0,0 +1,5 @@
+
+    hello.doLast {
+        println "I produce as much oxygen as all the other plants on earth together!"
+    }
+    
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/water/settings.gradle b/subprojects/gradle-docs/src/samples/water/settings.gradle
new file mode 100644
index 0000000..8f7c568
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/water/settings.gradle
@@ -0,0 +1 @@
+include 'bluewhale', 'krill', 'phytoplankton'
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/webApplication/customised/build.gradle b/subprojects/gradle-docs/src/samples/webApplication/customised/build.gradle
new file mode 100644
index 0000000..b148244
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/webApplication/customised/build.gradle
@@ -0,0 +1,65 @@
+import org.apache.commons.httpclient.HttpClient
+import org.apache.commons.httpclient.methods.GetMethod
+
+group = 'gradle'
+version = '1.0'
+apply plugin: 'war'
+apply plugin: 'jetty'
+
+configurations {
+   moreLibs
+}
+
+repositories {
+   flatDir(dirs: "$projectDir/lib")
+   mavenCentral()
+}
+
+dependencies {
+    compile module(":compile:1.0") {
+        dependency ":compile-transitive-1.0 at jar"
+        dependency ":providedCompile-transitive:1.0 at jar"
+    }
+    providedCompile "javax.servlet:servlet-api:2.5"
+    providedCompile module(":providedCompile:1.0") {
+        dependency ":providedCompile-transitive:1.0 at jar"
+    }
+    runtime ":runtime:1.0"
+    providedRuntime ":providedRuntime:1.0 at jar"
+    testCompile "junit:junit:3.8.2"
+    moreLibs ":otherLib:1.0"
+}
+
+war {
+    from 'src/rootContent' // adds a file-set to the root of the archive
+    webInf { from 'src/additionalWebInf' } // adds a file-set to the WEB-INF dir.
+    classpath fileTree('additionalLibs') // adds a file-set to the WEB-INF/lib dir.
+    classpath configurations.moreLibs // adds a configuration to the WEB-INF/lib dir.
+    webXml = file('src/someWeb.xml') // copies a file to WEB-INF/web.xml
+}
+
+// START SNIPPET enable-jar
+jar.enabled = true
+// END SNIPPET enable-jar
+
+[jettyRun, jettyRunWar]*.daemon = true
+stopKey = 'foo'
+stopPort = 9451
+httpPort = 8163
+
+task runTest(dependsOn: jettyRun) << {
+    callServlet()
+}
+
+task runWarTest(dependsOn: jettyRunWar) << {
+    callServlet()
+}
+
+private void callServlet() {
+    HttpClient client = new HttpClient()
+    GetMethod method = new GetMethod("http://localhost:$httpPort/customised/hello")
+    client.executeMethod(method)
+    new File(buildDir, "servlet-out.txt").write(method.getResponseBodyAsString())
+    jettyStop.execute()
+}
+
diff --git a/subprojects/gradle-docs/src/samples/webApplication/customised/readme.xml b/subprojects/gradle-docs/src/samples/webApplication/customised/readme.xml
new file mode 100644
index 0000000..ad69e48
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/webApplication/customised/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>Web application with customized WAR contents.</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml b/subprojects/gradle-docs/src/samples/webApplication/customised/src/additionalWebInf/additional.xml
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/webApplication/customised/src/main/java/org/gradle/HelloServlet.java b/subprojects/gradle-docs/src/samples/webApplication/customised/src/main/java/org/gradle/HelloServlet.java
new file mode 100644
index 0000000..3dd0d02
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/webApplication/customised/src/main/java/org/gradle/HelloServlet.java
@@ -0,0 +1,14 @@
+package org.gradle;
+
+import java.io.*;
+
+import javax.servlet.http.*;
+import javax.servlet.*;
+
+public class HelloServlet extends HttpServlet {
+    public void doGet (HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
+        PrintWriter out = res.getWriter();
+        out.print("Hello Gradle");
+        out.close();
+    }
+}
diff --git a/subprojects/gradle-docs/src/samples/webApplication/customised/src/main/java/org/gradle/MyClass.java b/subprojects/gradle-docs/src/samples/webApplication/customised/src/main/java/org/gradle/MyClass.java
new file mode 100644
index 0000000..6a93b19
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/webApplication/customised/src/main/java/org/gradle/MyClass.java
@@ -0,0 +1,11 @@
+package org.gradle;
+
+public class MyClass {
+    org.CompileClass compile;
+    org.ProvidedCompileClass providedCompile;
+
+    public void doSomething() throws Exception {
+        Class.forName("org.RuntimeClass");
+        Class.forName("org.ProvidedRuntimeClass");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/webApplication/customised/src/main/webapp/WEB-INF/webapp.xml b/subprojects/gradle-docs/src/samples/webApplication/customised/src/main/webapp/WEB-INF/webapp.xml
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/webApplication/customised/src/main/webapp/webapp.html b/subprojects/gradle-docs/src/samples/webApplication/customised/src/main/webapp/webapp.html
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/webApplication/customised/src/rootContent/root.txt b/subprojects/gradle-docs/src/samples/webApplication/customised/src/rootContent/root.txt
new file mode 100644
index 0000000..e69de29
diff --git a/subprojects/gradle-docs/src/samples/webApplication/customised/src/someWeb.xml b/subprojects/gradle-docs/src/samples/webApplication/customised/src/someWeb.xml
new file mode 100644
index 0000000..e19f781
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/webApplication/customised/src/someWeb.xml
@@ -0,0 +1,14 @@
+<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
+http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
+         version="2.4">
+    <servlet>
+        <servlet-name>Hello</servlet-name>
+        <servlet-class>org.gradle.HelloServlet</servlet-class>
+    </servlet>
+    <servlet-mapping>
+        <servlet-name>Hello</servlet-name>
+        <url-pattern>/hello</url-pattern>
+    </servlet-mapping>
+</web-app>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/webApplication/customised/src/test/java/org/MyClassTest.java b/subprojects/gradle-docs/src/samples/webApplication/customised/src/test/java/org/MyClassTest.java
new file mode 100644
index 0000000..9a49462
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/webApplication/customised/src/test/java/org/MyClassTest.java
@@ -0,0 +1,7 @@
+package org;
+
+public class MyClassTest extends junit.framework.TestCase {
+    public void testDoSomething() throws Exception {
+        new MyClass().doSomething();
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/webApplication/quickstart/build.gradle b/subprojects/gradle-docs/src/samples/webApplication/quickstart/build.gradle
new file mode 100644
index 0000000..51cb498
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/webApplication/quickstart/build.gradle
@@ -0,0 +1,38 @@
+// START SNIPPET use-war-plugin
+apply plugin: 'war'
+// END SNIPPET use-war-plugin
+// START SNIPPET use-jetty-plugin
+apply plugin: 'jetty'
+// END SNIPPET use-jetty-plugin
+
+repositories {
+    mavenCentral()
+}
+
+dependencies {
+    compile group: 'commons-io', name: 'commons-io', version: '1.4'
+    compile group: 'log4j', name: 'log4j', version: '1.2.15', ext: 'jar'
+}
+
+gradle.taskGraph.whenReady {graph ->
+    if (graph.hasTask(runTest) || graph.hasTask(runWarTest)) {
+        [jettyRun, jettyRunWar]*.daemon = true
+    }
+}
+stopKey = 'foo'
+stopPort = 9451
+httpPort = 8163
+
+task runTest(dependsOn: jettyRun) << {
+    callServlet()
+}
+
+task runWarTest(dependsOn: jettyRunWar) << {
+    callServlet()
+}
+
+private void callServlet() {
+    URL url = new URL("http://localhost:$httpPort/quickstart")
+    println url.text
+    jettyStop.execute()
+}
diff --git a/subprojects/gradle-docs/src/samples/webApplication/quickstart/readme.xml b/subprojects/gradle-docs/src/samples/webApplication/quickstart/readme.xml
new file mode 100644
index 0000000..e5f0b7b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/webApplication/quickstart/readme.xml
@@ -0,0 +1,3 @@
+<sample>
+    <para>Web application quickstart project</para>
+</sample>
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/webApplication/quickstart/src/main/java/org/gradle/sample/Greeter.java b/subprojects/gradle-docs/src/samples/webApplication/quickstart/src/main/java/org/gradle/sample/Greeter.java
new file mode 100644
index 0000000..a40eb4b
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/webApplication/quickstart/src/main/java/org/gradle/sample/Greeter.java
@@ -0,0 +1,18 @@
+package org.gradle.sample;
+
+import java.io.InputStream;
+import org.apache.log4j.LogManager;
+import org.apache.commons.io.IOUtils;
+
+public class Greeter {
+    public String getGreeting() throws Exception {
+        LogManager.getRootLogger().info("generating greeting.");
+        InputStream greetingStr = getClass().getResourceAsStream("/greeting.txt");
+        try {
+            return IOUtils.toString(greetingStr).trim();
+        }
+        finally {
+            greetingStr.close();
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/webApplication/quickstart/src/main/resources/greeting.txt b/subprojects/gradle-docs/src/samples/webApplication/quickstart/src/main/resources/greeting.txt
new file mode 100644
index 0000000..9d24e16
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/webApplication/quickstart/src/main/resources/greeting.txt
@@ -0,0 +1 @@
+hello Gradle
\ No newline at end of file
diff --git a/subprojects/gradle-docs/src/samples/webApplication/quickstart/src/main/webapp/index.jsp b/subprojects/gradle-docs/src/samples/webApplication/quickstart/src/main/webapp/index.jsp
new file mode 100644
index 0000000..f2094e8
--- /dev/null
+++ b/subprojects/gradle-docs/src/samples/webApplication/quickstart/src/main/webapp/index.jsp
@@ -0,0 +1,4 @@
+<jsp:useBean id="greeter" class="org.gradle.sample.Greeter"/>
+<html>
+<p>${greeter.greeting}</p>
+</html>
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/eclipse.gradle b/subprojects/gradle-eclipse/eclipse.gradle
new file mode 100644
index 0000000..9fad845
--- /dev/null
+++ b/subprojects/gradle-eclipse/eclipse.gradle
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+dependencies {
+    groovy libraries.groovy_depends
+
+    compile project(':scala')
+    compile project(':core')
+    compile project(':plugins')
+    compile libraries.commons_io
+    compile libraries.slf4j_api, libraries.jaxen
+    compile 'dom4j:dom4j:1.6.1 at jar'
+
+
+    testCompile project(path: ':core', configuration: 'testFixtures')
+    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+}
+
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/AbstractXmlGeneratorTask.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/AbstractXmlGeneratorTask.groovy
new file mode 100644
index 0000000..342bf0f
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/AbstractXmlGeneratorTask.groovy
@@ -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.plugins.eclipse
+
+import org.gradle.api.Action
+import org.gradle.api.internal.ConventionTask
+import org.gradle.listener.ListenerBroadcast
+
+/**
+ * @author Hans Dockter
+ */
+class AbstractXmlGeneratorTask extends ConventionTask {
+    ListenerBroadcast<Action> beforeConfiguredActions = new ListenerBroadcast<Action>(Action.class);
+    ListenerBroadcast<Action> whenConfiguredActions = new ListenerBroadcast<Action>(Action.class);
+    ListenerBroadcast<Action> withXmlActions = new ListenerBroadcast<Action>(Action.class);
+
+    void withXml(Closure closure) {
+        withXmlActions.add("execute", closure);
+    }
+
+    void beforeConfigured(Closure closure) {
+        beforeConfiguredActions.add("execute", closure);
+    }
+
+    void whenConfigured(Closure closure) {
+        whenConfiguredActions.add("execute", closure);
+    }
+
+
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseClasspath.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseClasspath.groovy
new file mode 100644
index 0000000..5dd0224
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseClasspath.groovy
@@ -0,0 +1,109 @@
+/*
+ * 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.plugins.eclipse
+
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import org.gradle.plugins.eclipse.model.internal.ModelFactory
+import org.gradle.plugins.eclipse.model.Container
+import org.gradle.plugins.eclipse.model.Classpath
+
+/**
+ * Generates an eclipse <i>.classpath</i> file.
+ *
+ * @author Hans Dockter
+ */
+public class EclipseClasspath extends AbstractXmlGeneratorTask {
+    /**
+     * The file that is merged into the to be produced classpath file. This file must not exist.
+     */
+    File inputFile
+
+    @OutputFile
+    /**
+     * The output file where to generate the classpath to.
+     */
+    File outputFile
+
+    /**
+     * The source sets to be added to the classpath.
+     */
+    NamedDomainObjectContainer sourceSets
+
+    /**
+     * The configurations which files are to be transformed into classpath entries.
+     */
+    Set<Configuration> plusConfigurations = new LinkedHashSet<Configuration>();
+
+    /**
+     * The configurations which files are to be excluded from the classpath entries.
+     */
+    Set<Configuration> minusConfigurations = new LinkedHashSet<Configuration>();
+
+    /**
+     * The variables to be used for replacing absolute paths in classpath entries.
+     */
+    Map variables = [:]
+
+    /**
+     * Containers to be added to the classpath
+     */
+    Set<Container> containers = new LinkedHashSet<Container>();
+
+    /**
+     * Whether to download and add sources associated with the dependency jars. Defaults to true.
+     */
+    boolean downloadSources = true
+
+    /**
+     * Whether to download and add javadocs associated with the dependency jars. Defaults to false.
+     */
+    boolean downloadJavadoc = false
+
+    protected ModelFactory modelFactory = new ModelFactory()
+
+    def EclipseClasspath() {
+        outputs.upToDateWhen { false }
+    }
+
+    @TaskAction
+    void generateXml() {
+        Classpath classpath = modelFactory.createClasspath(this)
+        classpath.toXml(getOutputFile())
+    }
+
+    /**
+     * Adds containers to the .classpath.
+     *
+     * @param containers the container names to be added to the .classpath.
+     */
+    void containers(String... containers) {
+        assert containers != null
+        this.containers.addAll(containers as List)
+    }
+
+    /**
+     * Adds variables to be used for replacing absolute paths in classpath entries.
+     *
+     * @param variables A map where the keys are the variable names and the values are the variable values.
+     */
+    void variables(Map variables) {
+        assert variables != null
+        this.variables.putAll variables
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipsePlugin.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipsePlugin.groovy
new file mode 100644
index 0000000..b11174c
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipsePlugin.groovy
@@ -0,0 +1,139 @@
+/*
+ * 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.plugins.eclipse
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.plugins.GroovyBasePlugin
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.plugins.WarPlugin
+import org.gradle.api.plugins.scala.ScalaBasePlugin
+import org.gradle.plugins.eclipse.model.BuildCommand
+
+/**
+ * <p>A plugin which generates Eclipse files.</p>
+ *
+ * @author Hans Dockter
+ */
+public class EclipsePlugin implements Plugin<Project> {
+    public static final String ECLIPSE_TASK_NAME = "eclipse";
+    public static final String CLEAN_ECLIPSE_TASK_NAME = "cleanEclipse";
+    public static final String ECLIPSE_PROJECT_TASK_NAME = "eclipseProject";
+    public static final String ECLIPSE_WTP_TASK_NAME = "eclipseWtp";
+    public static final String ECLIPSE_CP_TASK_NAME = "eclipseClasspath";
+
+    public void apply(final Project project) {
+        project.apply plugin: 'base' // We apply the base plugin to have the clean<taskname> rule
+        project.task('cleanEclipse') {
+            description = 'Cleans the generated eclipse files.'
+            group = 'IDE'
+        }
+        project.task('eclipse') {
+            description = 'Generates the Eclipse files.'
+            group = 'IDE'
+        }
+        configureEclipseProject(project)
+        configureEclipseClasspath(project)
+        project.plugins.withType(WarPlugin.class).allPlugins {
+            configureEclipseWtpModuleForWarProjects(project);
+        }
+    }
+
+    private void configureEclipseProject(Project project) {
+        EclipseProject eclipseProject = project.tasks.add(ECLIPSE_PROJECT_TASK_NAME, EclipseProject.class);
+        eclipseProject.setProjectName(project.name);
+        eclipseProject.description = "Generates an Eclipse .project file."
+        eclipseProject.setInputFile(project.file('.project'))
+        eclipseProject.setOutputFile(project.file('.project'))
+
+        project.plugins.withType(JavaBasePlugin.class).allPlugins {
+            project.configure(project.eclipseProject) {
+                buildCommands = [new BuildCommand("org.eclipse.jdt.core.javabuilder")]
+                natures = ["org.eclipse.jdt.core.javanature"]
+            }
+        }
+        project.plugins.withType(GroovyBasePlugin.class).allPlugins {
+            project.configure(project.eclipseProject) {
+                natures.add(natures.indexOf("org.eclipse.jdt.core.javanature"), "org.eclipse.jdt.groovy.core.groovyNature")
+            }
+        }
+        project.plugins.withType(ScalaBasePlugin.class).allPlugins {
+            project.configure(project.eclipseProject) {
+                buildCommands = buildCommands.collect { command ->
+                    command.name == "org.eclipse.jdt.core.javabuilder" ? new BuildCommand("ch.epfl.lamp.sdt.core.scalabuilder") : command
+                }
+                natures.add(natures.indexOf("org.eclipse.jdt.core.javanature"), "ch.epfl.lamp.sdt.core.scalanature")
+            }
+        }
+        project.plugins.withType(WarPlugin.class).allPlugins {
+            project.configure(project.eclipseProject) {
+                buildCommand 'org.eclipse.wst.common.project.facet.core.builder'
+                buildCommand 'org.eclipse.wst.validation.validationbuilder'
+                natures 'org.eclipse.wst.common.project.facet.core.nature', 'org.eclipse.wst.common.modulecore.ModuleCoreNature'
+            }
+        }
+
+        project."$ECLIPSE_TASK_NAME".dependsOn eclipseProject
+        project."$CLEAN_ECLIPSE_TASK_NAME".dependsOn 'cleanEclipseProject'
+    }
+
+    private void configureEclipseClasspath(final Project project) {
+        project.plugins.withType(JavaBasePlugin.class).allPlugins {
+            EclipseClasspath eclipseClasspath = project.tasks.add(ECLIPSE_CP_TASK_NAME, EclipseClasspath.class);
+            project.configure(eclipseClasspath) {
+                description = "Generates an Eclipse .classpath file."
+                containers 'org.eclipse.jdt.launching.JRE_CONTAINER'
+                sourceSets = project.sourceSets
+                inputFile = project.file('.classpath')
+                outputFile = project.file('.classpath')
+                variables = [GRADLE_CACHE: new File(project.gradle.getGradleUserHomeDir(), 'cache').canonicalPath]
+            }
+            project."$ECLIPSE_TASK_NAME".dependsOn eclipseClasspath
+            project."$CLEAN_ECLIPSE_TASK_NAME".dependsOn 'cleanEclipseClasspath'
+        }
+        project.plugins.withType(JavaPlugin.class).allPlugins {
+            project.configure(project.eclipseClasspath) {
+                plusConfigurations = [project.configurations.testRuntime]
+            }
+        }
+    }
+
+    private void configureEclipseWtpModuleForWarProjects(final Project project) {
+        final EclipseWtp eclipseWtp = project.getTasks().add(ECLIPSE_WTP_TASK_NAME, EclipseWtp.class);
+
+        project.configure(eclipseWtp) {
+            deployName = project.name
+            facet name: "jst.web", version: "2.4"
+            facet name: "jst.java", version: "1.4"
+            sourceSets = project.sourceSets.matching { sourceSet -> sourceSet.name == 'main' }
+            plusConfigurations = [project.configurations.runtime]
+            minusConfigurations = [project.configurations.providedRuntime]
+            variables = [GRADLE_CACHE: new File(project.gradle.getGradleUserHomeDir(), 'cache').canonicalPath]
+            resource deployPath: '/', sourcePath: project.convention.plugins.war.webAppDirName
+            orgEclipseWstCommonComponentInputFile = project.file('.settings/org.eclipse.wst.common.component.xml')
+            orgEclipseWstCommonComponentOutputFile = project.file('.settings/org.eclipse.wst.common.component.xml')
+            orgEclipseWstCommonProjectFacetCoreInputFile = project.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
+            orgEclipseWstCommonProjectFacetCoreOutputFile = project.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
+        }
+
+        project."$ECLIPSE_TASK_NAME".dependsOn eclipseWtp
+        project."$CLEAN_ECLIPSE_TASK_NAME".dependsOn 'cleanEclipseWtp'
+        project.cleanEclipseWtp {
+            delete project.file('.settings')
+        }
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseProject.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseProject.groovy
new file mode 100644
index 0000000..8f4e294
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseProject.groovy
@@ -0,0 +1,142 @@
+/*
+ * 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.plugins.eclipse;
+
+
+import org.gradle.api.InvalidUserDataException
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import org.gradle.plugins.eclipse.model.BuildCommand
+import org.gradle.plugins.eclipse.model.Link
+import org.gradle.plugins.eclipse.model.internal.ModelFactory
+import org.gradle.plugins.eclipse.model.Project
+
+/**
+ * Generates an eclipse <i>.project</i> file.
+ *
+ * @author Hans Dockter
+ */
+public class EclipseProject extends AbstractXmlGeneratorTask {
+    private static final LINK_ARGUMENTS = ['name', 'type', 'location', 'locationUri']
+
+    /**
+     * The file that is merged into the to be produced project file. This file must not exist.
+     */
+    File inputFile
+
+    @OutputFile
+    /**
+     * The output file where to generate the project metadata to.
+     */
+    File outputFile
+
+    /**
+     * The name used for the name of the eclipse project
+     */
+    String projectName;
+
+    /**
+     * A comment used for the eclipse project
+     */
+    String comment;
+
+    /**
+     * The referenced projects of this Eclipse project.
+     */
+    Set<String> referencedProjects = new LinkedHashSet<String>();
+
+    /**
+     * The natures to be added to this Eclipse project.
+     */
+    List<String> natures = []
+
+    /**
+     * The build commands to be added to this Eclipse project.
+     */
+    List<BuildCommand> buildCommands = []
+    
+    /**
+     * The links to be added to this Eclipse project.
+     */
+    Set<Link> links = new LinkedHashSet<Link>();
+
+    protected ModelFactory modelFactory = new ModelFactory()
+
+    def EclipseProject() {
+        outputs.upToDateWhen { false }
+    }
+
+    @TaskAction
+    void generateXml() {
+        Project project = modelFactory.createProject(this)
+        project.toXml(getOutputFile())
+    }
+
+    /**
+     * Adds natures entries to the eclipse project.
+     * @param natures the nature names
+     */
+    void natures(String... natures) {
+        assert natures != null
+        this.natures.addAll(natures as List)
+    }
+
+    /**
+     * Adds project references to the eclipse project.
+     *
+     * @param referencedProjects The name of the project references.
+     */
+    void referencedProjects(String... referencedProjects) {
+        assert referencedProjects != null
+        this.referencedProjects.addAll(referencedProjects as List)
+    }
+
+    /**
+     * Adds a build command with arguments to the eclipse project.
+     *
+     * @param args A map with arguments, where the key is the name of the argument and the value the value.
+     * @param buildCommand The name of the build command.
+     * @see #buildCommand(String) 
+     */
+    void buildCommand(Map args, String buildCommand) {
+        assert buildCommand != null
+        this.buildCommands.add(new BuildCommand(buildCommand, args))
+    }
+
+    /**
+     * Adds a build command to the eclipse project.
+     *
+     * @param buildCommand The name of the build command
+     * @see #buildCommand(Map, String) 
+     */
+    void buildCommand(String buildCommand) {
+        assert buildCommand != null
+        this.buildCommands.add(new BuildCommand(buildCommand))
+    }
+
+    /**
+     * Adds a link to the eclipse project.
+     *
+     * @param args A maps with the args for the link. Legal keys for the map are name, type, location and locationUri.
+     */
+    void link(Map args) {
+        def illegalArgs = LINK_ARGUMENTS - args.keySet()
+        if (illegalArgs) {
+            throw new InvalidUserDataException("You provided illegal argument for a link: " + illegalArgs)
+        }
+        this.links.add(new Link(args.name, args.type, args.location, args.locationUri))
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseWtp.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseWtp.groovy
new file mode 100644
index 0000000..5983d7a
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/EclipseWtp.groovy
@@ -0,0 +1,150 @@
+/*
+ * 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.plugins.eclipse;
+
+import org.gradle.api.NamedDomainObjectContainer
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import org.gradle.plugins.eclipse.model.Facet
+import org.gradle.plugins.eclipse.model.internal.ModelFactory
+import org.gradle.plugins.eclipse.model.Wtp
+import org.gradle.plugins.eclipse.model.WbResource
+import org.gradle.plugins.eclipse.model.WbProperty
+
+/**
+ * Generates Eclipse configuration files for Eclipse WTP.
+ *
+ * @author Hans Dockter
+ */
+public class EclipseWtp extends AbstractXmlGeneratorTask {
+    /**
+     * The file that is merged into the to be produced org.eclipse.wst.common.component file. This
+     * file must not exist.
+     */
+    File orgEclipseWstCommonComponentInputFile
+
+    @OutputFile
+    /**
+     * The output file for the org.eclipse.wst.common.component metadata.
+     */
+    File orgEclipseWstCommonComponentOutputFile
+
+    /**
+     * The file that is merged into the to be produced org.eclipse.wst.common.project.facet.core file. This
+     * file must not exist.
+     */
+    File orgEclipseWstCommonProjectFacetCoreInputFile
+
+    @OutputFile
+    /**
+     * The output file for the org.eclipse.wst.common.project.facet.core metadata.
+     */
+    File orgEclipseWstCommonProjectFacetCoreOutputFile
+
+    /**
+     * The source sets to be transformed into wb-resource elements.
+     */
+    NamedDomainObjectContainer sourceSets
+
+    /**
+     * The configurations which files are to be transformed into dependent-module elements of
+     * the org.eclipse.wst.common.component file.
+     */
+    Set<Configuration> plusConfigurations
+
+    /**
+     * The configurations which files are to be excluded from the dependent-module elements of
+     * the org.eclipse.wst.common.component file.
+     */
+    Set<Configuration> minusConfigurations
+
+    /**
+     * The facets to be added as installed elements to the org.eclipse.wst.common.project.facet.core file.
+     */
+    List<Facet> facets = []
+
+    /**
+     * The deploy name to be used in the org.eclipse.wst.common.component file.
+     */
+    String deployName;
+
+    /**
+     * The variables to be used for replacing absolute path in dependent-module elements of
+     * the org.eclipse.wst.common.component file.
+     */
+    Map<String, String> variables = [:]
+
+    /**
+     * Additional wb-resource elements.
+     */
+    List<WbResource> resources = []
+
+    /**
+     * Additional property elements.
+     */
+    List<WbProperty> properties = []
+
+    protected ModelFactory modelFactory = new ModelFactory()
+
+    def EclipseWtp() {
+        outputs.upToDateWhen { false }
+    }
+
+    @TaskAction
+    protected void generateXml() {
+        Wtp wtp = modelFactory.createWtp(this)
+        wtp.toXml(orgEclipseWstCommonComponentOutputFile, orgEclipseWstCommonProjectFacetCoreOutputFile)    
+    }
+
+    /**
+     * Adds a facet for the org.eclipse.wst.common.project.facet.core file.
+     *
+     * @param args A map that must contain a name and version key with corresponding values.
+     */
+    void facet(Map args) {
+        facets.add(new Facet(args.name, args.version))
+    }
+
+    /**
+     * Adds variables to be used for replacing absolute path in dependent-module elements of
+     * the org.eclipse.wst.common.component file.
+     *
+     * @param variables A map where the keys are the variable names and the values are the variable values.
+     */
+    void variables(Map variables) {
+        assert variables != null
+        this.variables.putAll variables
+    }
+
+    /**
+     * Adds a property to be added to the org.eclipse.wst.common.component file.
+     *
+     * @param args A map that must contain a name and value key with corresponding values.
+     */
+    void property(Map args) {
+        properties.add(new WbProperty(args.name, args.value))
+    }
+
+    /**
+     * Adds a wb-resource to be added to the org.eclipse.wst.common.component file.
+     *
+     * @param args A map that must contain a deployPath and sourcePath key with corresponding values.
+     */
+    void resource(Map args) {
+        resources.add(new WbResource(args.deployPath, args.sourcePath))
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/AbstractClasspathEntry.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/AbstractClasspathEntry.groovy
new file mode 100644
index 0000000..55f01d1
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/AbstractClasspathEntry.groovy
@@ -0,0 +1,146 @@
+/*
+ * 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.plugins.eclipse.model
+
+import org.gradle.plugins.eclipse.model.internal.PathUtil
+
+/**
+ * @author Hans Dockter
+ */
+abstract class AbstractClasspathEntry implements ClasspathEntry {
+    static final String NATIVE_LIBRARY_ATTRIBUTE = 'org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY'
+    String path
+    String nativeLibraryLocation
+    boolean exported
+    Set<AccessRule> accessRules
+
+    def AbstractClasspathEntry(Node node) {
+        this.path = normalizePath(node. at path)
+        this.exported = node. at exported
+        this.nativeLibraryLocation = readNativeLibraryLocation(node)
+        this.accessRules = readAccessRules(node)
+        assert path != null && accessRules != null
+    }
+
+    def AbstractClasspathEntry(String path, boolean exported, String nativeLibraryLocation, Set accessRules) {
+        assert path != null && accessRules != null
+        this.path = normalizePath(path);
+        this.exported = exported
+        this.nativeLibraryLocation = nativeLibraryLocation
+        this.accessRules = accessRules
+    }
+
+    Map removeNullEntries(Map args) {
+        def result = [:]
+        args.each { key, value ->
+            if (value) {
+                result[key] = value
+            }
+        }
+        result
+    }
+
+    Node addClasspathEntry(Node node, Map attributes) {
+        def allAttributes = removeNullEntries(attributes) + [
+                kind: getKind(),
+                path: path]
+        allAttributes += exported && !(this instanceof SourceFolder) ? [exported: exported] : [:]
+        Node entryNode = node.appendNode('classpathentry', allAttributes)
+        addNativeLibraryLocation(entryNode)
+        addAccessRules(entryNode)
+        entryNode
+    }
+
+    void addNativeLibraryLocation(Node node) {
+        addEntryAttributes(node, removeNullEntries([(NATIVE_LIBRARY_ATTRIBUTE): nativeLibraryLocation]))
+    }
+
+    String readNativeLibraryLocation(Node node) {
+        node.attributes.attribute.find { it. at name == 'org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY' }?.attributes()?.value
+    }
+
+    void addAccessRules(Node node) {
+        if (!accessRules) {
+            return
+        }
+        Node accessRulesNode = null
+        if (node.accessrules.size() == 0) {
+            accessRulesNode = node.appendNode('accessrules')
+        } else {
+            accessRulesNode = node.accessrules[0]
+        }
+        accessRules.each { AccessRule rule ->
+            accessRulesNode.appendNode('accessrule', [kind: rule.kind, pattern: rule.pattern])
+        }
+    }
+
+    def readAccessRules(Node node) {
+        node.accessrules.accessrule.collect { ruleNode ->
+            new AccessRule(ruleNode. at kind, ruleNode. at pattern)
+        }
+    }
+
+    void addEntryAttributes(Node node, Map attributes) {
+        if (!attributes) {
+            return
+        }
+        Node attributesNode = node.children().find { it.name()  == 'attributes' }
+        if (!attributesNode) {
+            attributesNode = node.appendNode('attributes')
+        }
+        attributes.each { key, value ->
+            attributesNode.appendNode('attribute', [name: key, value: value])
+        }
+    }
+
+    String normalizePath(String path) {
+        PathUtil.normalizePath(path)
+    }
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        AbstractClasspathEntry that = (AbstractClasspathEntry) o;
+
+        if (exported != that.exported) { return false }
+        if (accessRules != that.accessRules) { return false }
+        if (nativeLibraryLocation != that.nativeLibraryLocation) { return false }
+        if (path != that.path) { return false }
+
+        return true
+    }
+
+    int hashCode() {
+        int result;
+
+        result = path.hashCode();
+        result = 31 * result + (nativeLibraryLocation != null ? nativeLibraryLocation.hashCode() : 0);
+        result = 31 * result + (exported ? 1 : 0);
+        result = 31 * result + accessRules.hashCode();
+        return result;
+    }
+
+    public String toString() {
+        return "{" +
+                "path='" + path + '\'' +
+                ", nativeLibraryLocation='" + nativeLibraryLocation + '\'' +
+                ", exported=" + exported +
+                ", accessRules=" + accessRules +
+                '}';
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/AbstractLibrary.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/AbstractLibrary.groovy
new file mode 100644
index 0000000..8058137
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/AbstractLibrary.groovy
@@ -0,0 +1,84 @@
+/*
+ * 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.plugins.eclipse.model
+
+/**
+ * @author Hans Dockter
+ */
+abstract class AbstractLibrary extends AbstractClasspathEntry {
+    String sourcePath
+    String javadocPath
+
+    def AbstractLibrary(node) {
+        super(node)
+        javadocPath = normalizePath(node.attributes?.attribute?.find { it. at name == 'javadoc_location' }?. at value)
+        sourcePath = normalizePath(node. at sourcepath)
+    }
+
+    def AbstractLibrary(String path, boolean exported, String nativeLibraryLocation, Set accessRules, String sourcePath,
+                        String javadocPath) {
+        super(path, exported, nativeLibraryLocation, accessRules)
+        this.sourcePath = normalizePath(sourcePath);
+        this.javadocPath = normalizePath(javadocPath);
+    }
+
+    void appendNode(Node node) {
+        def entryNode = addClasspathEntry(node, [sourcepath: sourcePath])
+        if (javadocPath) {
+            addEntryAttributes(entryNode, [javadoc_location: javadocPath])
+        }
+    }
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        AbstractLibrary that = (AbstractLibrary) o;
+
+        if (exported != that.exported) { return false }
+        if (accessRules != that.accessRules) { return false }
+        if (javadocPath != that.javadocPath) { return false }
+        if (nativeLibraryLocation != that.nativeLibraryLocation) { return false }
+        if (path != that.path) { return false }
+        if (sourcePath != that.sourcePath) { return false }
+
+        return true
+    }
+
+    int hashCode() {
+        int result;
+
+        result = path.hashCode();
+        result = 31 * result + (nativeLibraryLocation != null ? nativeLibraryLocation.hashCode() : 0);
+        result = 31 * result + (exported ? 1 : 0);
+        result = 31 * result + accessRules.hashCode();
+        result = 31 * result + (sourcePath != null ? sourcePath.hashCode() : 0);
+        result = 31 * result + (javadocPath != null ? javadocPath.hashCode() : 0);
+        return result;
+    }
+
+    public String toString() {
+        return "{" +
+                "path='" + path + '\'' +
+                ", nativeLibraryLocation='" + nativeLibraryLocation + '\'' +
+                ", exported=" + exported +
+                ", accessRules=" + accessRules +
+                ", sourcePath='" + sourcePath + '\'' +
+                ", javadocPath='" + javadocPath + '\'' +
+                '}';
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/AccessRule.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/AccessRule.groovy
new file mode 100644
index 0000000..955e09f
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/AccessRule.groovy
@@ -0,0 +1,58 @@
+/*
+ * 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.plugins.eclipse.model
+
+/**
+ * @author Hans Dockter
+ */
+class AccessRule {
+    String kind
+    String pattern
+
+    def AccessRule(kind, pattern) {
+        assert kind != null && pattern != null
+        this.kind = kind;
+        this.pattern = pattern;
+    }
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        AccessRule that = (AccessRule) o;
+
+        if (kind != that.kind) { return false }
+        if (pattern != that.pattern) { return false }
+
+        return true
+    }
+
+    int hashCode() {
+        int result;
+
+        result = kind.hashCode();
+        result = 31 * result + pattern.hashCode();
+        return result;
+    }
+
+    public String toString() {
+        return "AccessRule{" +
+                "kind='" + kind + '\'' +
+                ", pattern='" + pattern + '\'' +
+                '}';
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/BuildCommand.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/BuildCommand.groovy
new file mode 100644
index 0000000..a451506
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/BuildCommand.groovy
@@ -0,0 +1,60 @@
+/*
+ * 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.plugins.eclipse.model
+
+/**
+ * @author Hans Dockter
+ */
+class BuildCommand {
+    String name
+    Map arguments
+
+    def BuildCommand(String name, Map arguments = [:]) {
+        assert name != null
+        assert arguments != null
+        this.name = name
+        this.arguments = arguments
+    }
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        BuildCommand that = (BuildCommand) o;
+
+        if (arguments != that.arguments) { return false }
+        if (name != that.name) { return false }
+
+        return true
+    }
+
+    int hashCode() {
+        int result;
+
+        result = name.hashCode();
+        result = 31 * result + arguments.hashCode();
+        return result;
+    }
+
+
+    public String toString() {
+        return "BuildCommand{" +
+                "name='" + name + '\'' +
+                ", arguments=" + arguments +
+                '}';
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Classpath.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Classpath.groovy
new file mode 100644
index 0000000..caf6002
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Classpath.groovy
@@ -0,0 +1,116 @@
+/*
+ * 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.plugins.eclipse.model
+
+import org.gradle.listener.ListenerBroadcast
+import org.gradle.plugins.eclipse.EclipseClasspath
+
+/**
+ * Represents the customizable elements of an eclipse classpath file. (via XML hooks everything is customizable).
+ *
+ * @author Hans Dockter
+ */
+class Classpath {
+    /**
+     * The classpath entries (contains by default an output entry pointing to bin).
+     */
+    List entries = [new Output('bin')]
+
+    private Node xml
+
+    private ListenerBroadcast withXmlActions
+
+    Classpath(EclipseClasspath eclipseClasspath, List entries, Reader inputXml) {
+        initFromXml(inputXml)
+
+        eclipseClasspath.beforeConfiguredActions.source.execute(this)
+
+        this.entries.addAll(entries)
+        this.entries.unique()
+        this.withXmlActions = eclipseClasspath.withXmlActions
+
+        eclipseClasspath.whenConfiguredActions.source.execute(this)
+    }
+
+    private def initFromXml(Reader inputXml) {
+        if (!inputXml) {
+            xml = new Node(null, 'classpath')
+            return
+        }
+
+        xml = new XmlParser().parse(inputXml)
+
+        xml.classpathentry.each { entryNode ->
+            def entry = null
+            switch (entryNode. at kind) {
+                case 'src':
+                    def path = entryNode. at path
+                    entry = path.startsWith('/') ? new ProjectDependency(entryNode) : new SourceFolder(entryNode)
+                    break
+                case 'var': entry = new Variable(entryNode)
+                    break
+                case 'con': entry = new Container(entryNode)
+                    break
+                case 'lib': entry = new Library(entryNode)
+                    break
+                case 'output': entry = new Output(entryNode)
+                    break
+            }
+            if (entry) {
+                entries.add(entry)
+            }
+        }
+    }
+
+    void toXml(File file) {
+        toXml(new FileWriter(file))
+    }
+
+    def toXml(Writer writer) {
+        xml.classpathentry.each { xml.remove(it) }
+        entries.each { ClasspathEntry entry ->
+            entry.appendNode(xml)
+        }
+        withXmlActions.source.execute(xml)
+
+        new XmlNodePrinter(new PrintWriter(writer)).print(xml)
+    }
+    
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        Classpath classpath = (Classpath) o;
+
+        if (entries != classpath.entries) { return false }
+
+        return true
+    }
+
+    int hashCode() {
+        int result;
+
+        result = entries.hashCode();
+        return result;
+    }
+
+    public String toString() {
+        return "Classpath{" +
+                "entries=" + entries +
+                '}';
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/ClasspathEntry.java b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/ClasspathEntry.java
new file mode 100644
index 0000000..a22d65d
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/ClasspathEntry.java
@@ -0,0 +1,26 @@
+/*
+ * 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.plugins.eclipse.model;
+
+import groovy.util.Node;
+
+/**
+ * @author Hans Dockter
+ */
+public interface ClasspathEntry {
+    String getKind();
+    void appendNode(Node node);
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Container.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Container.groovy
new file mode 100644
index 0000000..73a87d8
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Container.groovy
@@ -0,0 +1,41 @@
+/*
+ * 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.plugins.eclipse.model
+
+/**
+ * @author Hans Dockter
+ */
+class Container extends AbstractClasspathEntry {
+    def Container(node) {
+        super(node);
+    }
+
+    def Container(String path, boolean exported, String nativeLibraryLocation, Set accessRules) {
+        super(path, exported, nativeLibraryLocation, accessRules)
+    }
+
+    String getKind() {
+        'con'
+    }
+
+    void appendNode(Node node) {
+        addClasspathEntry(node, [:])
+    }
+
+    public String toString() {
+        return "Container{" + super.toString()
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Facet.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Facet.groovy
new file mode 100644
index 0000000..3fd862d
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Facet.groovy
@@ -0,0 +1,67 @@
+/*
+ * 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.plugins.eclipse.model
+
+/**
+ * @author Hans Dockter
+ */
+
+class Facet {
+    String name
+    String version
+
+    def Facet(Node node) {
+        this(node. at facet, node. at version)
+    }
+
+    def Facet(String name, String version) {
+        assert name != null && version != null
+        this.name = name
+        this.version = version
+    }
+
+    void appendNode(Node node) {
+        node.appendNode("installed", [facet: name, version: version])
+    }
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        Facet facet = (Facet) o;
+
+        if (name != facet.name) { return false }
+        if (version != facet.version) { return false }
+
+        return true
+    }
+
+    int hashCode() {
+        int result;
+
+        result = name.hashCode();
+        result = 31 * result + version.hashCode();
+        return result;
+    }
+
+    public String toString() {
+        return "Facet{" +
+                "name='" + name + '\'' +
+                ", version='" + version + '\'' +
+                '}';
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Library.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Library.groovy
new file mode 100644
index 0000000..109501e
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Library.groovy
@@ -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.plugins.eclipse.model
+
+/**
+ * @author Hans Dockter
+ */
+class Library extends AbstractLibrary {
+    def Library(Node node) {
+        super(node);
+    }
+
+    def Library(String path, boolean exported, String nativeLibraryLocation, Set accessRules, String sourcePath, String javadocPath) {
+        super(path, exported, nativeLibraryLocation, accessRules, sourcePath, javadocPath)
+    }
+
+    String getKind() {
+        'lib'
+    }
+
+    public String toString() {
+        return "Library{" + super.toString()
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Link.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Link.groovy
new file mode 100644
index 0000000..36a76f6
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Link.groovy
@@ -0,0 +1,65 @@
+/*
+ * 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.plugins.eclipse.model
+
+import org.gradle.plugins.eclipse.model.internal.PathUtil
+
+/**
+ * @author Hans Dockter
+ */
+class Link {
+    String name
+    String type
+    String location
+    String locationUri
+
+    def Link(String name, String type, String location, String locationUri) {
+        assert name
+        assert type
+        assert location || locationUri
+        assert !location || !locationUri 
+        this.name = name;
+        this.type = type;
+        this.location = PathUtil.normalizePath(location);
+        this.locationUri = locationUri;
+    }
+
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        Link link = (Link) o;
+
+        if (location != link.location) { return false }
+        if (locationUri != link.locationUri) { return false }
+        if (name != link.name) { return false }
+        if (type != link.type) { return false }
+
+        return true
+    }
+
+    int hashCode() {
+        int result;
+
+        result = name.hashCode();
+        result = 31 * result + type.hashCode();
+        result = 31 * result + (location != null ? location.hashCode() : 0);
+        result = 31 * result + (locationUri != null ? locationUri.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Output.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Output.groovy
new file mode 100644
index 0000000..fa26b29
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Output.groovy
@@ -0,0 +1,65 @@
+/*
+ * 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.plugins.eclipse.model
+
+import org.gradle.plugins.eclipse.model.internal.PathUtil
+
+/**
+ * @author Hans Dockter
+ */
+
+class Output implements ClasspathEntry {
+    String path
+
+    def Output(Node node) {
+        this(node. at path)
+    }
+
+    def Output(String path) {
+        assert path != null
+        this.path = PathUtil.normalizePath(path)
+    }
+
+    String getKind() {
+        'output'
+    }
+
+    void appendNode(Node node) {
+        node.appendNode('classpathentry', [kind: getKind(), path: path])
+    }
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        Output output = (Output) o;
+
+        if (path != output.path) { return false }
+
+        return true
+    }
+
+    int hashCode() {
+        return path.hashCode();
+    }
+
+    public String toString() {
+        return "Output{" +
+                "path='" + path + '\'' +
+                '}';
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Project.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Project.groovy
new file mode 100644
index 0000000..161bba6
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Project.groovy
@@ -0,0 +1,232 @@
+/*
+ * 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.plugins.eclipse.model
+
+import org.gradle.listener.ListenerBroadcast
+import org.gradle.plugins.eclipse.EclipseProject
+
+/**
+ * Represents the customizable elements of an eclipse project file. (via XML hooks everything is customizable).
+ *
+ * @author Hans Dockter
+ */
+class Project {
+    public static final String PROJECT_FILE_NAME = ".project";
+
+    /**
+     * The name used for the name of the eclipse project
+     */
+    String name;
+
+    /**
+     * A comment used for the eclipse project
+     */
+    String comment;
+
+    /**
+     * The referenced projects of this Eclipse project.
+     */
+    Set<String> referencedProjects = new LinkedHashSet<String>()
+
+    /**
+     * The natures to be added to this Eclipse project.
+     */
+    List<String> natures = []
+
+    /**
+     * The build commands to be added to this Eclipse project.
+     */
+    List buildCommands = []
+
+    /**
+     * The links to be added to this Eclipse project.
+     */
+    Set<Link> links = new LinkedHashSet<Link>()
+
+    private Node xml
+
+    private ListenerBroadcast withXmlActions
+
+    def Project(EclipseProject eclipseProjectTask, Reader inputXml) {
+        initFromXml(inputXml)
+
+        eclipseProjectTask.beforeConfiguredActions.source.execute(this)
+
+        if (eclipseProjectTask.projectName) {
+            this.name = eclipseProjectTask.projectName
+        }
+        if (eclipseProjectTask.comment) {
+            this.comment = eclipseProjectTask.comment
+        }
+        this.referencedProjects.addAll(eclipseProjectTask.referencedProjects)
+        this.natures.addAll(eclipseProjectTask.natures)
+        this.natures.unique()
+        this.buildCommands.addAll(eclipseProjectTask.buildCommands)
+        this.buildCommands.unique()
+        this.links.addAll(eclipseProjectTask.links);
+        this.withXmlActions = eclipseProjectTask.withXmlActions
+
+        eclipseProjectTask.whenConfiguredActions.source.execute(this)
+    }
+
+    private def initFromXml(Reader inputXml) {
+        if (!inputXml) {
+            xml = new Node(null, 'projectDescription')
+            return
+        }
+
+        xml = new XmlParser().parse(inputXml)
+
+        this.name = xml.name.text()
+        this.comment = xml.comment.text()
+        readReferencedProjects()
+        readNatures()
+        readBuildCommands()
+        readLinks()
+    }
+
+    private def readReferencedProjects() {
+        return xml.projects.project.each {
+            this.referencedProjects.add(it.text())
+        }
+    }
+
+    private def readNatures() {
+        return xml.natures.nature.each { this.natures.add(it.text()) }
+    }
+
+    private def readBuildCommands() {
+        return xml.buildSpec.buildCommand.each { command ->
+            Map args = [:]
+            command.arguments.dictionary.each { Node it ->
+                args[it.key.text()] = it.value.text()
+            }
+            this.buildCommands.add(new BuildCommand(command.name.text(), args))
+        }
+    }
+
+    private def readLinks() {
+        return xml.links.link.each { link ->
+            this.links.add(new Link(link.name?.text(), link.type?.text(), link.location?.text(), link.locationURI?.text()))
+        }
+    }
+
+    void toXml(File file) {
+        toXml(new FileWriter(file))
+    }
+
+    def toXml(Writer writer) {
+        ['name', 'comment', 'projects', 'natures', 'buildSpec', 'links'].each{ childNodeName ->
+            Node childNode = xml.children().find { it.name() == childNodeName }
+            if (childNode) {
+                xml.remove(childNode)
+            }
+        }
+        xml.appendNode('name', this.name)
+        xml.appendNode('comment', this.comment ?: null)
+        addReferencedProjectsToXml()
+        addNaturesToXml()
+        addBuildSpecToXml()
+        addLinksToXml()
+        withXmlActions.source.execute(xml)
+
+        new XmlNodePrinter(new PrintWriter(writer)).print(xml)
+    }
+
+    private def addReferencedProjectsToXml() {
+        def referencedProjectsNode = xml.appendNode('projects')
+        this.referencedProjects.each { projectName ->
+            referencedProjectsNode.appendNode('project', projectName)
+        }
+    }
+
+    private def addNaturesToXml() {
+        def naturesNode = xml.appendNode('natures')
+        this.natures.each { nature ->
+            naturesNode.appendNode('nature', nature)
+        }
+    }
+
+    private def addBuildSpecToXml() {
+        def buildSpec = xml.appendNode('buildSpec')
+        this.buildCommands.each { command ->
+            def commandNode = buildSpec.appendNode('buildCommand')
+            commandNode.appendNode('name', command.name)
+            def argumentsNode = commandNode.appendNode('arguments')
+            command.arguments.each { key, value ->
+                def dictionaryNode = argumentsNode.appendNode('dictionary')
+                dictionaryNode.appendNode('key', key)
+                dictionaryNode.appendNode('value', value)
+            }
+        }
+    }
+
+    private def addLinksToXml() {
+        def linksNode = xml.appendNode('links')
+        this.links.each { link ->
+            def linkNode = linksNode.appendNode('link')
+            linkNode.appendNode('name', link.name)
+            linkNode.appendNode('type', link.type)
+            if (link.location) {
+                linkNode.appendNode('location', link.location)
+            }
+            if (link.locationUri) {
+                linkNode.appendNode('locationURI', link.locationUri)
+            }
+        }
+    }
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        Project project = (Project) o;
+
+        if (buildCommands != project.buildCommands) { return false }
+        if (comment != project.comment) { return false }
+        if (links != project.links) { return false }
+        if (name != project.name) { return false }
+        if (natures != project.natures) { return false }
+        if (referencedProjects != project.referencedProjects) { return false }
+
+        return true
+    }
+
+    int hashCode() {
+        int result;
+
+        result = (name != null ? name.hashCode() : 0);
+        result = 31 * result + (comment != null ? comment.hashCode() : 0);
+        result = 31 * result + (referencedProjects != null ? referencedProjects.hashCode() : 0);
+        result = 31 * result + (natures != null ? natures.hashCode() : 0);
+        result = 31 * result + (buildCommands != null ? buildCommands.hashCode() : 0);
+        result = 31 * result + (links != null ? links.hashCode() : 0);
+        return result;
+    }
+
+
+    public String toString() {
+        return "Project{" +
+                "name='" + name + '\'' +
+                ", comment='" + comment + '\'' +
+                ", referencedProjects=" + referencedProjects +
+                ", natures=" + natures +
+                ", buildCommands=" + buildCommands +
+                ", links=" + links +
+                '}';
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/ProjectDependency.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/ProjectDependency.groovy
new file mode 100644
index 0000000..4440c9a
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/ProjectDependency.groovy
@@ -0,0 +1,47 @@
+/*
+ * 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.plugins.eclipse.model
+
+/**
+ * @author Hans Dockter
+ */
+class ProjectDependency extends AbstractClasspathEntry {
+    def ProjectDependency(Node node) {
+        super(node)
+        assertPathIsValid()
+    }
+
+    def ProjectDependency(String path, boolean exported, String nativeLibraryLocation, Set accessRules) {
+        super(path, exported, nativeLibraryLocation, accessRules)
+        assertPathIsValid()
+    }
+
+    void assertPathIsValid() {
+        assert path.startsWith('/')
+    }
+
+    String getKind() {
+        'src'
+    }
+
+    void appendNode(Node node) {
+        addClasspathEntry(node, [:])
+    }
+
+    public String toString() {
+        return "ProjectDependency{" + super.toString()
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/SourceFolder.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/SourceFolder.groovy
new file mode 100644
index 0000000..78eae82
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/SourceFolder.groovy
@@ -0,0 +1,92 @@
+/*
+ * 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.plugins.eclipse.model
+
+/**
+ * @author Hans Dockter
+ */
+class SourceFolder extends AbstractClasspathEntry {
+    String output
+    List excludes
+    List includes
+
+    def SourceFolder(Node node) {
+        super(node)
+        this.output = normalizePath(node. at output)
+        this.includes = node. at including?.split('\\|') ?: []
+        this.excludes = node. at excluding?.split('\\|') ?: []
+    }
+
+    def SourceFolder(String path, String nativeLibraryLocation, Set accessRules, String output,
+                     List includes, List excludes) {
+        super(path, false, nativeLibraryLocation, accessRules)
+        this.output = normalizePath(output);
+        this.includes = includes ?: [];
+        this.excludes = excludes ?: [];
+    }
+
+    String getKind() {
+        'src'
+    }
+
+    void appendNode(Node node) {
+        addClasspathEntry(node, [including: includes.join("|"), excluding: excludes.join("|"), output: output])
+    }
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        SourceFolder that = (SourceFolder) o;
+
+        if (exported != that.exported) { return false }
+        if (accessRules != that.accessRules) { return false }
+        if (excludes != that.excludes) { return false }
+        if (includes != that.includes) { return false }
+        if (nativeLibraryLocation != that.nativeLibraryLocation) { return false }
+        if (output != that.output) { return false }
+        if (path != that.path) { return false }
+
+        return true
+    }
+
+    int hashCode() {
+        int result;
+
+        result = path.hashCode();
+        result = 31 * result + (nativeLibraryLocation != null ? nativeLibraryLocation.hashCode() : 0);
+        result = 31 * result + (exported ? 1 : 0);
+        result = 31 * result + accessRules.hashCode();
+        result = 31 * result + (output != null ? output.hashCode() : 0);
+        result = 31 * result + excludes.hashCode();
+        result = 31 * result + includes.hashCode();
+        return result;
+    }
+
+
+    public String toString() {
+        return "SourceFolder{" +
+                "path='" + path + '\'' +
+                ", nativeLibraryLocation='" + nativeLibraryLocation + '\'' +
+                ", exported=" + exported +
+                ", accessRules=" + accessRules +
+                ", output='" + output + '\'' +
+                ", excludes=" + excludes +
+                ", includes=" + includes +
+                '}';
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Variable.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Variable.groovy
new file mode 100644
index 0000000..676916f
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Variable.groovy
@@ -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.plugins.eclipse.model
+
+/**
+ * @author Hans Dockter
+ */
+class Variable extends AbstractLibrary {
+    def Variable(Node node) {
+        super(node);
+    }
+
+    def Variable(String path, boolean exported, String nativeLibraryLocation, Set accessRules, String sourcePath, String javadocPath) {
+        super(path, exported, nativeLibraryLocation, accessRules, sourcePath, javadocPath)
+    }
+
+    String getKind() {
+        'var'
+    }
+
+    public String toString() {
+        return "Variable{" + super.toString()
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/WbDependentModule.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/WbDependentModule.groovy
new file mode 100644
index 0000000..6dba906
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/WbDependentModule.groovy
@@ -0,0 +1,70 @@
+/*
+ * 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.plugins.eclipse.model
+
+import org.gradle.plugins.eclipse.model.internal.PathUtil
+
+/**
+ * @author Hans Dockter
+ */
+
+class WbDependentModule {
+    String deployPath
+    String handle
+
+    def WbDependentModule(node) {
+        this(node.@'deploy-path', node. at handle)
+    }
+
+    def WbDependentModule(String deployPath, String handle) {
+        assert deployPath != null && handle != null
+        this.deployPath = PathUtil.normalizePath(deployPath)
+        this.handle = handle
+    }
+
+    void appendNode(Node parentNode) {
+        Node node = parentNode.appendNode("dependent-module", ['deploy-path': deployPath, 'handle': handle])
+        node.appendNode('dependency-type').setValue('uses')
+    }
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        WbDependentModule that = (WbDependentModule) o;
+
+        if (deployPath != that.deployPath) { return false }
+        if (handle != that.handle) { return false }
+
+        return true
+    }
+
+    int hashCode() {
+        int result;
+
+        result = deployPath.hashCode();
+        result = 31 * result + handle.hashCode();
+        return result;
+    }
+
+    public String toString() {
+        return "WbDependentModule{" +
+                "deployPath='" + deployPath + '\'' +
+                ", handle='" + handle + '\'' +
+                '}';
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/WbProperty.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/WbProperty.groovy
new file mode 100644
index 0000000..948bc92
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/WbProperty.groovy
@@ -0,0 +1,67 @@
+/*
+ * 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.plugins.eclipse.model
+
+/**
+ * @author Hans Dockter
+ */
+
+class WbProperty {
+    String name
+    String value
+
+    def WbProperty(node) {
+        this(node. at name, node. at value)
+    }
+
+    def WbProperty(String name, String value) {
+        assert name != null && value != null
+        this.name = name
+        this.value = value
+    }
+
+    void appendNode(Node node) {
+        node.appendNode("property", [name: name, value: value])
+    }
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        WbProperty that = (WbProperty) o;
+
+        if (name != that.name) { return false }
+        if (value != that.value) { return false }
+
+        return true
+    }
+
+    int hashCode() {
+        int result;
+
+        result = name.hashCode();
+        result = 31 * result + value.hashCode();
+        return result;
+    }
+
+    public String toString() {
+        return "WbProperty{" +
+                "name='" + name + '\'' +
+                ", value='" + value + '\'' +
+                '}';
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/WbResource.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/WbResource.groovy
new file mode 100644
index 0000000..7e586e3
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/WbResource.groovy
@@ -0,0 +1,69 @@
+/*
+ * 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.plugins.eclipse.model
+
+import org.gradle.plugins.eclipse.model.internal.PathUtil
+
+/**
+ * @author Hans Dockter
+ */
+
+class WbResource {
+    String deployPath
+    String sourcePath
+
+    def WbResource(node) {
+        this(node.@'deploy-path', node.@'source-path')
+    }
+
+    def WbResource(String deployPath, String sourcePath) {
+        assert deployPath != null && sourcePath != null
+        this.deployPath = PathUtil.normalizePath(deployPath)
+        this.sourcePath = PathUtil.normalizePath(sourcePath)
+    }
+
+    void appendNode(Node node) {
+        node.appendNode("wb-resource", ['deploy-path': deployPath, 'source-path': sourcePath])
+    }
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        WbResource that = (WbResource) o;
+
+        if (deployPath != that.deployPath) { return false }
+        if (sourcePath != that.sourcePath) { return false }
+
+        return true
+    }
+
+    int hashCode() {
+        int result;
+
+        result = deployPath.hashCode();
+        result = 31 * result + sourcePath.hashCode();
+        return result;
+    }
+
+    public String toString() {
+        return "WbResource{" +
+                "deployPath='" + deployPath + '\'' +
+                ", sourcePath='" + sourcePath + '\'' +
+                '}';
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Wtp.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Wtp.groovy
new file mode 100644
index 0000000..ee618fb
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/Wtp.groovy
@@ -0,0 +1,159 @@
+/*
+ * 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.plugins.eclipse.model
+
+import org.gradle.listener.ListenerBroadcast
+import org.gradle.plugins.eclipse.EclipseWtp
+
+/**
+ * @author Hans Dockter
+ */
+class Wtp {
+    List wbModuleEntries = []
+
+    List facets = []
+
+    String deployName
+
+    private Node orgEclipseWstCommonComponentXml
+    private Node orgEclipseWstCommonProjectFacetCoreXml
+
+    private ListenerBroadcast withXmlActions
+
+    Wtp(EclipseWtp eclipseWtp, List wbModuleEntries, Reader inputOrgEclipseWstCommonComponentXml,
+        Reader inputOrgEclipseWstCommonProjectFacetCoreXml) {
+        initFromXml(inputOrgEclipseWstCommonComponentXml, inputOrgEclipseWstCommonProjectFacetCoreXml)
+
+        eclipseWtp.beforeConfiguredActions.source.execute(this)
+
+        this.wbModuleEntries.addAll(wbModuleEntries)
+        this.wbModuleEntries.unique()
+        this.facets.addAll(eclipseWtp.facets)
+        this.facets.unique()
+        if (eclipseWtp.deployName) {
+            this.deployName = eclipseWtp.deployName
+        }
+        this.withXmlActions = eclipseWtp.withXmlActions
+
+        eclipseWtp.whenConfiguredActions.source.execute(this)
+    }
+
+    private def initFromXml(Reader inputOrgEclipseWstCommonComponentXml, Reader inputOrgEclipseWstCommonProjectFacetCoreXml) {
+        if (!inputOrgEclipseWstCommonComponentXml) {
+            orgEclipseWstCommonComponentXml =
+                new Node(null, 'project-modules', [id: "moduleCoreId", 'project-version': "2.0"])
+            orgEclipseWstCommonComponentXml.appendNode('wb-module')
+            orgEclipseWstCommonProjectFacetCoreXml = new Node(null, 'faceted-project')
+            orgEclipseWstCommonProjectFacetCoreXml.appendNode('fixed', [facet: 'jst.java'])
+            orgEclipseWstCommonProjectFacetCoreXml.appendNode('fixed', [facet: 'jst.web'])
+            return
+        }
+
+        orgEclipseWstCommonComponentXml = readOrgEclipseWstCommonComponentXml(inputOrgEclipseWstCommonComponentXml)
+        orgEclipseWstCommonProjectFacetCoreXml = readOrgEclipseWstCommonProjectFacetCoreXml(inputOrgEclipseWstCommonProjectFacetCoreXml)
+    }
+
+    private def readOrgEclipseWstCommonComponentXml(Reader inputXml) {
+        def rootNode = new XmlParser().parse(inputXml)
+
+        deployName = rootNode.'wb-module'[0].@'deploy-name' 
+        rootNode.'wb-module'[0].children().each { entryNode ->
+            def entry = null
+            switch (entryNode.name()) {
+                case 'property': entry = new WbProperty(entryNode)
+                    break
+                case 'wb-resource': entry = new WbResource(entryNode)
+                    break
+                case 'dependent-module': entry = new WbDependentModule(entryNode)
+                    break
+            }
+            if (entry) {
+                wbModuleEntries.add(entry)
+            }
+        }
+        rootNode
+    }
+
+    private def readOrgEclipseWstCommonProjectFacetCoreXml(Reader inputXml) {
+        def rootNode = new XmlParser().parse(inputXml)
+
+        rootNode.installed.each { entryNode ->
+            facets.add(new Facet(entryNode))
+        }
+        rootNode
+    }
+
+    void toXml(File orgEclipseWstCommonComponentXmlFile, File orgEclipseWstCommonProjectFacetCoreXmlFile) {
+        toXml(new FileWriter(orgEclipseWstCommonComponentXmlFile), new FileWriter(orgEclipseWstCommonProjectFacetCoreXmlFile))
+    }
+
+    def toXml(Writer orgEclipseWstCommonComponentXmlWriter, Writer orgEclipseWstCommonProjectFacetCoreXmlWriter) {
+        removeConfigurableDataFromXml()
+        orgEclipseWstCommonComponentXml.'wb-module'[0].@'deploy-name' = deployName
+        wbModuleEntries.each { entry ->
+            entry.appendNode(orgEclipseWstCommonComponentXml.'wb-module')
+        }
+        facets.each { facet ->
+            facet.appendNode(orgEclipseWstCommonProjectFacetCoreXml)
+        }
+        withXmlActions.source.execute([
+                'org.eclipse.wst.commons.component': orgEclipseWstCommonComponentXml,
+                'org.eclipse.wst.commons.project.facet.core': orgEclipseWstCommonProjectFacetCoreXml])
+
+        new XmlNodePrinter(new PrintWriter(orgEclipseWstCommonComponentXmlWriter)).print(orgEclipseWstCommonComponentXml)
+        new XmlNodePrinter(new PrintWriter(orgEclipseWstCommonProjectFacetCoreXmlWriter)).print(orgEclipseWstCommonProjectFacetCoreXml)
+    }
+
+    private def removeConfigurableDataFromXml() {
+        ['property', 'wb-resource', 'dependent-module'].each { elementName ->
+            orgEclipseWstCommonComponentXml.'wb-module'."$elementName".each { elementNode ->
+                orgEclipseWstCommonComponentXml.'wb-module'[0].remove(elementNode)
+            }
+        }
+        orgEclipseWstCommonProjectFacetCoreXml.installed.each { orgEclipseWstCommonProjectFacetCoreXml.remove(it) }
+    }
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        Wtp wtp = (Wtp) o;
+
+        if (deployName != wtp.deployName) { return false }
+        if (facets != wtp.facets) { return false }
+        if (wbModuleEntries != wtp.wbModuleEntries) { return false }
+
+        return true
+    }
+
+    int hashCode() {
+        int result;
+
+        result = wbModuleEntries.hashCode();
+        result = 31 * result + facets.hashCode();
+        result = 31 * result + deployName.hashCode();
+        return result;
+    }
+
+    public String toString() {
+        return "Wtp{" +
+                "wbModuleEntries=" + wbModuleEntries +
+                ", facets=" + facets +
+                ", deployName='" + deployName + '\'' +
+                '}';
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/ClasspathFactory.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/ClasspathFactory.groovy
new file mode 100644
index 0000000..116a24d
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/ClasspathFactory.groovy
@@ -0,0 +1,186 @@
+/*
+ * 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.plugins.eclipse.model.internal
+
+import org.gradle.api.file.SourceDirectorySet
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
+import org.gradle.api.specs.Specs
+import org.gradle.api.tasks.SourceSet
+import org.gradle.api.artifacts.*
+import org.gradle.plugins.eclipse.model.*
+import org.gradle.plugins.eclipse.EclipseClasspath
+
+/**
+ * @author Hans Dockter
+ */
+class ClasspathFactory {
+    Classpath createClasspath(EclipseClasspath eclipseClasspath) {
+        File inputFile = eclipseClasspath.inputFile
+        FileReader inputReader = inputFile != null && inputFile.exists() ? new FileReader(inputFile) : null
+        List entries = getEntriesFromSourceSets(eclipseClasspath.sourceSets, eclipseClasspath.project)
+        entries.addAll(getEntriesFromContainers(eclipseClasspath.getContainers()))
+        entries.addAll(getEntriesFromConfigurations(eclipseClasspath))
+        return new Classpath(eclipseClasspath, entries, inputReader)
+    }
+
+    List getEntriesFromSourceSets(def sourceSets, def project) {
+        List entries = []
+        sourceSets.each { SourceSet sourceSet ->
+            sourceSet.allSource.sourceTrees.each { SourceDirectorySet sourceDirectorySet ->
+                sourceDirectorySet.srcDirs.each { dir ->
+                    if (dir.isDirectory()) {
+                        entries.add(new SourceFolder(
+                                project.relativePath(dir),
+                                null,
+                                [] as Set,
+                                project.relativePath(sourceSet.classesDir),
+                                sourceDirectorySet.getIncludes() as List,
+                                sourceDirectorySet.getExcludes() as List))
+                    }
+                }
+            }
+        }
+        entries
+    }
+
+    List getEntriesFromContainers(Set containers) {
+        containers.collect { container ->
+            new Container(container, true, null, [] as Set)
+        }
+    }
+
+    List getEntriesFromConfigurations(EclipseClasspath eclipseClasspath) {
+        getModules(eclipseClasspath) + getLibraries(eclipseClasspath) 
+    }
+
+    protected List getModules(EclipseClasspath eclipseClasspath) {
+        return getDependencies(eclipseClasspath.plusConfigurations, eclipseClasspath.minusConfigurations, { it instanceof org.gradle.api.artifacts.ProjectDependency }).collect { projectDependency ->
+            projectDependency.dependencyProject
+        }.collect { dependencyProject ->
+            new org.gradle.plugins.eclipse.model.ProjectDependency('/' + dependencyProject.name, true, null, [] as Set)
+        }
+    }
+
+    protected Set getLibraries(EclipseClasspath eclipseClasspath) {
+        Set declaredDependencies = getDependencies(eclipseClasspath.plusConfigurations, eclipseClasspath.minusConfigurations,
+                { it instanceof ExternalDependency})
+
+        ResolvedConfiguration resolvedConfiguration = eclipseClasspath.project.configurations.
+                detachedConfiguration((declaredDependencies as Dependency[])).resolvedConfiguration
+        def allResolvedDependencies = getAllDeps(resolvedConfiguration.firstLevelModuleDependencies)
+
+        Set sourceDependencies = getResolvableDependenciesForAllResolvedDependencies(allResolvedDependencies) { dependency ->
+            addSourceArtifact(dependency)
+        }
+        Map sourceFiles = eclipseClasspath.downloadSources ? getFiles(eclipseClasspath.project, sourceDependencies, "sources") : [:]
+
+        Set javadocDependencies = getResolvableDependenciesForAllResolvedDependencies(allResolvedDependencies) { dependency ->
+            addJavadocArtifact(dependency)
+        }
+        Map javadocFiles = eclipseClasspath.downloadJavadoc ? getFiles(eclipseClasspath.project, javadocDependencies, "javadoc") : [:]
+
+        List moduleLibraries = resolvedConfiguration.getFiles(Specs.SATISFIES_ALL).collect { File binaryFile ->
+            File sourceFile = sourceFiles[binaryFile.name]
+            File javadocFile = javadocFiles[binaryFile.name]
+            createLibraryEntry(binaryFile, sourceFile, javadocFile, eclipseClasspath.variables)
+        }
+        moduleLibraries.addAll(getSelfResolvingFiles(getDependencies(eclipseClasspath.plusConfigurations, eclipseClasspath.minusConfigurations,
+                { it instanceof SelfResolvingDependency && !(it instanceof org.gradle.api.artifacts.ProjectDependency)}), eclipseClasspath.variables))
+        moduleLibraries
+    }
+
+    private def getSelfResolvingFiles(Collection dependencies, Map variables) {
+        dependencies.inject([] as LinkedHashSet) { result, SelfResolvingDependency selfResolvingDependency ->
+            result.addAll(selfResolvingDependency.resolve().collect { File file ->
+                createLibraryEntry(file, null, null, variables)
+            })
+            result
+        }
+    }
+
+    AbstractLibrary createLibraryEntry(File binary, File source, File javadoc, Map variables) {
+        def usedVariableEntry = variables.find { name, value -> binary.canonicalPath.startsWith(value) }
+        if (usedVariableEntry) {
+            String name = usedVariableEntry.key
+            String value = usedVariableEntry.value
+            String binaryPath = name + binary.canonicalPath.substring(value.length())
+            String sourcePath = source ? name + source.canonicalPath.substring(value.length()) : null
+            String javadocPath = javadoc ? name + javadoc.canonicalPath.substring(value.length()) : null
+            return new Variable(binaryPath, true, null, [] as Set, sourcePath, javadocPath)
+        }
+        new Library(binary.canonicalPath, true, null, [] as Set, source ? source.canonicalPath : null, javadoc ? javadoc.canonicalPath : null)
+    }
+
+    private Set getDependencies(Set plusConfigurations, Set minusConfigurations, Closure filter) {
+        Set declaredDependencies = new LinkedHashSet()
+        plusConfigurations.each { configuration ->
+            declaredDependencies.addAll(configuration.getAllDependencies().findAll(filter))
+        }
+        minusConfigurations.each { configuration ->
+            configuration.getAllDependencies().findAll(filter).each { minusDep ->
+                declaredDependencies.remove(minusDep)
+            }
+        }
+        return declaredDependencies
+    }
+
+    private def getFiles(def project, Set dependencies, String classifier) {
+        return project.configurations.detachedConfiguration((dependencies as Dependency[])).files.inject([:]) { result, sourceFile ->
+            String key = sourceFile.name.replace("-${classifier}.jar", '.jar')
+            result[key] = sourceFile
+            result
+        }
+    }
+
+    private List getResolvableDependenciesForAllResolvedDependencies(Set allResolvedDependencies, Closure configureClosure) {
+        return allResolvedDependencies.collect { ResolvedDependency resolvedDependency ->
+            def dependency = new DefaultExternalModuleDependency(resolvedDependency.moduleGroup, resolvedDependency.moduleName, resolvedDependency.moduleVersion,
+                    resolvedDependency.configuration)
+            dependency.transitive = false
+            configureClosure.call(dependency)
+            dependency
+        }
+    }
+
+    protected Set getAllDeps(Set deps) {
+        Set result = []
+        deps.each { ResolvedDependency resolvedDependency ->
+            if (resolvedDependency.children) {
+                result.addAll(getAllDeps(resolvedDependency.children))
+            }
+            result.add(resolvedDependency)
+        }
+        result
+    }
+
+    protected def addSourceArtifact(DefaultExternalModuleDependency dependency) {
+        dependency.artifact { artifact ->
+            artifact.name = dependency.name
+            artifact.type = 'source'
+            artifact.extension = 'jar'
+            artifact.classifier = 'sources'
+        }
+    }
+
+    protected def addJavadocArtifact(DefaultExternalModuleDependency dependency) {
+        dependency.artifact { artifact ->
+            artifact.name = dependency.name
+            artifact.type = 'javadoc'
+            artifact.extension = 'jar'
+            artifact.classifier = 'javadoc'
+        }
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/ModelFactory.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/ModelFactory.groovy
new file mode 100644
index 0000000..05e4b0b
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/ModelFactory.groovy
@@ -0,0 +1,45 @@
+/*
+ * 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.plugins.eclipse.model.internal
+
+import org.gradle.plugins.eclipse.model.Classpath
+import org.gradle.plugins.eclipse.model.Project
+import org.gradle.plugins.eclipse.model.Wtp
+import org.gradle.plugins.eclipse.EclipseProject
+import org.gradle.plugins.eclipse.EclipseClasspath
+import org.gradle.plugins.eclipse.EclipseWtp
+
+/**
+ * @author Hans Dockter
+ */
+class ModelFactory {
+    private ClasspathFactory classpathFactory = new ClasspathFactory()
+    private WtpFactory wtpFactory = new WtpFactory()
+
+    Project createProject(EclipseProject eclipseProject) {
+        File inputFile = eclipseProject.getInputFile()
+        FileReader inputReader = inputFile != null && inputFile.exists() ? new FileReader(inputFile) : null
+        return new Project(eclipseProject, inputReader)
+    }
+
+    Classpath createClasspath(EclipseClasspath eclipseClasspath) {
+        classpathFactory.createClasspath(eclipseClasspath)
+    }
+
+    Wtp createWtp(EclipseWtp eclipseWtp) {
+        wtpFactory.createWtp(eclipseWtp)
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/PathUtil.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/PathUtil.groovy
new file mode 100644
index 0000000..6cb16f4
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/PathUtil.groovy
@@ -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.plugins.eclipse.model.internal
+
+import org.apache.commons.io.FilenameUtils
+
+/**
+ * @author Hans Dockter
+ */
+class PathUtil {
+  static String normalizePath(String path) {
+        FilenameUtils.separatorsToUnix(path)
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/WtpFactory.groovy b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/WtpFactory.groovy
new file mode 100644
index 0000000..a2b2aa4
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/model/internal/WtpFactory.groovy
@@ -0,0 +1,117 @@
+/*
+ * 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.plugins.eclipse.model.internal
+
+import org.apache.commons.io.FilenameUtils
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.artifacts.ExternalDependency
+import org.gradle.api.artifacts.SelfResolvingDependency
+import org.gradle.api.file.SourceDirectorySet
+import org.gradle.api.tasks.SourceSet
+import org.gradle.plugins.eclipse.model.WbDependentModule
+import org.gradle.plugins.eclipse.model.WbProperty
+import org.gradle.plugins.eclipse.model.WbResource
+import org.gradle.plugins.eclipse.model.Wtp
+import org.gradle.plugins.eclipse.EclipseWtp
+
+/**
+ * @author Hans Dockter
+ */
+class WtpFactory {
+    Wtp createWtp(EclipseWtp eclipseWtp) {
+        File componentInputFile = eclipseWtp.orgEclipseWstCommonComponentInputFile
+        File facetInputFile = eclipseWtp.orgEclipseWstCommonProjectFacetCoreInputFile
+        FileReader componentReader = componentInputFile != null && componentInputFile.exists() ? new FileReader(componentInputFile) : null
+        FileReader facetReader = facetInputFile != null && facetInputFile.exists() ? new FileReader(facetInputFile) : null
+        List entries = getEntriesFromSourceSets(eclipseWtp.sourceSets, eclipseWtp.project)
+        entries.addAll(eclipseWtp.resources)
+        entries.addAll(eclipseWtp.properties)
+        entries.addAll(getEntriesFromConfigurations(eclipseWtp))
+        return new Wtp(eclipseWtp, entries, componentReader, facetReader)
+    }
+
+    List getEntriesFromSourceSets(def sourceSets, def project) {
+        List entries = []
+        sourceSets.each { SourceSet sourceSet ->
+            entries.add(new WbProperty('java-output-path', PathUtil.normalizePath(project.relativePath(sourceSet.classesDir))))
+            sourceSet.allSource.sourceTrees.each { SourceDirectorySet sourceDirectorySet ->
+                sourceDirectorySet.srcDirs.each { dir ->
+                    if (dir.isDirectory()) {
+                        entries.add(new WbResource("/WEB-INF/classes", project.relativePath(dir)))
+                    }
+                }
+            }
+        }
+        entries
+    }
+
+    List getEntriesFromConfigurations(EclipseWtp eclipseWtp) {
+        (getProjectsDependencies(eclipseWtp) as List) + (getLibraries(eclipseWtp) as List)
+    }
+
+    protected Set getProjectsDependencies(EclipseWtp eclipseWtp) {
+        return getDependencies(eclipseWtp.plusConfigurations, eclipseWtp.minusConfigurations, { it instanceof org.gradle.api.artifacts.ProjectDependency }).collect { projectDependency ->
+            projectDependency.dependencyProject
+        }.collect { dependencyProject ->
+            new WbDependentModule("/WEB-INF/lib", "module:/resource/" + dependencyProject.name + "/" + dependencyProject.name)
+        }
+    }
+
+    protected Set getLibraries(EclipseWtp eclipseWtp) {
+        Set declaredDependencies = getDependencies(eclipseWtp.plusConfigurations, eclipseWtp.minusConfigurations,
+                { it instanceof ExternalDependency})
+
+        Set libFiles = eclipseWtp.project.configurations.detachedConfiguration((declaredDependencies as Dependency[])).files +
+                getSelfResolvingFiles(getDependencies(eclipseWtp.plusConfigurations, eclipseWtp.minusConfigurations,
+                        { it instanceof SelfResolvingDependency && !(it instanceof org.gradle.api.artifacts.ProjectDependency)}))
+
+        libFiles.collect { file ->
+            createWbDependentModuleEntry(file, eclipseWtp.variables)
+        }
+    }
+
+    private def getSelfResolvingFiles(Collection dependencies) {
+        dependencies.inject([] as LinkedHashSet) { result, SelfResolvingDependency selfResolvingDependency ->
+            result.addAll(selfResolvingDependency.resolve())
+            result
+        }
+    }
+
+    WbDependentModule createWbDependentModuleEntry(File file, Map variables) {
+        def usedVariableEntry = variables.find { name, value -> file.canonicalPath.startsWith(value) }
+        String handleSnippet = null;
+        if (usedVariableEntry) {
+            handleSnippet = "var/$usedVariableEntry.key/${file.canonicalPath.substring(usedVariableEntry.value.length())}"
+        } else {
+            handleSnippet = "lib/${file.canonicalPath.substring(usedVariableEntry.value.length())}"
+        }
+        handleSnippet = FilenameUtils.separatorsToUnix(handleSnippet)
+        return new WbDependentModule('/WEB-INF/lib', "module:/classpath/$handleSnippet")
+    }
+
+    private Set getDependencies(Set plusConfigurations, Set minusConfigurations, Closure filter) {
+        Set declaredDependencies = new LinkedHashSet()
+        plusConfigurations.each { configuration ->
+            declaredDependencies.addAll(configuration.getAllDependencies().findAll(filter))
+        }
+        minusConfigurations.each { configuration ->
+            configuration.getAllDependencies().findAll(filter).each { minusDep ->
+                declaredDependencies.remove(minusDep)
+            }
+        }
+        return declaredDependencies
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/package-info.java b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/package-info.java
new file mode 100644
index 0000000..d6de80b
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/groovy/org/gradle/plugins/eclipse/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * The eclipse project generation {@link org.gradle.api.Task} implementations.
+ */
+package org.gradle.plugins.eclipse;
diff --git a/subprojects/gradle-eclipse/src/main/resources/META-INF/gradle-plugins/eclipse.properties b/subprojects/gradle-eclipse/src/main/resources/META-INF/gradle-plugins/eclipse.properties
new file mode 100644
index 0000000..961d42c
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/main/resources/META-INF/gradle-plugins/eclipse.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+implementation-class=org.gradle.plugins.eclipse.EclipsePlugin
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseClasspathTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseClasspathTest.groovy
new file mode 100644
index 0000000..fab0fb2
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseClasspathTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * 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.plugins.eclipse;
+
+
+import org.gradle.api.internal.ConventionTask
+import org.gradle.api.tasks.AbstractSpockTaskTest
+import org.gradle.plugins.eclipse.model.Classpath
+import org.gradle.plugins.eclipse.model.internal.ModelFactory
+
+/**
+ * @author Hans Dockter
+ */
+public class EclipseClasspathTest extends AbstractSpockTaskTest {
+
+    private EclipseClasspath eclipseClasspath;
+
+    ConventionTask getTask() {
+        return eclipseClasspath
+    }
+
+    def setup() {
+        eclipseClasspath = createTask(EclipseClasspath.class);
+    }
+
+    def containers_shouldAdd() {
+        when:
+        eclipseClasspath.containers "container1"
+        eclipseClasspath.containers "container2"
+
+        then:
+        eclipseClasspath.containers = ['container1', 'container2'] as Set
+    }
+
+    def variables_shouldAdd() {
+        when:
+        eclipseClasspath.variables variable1: 'value1'
+        eclipseClasspath.variables variable2: 'value2'
+
+        then:
+        eclipseClasspath.variables == [variable1: 'value1', variable2: 'value2']
+    }
+
+    def generateXml() {
+        ModelFactory modelFactory = Mock()
+        Classpath classpath = Mock()
+        eclipseClasspath.modelFactory = modelFactory
+        eclipseClasspath.setOutputFile(new File('nonexisting'))
+        modelFactory.createClasspath(eclipseClasspath) >> classpath
+
+        when:
+        eclipseClasspath.generateXml()
+
+        then:
+        1 * classpath.toXml(eclipseClasspath.getOutputFile())
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipsePluginTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipsePluginTest.groovy
new file mode 100644
index 0000000..53821c7
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipsePluginTest.groovy
@@ -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.plugins.eclipse
+
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.api.tasks.Delete
+import org.gradle.plugins.eclipse.model.BuildCommand
+import org.gradle.plugins.eclipse.model.Facet
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+import org.gradle.plugins.eclipse.model.WbResource
+
+/**
+ * @author Hans Dockter
+ */
+class EclipsePluginTest extends Specification {
+    private final DefaultProject project = HelperUtil.createRootProject()
+    private final EclipsePlugin eclipsePlugin = new EclipsePlugin()
+
+    def applyToBaseProject_shouldOnlyHaveEclipseProjectTask() {
+        when:
+        eclipsePlugin.apply(project)
+
+        then:
+        project.tasks.findByPath(':eclipseClasspath') == null
+        assertThatCleanEclipseDependsOn(project, project.cleanEclipseProject)
+        checkEclipseProjectTask([], [])
+    }
+
+    def applyToJavaProject_shouldOnlyHaveProjectAndClasspathTaskForJava() {
+        when:
+        project.apply(plugin: 'java-base')
+        eclipsePlugin.apply(project)
+
+        then:
+        assertThatCleanEclipseDependsOn(project, project.cleanEclipseProject)
+        assertThatCleanEclipseDependsOn(project, project.cleanEclipseClasspath)
+        checkEclipseProjectTask([new BuildCommand('org.eclipse.jdt.core.javabuilder')], ['org.eclipse.jdt.core.javanature'])
+        checkEclipseClasspath([] as Set)
+        project.apply(plugin: 'java')
+        checkEclipseClasspath([project.configurations.testRuntime] as Set)
+    }
+
+    def applyToWarProject_shouldHaveProjectForWebAndClasspathTask() {
+        when:
+        project.apply(plugin: 'war')
+        eclipsePlugin.apply(project)
+
+        then:
+        assertThatCleanEclipseDependsOn(project, project.cleanEclipseProject)
+        assertThatCleanEclipseDependsOn(project, project.cleanEclipseClasspath)
+        assertThatCleanEclipseDependsOn(project, project.cleanEclipseWtp)
+        project.cleanEclipseWtp.targetFiles.contains(project.file('.settings'))
+        checkEclipseProjectTask([
+                new BuildCommand('org.eclipse.jdt.core.javabuilder'),
+                new BuildCommand('org.eclipse.wst.common.project.facet.core.builder'),
+                new BuildCommand('org.eclipse.wst.validation.validationbuilder')],
+                ['org.eclipse.jdt.core.javanature',
+                        'org.eclipse.wst.common.project.facet.core.nature',
+                        'org.eclipse.wst.common.modulecore.ModuleCoreNature'])
+        checkEclipseClasspath([project.configurations.testRuntime] as Set)
+        checkEclipseWtp()
+    }
+
+    def applyToScalaProject_shouldHaveProjectAndClasspathTaskForScala() {
+        when:
+        project.apply(plugin: 'scala-base')
+        eclipsePlugin.apply(project)
+
+        then:
+        assertThatCleanEclipseDependsOn(project, project.cleanEclipseProject)
+        assertThatCleanEclipseDependsOn(project, project.cleanEclipseClasspath)
+        checkEclipseProjectTask([new BuildCommand('ch.epfl.lamp.sdt.core.scalabuilder')],
+                ['ch.epfl.lamp.sdt.core.scalanature', 'org.eclipse.jdt.core.javanature'])
+        checkEclipseClasspath([] as Set)
+        project.apply(plugin: 'scala')
+        checkEclipseClasspath([project.configurations.testRuntime] as Set)
+    }
+
+    def applyToGroovyProject_shouldHaveProjectAndClasspathTaskForGroovy() {
+        when:
+        project.apply(plugin: 'groovy-base')
+        eclipsePlugin.apply(project)
+
+        then:
+        assertThatCleanEclipseDependsOn(project, project.cleanEclipseProject)
+        assertThatCleanEclipseDependsOn(project, project.cleanEclipseClasspath)
+        checkEclipseProjectTask([new BuildCommand('org.eclipse.jdt.core.javabuilder')], ['org.eclipse.jdt.groovy.core.groovyNature',
+                'org.eclipse.jdt.core.javanature'])
+        checkEclipseClasspath([] as Set)
+        project.apply(plugin: 'groovy')
+        checkEclipseClasspath([project.configurations.testRuntime] as Set)
+    }
+
+    private void checkEclipseProjectTask(List buildCommands, List natures) {
+        EclipseProject eclipseProjectTask = project.eclipseProject
+        assert eclipseProjectTask instanceof EclipseProject
+        assert project.eclipse.taskDependencies.getDependencies(project.eclipse).contains(eclipseProjectTask)
+        assert eclipseProjectTask.buildCommands == buildCommands
+        assert eclipseProjectTask.natures == natures
+        assert eclipseProjectTask.links == [] as Set
+        assert eclipseProjectTask.referencedProjects == [] as Set
+        assert eclipseProjectTask.comment == null
+        assert eclipseProjectTask.projectName == project.name
+        assert eclipseProjectTask.inputFile == project.file('.project')
+        assert eclipseProjectTask.outputFile == project.file('.project')
+    }
+
+    private void checkEclipseClasspath(def configurations) {
+        EclipseClasspath eclipseClasspath = project.eclipseClasspath
+        assert eclipseClasspath instanceof EclipseClasspath
+        assert project.eclipse.taskDependencies.getDependencies(project.eclipse).contains(eclipseClasspath)
+        assert eclipseClasspath.sourceSets == project.sourceSets
+        assert eclipseClasspath.plusConfigurations == configurations
+        assert eclipseClasspath.minusConfigurations == [] as Set
+        assert eclipseClasspath.containers == ['org.eclipse.jdt.launching.JRE_CONTAINER'] as Set
+        assert eclipseClasspath.inputFile == project.file('.classpath')
+        assert eclipseClasspath.outputFile == project.file('.classpath')
+        assert eclipseClasspath.variables == [GRADLE_CACHE: new File(project.gradle.gradleUserHomeDir, 'cache').canonicalPath]
+    }
+
+    private void checkEclipseWtp() {
+        EclipseWtp eclipseWtp = project.eclipseWtp
+        assert eclipseWtp instanceof EclipseWtp
+        assert project.eclipse.taskDependencies.getDependencies(project.eclipse).contains(eclipseWtp)
+        assert eclipseWtp.sourceSets.all == [project.sourceSets.main] as Set
+        assert eclipseWtp.plusConfigurations == [project.configurations.runtime] as Set
+        assert eclipseWtp.minusConfigurations == [project.configurations.providedRuntime] as Set
+        assert eclipseWtp.deployName == project.name
+        assert eclipseWtp.orgEclipseWstCommonComponentInputFile == project.file('.settings/org.eclipse.wst.common.component.xml')
+        assert eclipseWtp.orgEclipseWstCommonComponentOutputFile == project.file('.settings/org.eclipse.wst.common.component.xml')
+        assert eclipseWtp.orgEclipseWstCommonProjectFacetCoreInputFile == project.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
+        assert eclipseWtp.orgEclipseWstCommonProjectFacetCoreOutputFile == project.file('.settings/org.eclipse.wst.common.project.facet.core.xml')
+        assert eclipseWtp.facets == [new Facet("jst.web", "2.4"), new Facet("jst.java", "1.4")]
+        assert eclipseWtp.variables == [GRADLE_CACHE: new File(project.gradle.gradleUserHomeDir, 'cache').canonicalPath]
+        assert eclipseWtp.resources == [new WbResource('/', project.convention.plugins.war.webAppDirName)]
+    }
+
+    void assertThatCleanEclipseDependsOn(Project project, Task dependsOnTask) {
+        assert dependsOnTask instanceof Delete
+        assert project.cleanEclipse.taskDependencies.getDependencies(project.cleanEclipse).contains(dependsOnTask)
+    }
+}
+
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseProjectTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseProjectTest.groovy
new file mode 100644
index 0000000..1504827
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseProjectTest.groovy
@@ -0,0 +1,69 @@
+/*
+ * 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.plugins.eclipse
+
+import org.gradle.api.internal.ConventionTask
+import org.gradle.api.tasks.AbstractSpockTaskTest
+import org.gradle.plugins.eclipse.model.BuildCommand
+import org.gradle.plugins.eclipse.model.Project
+import org.gradle.plugins.eclipse.model.internal.ModelFactory
+
+/**
+ * @author Hans Dockter
+ */
+class EclipseProjectTest extends AbstractSpockTaskTest {
+    EclipseProject eclipseProject
+
+    ConventionTask getTask() {
+        return eclipseProject
+    }
+
+    def setup() {
+        eclipseProject = createTask(EclipseProject.class);
+    }
+
+    def natures_shouldAdd() {
+        when:
+        eclipseProject.natures 'nature1'
+        eclipseProject.natures 'nature2'
+
+        then:
+        eclipseProject.natures = ['nature1', 'nature2']
+    }
+
+    def buildCommands_shouldAdd() {
+        when:
+        eclipseProject.buildCommand 'command1', key1: 'value1'
+        eclipseProject.buildCommand 'command2'
+
+        then:
+        eclipseProject.buildCommands as List == [new BuildCommand('command1', [key1: 'value1']), new BuildCommand('command2')]
+    }
+
+    def generateXml() {
+        ModelFactory modelFactory = Mock()
+        Project project = Mock()
+        eclipseProject.modelFactory = modelFactory
+        eclipseProject.setOutputFile(new File('nonexisting'))
+        modelFactory.createProject(eclipseProject) >> project
+
+        when:
+        eclipseProject.generateXml()
+
+        then:
+        1 * project.toXml(eclipseProject.getOutputFile())
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseWtpTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseWtpTest.groovy
new file mode 100644
index 0000000..0494243
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/EclipseWtpTest.groovy
@@ -0,0 +1,96 @@
+/*
+ * 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.plugins.eclipse;
+
+
+import org.gradle.api.internal.ConventionTask
+import org.gradle.api.tasks.AbstractSpockTaskTest
+import org.gradle.plugins.eclipse.model.Facet
+import org.gradle.plugins.eclipse.model.Wtp
+import org.gradle.plugins.eclipse.model.internal.ModelFactory
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import org.gradle.plugins.eclipse.model.WbProperty
+import org.gradle.plugins.eclipse.model.WbResource
+
+/**
+ * @author Hans Dockter
+ */
+public class EclipseWtpTest extends AbstractSpockTaskTest {
+    EclipseWtp eclipseWtp
+
+    @Rule
+    TemporaryFolder tmpDir = new TemporaryFolder()
+
+    ConventionTask getTask() {
+        return eclipseWtp
+    }
+
+    def setup() {
+        eclipseWtp = createTask(EclipseWtp.class);
+    }
+
+    def facet_shouldAdd() {
+        when:
+        eclipseWtp.facet name: 'facet1', version: '1.0'
+        eclipseWtp.facet name: 'facet2', version: '2.0'
+
+        then:
+        eclipseWtp.facets == [new Facet('facet1', '1.0'), new Facet('facet2', '2.0')]
+    }
+
+    def property_shouldAdd() {
+        when:
+        eclipseWtp.property name: 'prop1', value: 'value1'
+        eclipseWtp.property name: 'prop2', value: 'value2'
+
+        then:
+        eclipseWtp.properties == [new WbProperty('prop1', 'value1'), new WbProperty('prop2', 'value2')]
+    }
+
+    def resource_shouldAdd() {
+        when:
+        eclipseWtp.resource deployPath: 'path1', sourcePath: 'sourcepath1'
+        eclipseWtp.resource deployPath: 'path2', sourcePath: 'sourcepath2'
+
+        then:
+        eclipseWtp.resources == [new WbResource('path1', 'sourcepath1'), new WbResource('path2', 'sourcepath2')]
+    }
+
+    def generateXml() {
+        ModelFactory modelFactory = Mock()
+        Wtp wtp = Mock()
+        eclipseWtp.modelFactory = modelFactory
+        eclipseWtp.setOrgEclipseWstCommonComponentOutputFile(tmpDir.file("component"))
+        eclipseWtp.setOrgEclipseWstCommonProjectFacetCoreOutputFile(tmpDir.file("facet"))
+        modelFactory.createWtp(eclipseWtp) >> wtp
+
+        when:
+        eclipseWtp.generateXml()
+
+        then:
+        1 * wtp.toXml(eclipseWtp.orgEclipseWstCommonComponentOutputFile, eclipseWtp.orgEclipseWstCommonProjectFacetCoreOutputFile)
+    }
+
+    def variables_shouldAdd() {
+        when:
+        eclipseWtp.variables variable1: 'value1'
+        eclipseWtp.variables variable2: 'value2'
+
+        then:
+        eclipseWtp.variables == [variable1: 'value1', variable2: 'value2']
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/ClasspathTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/ClasspathTest.groovy
new file mode 100644
index 0000000..a7353ea
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/ClasspathTest.groovy
@@ -0,0 +1,176 @@
+/*
+ * 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.plugins.eclipse.model;
+
+
+import org.gradle.api.Action
+import org.gradle.listener.ListenerBroadcast
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+import org.gradle.plugins.eclipse.EclipseClasspath
+
+/**
+ * @author Hans Dockter
+ */
+
+public class ClasspathTest extends Specification {
+    private static final DEFAULT_ENTRIES = [new Output('bin')]
+    private static final CUSTOM_ENTRIES = [
+            new ProjectDependency("/test2", false, null, [] as Set),
+            new Container("org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6",
+                false, null, [] as Set),
+            new Library("/apache-ant-1.7.1/lib/ant-antlr.jar", false, null, [] as Set, null, null),
+            new SourceFolder("src", null, [] as Set, "bin2", [], []),
+            new Variable("GRADLE_CACHE/ant-1.6.5.jar", false, null, [] as Set, null, null),
+            new Container("org.eclipse.jdt.USER_LIBRARY/gradle", false, null, [] as Set)]
+
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    def initWithReader() {
+        Classpath classpath = createClasspath(reader: customClasspathReader)
+
+        expect:
+        classpath.entries == DEFAULT_ENTRIES + CUSTOM_ENTRIES
+
+    }
+
+    def initWithReaderAndValues_shouldBeMerged() {
+        def constructorDefaultOutput = 'build'
+        def constructorEntries = [createSomeLibrary()]
+
+        Classpath classpath = createClasspath(entries: constructorEntries + [CUSTOM_ENTRIES[0]], defaultOutput: constructorDefaultOutput,
+                reader: customClasspathReader)
+
+        expect:
+        classpath.entries == DEFAULT_ENTRIES + CUSTOM_ENTRIES + constructorEntries
+    }
+
+    def initWithNullReader() {
+        def constructorDefaultOutput = 'build'
+        def constructorEntries = [createSomeLibrary()]
+
+        Classpath classpath = createClasspath(entries: constructorEntries, defaultOutput: constructorDefaultOutput)
+
+        expect:
+        classpath.xml != null
+        classpath.entries == DEFAULT_ENTRIES + constructorEntries
+    }
+
+    def toXml() {
+        when:
+        Classpath classpath = createClasspath(reader: customClasspathReader)
+
+        then:
+        File eclipseFile = tmpDir.file("eclipse.xml")
+        classpath.toXml(eclipseFile)
+        StringWriter stringWriterFileXml = new StringWriter()
+        new XmlNodePrinter(new PrintWriter(stringWriterFileXml)).print(new XmlParser().parse(eclipseFile))
+        StringWriter stringWriterWrittenXml = new StringWriter()
+        new XmlNodePrinter(new PrintWriter(stringWriterWrittenXml)).print(new XmlParser().parse(getToXmlReader(classpath)))
+        StringWriter stringWriterInternalXml = new StringWriter()
+        new XmlNodePrinter(new PrintWriter(stringWriterInternalXml)).print(classpath.xml)
+
+        stringWriterWrittenXml.toString() == stringWriterInternalXml.toString()
+        stringWriterWrittenXml.toString() == stringWriterFileXml.toString()
+    }
+
+    def toXml_shouldContainCustomValues() {
+        def constructorEntries = [createSomeLibrary()]
+
+        when:
+        Classpath classpath = createClasspath(entries: constructorEntries,
+                reader: customClasspathReader)
+        def classpathFromXml = createClasspath(reader: getToXmlReader(classpath))
+
+        then:
+        classpath == classpathFromXml
+    }
+
+    def beforeConfigured() {
+        def constructorEntries = [createSomeLibrary()]
+        ListenerBroadcast beforeConfiguredActions = new ListenerBroadcast(Action)
+        beforeConfiguredActions.add("execute") { Classpath classpath ->
+            classpath.entries.clear()
+        }
+
+        when:
+        Classpath classpath = createClasspath(entries: constructorEntries, reader: customClasspathReader,
+                beforeConfiguredActions: beforeConfiguredActions)
+
+        then:
+        createClasspath(reader: getToXmlReader(classpath)).entries == DEFAULT_ENTRIES + constructorEntries
+    }
+
+    def whenConfigured() {
+        def constructorEntry = createSomeLibrary()
+        def configureActionEntry = createSomeLibrary()
+        configureActionEntry.path = constructorEntry.path + 'Other'
+
+        ListenerBroadcast whenConfiguredActions = new ListenerBroadcast(Action)
+        whenConfiguredActions.add("execute") { Classpath classpath ->
+            assert classpath.entries.contains((CUSTOM_ENTRIES as List)[0])
+            assert classpath.entries.contains(constructorEntry)
+            classpath.entries.add(configureActionEntry)
+        }
+
+        when:
+        Classpath classpath = createClasspath(entries: [constructorEntry], reader: customClasspathReader,
+                whenConfiguredActions: whenConfiguredActions)
+
+        then:
+        createClasspath(reader: getToXmlReader(classpath)).entries == DEFAULT_ENTRIES + CUSTOM_ENTRIES +
+                ([constructorEntry, configureActionEntry])
+    }
+
+    def withXml() {
+        ListenerBroadcast withXmlActions = new ListenerBroadcast(Action)
+        Classpath classpath = createClasspath(reader: customClasspathReader, withXmlActions: withXmlActions)
+
+        when:
+        withXmlActions.add("execute") { Node xml ->
+            xml.classpathentry.find { it. at kind == 'output' }. at path = 'newPath'
+        }
+
+        then:
+        new XmlParser().parse(getToXmlReader(classpath)).classpathentry.find { it. at kind == 'output' }. at path == 'newPath'
+    }
+
+    private InputStreamReader getCustomClasspathReader() {
+        return new InputStreamReader(getClass().getResourceAsStream('customClasspath.xml'))
+    }
+
+    private Library createSomeLibrary() {
+        return new Library("/somepath", true, null, [] as Set, null, null)
+    }
+
+    private Classpath createClasspath(Map customArgs) {
+        ListenerBroadcast dummyBroadcast = new ListenerBroadcast(Action)
+        Map args = [entries: [], reader: null, beforeConfiguredActions: dummyBroadcast, whenConfiguredActions: dummyBroadcast, withXmlActions: dummyBroadcast] + customArgs
+        EclipseClasspath eclipseClasspathStub = Mock()
+        eclipseClasspathStub.getBeforeConfiguredActions() >> args.beforeConfiguredActions
+        eclipseClasspathStub.getWhenConfiguredActions() >> args.whenConfiguredActions
+        eclipseClasspathStub.getWithXmlActions() >> args.withXmlActions
+        return new Classpath(eclipseClasspathStub, args.entries, args.reader)
+    }
+
+    private StringReader getToXmlReader(Classpath classpath) {
+        StringWriter toXmlText = new StringWriter()
+        classpath.toXml(toXmlText)
+        return new StringReader(toXmlText.toString())
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/ContainerTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/ContainerTest.groovy
new file mode 100644
index 0000000..21a544a
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/ContainerTest.groovy
@@ -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.plugins.eclipse.model
+
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+
+class ContainerTest extends Specification {
+    final static String XML_TEXT = '''
+                <classpathentry exported="true" kind="con" path="somePath">
+                    <attributes>
+                        <attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="mynative"/>
+                    </attributes>
+                    <accessrules>
+                        <accessrule kind="nonaccessible" pattern="secret**"/>
+                    </accessrules>
+                </classpathentry>'''
+
+    def canReadFromXml() {
+        when:
+        Container container = new Container(new XmlParser().parseText(XML_TEXT))
+
+        then:
+        container == createContainer()
+    }
+
+    def canWriteToXml() {
+        Node rootNode = new Node(null, 'root')
+
+        when:
+        createContainer().appendNode(rootNode)
+
+        then:
+        new Container(rootNode.classpathentry[0]) == createContainer()
+    }
+
+    def equality() {
+        Container container = createContainer()
+        container.nativeLibraryLocation += 'x'
+
+        expect:
+        container != createContainer()
+    }
+
+    private Container createContainer() {
+        return new Container('somePath', true, 'mynative', [new AccessRule('nonaccessible', 'secret**')] as Set)
+    }
+
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/FacetTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/FacetTest.groovy
new file mode 100644
index 0000000..6afc7d7
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/FacetTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * 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.plugins.eclipse.model
+
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+
+class FacetTest extends Specification {
+    final static String XML_TEXT = '<installed facet="jst.web" version="2.4"/>'
+
+    def canReadFromXml() {
+        when:
+        Facet facet = new Facet(new XmlParser().parseText(XML_TEXT))
+
+        then:
+        facet == createFacet()
+    }
+
+    def canWriteToXml() {
+        Node rootNode = new Node(null, 'root')
+
+        when:
+        createFacet().appendNode(rootNode)
+
+        then:
+        new Facet(rootNode.installed[0]) == createFacet()
+    }
+
+    def equality() {
+        Facet facet = createFacet()
+        facet.name += 'x'
+
+        expect:
+        facet != createFacet()
+    }
+
+    private Facet createFacet() {
+        return new Facet("jst.web", "2.4")
+    }
+
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/LibraryTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/LibraryTest.groovy
new file mode 100644
index 0000000..af50a78
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/LibraryTest.groovy
@@ -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.plugins.eclipse.model
+
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+
+class LibraryTest extends Specification {
+    final static String XML_TEXT = '''
+                    <classpathentry exported="true" kind="lib" path="/ant.jar" sourcepath="/ant-src.jar">
+                        <attributes>
+                            <attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="mynative"/>
+                            <attribute name="javadoc_location" value="jar:file:/ant-javadoc.jar!/path"/>
+                        </attributes>
+                        <accessrules>
+                            <accessrule kind="nonaccessible" pattern="secret**"/>
+                        </accessrules>
+                    </classpathentry>'''
+
+    def canReadFromXml() {
+        when:
+        Library library = new Library(new XmlParser().parseText(XML_TEXT))
+
+        then:
+        library == createLibrary()
+    }
+
+    def canWriteToXml() {
+        Node rootNode = new Node(null, 'root')
+
+        when:
+        createLibrary().appendNode(rootNode)
+
+        then:
+        new Library(rootNode.classpathentry[0]) == createLibrary()
+    }
+
+    def equality() {
+        Library library = createLibrary()
+        library.javadocPath += 'x'
+
+        expect:
+        library != createLibrary()
+    }
+
+    private Library createLibrary() {
+        return new Library('/ant.jar', true, 'mynative', [new AccessRule('nonaccessible', 'secret**')] as Set,
+                "/ant-src.jar", "jar:file:/ant-javadoc.jar!/path")
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/OutputTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/OutputTest.groovy
new file mode 100644
index 0000000..756ddb2
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/OutputTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * 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.plugins.eclipse.model
+
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+
+class OutputTest extends Specification {
+    final static String XML_TEXT = '<classpathentry kind="output" path="somePath"/>'
+
+    def canReadFromXml() {
+        when:
+        Output output = new Output(new XmlParser().parseText(XML_TEXT))
+
+        then:
+        output == createOutput()
+    }
+
+    def canWriteToXml() {
+        Node rootNode = new Node(null, 'root')
+
+        when:
+        createOutput().appendNode(rootNode)
+
+        then:
+        new Output(rootNode.classpathentry[0]) == createOutput()
+    }
+
+    def equality() {
+        Output output = createOutput()
+        output.path += 'x'
+
+        expect:
+        output != createOutput()
+    }
+
+    private Output createOutput() {
+        return new Output('somePath')
+    }
+
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/ProjectDependencyTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/ProjectDependencyTest.groovy
new file mode 100644
index 0000000..5b270ee
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/ProjectDependencyTest.groovy
@@ -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.plugins.eclipse.model
+
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+
+class ProjectDependencyTest extends Specification {
+    final static String XML_TEXT = '''
+                <classpathentry kind="src" path="/test2" exported="true">
+                    <attributes>
+                        <attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="mynative"/>
+                    </attributes>
+                    <accessrules>
+                        <accessrule kind="nonaccessible" pattern="secret**"/>
+                    </accessrules>
+                </classpathentry>'''
+
+    def canReadFromXml() {
+        when:
+        ProjectDependency projectDependency = new ProjectDependency(new XmlParser().parseText(XML_TEXT))
+
+        then:
+        projectDependency == createProjectDependency()
+    }
+
+    def canWriteToXml() {
+        Node rootNode = new Node(null, 'root')
+
+        when:
+        createProjectDependency().appendNode(rootNode)
+
+        then:
+        new ProjectDependency(rootNode.classpathentry[0]) == createProjectDependency()
+    }
+
+    def equality() {
+        ProjectDependency projectDependency = createProjectDependency()
+        projectDependency.nativeLibraryLocation += 'x'
+
+        expect:
+        projectDependency != createProjectDependency()
+    }
+
+    private ProjectDependency createProjectDependency() {
+        return new ProjectDependency('/test2', true, 'mynative', [new AccessRule('nonaccessible', 'secret**')] as Set)
+    }
+
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/ProjectTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/ProjectTest.groovy
new file mode 100644
index 0000000..ae19c77
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/ProjectTest.groovy
@@ -0,0 +1,198 @@
+/*
+ * 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.plugins.eclipse.model;
+
+
+import org.gradle.api.Action
+import org.gradle.listener.ListenerBroadcast
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+import org.gradle.plugins.eclipse.EclipseProject
+
+/**
+ * @author Hans Dockter
+ */
+public class ProjectTest extends Specification {
+    def static final CUSTOM_REFERENCED_PROJECTS = ['refProject'] as LinkedHashSet
+    def static final CUSTOM_BUILD_COMMANDS = [new BuildCommand('org.eclipse.jdt.core.scalabuilder', [climate: 'cold'])]
+    def static final CUSTOM_NATURES = ['org.eclipse.jdt.core.scalanature'] 
+    def static final CUSTOM_LINKS = [new Link('somename', 'sometype', 'somelocation', '')] as Set
+
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    def initWithReader() {
+        Project project = createProject(reader: customProjectReader)
+
+        expect:
+        project.name == 'test'
+        project.comment == 'for testing'
+        project.referencedProjects == CUSTOM_REFERENCED_PROJECTS
+        project.buildCommands == CUSTOM_BUILD_COMMANDS
+        project.natures == CUSTOM_NATURES
+        project.links == CUSTOM_LINKS
+    }
+
+    def initWithReaderAndValues_shouldBeMerged() {
+        def constructorName = 'constructorName'
+        def constructorComment = 'constructorComment'
+        def constructorReferencedProjects = ['constructorRefProject'] as LinkedHashSet
+        def constructorBuildCommands = [new BuildCommand('constructorbuilder')] 
+        def constructorNatures = ['constructorNature']
+        def constructorLinks = [new Link('constructorName', 'constructorType', 'constructorLocation', '')] as Set
+
+        Project project = createProject(name: constructorName, comment: constructorComment, referencedProjects: constructorReferencedProjects,
+                natures: constructorNatures, buildCommands: constructorBuildCommands, links: constructorLinks, reader: customProjectReader)
+
+        expect:
+        project.name == constructorName
+        project.comment == constructorComment
+        project.referencedProjects == constructorReferencedProjects + CUSTOM_REFERENCED_PROJECTS
+        project.buildCommands == CUSTOM_BUILD_COMMANDS + constructorBuildCommands
+        project.natures == CUSTOM_NATURES + constructorNatures 
+        project.links == constructorLinks + CUSTOM_LINKS
+    }
+
+    def initWithNullReader() {
+        def constructorName = 'constructorName'
+        def constructorComment = 'constructorComment'
+        def constructorReferencedProjects = ['constructorRefProject'] as Set
+        def constructorBuildCommands = [new BuildCommand('constructorbuilder')]
+        def constructorNatures = ['constructorNature'] 
+        def constructorLinks = [new Link('constructorName', 'constructorType', 'constructorLocation', '')] as Set
+
+        Project project = createProject(name: constructorName, comment: constructorComment, referencedProjects: constructorReferencedProjects,
+                natures: constructorNatures, buildCommands: constructorBuildCommands, links: constructorLinks)
+
+        expect:
+        project.xml != null
+        project.name == constructorName
+        project.comment == constructorComment
+        project.referencedProjects == constructorReferencedProjects
+        project.buildCommands == constructorBuildCommands
+        project.natures == constructorNatures
+        project.links == constructorLinks
+    }
+
+    def toXml() {
+        when:
+        Project project = createProject(reader: customProjectReader)
+
+        then:
+        File eclipseFile = tmpDir.file("eclipse.xml")
+        project.toXml(eclipseFile)
+        StringWriter stringWriterFileXml = new StringWriter()
+        new XmlNodePrinter(new PrintWriter(stringWriterFileXml)).print(new XmlParser().parse(eclipseFile))
+        StringWriter stringWriterWrittenXml = new StringWriter()
+        new XmlNodePrinter(new PrintWriter(stringWriterWrittenXml)).print(new XmlParser().parse(getToXmlReader(project)))
+        StringWriter stringWriterInternalXml = new StringWriter()
+        new XmlNodePrinter(new PrintWriter(stringWriterInternalXml)).print(project.xml)
+
+        stringWriterWrittenXml.toString() == stringWriterInternalXml.toString()
+        stringWriterWrittenXml.toString() == stringWriterFileXml.toString()
+    }
+
+    def toXml_shouldContainCustomValues() {
+        def constructorName = 'constructorName'
+        def constructorComment = 'constructorComment'
+        def constructorReferencedProjects = ['constructorRefProject'] as LinkedHashSet
+
+        when:
+        Project project = createProject(name: constructorName, comment: constructorComment,
+                referencedProjects: constructorReferencedProjects, reader: customProjectReader)
+        def projectFromXml = createProject(reader: getToXmlReader(project))
+
+        then:
+        project == projectFromXml
+    }
+
+    def beforeConfigured() {
+        def constructorNatures = ['constructorNature'] 
+        ListenerBroadcast beforeConfiguredActions = new ListenerBroadcast(Action)
+        beforeConfiguredActions.add("execute") { Project project ->
+            project.natures.clear()
+        }
+
+        when:
+        Project project = createProject(natures: constructorNatures, reader: customProjectReader, beforeConfiguredActions: beforeConfiguredActions)
+
+        then:
+        createProject(reader: getToXmlReader(project)).natures == constructorNatures
+    }
+
+    def whenConfigured() {
+        def constructorNature = 'constructorNature'
+        def configureActionNature = 'configureNature'
+
+        ListenerBroadcast whenConfiguredActions = new ListenerBroadcast(Action)
+        whenConfiguredActions.add("execute") { Project project ->
+            assert project.natures.contains(CUSTOM_NATURES[0])
+            assert project.natures.contains(constructorNature)
+            project.natures.add(configureActionNature)
+        }
+
+        when:
+        Project project = createProject(natures: [constructorNature], reader: customProjectReader,
+                whenConfiguredActions: whenConfiguredActions)
+
+        then:
+        createProject(reader: getToXmlReader(project)).natures == CUSTOM_NATURES + ([constructorNature, configureActionNature] as LinkedHashSet)
+    }
+
+    def withXml() {
+        ListenerBroadcast withXmlActions = new ListenerBroadcast(Action)
+        Project project = createProject(reader: customProjectReader, withXmlActions: withXmlActions)
+
+        when:
+        def newName
+        withXmlActions.add("execute") { Node xml ->
+            newName = xml.name.text() + 'x'
+            xml.remove(xml.name)
+            xml.appendNode('name', newName)
+        }
+
+        then:
+        new XmlParser().parse(getToXmlReader(project)).name.text() == newName
+    }
+
+    private InputStreamReader getCustomProjectReader() {
+        return new InputStreamReader(getClass().getResourceAsStream('customProject.xml'))
+    }
+
+    private Project createProject(Map customArgs) {
+        ListenerBroadcast dummyBroadcast = new ListenerBroadcast(Action)
+        Map args = [name: null, comment: null, referencedProjects: [] as Set, natures: [], buildCommands: [],
+                links: [] as Set, reader: null, beforeConfiguredActions: dummyBroadcast, whenConfiguredActions: dummyBroadcast, withXmlActions: dummyBroadcast] + customArgs
+        EclipseProject eclipseProjectStub = Mock()
+        eclipseProjectStub.getProjectName() >> args.name
+        eclipseProjectStub.getComment() >> args.comment
+        eclipseProjectStub.getReferencedProjects() >> args.referencedProjects
+        eclipseProjectStub.getNatures() >> args.natures
+        eclipseProjectStub.getBuildCommands() >> args.buildCommands
+        eclipseProjectStub.getLinks() >> args.links
+        eclipseProjectStub.getBeforeConfiguredActions() >> args.beforeConfiguredActions
+        eclipseProjectStub.getWhenConfiguredActions() >> args.whenConfiguredActions
+        eclipseProjectStub.getWithXmlActions() >> args.withXmlActions
+        return new Project(eclipseProjectStub, args.reader)
+    }
+
+    private StringReader getToXmlReader(Project project) {
+        StringWriter toXmlText = new StringWriter()
+        project.toXml(toXmlText)
+        return new StringReader(toXmlText.toString())
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/SourceFolderTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/SourceFolderTest.groovy
new file mode 100644
index 0000000..cda61e1
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/SourceFolderTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * 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.plugins.eclipse.model
+
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+class SourceFolderTest extends Specification {
+    final static String XML_TEXT = '''
+                <classpathentry including="**/Test1*|**/Test2*" excluding="**/Test3*|**/Test4*" kind="src" output="bin2" path="src">
+                    <attributes>
+                        <attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="mynative"/>
+                    </attributes>
+                    <accessrules>
+                        <accessrule kind="nonaccessible" pattern="secret**"/>
+                    </accessrules>
+                </classpathentry>'''
+
+    def canReadFromXml() {
+        expect:
+        new SourceFolder(new XmlParser().parseText(XML_TEXT)) == createSourceFolder()
+    }
+
+    def canWriteToXml() {
+        Node rootNode = new Node(null, 'root')
+
+        when:
+        createSourceFolder().appendNode(rootNode)
+
+        then:
+        new SourceFolder(rootNode.classpathentry[0]) == createSourceFolder()
+    }
+
+    def equality() {
+        SourceFolder sourceFolder = createSourceFolder()
+        sourceFolder.nativeLibraryLocation += 'x'
+
+        expect:
+        sourceFolder != createSourceFolder()
+    }
+
+    def createSourceFolder() {
+        return new SourceFolder('src', 'mynative', [new AccessRule('nonaccessible', 'secret**')] as Set,
+            'bin2', ['**/Test1*' ,'**/Test2*'], ['**/Test3*' ,'**/Test4*'])
+    }
+}
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/VariableTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/VariableTest.groovy
new file mode 100644
index 0000000..b1d7fe7
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/VariableTest.groovy
@@ -0,0 +1,68 @@
+/*
+ * 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.plugins.eclipse.model
+
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+
+class VariableTest extends Specification {
+    final static String XML_TEXT = '''
+                <classpathentry exported="true" kind="var" path="/GRADLE_CACHE/ant.jar" sourcepath="/GRADLE_CACHE/ant-src.jar">
+                    <attributes>
+                        <attribute name="org.eclipse.jdt.launching.CLASSPATH_ATTR_LIBRARY_PATH_ENTRY" value="mynative"/>
+                        <attribute name="javadoc_location" value="jar:file:/GRADLE_CACHE/ant-javadoc.jar!/path"/>
+                    </attributes>
+                    <accessrules>
+                        <accessrule kind="nonaccessible" pattern="secret**"/>
+                    </accessrules>
+                </classpathentry>'''
+
+    def canReadFromXml() {
+        when:
+        Variable variable = new Variable(new XmlParser().parseText(XML_TEXT))
+
+        then:
+        variable == createVariable()
+    }
+
+    def canWriteToXml() {
+        Node rootNode = new Node(null, 'root')
+
+        when:
+        createVariable().appendNode(rootNode)
+
+        then:
+        new Variable(rootNode.classpathentry[0]) == createVariable()
+    }
+
+    def equality() {
+        Variable variable = createVariable()
+        variable.sourcePath += 'x'
+
+        expect:
+        variable != createVariable()
+    }
+
+    private Variable createVariable() {
+        return new Variable('/GRADLE_CACHE/ant.jar', true, 'mynative', [new AccessRule('nonaccessible', 'secret**')] as Set,
+                "/GRADLE_CACHE/ant-src.jar", "jar:file:/GRADLE_CACHE/ant-javadoc.jar!/path")
+    }
+
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/WbDependentModuleTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/WbDependentModuleTest.groovy
new file mode 100644
index 0000000..dc81592
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/WbDependentModuleTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * 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.plugins.eclipse.model
+
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+
+class WbDependentModuleTest extends Specification {
+    final static String XML_TEXT = '''
+                <dependent-module deploy-path="/WEB-INF/lib" handle="module:/classpath/gradle-1.0.0.jar">
+                    <dependency-type>uses</dependency-type>
+                </dependent-module>'''
+
+    def canReadFromXml() {
+        when:
+        WbDependentModule wbDependentModule = new WbDependentModule(new XmlParser().parseText(XML_TEXT))
+
+        then:
+        wbDependentModule == createWbDependentModule()
+    }
+
+    def canWriteToXml() {
+        Node rootNode = new Node(null, 'root')
+
+        when:
+        createWbDependentModule().appendNode(rootNode)
+
+        then:
+        new WbDependentModule(rootNode.'dependent-module'[0]) == createWbDependentModule()
+    }
+
+    def equality() {
+        WbDependentModule wbDependentModule = createWbDependentModule()
+        wbDependentModule.handle += 'x'
+
+        expect:
+        wbDependentModule != createWbDependentModule()
+    }
+
+    private WbDependentModule createWbDependentModule() {
+        return new WbDependentModule("/WEB-INF/lib", "module:/classpath/gradle-1.0.0.jar")
+    }
+
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/WbPropertyTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/WbPropertyTest.groovy
new file mode 100644
index 0000000..1f38af0
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/WbPropertyTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * 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.plugins.eclipse.model
+
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+
+class WbPropertyTest extends Specification {
+    final static String XML_TEXT = '<property name="java-output-path" value="/build/classes"/>'
+
+    def canReadFromXml() {
+        when:
+        WbProperty wbProperty = new WbProperty(new XmlParser().parseText(XML_TEXT))
+
+        then:
+        wbProperty == createWbProperty()
+    }
+
+    def canWriteToXml() {
+        Node rootNode = new Node(null, 'root')
+
+        when:
+        createWbProperty().appendNode(rootNode)
+
+        then:
+        new WbProperty(rootNode.property[0]) == createWbProperty()
+    }
+
+    def equality() {
+        WbProperty wbProperty = createWbProperty()
+        wbProperty.name += 'x'
+
+        expect:
+        wbProperty != createWbProperty()
+    }
+
+    private WbProperty createWbProperty() {
+        return new WbProperty("java-output-path", "/build/classes")
+    }
+
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/WbResourceTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/WbResourceTest.groovy
new file mode 100644
index 0000000..c7075f9
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/WbResourceTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * 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.plugins.eclipse.model
+
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+
+class WbResourceTest extends Specification {
+    final static String XML_TEXT = '<wb-resource deploy-path="/" source-path="src/main/webapp"/>'
+
+    def canReadFromXml() {
+        when:
+        WbResource wbResource = new WbResource(new XmlParser().parseText(XML_TEXT))
+
+        then:
+        wbResource == createWbResource()
+    }
+
+    def canWriteToXml() {
+        Node rootNode = new Node(null, 'root')
+
+        when:
+        createWbResource().appendNode(rootNode)
+
+        then:
+        new WbResource(rootNode.'wb-resource'[0]) == createWbResource()
+    }
+
+    def equality() {
+        WbResource wbResource = createWbResource()
+        wbResource.sourcePath += 'x'
+
+        expect:
+        wbResource != createWbResource()
+    }
+
+    private WbResource createWbResource() {
+        return new WbResource("/", "src/main/webapp")
+    }
+
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/WtpTest.groovy b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/WtpTest.groovy
new file mode 100644
index 0000000..d21884b
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/groovy/org/gradle/plugins/eclipse/model/WtpTest.groovy
@@ -0,0 +1,216 @@
+/*
+ * 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.plugins.eclipse.model;
+
+
+import org.gradle.api.Action
+import org.gradle.listener.ListenerBroadcast
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Specification
+import org.gradle.plugins.eclipse.EclipseWtp
+
+/**
+ * @author Hans Dockter
+ */
+
+public class WtpTest extends Specification {
+    private static final List CUSTOM_WB_MODULE_ENTRIES = [
+            new WbProperty('context-root', 'recu'),
+            new WbDependentModule('/WEB-INF/lib', "module:/classpath/myapp-1.0.0.jar"),
+            new WbResource("/WEB-INF/classes", "src/main/java")]
+    private static final List CUSTOM_FACETS = [new Facet('jst.web', '2.4'), new Facet('jst.java', '1.4')]
+
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    def initWithReader() {
+        Wtp wtp = createWtp(componentReader: customComponentReader, facetReader: customFacetReader)
+
+        expect:
+        wtp.deployName == 'recu'
+        wtp.wbModuleEntries == CUSTOM_WB_MODULE_ENTRIES
+        wtp.facets == CUSTOM_FACETS
+    }
+
+    def initWithReaderAndValues_shouldBeMerged() {
+        def constructorDeployName = 'build'
+        def constructorWbModuleEntries = [createSomeWbModuleEntry()]
+        def constructorFacets = [createSomeFacet()]
+
+        Wtp wtp = createWtp(wbModuleEntries: constructorWbModuleEntries + [CUSTOM_WB_MODULE_ENTRIES[0]], facets: constructorFacets + [CUSTOM_FACETS[0]],
+                deployName: constructorDeployName, componentReader: customComponentReader, facetReader: customFacetReader)
+
+        expect:
+        wtp.wbModuleEntries == CUSTOM_WB_MODULE_ENTRIES + constructorWbModuleEntries
+        wtp.deployName == constructorDeployName
+        wtp.facets == CUSTOM_FACETS + constructorFacets
+    }
+
+    def initWithNullReader() {
+        def constructorDeployName = 'build'
+        def constructorWbModuleEntries = [createSomeWbModuleEntry()]
+        def constructorFacets = [createSomeFacet()]
+
+        Wtp wtp = createWtp(wbModuleEntries: constructorWbModuleEntries, facets: constructorFacets,
+                deployName: constructorDeployName)
+
+        expect:
+        wtp.orgEclipseWstCommonComponentXml != null
+        wtp.orgEclipseWstCommonProjectFacetCoreXml != null
+        wtp.wbModuleEntries == constructorWbModuleEntries
+        wtp.deployName == constructorDeployName
+        wtp.facets == constructorFacets
+    }
+
+    def toXml() {
+        when:
+        Wtp wtp = createWtp(componentReader: customComponentReader, facetReader: customFacetReader)
+
+        then:
+        File componentFile = tmpDir.file("component.xml")
+        File facetFile = tmpDir.file("facet.xml")
+        def (componentReader, facetReader) = getToXmlReaders(wtp)
+        wtp.toXml(componentFile, facetFile)
+        assertXmlIsWritten(componentFile, wtp.orgEclipseWstCommonComponentXml, componentReader)
+        assertXmlIsWritten(facetFile, wtp.orgEclipseWstCommonProjectFacetCoreXml, facetReader)
+    }
+
+    void assertXmlIsWritten(TestFile file, Node xml, Reader reader) {
+        StringWriter stringWriterFileXml = new StringWriter()
+        new XmlNodePrinter(new PrintWriter(stringWriterFileXml)).print(new XmlParser().parse(file))
+        StringWriter stringWriterWrittenXml = new StringWriter()
+        new XmlNodePrinter(new PrintWriter(stringWriterWrittenXml)).print(new XmlParser().parse(reader))
+        StringWriter stringWriterInternalXml = new StringWriter()
+        new XmlNodePrinter(new PrintWriter(stringWriterInternalXml)).print(xml)
+
+        assert stringWriterWrittenXml.toString() == stringWriterInternalXml.toString()
+        assert stringWriterWrittenXml.toString() == stringWriterFileXml.toString()
+    }
+
+    def toXml_shouldContainCustomValues() {
+        def constructorDeployName = 'build'
+        def constructorWbModuleEntries = [createSomeWbModuleEntry()]
+        def constructorFacets = [createSomeFacet()]
+
+        Wtp wtp = createWtp(wbModuleEntries: constructorWbModuleEntries, facets: constructorFacets,
+                deployName: constructorDeployName, componentReader: customComponentReader, facetReader: customFacetReader)
+        def (componentReader, facetReader) = getToXmlReaders(wtp)
+
+        when:
+        def wtpFromXml = createWtp(componentReader: componentReader, facetReader: facetReader)
+
+        then:
+        wtp == wtpFromXml
+    }
+
+    def beforeConfigured() {
+        def constructorWbModuleEntries = [createSomeWbModuleEntry()]
+        def constructorFacets = [createSomeFacet()]
+        ListenerBroadcast beforeConfiguredActions = new ListenerBroadcast(Action)
+        beforeConfiguredActions.add("execute") { Wtp wtp ->
+            wtp.wbModuleEntries.clear()
+            wtp.facets.clear()
+        }
+
+        when:
+        Wtp wtp = createWtp(wbModuleEntries: constructorWbModuleEntries, facets: constructorFacets,
+                componentReader: customComponentReader,
+                facetReader: customFacetReader,
+                beforeConfiguredActions: beforeConfiguredActions)
+        def (componentReader, facetReader) = getToXmlReaders(wtp)
+        def wtpFromXml = createWtp(componentReader: componentReader, facetReader: facetReader)
+
+        then:
+        wtpFromXml.wbModuleEntries == constructorWbModuleEntries
+        wtpFromXml.facets == constructorFacets
+    }
+
+    def whenConfigured() {
+        def constructorWbModuleEntry = createSomeWbModuleEntry()
+        def configureActionWbModuleEntry = createSomeWbModuleEntry()
+        configureActionWbModuleEntry.name = configureActionWbModuleEntry.name + 'Other'
+
+        ListenerBroadcast whenConfiguredActions = new ListenerBroadcast(Action)
+        whenConfiguredActions.add("execute") { Wtp wtp ->
+            assert wtp.wbModuleEntries.contains(CUSTOM_WB_MODULE_ENTRIES[0])
+            assert wtp.wbModuleEntries.contains(constructorWbModuleEntry)
+            wtp.wbModuleEntries.add(configureActionWbModuleEntry)
+        }
+
+        when:
+        Wtp wtp = createWtp(wbModuleEntries: [constructorWbModuleEntry], componentReader: customComponentReader,
+                facetReader: customFacetReader, whenConfiguredActions: whenConfiguredActions)
+        def (componentReader, facetReader) = getToXmlReaders(wtp)
+        then:
+        createWtp(componentReader: componentReader, facetReader: facetReader).wbModuleEntries == CUSTOM_WB_MODULE_ENTRIES +
+                ([constructorWbModuleEntry, configureActionWbModuleEntry])
+    }
+
+    def withXml() {
+        ListenerBroadcast withXmlActions = new ListenerBroadcast(Action)
+        Wtp wtp = createWtp(componentReader: customComponentReader,
+                facetReader: customFacetReader, withXmlActions: withXmlActions)
+
+        when:
+        withXmlActions.add("execute") { xmls ->
+            xmls['org.eclipse.wst.commons.component'].'wb-module'.property.find { it. at name == 'context-root' }. at value = 'newValue'
+            xmls['org.eclipse.wst.commons.project.facet.core'].installed.find { it. at facet == 'jst.java' }. at version = '-5x'
+        }
+        def (componentReader, facetReader) = getToXmlReaders(wtp)
+
+        then:
+        new XmlParser().parse(componentReader).'wb-module'.property.find { it. at name == 'context-root' }. at value == 'newValue'
+        new XmlParser().parse(facetReader).installed.find { it. at facet == 'jst.java' }. at version == '-5x'
+    }
+
+    private InputStreamReader getCustomComponentReader() {
+        return new InputStreamReader(getClass().getResourceAsStream('customOrgEclipseWstCommonComponent.xml'))
+    }
+
+    private InputStreamReader getCustomFacetReader() {
+        return new InputStreamReader(getClass().getResourceAsStream('customOrgEclipseWstCommonProjectFacetCoreXml.xml'))
+    }
+
+    private WbProperty createSomeWbModuleEntry() {
+        return new WbProperty('someProp', 'someValue')
+    }
+
+    private Facet createSomeFacet() {
+        return new Facet('someName', '1.0.0')
+    }
+
+    private Wtp createWtp(Map customArgs) {
+        ListenerBroadcast dummyBroadcast = new ListenerBroadcast(Action)
+        Map args = [wbModuleEntries: [], facets: [], deployName: null, defaultOutput: null, componentReader: null,
+                facetReader: null, beforeConfiguredActions: dummyBroadcast, whenConfiguredActions: dummyBroadcast, withXmlActions: dummyBroadcast] + customArgs
+        EclipseWtp eclipseWtpStub = Mock()
+        eclipseWtpStub.getBeforeConfiguredActions() >> args.beforeConfiguredActions
+        eclipseWtpStub.getWhenConfiguredActions() >> args.whenConfiguredActions
+        eclipseWtpStub.getWithXmlActions() >> args.withXmlActions
+        eclipseWtpStub.getDeployName() >> args.deployName
+        eclipseWtpStub.getFacets() >> args.facets
+        return new Wtp(eclipseWtpStub, args.wbModuleEntries, args.componentReader, args.facetReader)
+    }
+
+    private def getToXmlReaders(Wtp wtp) {
+        StringWriter toComponentXmlText = new StringWriter()
+        StringWriter toFacetXmlText = new StringWriter()
+        wtp.toXml(toComponentXmlText, toFacetXmlText)
+        [new StringReader(toComponentXmlText.toString()), new StringReader(toFacetXmlText.toString())]
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/src/test/resources/org/gradle/plugins/eclipse/model/customClasspath.xml b/subprojects/gradle-eclipse/src/test/resources/org/gradle/plugins/eclipse/model/customClasspath.xml
new file mode 100644
index 0000000..4556110
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/resources/org/gradle/plugins/eclipse/model/customClasspath.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="/test2"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+	<classpathentry kind="lib" path="/apache-ant-1.7.1/lib/ant-antlr.jar"/>
+	<classpathentry kind="src" output="bin2" path="src"/>
+	<classpathentry kind="var" path="GRADLE_CACHE/ant-1.6.5.jar"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.USER_LIBRARY/gradle"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/subprojects/gradle-eclipse/src/test/resources/org/gradle/plugins/eclipse/model/customOrgEclipseWstCommonComponent.xml b/subprojects/gradle-eclipse/src/test/resources/org/gradle/plugins/eclipse/model/customOrgEclipseWstCommonComponent.xml
new file mode 100644
index 0000000..65458c8
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/resources/org/gradle/plugins/eclipse/model/customOrgEclipseWstCommonComponent.xml
@@ -0,0 +1,9 @@
+<project-modules id="moduleCoreId" project-version="2.0">
+  <wb-module deploy-name="recu">
+    <property name="context-root" value="recu"/>
+    <dependent-module deploy-path="/WEB-INF/lib" handle="module:/classpath/myapp-1.0.0.jar">
+      <dependency-type>uses</dependency-type>
+    </dependent-module>
+    <wb-resource deploy-path="/WEB-INF/classes" source-path="src/main/java"/>
+  </wb-module>
+</project-modules>
diff --git a/subprojects/gradle-eclipse/src/test/resources/org/gradle/plugins/eclipse/model/customOrgEclipseWstCommonProjectFacetCoreXml.xml b/subprojects/gradle-eclipse/src/test/resources/org/gradle/plugins/eclipse/model/customOrgEclipseWstCommonProjectFacetCoreXml.xml
new file mode 100644
index 0000000..899cd0e
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/resources/org/gradle/plugins/eclipse/model/customOrgEclipseWstCommonProjectFacetCoreXml.xml
@@ -0,0 +1,6 @@
+<faceted-project>
+  <fixed facet="jst.java"/>
+  <fixed facet="jst.web"/>
+  <installed facet="jst.web" version="2.4"/>
+  <installed facet="jst.java" version="1.4"/>
+</faceted-project>
\ No newline at end of file
diff --git a/subprojects/gradle-eclipse/src/test/resources/org/gradle/plugins/eclipse/model/customProject.xml b/subprojects/gradle-eclipse/src/test/resources/org/gradle/plugins/eclipse/model/customProject.xml
new file mode 100644
index 0000000..0f0facc
--- /dev/null
+++ b/subprojects/gradle-eclipse/src/test/resources/org/gradle/plugins/eclipse/model/customProject.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+    <name>test</name>
+    <comment>for testing</comment> 
+    <projects>
+        <project>refProject</project>
+    </projects>
+    <buildSpec>
+        <buildCommand>
+            <name>org.eclipse.jdt.core.scalabuilder</name>
+            <arguments>
+                <dictionary>
+                    <key>climate</key>
+                    <value>cold</value>
+                </dictionary>
+            </arguments>
+        </buildCommand>
+    </buildSpec>
+    <natures>
+        <nature>org.eclipse.jdt.core.scalanature</nature>
+    </natures>
+    <links>
+        <link>
+            <name>somename</name>
+            <type>sometype</type>
+            <location>somelocation</location>
+        </link>
+    </links>
+</projectDescription>
\ No newline at end of file
diff --git a/subprojects/gradle-idea/idea.gradle b/subprojects/gradle-idea/idea.gradle
new file mode 100644
index 0000000..b70403c
--- /dev/null
+++ b/subprojects/gradle-idea/idea.gradle
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+dependencies {
+    groovy libraries.groovy_depends
+
+    compile project(':core')
+    compile project(':plugins')
+    compile libraries.slf4j_api
+
+    testCompile project(path: ':core', configuration: 'testFixtures')
+    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+}
+
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaModule.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaModule.groovy
new file mode 100644
index 0000000..6fc247c
--- /dev/null
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaModule.groovy
@@ -0,0 +1,303 @@
+/*
+ * 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.plugins.idea
+
+import org.gradle.api.Action
+import org.gradle.api.internal.ConventionTask
+import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
+import org.gradle.api.specs.Specs
+import org.gradle.listener.ListenerBroadcast
+import org.gradle.api.artifacts.*
+import org.gradle.api.tasks.*
+import org.gradle.plugins.idea.model.*
+
+/**
+ * @author Hans Dockter
+ */
+public class IdeaModule extends ConventionTask {
+    /**
+     * The content root directory of the module. Must not be null.
+     */
+    @InputFiles
+    File moduleDir
+
+    /**
+     * The iml file. Used to look for existing files as well as the target for generation. Must not be null. 
+     */
+    @OutputFile
+    File outputFile
+
+    /**
+     * The dirs containing the production sources. Must not be null.
+     */
+    @InputFiles
+    Set sourceDirs
+
+    /**
+     * The dirs containing the test sources. Must not be null.
+     */
+    @InputFiles
+    Set testSourceDirs
+
+    /**
+     * The dirs to be excluded by idea. Must not be null.
+     */
+    @InputFiles
+    Set excludeDirs
+
+    /**
+     * The idea output dir for the production sources. If null no entry for output dirs is created.
+     */
+    @InputFiles @Optional
+    File outputDir
+
+    /**
+     * The idea output dir for the test sources. If null no entry for test output dirs is created.
+     */
+    @InputFiles @Optional
+    File testOutputDir
+
+    /**
+     * If this is null the value of the existing or default ipr XML (inherited) is used. If it is set
+     * to <code>inherited</code>, the project SDK is used. Otherwise the SDK for the corresponding
+     * value of java version is used for this module
+     */
+    @Input @Optional
+    String javaVersion = org.gradle.plugins.idea.model.Module.INHERITED
+
+    /**
+     * Whether to download and add sources associated with the dependency jars. Defaults to true. 
+     */
+    @Input
+    boolean downloadSources = true
+
+    /**
+     * Whether to download and add javadoc associated with the dependency jars. Defaults to false.
+     */
+    @Input
+    boolean downloadJavadoc = false
+
+    /**
+     * If this variable is set, dependencies in the existing iml file will be parsed for this variable.
+     * If they use it, it will be replaced with a path that has the $MODULE_DIR$ variable as a root and
+     * then a relative path to  {@link #gradleCacheHome} . That way Gradle can recognize equal dependencies.
+     */
+    @Input @Optional
+    String gradleCacheVariable
+
+    /**
+     * This variable is used in conjunction with the {@link #gradleCacheVariable}.
+     */
+    @InputFiles @Optional
+    File gradleCacheHome
+                                 
+    /**
+     * The keys of this map are the Intellij scopes. Each key points to another map that has two keys, plus and minus.
+     * The values of those keys are sets of  {@link org.gradle.api.artifacts.Configuration}  objects. The files of the
+     * plus configurations are added minus the files from the minus configurations.
+     */
+    Map scopes = [:]
+
+    private ListenerBroadcast<Action> beforeConfiguredActions = new ListenerBroadcast<Action>(Action.class);
+    private ListenerBroadcast<Action> whenConfiguredActions = new ListenerBroadcast<Action>(Action.class);
+    private ListenerBroadcast<Action> withXmlActions = new ListenerBroadcast<Action>(Action.class);
+
+    def IdeaModule() {
+        outputs.upToDateWhen { false }
+    }
+
+    @TaskAction
+    void updateXML() {
+        Reader xmlreader = getOutputFile().exists() ? new FileReader(getOutputFile()) : null;
+        org.gradle.plugins.idea.model.Module module = new org.gradle.plugins.idea.model.Module(getContentPath(), getSourcePaths(), getTestSourcePaths(), getExcludePaths(), getOutputPath(), getTestOutputPath(),
+                getDependencies(), getVariableReplacement(), javaVersion, xmlreader, beforeConfiguredActions, whenConfiguredActions, withXmlActions)
+        module.toXml(new FileWriter(getOutputFile()))
+    }
+
+    protected Path getContentPath() {
+       getPath(project.projectDir)
+    }
+
+    protected Path getOutputPath() {
+        getOutputDir() ? getPath(getOutputDir()) : null
+    }
+
+    protected Path getTestOutputPath() {
+        getTestOutputDir() ? getPath(getTestOutputDir()) : null
+    }
+
+    protected Set getSourcePaths() {
+        getSourceDirs().collect { getPath(it) }
+    }
+
+    protected Set getTestSourcePaths() {
+        getTestSourceDirs().collect { getPath(it) }
+    }
+
+    protected Set getExcludePaths() {
+
+        getExcludeDirs().collect { getPath(it) }
+    }
+
+    protected Set getDependencies() {
+        scopes.keySet().inject([] as LinkedHashSet) {result, scope ->
+            result + (getModuleLibraries(scope) + getModules(scope))
+        }
+    }
+
+    protected getVariableReplacement() {
+        if (getGradleCacheHome() && getGradleCacheVariable()) {
+            String replacer = org.gradle.plugins.idea.model.Path.getRelativePath(getOutputFile().parentFile, '$MODULE_DIR$', getGradleCacheHome())
+            return new VariableReplacement(replacer: replacer, replacable: '$' + getGradleCacheVariable() + '$')
+        }
+        return VariableReplacement.NO_REPLACEMENT
+    }
+
+    protected Set getModules(String scope) {
+        if (scopes[scope]) {
+            return getScopeDependencies(scopes[scope], { it instanceof ProjectDependency}).collect { ProjectDependency projectDependency ->
+                projectDependency.dependencyProject
+            }.collect { project ->
+                new org.gradle.plugins.idea.model.ModuleDependency(project.name, scope)
+            }
+        }
+        return []
+    }
+
+    protected Set getModuleLibraries(String scope) {
+        if (scopes[scope]) {
+            Set firstLevelDependencies = getScopeDependencies(scopes[scope], { it instanceof ExternalDependency})
+
+            ResolvedConfiguration resolvedConfiguration = project.configurations.detachedConfiguration((firstLevelDependencies as Dependency[])).resolvedConfiguration
+            def allResolvedDependencies = getAllDeps(resolvedConfiguration.firstLevelModuleDependencies)
+
+            Set sourceDependencies = getResolvableDependenciesForAllResolvedDependencies(allResolvedDependencies) { dependency ->
+                addSourceArtifact(dependency)
+            }
+            Map sourceFiles = downloadSources ? getFiles(sourceDependencies, "sources") : [:]
+
+            Set javadocDependencies = getResolvableDependenciesForAllResolvedDependencies(allResolvedDependencies) { dependency ->
+                addJavadocArtifact(dependency)
+            }
+            Map javadocFiles = downloadJavadoc ? getFiles(javadocDependencies, "javadoc") : [:]
+
+            List moduleLibraries = resolvedConfiguration.getFiles(Specs.SATISFIES_ALL).collect { File binaryFile ->
+                File sourceFile = sourceFiles[binaryFile.name]
+                File javadocFile = javadocFiles[binaryFile.name]
+                new ModuleLibrary([getPath(binaryFile)] as Set, javadocFile ? [getPath(javadocFile)] as Set : [] as Set, sourceFile ? [getPath(sourceFile)] as Set : [] as Set, [] as Set, scope)
+            }
+            moduleLibraries.addAll(getSelfResolvingFiles(getScopeDependencies(scopes[scope],
+                    { it instanceof SelfResolvingDependency && !(it instanceof ProjectDependency)}), scope))
+            return moduleLibraries as LinkedHashSet
+        }
+        return []
+    }
+
+    private def getSelfResolvingFiles(Collection dependencies, String scope) {
+        dependencies.inject([] as LinkedHashSet) { result, SelfResolvingDependency selfResolvingDependency ->
+            result.addAll(selfResolvingDependency.resolve().collect { File file ->
+                new ModuleLibrary([getPath(file)] as Set, [] as Set, [] as Set, [] as Set, scope)
+            })
+            result
+        }
+    }
+
+    private Set getScopeDependencies(Map configurations, Closure filter) {
+        Set firstLevelDependencies = new LinkedHashSet()
+        configurations.plus.each { configuration ->
+            firstLevelDependencies.addAll(configuration.getAllDependencies().findAll(filter))
+        }
+        configurations.minus.each { configuration ->
+            configuration.getAllDependencies().findAll(filter).each { minusDep ->
+                // This deals with dependencies that are defined in different scopes with different
+                // artifacts. Right now we accept the fact, that in such a situation some artifacts
+                // might be duplicated in Idea (they live in different scopes then). 
+                if (minusDep instanceof ExternalDependency) {
+                    ExternalDependency removeCandidate = firstLevelDependencies.find { it == minusDep }
+                    if (removeCandidate && removeCandidate.artifacts == minusDep.artifacts) {
+                        firstLevelDependencies.remove(removeCandidate)
+                    }
+                } else {
+                    firstLevelDependencies.remove(minusDep)
+                }
+            }
+        }
+        return firstLevelDependencies
+    }
+
+    private def getFiles(Set dependencies, String classifier) {
+        return project.configurations.detachedConfiguration((dependencies as Dependency[])).files.inject([:]) { result, sourceFile ->
+            String key = sourceFile.name.replace("-${classifier}.jar", '.jar')
+            result[key] = sourceFile
+            result
+        }
+    }
+
+    private List getResolvableDependenciesForAllResolvedDependencies(Set allResolvedDependencies, Closure configureClosure) {
+        return allResolvedDependencies.collect { ResolvedDependency resolvedDependency ->
+            def dependency = new DefaultExternalModuleDependency(resolvedDependency.moduleGroup, resolvedDependency.moduleName, resolvedDependency.moduleVersion,
+                    resolvedDependency.configuration)
+            dependency.transitive = false
+            configureClosure.call(dependency)
+            dependency
+        }
+    }
+
+    protected Set getAllDeps(Set deps) {
+        Set result = []
+        deps.each { ResolvedDependency resolvedDependency ->
+            if (resolvedDependency.children) {
+                result.addAll(getAllDeps(resolvedDependency.children))
+            }
+            result.add(resolvedDependency)
+        }
+        result
+    }
+
+    protected def addSourceArtifact(DefaultExternalModuleDependency dependency) {
+        dependency.artifact { artifact ->
+            artifact.name = dependency.name
+            artifact.type = 'source'
+            artifact.extension = 'jar'
+            artifact.classifier = 'sources'
+        }
+    }
+
+    protected def addJavadocArtifact(DefaultExternalModuleDependency dependency) {
+        dependency.artifact { artifact ->
+            artifact.name = dependency.name
+            artifact.type = 'javadoc'
+            artifact.extension = 'jar'
+            artifact.classifier = 'javadoc'
+        }
+    }
+
+    protected Path getPath(File file) {
+        new Path(getOutputFile().parentFile, '$MODULE_DIR$', file)
+    }
+
+    void withXml(Closure closure) {
+        withXmlActions.add("execute", closure);
+    }
+
+    void beforeConfigured(Closure closure) {
+        beforeConfiguredActions.add("execute", closure);
+    }
+
+    void whenConfigured(Closure closure) {
+        whenConfiguredActions.add("execute", closure);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaPlugin.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaPlugin.groovy
new file mode 100644
index 0000000..4590523
--- /dev/null
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaPlugin.groovy
@@ -0,0 +1,121 @@
+/*
+ * 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.plugins.idea;
+
+
+import org.gradle.api.JavaVersion
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaPlugin
+
+/**
+ * @author Hans Dockter
+ *
+ * When applied to a project, this plugin add one IdeaModule task. If the project is the root project, the plugin
+ * adds also an IdeaProject task.
+ *
+ * If the java plugin is or has been added to a project where this plugin is applied to, the IdeaModule task gets some
+ * Java specific configuration.
+ */
+class IdeaPlugin implements Plugin<Project> {
+    void apply(Project project) {
+        project.apply plugin: 'base' // We apply the base plugin to have the clean<taskname> rule
+        def task = project.task('cleanIdea')
+        task.description = 'Cleans IDEA project files (IML, IPR)'
+        task.group = 'IDE'
+        task = project.task('idea')
+        task.description = 'Generates IDEA project files (IML, IPR, IWS)'
+        task.group = 'IDE'
+        configureIdeaWorkspace(project)
+        configureIdeaProject(project)
+        configureIdeaModule(project)
+        configureForJavaPlugin(project)
+    }
+
+    private def configureIdeaWorkspace(Project project) {
+        if (isRoot(project)) {
+            project.task('ideaWorkspace', description: 'Generates an IDEA workspace file (IWS)', type: IdeaWorkspace) {
+                outputFile = new File(project.projectDir, project.name + ".iws")
+            }
+            project.idea.dependsOn 'ideaWorkspace'
+
+            project.cleanIdea.dependsOn "cleanIdeaWorkspace"
+        }
+    }
+
+    private def configureIdeaModule(Project project) {
+        project.task('ideaModule', description: 'Generates IDEA module files (IML)', type: IdeaModule) {
+            conventionMapping.outputFile = { new File(project.projectDir, project.name + ".iml") }
+            conventionMapping.moduleDir = { project.projectDir }
+            conventionMapping.sourceDirs = { [] as Set }
+            conventionMapping.excludeDirs = { [project.buildDir, project.file('.gradle')] as Set }
+            conventionMapping.testSourceDirs = { [] as Set }
+            conventionMapping.gradleCacheHome = { new File(project.gradle.gradleUserHomeDir, '/cache') }
+        }
+        project.idea.dependsOn 'ideaModule'
+
+        project.cleanIdea.dependsOn "cleanIdeaModule"
+    }
+
+    private def configureIdeaProject(Project project) {
+        if (isRoot(project)) {
+            project.task('ideaProject', description: 'Generates IDEA project file (IPR)', type: IdeaProject) {
+                outputFile = new File(project.projectDir, project.name + ".ipr")
+                subprojects = project.rootProject.allprojects
+                javaVersion = JavaVersion.VERSION_1_6.toString()
+                wildcards = ['!?*.java', '!?*.groovy']
+            }
+            project.idea.dependsOn 'ideaProject'
+
+            project.cleanIdea.dependsOn "cleanIdeaProject"
+        }
+    }
+
+    private def configureForJavaPlugin(Project project) {
+        project.plugins.withType(JavaPlugin).allPlugins {
+            configureIdeaProjectForJava(project)
+            configureIdeaModuleForJava(project)
+        }
+    }
+
+    private def configureIdeaProjectForJava(Project project) {
+        if (isRoot(project)) {
+            project.ideaProject {
+                javaVersion = project.sourceCompatibility
+            }
+        }
+    }
+
+    private def configureIdeaModuleForJava(Project project) {
+        project.ideaModule {
+            conventionMapping.sourceDirs = { project.sourceSets.main.allSource.sourceTrees.srcDirs.flatten() as Set }
+            conventionMapping.testSourceDirs = { project.sourceSets.test.allSource.sourceTrees.srcDirs.flatten() as Set }
+            conventionMapping.outputDir = { project.sourceSets.main.classesDir } 
+            conventionMapping.testOutputDir = { project.sourceSets.test.classesDir }
+            def configurations = project.configurations
+            scopes = [
+                    COMPILE: [plus: [configurations.compile], minus: []],
+                    RUNTIME: [plus: [configurations.runtime], minus: [configurations.compile]],
+                    TEST: [plus: [configurations.testRuntime], minus: [configurations.runtime]]
+            ]
+        }
+    }
+
+    private boolean isRoot(Project project) {
+        return project.parent == null
+    }
+}
+
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaProject.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaProject.groovy
new file mode 100644
index 0000000..5d6a0f1
--- /dev/null
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaProject.groovy
@@ -0,0 +1,130 @@
+/*
+ * 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.plugins.idea
+
+import org.gradle.api.DefaultTask
+
+import org.gradle.api.tasks.TaskAction
+
+import org.gradle.api.Action
+import org.gradle.listener.ListenerBroadcast
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.OutputFile
+import org.gradle.plugins.idea.model.Path
+import org.gradle.plugins.idea.model.Project
+import org.gradle.plugins.idea.model.ModulePath
+
+/**
+ * A task that generates and Idea ipr file.
+ *
+ * @author Hans Dockter
+ */
+public class IdeaProject extends DefaultTask {
+    /**
+     * The subprojects that should be mapped to modules in the ipr file. The subprojects will only be mapped, if the Idea plugin has been
+     * applied to them.
+     */
+    Set subprojects
+
+    /**
+     * The ipr file
+     */
+    @OutputFile
+    File outputFile
+
+    /**
+     * The java version used for defining the project sdk.
+     */
+    @Input
+    String javaVersion
+
+    /**
+     * The wildcard resource patterns. Must not be null.
+     */
+    @Input
+    Set wildcards
+
+    private ListenerBroadcast<Action> beforeConfiguredActions = new ListenerBroadcast<Action>(Action.class);
+    private ListenerBroadcast<Action> whenConfiguredActions = new ListenerBroadcast<Action>(Action.class);
+    private ListenerBroadcast<Action> withXmlActions = new ListenerBroadcast<Action>(Action.class);
+
+    def IdeaProject() {
+        outputs.upToDateWhen { false }
+    }
+
+    @TaskAction
+    void updateXML() {
+        Reader xmlreader = outputFile.exists() ? new FileReader(outputFile) : null;
+        Set modules = subprojects.collect { subproject ->
+            if (subproject.plugins.hasPlugin(IdeaPlugin)) {
+                File imlFile = subproject.ideaModule.outputFile
+                new ModulePath(outputFile.parentFile, '$PROJECT_DIR$', imlFile)
+            }
+        }
+        Project ideaProject = new Project(modules, javaVersion, wildcards, xmlreader, beforeConfiguredActions, whenConfiguredActions, withXmlActions)
+        ideaProject.toXml(new FileWriter(outputFile))
+    }
+
+    /**
+     * Returns a relative URL to the given file in the standard URL format used by IDEA.  The resulting
+     * URL is relative to the project output directory, using the $PROJECT_DIR$ macro.
+     * @param file The file to which the relative path should point.
+     * @return The relative URL as a String. 
+     */
+    String projectURL(File file) {
+       return new Path(outputFile.parentFile, '$PROJECT_DIR$', file).url
+    }
+
+    /**
+     * Adds a closure to be called when the ipr xml has been created. The xml is passed to the closure as a
+     * parameter in form of a {@link groovy.util.Node}. The xml might be modified.
+     *
+     * @param closure The closure to execute when the ipr xml has been created.
+     * @return this
+     */
+    IdeaProject withXml(Closure closure) {
+        withXmlActions.add("execute", closure);
+        return this;
+    }
+
+    /**
+     * Adds a closure to be called after the existing ipr xml or the default xml has been parsed. The information
+     * of this xml is used to populate the domain objects that model the customizable aspects of the ipr file.
+     * The closure is called before the parameter of this task are added to the domain objects. This hook allows you
+     * to do a partial clean for example. You can delete all modules from the existing xml while keeping all the other
+     * parts. The closure gets an instance of {@link org.gradle.plugins.idea.model.Project} which can be modified.
+     *
+     * @param closure The closure to execute when the existing or default ipr xml has been parsed.
+     * @return this
+     */
+    IdeaProject beforeConfigured(Closure closure) {
+        beforeConfiguredActions.add("execute", closure);
+        return this;
+    }
+
+    /**
+     * Adds a closure after the domain objects that model the customizable aspects of the ipr file are fully populated.
+     * Those objects are populated with the content of the existing or default ipr xml and the arguments of this task.
+     * The closure gets an instance of {@link Project} which can be modified.
+     *
+     * @param closure The closure to execute after the {@link org.gradle.plugins.idea.model.Project} object has been fully populated.
+     * @return this
+     */
+    IdeaProject whenConfigured(Closure closure) {
+        whenConfiguredActions.add("execute", closure);
+        return this;
+    }
+}
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaWorkspace.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaWorkspace.groovy
new file mode 100644
index 0000000..b9769aa
--- /dev/null
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/IdeaWorkspace.groovy
@@ -0,0 +1,51 @@
+/*
+ * 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.plugins.idea
+
+import org.gradle.api.Action
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.TaskAction
+import org.gradle.listener.ListenerBroadcast
+import org.gradle.plugins.idea.model.Workspace
+
+/**
+ * @author Hans Dockter
+ */
+public class IdeaWorkspace extends DefaultTask {
+    /**
+     * The iws file. Used to look for existing files as well as the target for generation. Must not be null.
+     */
+    @OutputFile
+    File outputFile
+
+    private ListenerBroadcast<Action> withXmlActions = new ListenerBroadcast<Action>(Action.class);
+
+    def IdeaWorkspace() {
+        outputs.upToDateWhen { false }
+    }
+
+    @TaskAction
+    void updateXML() {
+        Reader xmlreader = outputFile.exists() ? new FileReader(outputFile) : null;
+        Workspace workspace = new Workspace(xmlreader, withXmlActions)
+        workspace.toXml(new FileWriter(outputFile))
+    }
+
+    void withXml(Closure closure) {
+        withXmlActions.add("execute", closure);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Dependency.java b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Dependency.java
new file mode 100644
index 0000000..2b9ade1
--- /dev/null
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Dependency.java
@@ -0,0 +1,25 @@
+/*
+ * 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.plugins.idea.model;
+
+import groovy.util.Node;
+
+/**
+ * @author Hans Dockter
+ */
+public interface Dependency {
+    void addToNode(Node parentNode);
+}
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/JarDirectory.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/JarDirectory.groovy
new file mode 100644
index 0000000..6f52f97
--- /dev/null
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/JarDirectory.groovy
@@ -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.plugins.idea.model
+
+/**
+ * Represents a jar directory element of an idea module library.
+ *
+ * @author Hans Dockter
+ */
+class JarDirectory {
+    /**
+     * The path of the jar directory
+     */
+    Path path
+
+    /**
+     * The value for the recursive attribute of the jar directory element.
+     */
+    boolean recursive
+
+    def JarDirectory(path, recursive) {
+        this.path = path;
+        this.recursive = recursive;
+    }
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        JarDirectory that = (JarDirectory) o;
+
+        if (recursive != that.recursive) { return false }
+        if (path != that.path) { return false }             
+
+        return true;
+    }
+
+    int hashCode() {
+        int result;
+
+        result = path.hashCode();
+        result = 31 * result + (recursive ? 1 : 0);
+        return result;
+    }
+
+    public String toString() {
+        return "JarDirectory{" +
+                "path=" + path +
+                ", recursive=" + recursive +
+                '}';
+    }
+}
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Jdk.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Jdk.groovy
new file mode 100644
index 0000000..cd5342e
--- /dev/null
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Jdk.groovy
@@ -0,0 +1,93 @@
+/*
+ * 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.plugins.idea.model
+
+/**
+ * Represents a the information for the project JavaSDK. This information are attributes of the ProjectRootManager
+ * element in the ipr.
+ * 
+ * @author Hans Dockter
+ */
+class Jdk {
+    boolean assertKeyword
+    boolean jdk15 = false
+    String languageLevel
+    String projectJdkName
+
+    def Jdk(String javaVersion) {
+        if (javaVersion.startsWith("1.4")) {
+            assertKeyword = true
+            jdk15 = false
+            languageLevel = 'JDK_1_4'
+        }
+        else if (javaVersion.startsWith("1.5")) {
+            assertKeyword = true
+            jdk15 = true
+            languageLevel = 'JDK_1_5'
+        }
+        else if (javaVersion.compareTo("1.6") >= 0) {
+            assertKeyword = true
+            jdk15 = true
+            languageLevel = 'JDK_1_6'
+        }
+        else {
+            assertKeyword = false
+        }
+        projectJdkName = javaVersion
+    }
+
+    def Jdk(assertKeyword, jdk15, languageLevel, projectJdkName) {
+        this.assertKeyword = assertKeyword;
+        this.jdk15 = jdk15;
+        this.languageLevel = languageLevel
+        this.projectJdkName = projectJdkName;
+    }
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        Jdk jdk = (Jdk) o;
+
+        if (assertKeyword != jdk.assertKeyword) { return false }
+        if (jdk15 != jdk.jdk15) { return false }
+        if (languageLevel != jdk.languageLevel) { return false }
+        if (projectJdkName != jdk.projectJdkName) { return false }
+
+        return true;
+    }
+
+    int hashCode() {
+        int result;
+
+        result = 31 * result + (assertKeyword ? 1 : 0);
+        result = 31 * result + (jdk15 ? 1 : 0);
+        result = 31 * result + (languageLevel != null ? languageLevel.hashCode() : 0);
+        result = 31 * result + (projectJdkName != null ? projectJdkName.hashCode() : 0);
+        return result;
+    }
+
+
+    public String toString() {
+        return "Jdk{" +
+                "assertKeyword=" + assertKeyword +
+                ", jdk15=" + jdk15 +
+                ", languageLevel=" + languageLevel +
+                ", projectJdkName='" + projectJdkName + '\'' +
+                '}';
+    }
+}
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Module.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Module.groovy
new file mode 100644
index 0000000..94c8fed
--- /dev/null
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Module.groovy
@@ -0,0 +1,345 @@
+/*
+ * 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.plugins.idea.model
+
+import org.gradle.api.Action
+import org.gradle.listener.ListenerBroadcast
+
+/**
+ * Represents the customizable elements of an iml (via XML hooks everything of the iml is customizable).
+ *
+ * @author Hans Dockter
+ */
+class Module {
+    static final String INHERITED = "inherited"
+
+    /**
+     * The dir for the content root of the module.  Defaults to the projectDir for the project.  If null,
+     * the directory that contains the output file will be used.
+     */
+    Path contentPath;
+
+    /**
+     * The foldes for the production code. Must not be null.
+     */
+    Set sourceFolders = [] as LinkedHashSet
+
+    /**
+     * The folders for the test code. Must not be null.
+     */
+    Set testSourceFolders = [] as LinkedHashSet
+
+    /**
+     * Folders to be excluded. Must not be null.
+     */
+    Set excludeFolders = [] as LinkedHashSet
+
+    /**
+     * The dir for the production source classes. If null this output dir element is not added.
+     */
+    Path outputDir
+
+    /**
+     * The dir for the compiled test source classes. If null this output element is not added.
+     */
+    Path testOutputDir
+
+    /**
+     * The dependencies of this module. Must not be null. Has instances of type    {@link Dependency}   .
+     */
+    Set dependencies = [] as LinkedHashSet
+
+    String javaVersion
+
+    private Node xml
+
+    private ListenerBroadcast<Action> withXmlActions
+
+    def Module(Path contentPath, Set sourceFolders, Set testSourceFolders, Set excludeFolders, Path outputDir, Path testOutputDir, Set dependencies,
+               VariableReplacement dependencyVariableReplacement, String javaVersion, Reader inputXml,
+               ListenerBroadcast<Action> beforeConfiguredActions, ListenerBroadcast<Action> whenConfiguredActions,
+               ListenerBroadcast<Action> withXmlActions) {
+        initFromXml(inputXml, dependencyVariableReplacement)
+
+        beforeConfiguredActions.source.execute(this)
+
+        this.contentPath = contentPath
+        this.sourceFolders.addAll(sourceFolders);
+        this.testSourceFolders.addAll(testSourceFolders);
+        this.excludeFolders.addAll(excludeFolders);
+        if (outputDir) {
+            this.outputDir = outputDir
+        }
+        if (testOutputDir) {
+            this.testOutputDir = testOutputDir;
+        }
+        this.dependencies.addAll(dependencies);
+        if (javaVersion) {
+            this.javaVersion = javaVersion
+        }
+        this.withXmlActions = withXmlActions;
+
+        whenConfiguredActions.source.execute(this)
+    }
+
+    private def initFromXml(Reader inputXml, VariableReplacement dependencyVariableReplacement) {
+        Reader reader = inputXml ?: new InputStreamReader(getClass().getResourceAsStream('defaultModule.xml'))
+        xml = new XmlParser().parse(reader)
+        readJdkFromXml()
+        readSourceAndExcludeFolderFromXml()
+        readOutputDirsFromXml()
+        readDependenciesFromXml(dependencyVariableReplacement)
+    }
+
+    private def readJdkFromXml() {
+        def jdk = findOrderEntries().find { it. at type == 'jdk' }
+        if (jdk) {
+            this.javaVersion = jdk. at jdkName
+        } else {
+            this.javaVersion = INHERITED
+        }
+    }
+
+    private def readOutputDirsFromXml() {
+        def outputDirUrl = findOutputDir()?. at url
+        def testOutputDirUrl = findTestOutputDir()?. at url
+        this.outputDir = outputDirUrl ? new Path(outputDirUrl) : null
+        this.testOutputDir = testOutputDirUrl ? new Path(testOutputDirUrl) : null
+    }
+
+    private def readDependenciesFromXml(VariableReplacement dependencyVariableReplacement) {
+        return findOrderEntries().each { orderEntry ->
+            switch (orderEntry. at type) {
+                case "module-library":
+                    Set classes = orderEntry.library.CLASSES.root.collect {
+                        new Path(dependencyVariableReplacement.replace(it. at url))
+                    }
+                    Set javadoc = orderEntry.library.JAVADOC.root.collect {
+                        new Path(dependencyVariableReplacement.replace(it. at url))
+                    }
+                    Set sources = orderEntry.library.SOURCES.root.collect {
+                        new Path(dependencyVariableReplacement.replace(it. at url))
+                    }
+                    Set jarDirectories = orderEntry.library.jarDirectory.collect { new JarDirectory(new Path(it. at url), Boolean.parseBoolean(it. at recursive)) }
+                    def moduleLibrary = new ModuleLibrary(classes, javadoc, sources, jarDirectories, orderEntry. at scope)
+                    dependencies.add(moduleLibrary)
+                    break
+                case "module":
+                    dependencies.add(new ModuleDependency(orderEntry.@'module-name', orderEntry. at scope))
+            }
+        }
+    }
+
+    private def readSourceAndExcludeFolderFromXml() {
+        findSourceFolder().each { sourceFolder ->
+            if (sourceFolder. at isTestSource == 'false') {
+                this.sourceFolders.add(new Path(sourceFolder. at url))
+            } else {
+                this.testSourceFolders.add(new Path(sourceFolder. at url))
+            }
+        }
+        findExcludeFolder().each { excludeFolder ->
+            this.excludeFolders.add(new Path(excludeFolder. at url))
+        }
+    }
+
+    /**
+     * Generates the XML for the iml.
+     *
+     * @param writer The writer where the iml xml is generated into.
+     */
+    def toXml(Writer writer) {
+        addJdkToXml()
+        setContentURL()
+        removeSourceAndExcludeFolderFromXml()
+        addSourceAndExcludeFolderToXml()
+        addOutputDirsToXml()
+
+        removeDependenciesFromXml()
+        addDependenciesToXml()
+
+        withXmlActions.source.execute(xml)
+
+        new XmlNodePrinter(new PrintWriter(writer)).print(xml)
+    }
+
+    private def addJdkToXml() {
+        assert javaVersion != null
+        Node moduleJdk = findOrderEntries().find { it. at type == 'jdk' }
+        if (javaVersion != INHERITED) {
+            Node inheritedJdk = findOrderEntries().find { it. at type == "inheritedJdk" }
+            if (inheritedJdk) {
+                inheritedJdk.parent().remove(inheritedJdk)
+            }
+            if (moduleJdk) {
+                findNewModuleRootManager().remove(moduleJdk)
+            }
+            findNewModuleRootManager().appendNode("orderEntry", [type: "jdk", jdkName: javaVersion, jdkType: "JavaSDK"])
+        } else if (!(findOrderEntries().find { it. at type == "inheritedJdk" })) {
+            if (moduleJdk) {
+                findNewModuleRootManager().remove(moduleJdk)
+            }
+            findNewModuleRootManager().appendNode("orderEntry", [type: "inheritedJdk"])
+        }
+    }
+
+    private def setContentURL() {
+        if (contentPath != null) {
+            findContent(). at url = contentPath.url
+        }
+    }
+
+    private def addOutputDirsToXml() {
+        if (outputDir) {
+            findOrCreateOutputDir(). at url = outputDir.url
+        }
+        if (testOutputDir) {
+            findOrCreateTestOutputDir(). at url = testOutputDir.url
+        }
+    }
+
+    private Node findOrCreateOutputDir() {
+        return findOutputDir() ?: findNewModuleRootManager().appendNode("output")
+    }
+
+    private Node findOrCreateTestOutputDir() {
+        return findTestOutputDir() ?: findNewModuleRootManager().appendNode("output-test")
+    }
+
+    private Set addDependenciesToXml() {
+        return dependencies.each { Dependency dependency ->
+            dependency.addToNode(findNewModuleRootManager())
+        }
+    }
+
+    private def addSourceAndExcludeFolderToXml() {
+        sourceFolders.each { Path path ->
+            findContent().appendNode('sourceFolder', [url: path.url, isTestSource: 'false'])
+        }
+        testSourceFolders.each { Path path ->
+            findContent().appendNode('sourceFolder', [url: path.url, isTestSource: 'true'])
+        }
+        excludeFolders.each { Path path ->
+            findContent().appendNode('excludeFolder', [url: path.url])
+        }
+    }
+
+    private def removeSourceAndExcludeFolderFromXml() {
+        findSourceFolder().each { sourceFolder ->
+            findContent().remove(sourceFolder)
+        }
+        findExcludeFolder().each { excludeFolder ->
+            findContent().remove(excludeFolder)
+        }
+    }
+
+    private def removeDependenciesFromXml() {
+        return findOrderEntries().each { orderEntry ->
+            if (isDependencyOrderEntry(orderEntry)) {
+                findNewModuleRootManager().remove(orderEntry)
+            }
+        }
+    }
+
+    protected boolean isDependencyOrderEntry(def orderEntry) {
+        ['module-library', 'module'].contains(orderEntry. at type)
+    }
+
+    private Node findContent() {
+        findNewModuleRootManager().content[0]
+    }
+
+    private def findSourceFolder() {
+        return findContent().sourceFolder
+    }
+
+    private def findExcludeFolder() {
+        return findContent().excludeFolder
+    }
+
+    private Node findOutputDir() {
+        return findNewModuleRootManager().output[0]
+    }
+
+    private Node findNewModuleRootManager() {
+        return xml.component.find { it. at name == 'NewModuleRootManager'}
+    }
+
+    private Node findTestOutputDir() {
+        return findNewModuleRootManager().'output-test'[0]
+    }
+
+    private def findOrderEntries() {
+        return findNewModuleRootManager().orderEntry
+    }
+
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        Module module = (Module) o;
+
+        if (dependencies != module.dependencies) { return false }
+        if (excludeFolders != module.excludeFolders) { return false }
+        if (outputDir != module.outputDir) { return false }
+        if (sourceFolders != module.sourceFolders) { return false }
+        if (testOutputDir != module.testOutputDir) { return false }
+        if (testSourceFolders != module.testSourceFolders) { return false }
+
+        return true;
+    }
+
+    int hashCode() {
+        int result;
+
+        result = (sourceFolders != null ? sourceFolders.hashCode() : 0);
+        result = 31 * result + (testSourceFolders != null ? testSourceFolders.hashCode() : 0);
+        result = 31 * result + (excludeFolders != null ? excludeFolders.hashCode() : 0);
+        result = 31 * result + outputDir.hashCode();
+        result = 31 * result + testOutputDir.hashCode();
+        result = 31 * result + (dependencies != null ? dependencies.hashCode() : 0);
+        return result;
+    }
+
+
+    public String toString() {
+        return "Module{" +
+                "dependencies=" + dependencies +
+                ", sourceFolders=" + sourceFolders +
+                ", testSourceFolders=" + testSourceFolders +
+                ", excludeFolders=" + excludeFolders +
+                ", outputDir=" + outputDir +
+                ", testOutputDir=" + testOutputDir +
+                '}';
+    }
+}
+
+// todo make this an inner class once codenarc understands groovy inner classes
+public class VariableReplacement {
+    public static final VariableReplacement NO_REPLACEMENT = new VariableReplacement()
+
+    String replacable
+    String replacer
+
+    String replace(String source) {
+        if (replacable && replacer != null) {
+            return source.replace(replacable, replacer)
+        }
+        return source
+    }
+}
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/ModuleDependency.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/ModuleDependency.groovy
new file mode 100644
index 0000000..32da7d1
--- /dev/null
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/ModuleDependency.groovy
@@ -0,0 +1,91 @@
+/*
+ * 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.plugins.idea.model
+
+/**
+ * Represents an orderEntry of type module in the iml xml.
+ *
+ * @author Hans Dockter
+ */
+class ModuleDependency implements Dependency {
+    /**
+     * The name of the module the module depends on. Must not be null.
+     */
+    String name
+
+    /**
+     * The scope for this dependency. If null the scope attribute is not added.
+     */
+    String scope
+
+    boolean exported
+
+    def ModuleDependency(name, scope) {
+        this.name = name;
+        this.scope = scope;
+        this.exported = !scope || scope == 'COMPILE' || scope == 'RUNTIME'
+    }
+
+    void addToNode(Node parentNode) {
+        parentNode.appendNode('orderEntry', [type: 'module', 'module-name': name] + getAttributeMapForScopeAndExported())
+    }
+
+    private Map getAttributeMapForScopeAndExported() {
+        return (exported ? [exported: ""] : [:]) + ((scope && scope != 'COMPILE') ? [scope: scope] : [:])
+    }
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        ModuleDependency that = (ModuleDependency) o;
+
+        if (name != that.name) { return false }
+        if (!scopeEquals(scope, that.scope)) { return false }
+
+        return true;
+    }
+
+    private boolean scopeEquals(String lhs, String rhs) {
+        if (lhs == 'COMPILE') {
+            return !rhs || rhs == 'COMPILE'
+        } else if (rhs == 'COMPILE') {
+            return !lhs || lhs == 'COMPILE'
+        } else {
+            return lhs == rhs
+        }
+    }
+
+    int hashCode() {
+        int result;
+
+        result = name.hashCode();
+        result = 31 * result + getScopeHash();
+        return result;
+    }
+
+    private int getScopeHash() {
+        (scope && scope != 'COMPILE' ? scope.hashCode() : 0)
+    }
+
+    public String toString() {
+        return "ModuleDependency{" +
+                "name='" + name + '\'' +
+                ", scope='" + scope + '\'' +
+                '}';
+    }
+}
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/ModuleLibrary.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/ModuleLibrary.groovy
new file mode 100644
index 0000000..d6e1b00
--- /dev/null
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/ModuleLibrary.groovy
@@ -0,0 +1,135 @@
+/*
+ * 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.plugins.idea.model
+
+/**
+ * Represents an orderEntry of type module-library in the iml xml.
+ *
+ * @author Hans Dockter
+ */
+class ModuleLibrary implements Dependency {
+    /**
+     * A set of  {@link org.gradle.plugins.idea.model.Path}  instances for class libraries.
+     */
+    Set classes
+
+    /**
+     * A set of  {@link org.gradle.plugins.idea.model.JarDirectory}  instances for directories containing jars.
+     */
+    Set jarDirectories
+
+    /**
+     * A set of  {@link org.gradle.plugins.idea.model.Path}  instances for javadoc associated with the library elements.
+     */
+    Set javadoc
+
+    /**
+     * A set of  {@link org.gradle.plugins.idea.model.Path}  instances for source code associated with the library elements.
+     */
+    Set sources
+
+    /**
+     * The scope of this dependency. If null the scope attribute is not added.
+     */
+    String scope
+
+    boolean exported
+
+    def ModuleLibrary(classes, javadoc, sources, jarDirectories, scope) {
+        this.classes = classes;
+        this.jarDirectories = jarDirectories;
+        this.javadoc = javadoc;
+        this.sources = sources;
+        this.scope = scope
+        this.exported = !scope || scope == 'COMPILE' || scope == 'RUNTIME'
+    }
+
+    void addToNode(Node parentNode) {
+        Node libraryNode = parentNode.appendNode('orderEntry', [type: 'module-library'] + getAttributeMapForScopeAndExported()).appendNode('library')
+        Node classesNode = libraryNode.appendNode('CLASSES')
+        Node javadocNode = libraryNode.appendNode('JAVADOC')
+        Node sourcesNode = libraryNode.appendNode('SOURCES')
+        classes.each { Path path ->
+            classesNode.appendNode('root', [url: path.url])
+        }
+        javadoc.each { Path path ->
+            javadocNode.appendNode('root', [url: path.url])
+        }
+        sources.each { Path path ->
+            sourcesNode.appendNode('root', [url: path.url])
+        }
+        jarDirectories.each { JarDirectory jarDirectory ->
+            libraryNode.appendNode('jarDirectory', [url: jarDirectory.path.url, recursive: jarDirectory.recursive])
+        }
+    }
+
+    private Map getAttributeMapForScopeAndExported() {
+        return (exported ? [exported: ""] : [:]) + ((scope && scope != 'COMPILE') ? [scope: scope] : [:])
+    }
+
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        ModuleLibrary that = (ModuleLibrary) o;
+
+        if (classes != that.classes) { return false }
+        if (jarDirectories != that.jarDirectories) { return false }
+        if (javadoc != that.javadoc) { return false }
+        if (!scopeEquals(scope, that.scope)) { return false }
+        if (sources != that.sources) { return false }
+
+        return true;
+    }
+
+    private boolean scopeEquals(String lhs, String rhs) {
+        if (lhs == 'COMPILE') {
+            return !rhs || rhs == 'COMPILE'
+        } else if (rhs == 'COMPILE') {
+            return !lhs || lhs == 'COMPILE'
+        } else {
+            return lhs == rhs
+        }
+    }
+
+
+    int hashCode() {
+        int result;
+
+        result = classes.hashCode();
+        result = 31 * result + jarDirectories.hashCode();
+        result = 31 * result + javadoc.hashCode();
+        result = 31 * result + sources.hashCode();
+        result = 31 * result + getScopeHash()
+        return result;
+    }
+
+    private int getScopeHash() {
+        (scope && scope != 'COMPILE' ? scope.hashCode() : 0)
+    }
+
+    public String toString() {
+        return "ModuleLibrary{" +
+                "classes=" + classes +
+                ", jarDirectories=" + jarDirectories +
+                ", javadoc=" + javadoc +
+                ", sources=" + sources +
+                ", scope='" + scope + '\'' +
+                '}';
+    }
+}
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/ModulePath.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/ModulePath.groovy
new file mode 100644
index 0000000..fdf75aa
--- /dev/null
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/ModulePath.groovy
@@ -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.plugins.idea.model
+
+/**
+ * Represents a path in a format as used often in ipr and iml files.
+ *
+ * @author Hans Dockter
+ */
+
+class ModulePath extends Path {
+    /**
+     * The path string of this path.
+     */
+    String path
+
+    def ModulePath(rootDir, rootDirString, file) {
+        super(rootDir, rootDirString, file)
+        path = getRelativePath(rootDir, rootDirString, file)
+    }
+
+    def ModulePath(url) {
+        super(url)
+    }
+
+    def ModulePath(url, path) {
+        super(url)
+        assert path != null
+        this.path = path;
+    }
+
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+        if (!super.equals(o)) { return false }
+
+        ModulePath that = (ModulePath) o;
+
+        if (path != that.path) { return false }
+
+        return true;
+    }
+
+    int hashCode() {
+        int result = super.hashCode();
+
+        result = 31 * result + path.hashCode();
+        return result;
+    }                                
+
+
+    public String toString() {
+        return "ModulePath{" +
+                "url='" + url +
+                ", path='" + path + '\'' +
+                '}';
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Path.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Path.groovy
new file mode 100644
index 0000000..ef7979a
--- /dev/null
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Path.groovy
@@ -0,0 +1,117 @@
+/*
+ * 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.plugins.idea.model
+
+/**
+ * Represents a path in a format as used often in ipr and iml files.
+ *
+ * @author Hans Dockter
+ */
+class Path {
+    /**
+     * The url of the path. Must not be null
+     */
+    String url
+
+    def Path(rootDir, rootDirString, file) {
+        String path = getRelativePath(rootDir, rootDirString, file)
+        url = relativePathToURI(path)
+    }
+
+    def Path(url) {
+        this.url = url
+    }
+
+    public static String getRelativePath(File rootDir, String rootDirString, File file) {
+        String relpath = getRelativePath(rootDir, file)
+        return rootDirString + '/' + relpath
+    }
+
+    public static String relativePathToURI(String relpath) {
+        if (relpath.endsWith('.jar')) {
+            return 'jar://' + relpath + '!/';
+        } else {
+            return 'file://' + relpath;
+        }
+    }
+
+    // This gets a relative path even if neither path is an ancestor of the other.
+    // implemenation taken from http://www.devx.com/tips/Tip/13737 and slighly modified
+    //@param relativeTo  the destinationFile
+    //@param fromFile    where the relative path starts
+
+    private static String getRelativePath(File relativeTo, File fromFile) {
+        return matchPathLists(getPathList(relativeTo), getPathList(fromFile))
+    }
+
+    private static List getPathList(File f) {
+        List list = []
+        File r = f.getCanonicalFile()
+        while (r != null) {
+            list.add(r.getName())
+            r = r.getParentFile()
+        }
+
+        return list
+    }
+
+    private static String matchPathLists(List r, List f) {
+        StringBuilder s = new StringBuilder();
+
+        // eliminate the common root
+        int i = r.size() - 1
+        int j = f.size() - 1
+        while ((i >= 0) && (j >= 0) && (r[i] == f[j])) {
+            i--
+            j--
+        }
+
+        // for each remaining level in the relativeTo path, add a ..
+        for (; i >= 0; i--) {
+            s.append('..').append('/')
+        }
+
+        // for each level in the file path, add the path
+        for (; j >= 1; j--) {
+            s.append(f[j]).append('/')
+        }
+
+        // add the file name and return the result
+        return s.append(f[j]).toString()
+    }
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        Path path = (Path) o;
+
+        if (url != path.url) { return false }
+
+        return true;
+    }
+
+    int hashCode() {
+        return url.hashCode();
+    }
+
+    public String toString() {
+        return "Path{" +
+                "url='" + url + '\'' +
+                '}';
+    }
+}
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Project.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Project.groovy
new file mode 100644
index 0000000..c002ede
--- /dev/null
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Project.groovy
@@ -0,0 +1,144 @@
+/*
+ * 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.plugins.idea.model
+
+import org.gradle.listener.ListenerBroadcast
+import org.gradle.api.Action
+
+/**
+ * Represents the customizable elements of an ipr (via XML hooks everything of the ipr is customizable).
+ *
+ * @author Hans Dockter
+ */
+class Project {
+    /**
+     * A set of {@link ModulePath} instances pointing to the modules contained in the ipr.
+     */
+    Set modulePaths = []
+
+    /**
+     * A set of wildcard string to be included/excluded from the resources.
+     */
+    Set wildcards = []
+
+    /**
+     * Represent the jdk information of the project java sdk.
+     */
+    Jdk jdk
+
+    private Node xml
+    
+    private ListenerBroadcast<Action> withXmlActions
+
+    def Project(Set modulePaths, String javaVersion, Set wildcards, Reader inputXml, ListenerBroadcast<Action> beforeConfiguredActions,
+                ListenerBroadcast<Action> whenConfiguredActions, ListenerBroadcast<Action> withXmlActions) {
+        initFromXml(inputXml, javaVersion)
+
+        beforeConfiguredActions.source.execute(this)
+
+        this.modulePaths.addAll(modulePaths)
+        this.wildcards.addAll(wildcards)
+        this.withXmlActions = withXmlActions
+
+        whenConfiguredActions.source.execute(this)
+    }
+
+    private def initFromXml(Reader inputXml, String javaVersion) {
+        Reader reader = inputXml ?: new InputStreamReader(getClass().getResourceAsStream('defaultProject.xml'))
+        xml = new XmlParser().parse(reader)
+
+        findModules().module.each { module ->
+            this.modulePaths.add(new ModulePath(module. at fileurl, module. at filepath))
+        }
+
+        findWildcardResourcePatterns().entry.each { entry ->
+            this.wildcards.add(entry. at name)
+        }
+        def jdkValues = findProjectRootManager().attributes()
+
+        if (javaVersion) {
+            jdk = new Jdk(javaVersion)
+        } else {
+            jdk = new Jdk(Boolean.parseBoolean(jdkValues.'assert-keyword'), Boolean.parseBoolean(jdkValues.'jdk-15'),
+                          jdkValues.languageLevel, jdkValues.'project-jdk-name')
+        }
+    }
+
+    def toXml(Writer writer) {
+        findModules().replaceNode {
+            modules {
+                modulePaths.each { ModulePath modulePath ->
+                    module(fileurl: modulePath.url, filepath: modulePath.path)
+                }
+            }
+        }
+        findWildcardResourcePatterns().replaceNode {
+            wildcardResourcePatterns {
+                this.wildcards.each { wildcard ->
+                    entry(name: wildcard)
+                }
+            }
+        }
+        findProjectRootManager().@'assert-keyword' = jdk.assertKeyword
+        findProjectRootManager().@'assert-jdk-15' = jdk.jdk15
+        findProjectRootManager(). at languageLevel = jdk.languageLevel
+        findProjectRootManager().@'project-jdk-name' = jdk.projectJdkName
+
+        withXmlActions.source.execute(xml)
+
+        new XmlNodePrinter(new PrintWriter(writer)).print(xml)
+    }
+
+    private def findProjectRootManager() {
+        return xml.component.find { it. at name == 'ProjectRootManager'}
+    }
+
+    private def findWildcardResourcePatterns() {
+        xml.component.find { it. at name == 'CompilerConfiguration'}.wildcardResourcePatterns
+    }
+
+    private def findModules() {
+        def moduleManager = xml.component.find { it. at name == 'ProjectModuleManager'}
+        if (!moduleManager.modules) {
+            moduleManager.appendNode('modules')
+        }
+        moduleManager.modules
+    }
+
+
+    boolean equals(o) {
+        if (this.is(o)) { return true }
+
+        if (getClass() != o.class) { return false }
+
+        Project project = (Project) o;
+
+        if (jdk != project.jdk) { return false }
+        if (modulePaths != project.modulePaths) { return false }
+        if (wildcards != project.wildcards) { return false }
+
+        return true;
+    }
+
+    int hashCode() {
+        int result;
+
+        result = (modulePaths != null ? modulePaths.hashCode() : 0);
+        result = 31 * result + (wildcards != null ? wildcards.hashCode() : 0);
+        result = 31 * result + (jdk != null ? jdk.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Workspace.groovy b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Workspace.groovy
new file mode 100644
index 0000000..ca61fda
--- /dev/null
+++ b/subprojects/gradle-idea/src/main/groovy/org/gradle/plugins/idea/model/Workspace.groovy
@@ -0,0 +1,48 @@
+/*
+ * 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.plugins.idea.model
+
+import org.gradle.api.Action
+import org.gradle.listener.ListenerBroadcast
+
+/**
+ * Represents the customizable elements of an ipr (via XML hooks everything of the ipr is customizable).
+ *
+ * @author Hans Dockter
+ */
+
+class Workspace {
+    private Node xml
+
+    private ListenerBroadcast<Action> withXmlActions
+
+    def Workspace(Reader inputXml, ListenerBroadcast<Action> withXmlActions) {
+        initFromXml(inputXml)
+
+        this.withXmlActions = withXmlActions
+    }
+
+    private def initFromXml(Reader inputXml) {
+        Reader reader = inputXml ?: new InputStreamReader(getClass().getResourceAsStream('defaultWorkspace.xml'))
+        xml = new XmlParser().parse(reader)
+    }
+
+    def toXml(Writer writer) {
+        withXmlActions.source.execute(xml)
+
+        new XmlNodePrinter(new PrintWriter(writer)).print(xml)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-idea/src/main/resources/META-INF/gradle-plugins/idea.properties b/subprojects/gradle-idea/src/main/resources/META-INF/gradle-plugins/idea.properties
new file mode 100644
index 0000000..7845246
--- /dev/null
+++ b/subprojects/gradle-idea/src/main/resources/META-INF/gradle-plugins/idea.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+implementation-class=org.gradle.plugins.idea.IdeaPlugin
diff --git a/subprojects/gradle-idea/src/main/resources/org/gradle/plugins/idea/model/defaultModule.xml b/subprojects/gradle-idea/src/main/resources/org/gradle/plugins/idea/model/defaultModule.xml
new file mode 100644
index 0000000..d89aa25
--- /dev/null
+++ b/subprojects/gradle-idea/src/main/resources/org/gradle/plugins/idea/model/defaultModule.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+    <component name="NewModuleRootManager">
+        <exclude-output/>
+        <orderEntry type="inheritedJdk"/>
+        <content url="file://$MODULE_DIR$">
+        </content>
+        <orderEntry type="sourceFolder" forTests="false"/>
+    </component>
+    <component name="ModuleRootManager"/>
+</module>
+
diff --git a/subprojects/gradle-idea/src/main/resources/org/gradle/plugins/idea/model/defaultProject.xml b/subprojects/gradle-idea/src/main/resources/org/gradle/plugins/idea/model/defaultProject.xml
new file mode 100644
index 0000000..eaf2ff0
--- /dev/null
+++ b/subprojects/gradle-idea/src/main/resources/org/gradle/plugins/idea/model/defaultProject.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+    <component name="CompilerConfiguration">
+        <option name="DEFAULT_COMPILER" value="Javac"/>
+        <resourceExtensions>
+            <entry name=".+\.(properties|xml|html|dtd|tld)"/>
+            <entry name=".+\.(gif|png|jpeg|jpg)"/>
+        </resourceExtensions>
+        <wildcardResourcePatterns>
+        </wildcardResourcePatterns>
+        <annotationProcessing enabled="false" useClasspath="true"/>
+    </component>
+    <component name="CopyrightManager" default="">
+        <module2copyright/>
+    </component>
+    <component name="DependencyValidationManager">
+        <option name="SKIP_IMPORT_STATEMENTS" value="false"/>
+    </component>
+    <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false"/>
+    <component name="GradleUISettings">
+        <setting name="root"/>
+    </component>
+    <component name="GradleUISettings2">
+        <setting name="root"/>
+    </component>
+    <component name="IdProvider" IDEtalkID="11DA1DB66DD62DDA1ED602B7079FE97C"/>
+    <component name="JavadocGenerationManager">
+        <option name="OUTPUT_DIRECTORY"/>
+        <option name="OPTION_SCOPE" value="protected"/>
+        <option name="OPTION_HIERARCHY" value="true"/>
+        <option name="OPTION_NAVIGATOR" value="true"/>
+        <option name="OPTION_INDEX" value="true"/>
+        <option name="OPTION_SEPARATE_INDEX" value="true"/>
+        <option name="OPTION_DOCUMENT_TAG_USE" value="false"/>
+        <option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false"/>
+        <option name="OPTION_DOCUMENT_TAG_VERSION" value="false"/>
+        <option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true"/>
+        <option name="OPTION_DEPRECATED_LIST" value="true"/>
+        <option name="OTHER_OPTIONS" value=""/>
+        <option name="HEAP_SIZE"/>
+        <option name="LOCALE"/>
+        <option name="OPEN_IN_BROWSER" value="true"/>
+    </component>
+    <component name="ProjectModuleManager">
+    </component>
+    <component name="ProjectRootManager" version="2" languageLevel="JDK_1_5" assert-keyword="true" jdk-15="true"
+               project-jdk-type="JavaSDK">
+        <output url="file://$PROJECT_DIR$/out"/>
+    </component>
+    <component name="SvnBranchConfigurationManager">
+        <option name="mySupportsUserInfoFilter" value="true"/>
+    </component>
+    <component name="VcsDirectoryMappings">
+        <mapping directory="" vcs=""/>
+    </component>
+    <component name="masterDetails">
+        <states>
+            <state key="ArtifactsStructureConfigurable.UI">
+                <UIState>
+                    <splitter-proportions>
+                        <SplitterProportionsDataImpl/>
+                    </splitter-proportions>
+                    <settings/>
+                </UIState>
+            </state>
+            <state key="Copyright.UI">
+                <UIState>
+                    <splitter-proportions>
+                        <SplitterProportionsDataImpl/>
+                    </splitter-proportions>
+                </UIState>
+            </state>
+            <state key="ProjectJDKs.UI">
+                <UIState>
+                    <splitter-proportions>
+                        <SplitterProportionsDataImpl>
+                            <option name="proportions">
+                                <list>
+                                    <option value="0.2"/>
+                                </list>
+                            </option>
+                        </SplitterProportionsDataImpl>
+                    </splitter-proportions>
+                    <last-edited>1.6</last-edited>
+                </UIState>
+            </state>
+            <state key="ScopeChooserConfigurable.UI">
+                <UIState>
+                    <splitter-proportions>
+                        <SplitterProportionsDataImpl/>
+                    </splitter-proportions>
+                    <settings/>
+                </UIState>
+            </state>
+        </states>
+    </component>
+</project>
diff --git a/subprojects/gradle-idea/src/main/resources/org/gradle/plugins/idea/model/defaultWorkspace.xml b/subprojects/gradle-idea/src/main/resources/org/gradle/plugins/idea/model/defaultWorkspace.xml
new file mode 100644
index 0000000..1d2f5fe
--- /dev/null
+++ b/subprojects/gradle-idea/src/main/resources/org/gradle/plugins/idea/model/defaultWorkspace.xml
@@ -0,0 +1,208 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ChangeListManager">
+    <option name="TRACKING_ENABLED" value="true" />
+    <option name="SHOW_DIALOG" value="false" />
+    <option name="HIGHLIGHT_CONFLICTS" value="true" />
+    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
+    <option name="LAST_RESOLUTION" value="IGNORE" />
+  </component>
+  <component name="ChangesViewManager" flattened_view="true" show_ignored="false" />
+  <component name="CreatePatchCommitExecutor">
+    <option name="PATCH_PATH" value="" />
+    <option name="REVERSE_PATCH" value="false" />
+  </component>
+  <component name="DaemonCodeAnalyzer">
+    <disable_hints />
+  </component>
+  <component name="DebuggerManager">
+    <breakpoint_any>
+      <breakpoint>
+        <option name="NOTIFY_CAUGHT" value="true" />
+        <option name="NOTIFY_UNCAUGHT" value="true" />
+        <option name="ENABLED" value="false" />
+        <option name="LOG_ENABLED" value="false" />
+        <option name="LOG_EXPRESSION_ENABLED" value="false" />
+        <option name="SUSPEND_POLICY" value="SuspendAll" />
+        <option name="COUNT_FILTER_ENABLED" value="false" />
+        <option name="COUNT_FILTER" value="0" />
+        <option name="CONDITION_ENABLED" value="false" />
+        <option name="CLASS_FILTERS_ENABLED" value="false" />
+        <option name="INSTANCE_FILTERS_ENABLED" value="false" />
+        <option name="CONDITION" value="" />
+        <option name="LOG_MESSAGE" value="" />
+      </breakpoint>
+      <breakpoint>
+        <option name="NOTIFY_CAUGHT" value="true" />
+        <option name="NOTIFY_UNCAUGHT" value="true" />
+        <option name="ENABLED" value="false" />
+        <option name="LOG_ENABLED" value="false" />
+        <option name="LOG_EXPRESSION_ENABLED" value="false" />
+        <option name="SUSPEND_POLICY" value="SuspendAll" />
+        <option name="COUNT_FILTER_ENABLED" value="false" />
+        <option name="COUNT_FILTER" value="0" />
+        <option name="CONDITION_ENABLED" value="false" />
+        <option name="CLASS_FILTERS_ENABLED" value="false" />
+        <option name="INSTANCE_FILTERS_ENABLED" value="false" />
+        <option name="CONDITION" value="" />
+        <option name="LOG_MESSAGE" value="" />
+      </breakpoint>
+    </breakpoint_any>
+    <breakpoint_rules />
+    <ui_properties />
+  </component>
+  <component name="ModuleEditorState">
+    <option name="LAST_EDITED_MODULE_NAME" />
+    <option name="LAST_EDITED_TAB_NAME" />
+  </component>
+  <component name="ProjectInspectionProfilesVisibleTreeState">
+    <entry key="Project Default">
+      <profile-state />
+    </entry>
+  </component>
+  <component name="ProjectLevelVcsManager">
+    <OptionsSetting value="true" id="Add" />
+    <OptionsSetting value="true" id="Remove" />
+    <OptionsSetting value="true" id="Checkout" />
+    <OptionsSetting value="true" id="Update" />
+    <OptionsSetting value="true" id="Status" />
+    <OptionsSetting value="true" id="Edit" />
+    <ConfirmationsSetting value="0" id="Add" />
+    <ConfirmationsSetting value="0" id="Remove" />
+  </component>
+  <component name="ProjectReloadState">
+    <option name="STATE" value="0" />
+  </component>
+  <component name="PropertiesComponent">
+    <property name="GoToFile.includeJavaFiles" value="false" />
+    <property name="GoToClass.toSaveIncludeLibraries" value="false" />
+    <property name="MemberChooser.sorted" value="false" />
+    <property name="MemberChooser.showClasses" value="true" />
+    <property name="GoToClass.includeLibraries" value="false" />
+    <property name="MemberChooser.copyJavadoc" value="false" />
+  </component>
+  <component name="RunManager">
+    <configuration default="true" type="Remote" factoryName="Remote">
+      <option name="USE_SOCKET_TRANSPORT" value="true" />
+      <option name="SERVER_MODE" value="false" />
+      <option name="SHMEM_ADDRESS" value="javadebug" />
+      <option name="HOST" value="localhost" />
+      <option name="PORT" value="5005" />
+      <method>
+        <option name="BuildArtifacts" enabled="false" />
+      </method>
+    </configuration>
+    <configuration default="true" type="Applet" factoryName="Applet">
+      <module name="" />
+      <option name="MAIN_CLASS_NAME" />
+      <option name="HTML_FILE_NAME" />
+      <option name="HTML_USED" value="false" />
+      <option name="WIDTH" value="400" />
+      <option name="HEIGHT" value="300" />
+      <option name="POLICY_FILE" value="$APPLICATION_HOME_DIR$/bin/appletviewer.policy" />
+      <option name="VM_PARAMETERS" />
+      <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+      <option name="ALTERNATIVE_JRE_PATH" />
+      <method>
+        <option name="BuildArtifacts" enabled="false" />
+        <option name="Make" enabled="true" />
+      </method>
+    </configuration>
+    <configuration default="true" type="Application" factoryName="Application">
+      <extension name="coverage" enabled="false" merge="false" />
+      <option name="MAIN_CLASS_NAME" />
+      <option name="VM_PARAMETERS" />
+      <option name="PROGRAM_PARAMETERS" />
+      <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
+      <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+      <option name="ALTERNATIVE_JRE_PATH" />
+      <option name="ENABLE_SWING_INSPECTOR" value="false" />
+      <option name="ENV_VARIABLES" />
+      <option name="PASS_PARENT_ENVS" value="true" />
+      <module name="" />
+      <envs />
+      <method>
+        <option name="BuildArtifacts" enabled="false" />
+        <option name="Make" enabled="true" />
+      </method>
+    </configuration>
+    <configuration default="true" type="JUnit" factoryName="JUnit">
+      <extension name="coverage" enabled="false" merge="false" />
+      <module name="" />
+      <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+      <option name="ALTERNATIVE_JRE_PATH" />
+      <option name="PACKAGE_NAME" />
+      <option name="MAIN_CLASS_NAME" />
+      <option name="METHOD_NAME" />
+      <option name="TEST_OBJECT" value="class" />
+      <option name="VM_PARAMETERS" />
+      <option name="PARAMETERS" />
+      <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
+      <option name="ENV_VARIABLES" />
+      <option name="PASS_PARENT_ENVS" value="true" />
+      <option name="TEST_SEARCH_SCOPE">
+        <value defaultName="moduleWithDependencies" />
+      </option>
+      <envs />
+      <method>
+        <option name="BuildArtifacts" enabled="false" />
+        <option name="Make" enabled="true" />
+      </method>
+    </configuration>
+    <list size="0" />
+    <configuration name="<template>" type="WebApp" default="true" selected="false">
+      <Host>localhost</Host>
+      <Port>5050</Port>
+    </configuration>
+  </component>
+  <component name="ShelveChangesManager" show_recycled="false" />
+  <component name="SvnConfiguration" maxAnnotateRevisions="500">
+    <option name="USER" value="" />
+    <option name="PASSWORD" value="" />
+    <option name="LAST_MERGED_REVISION" />
+    <option name="UPDATE_RUN_STATUS" value="false" />
+    <option name="MERGE_DRY_RUN" value="false" />
+    <option name="MERGE_DIFF_USE_ANCESTRY" value="true" />
+    <option name="UPDATE_LOCK_ON_DEMAND" value="false" />
+    <option name="IGNORE_SPACES_IN_MERGE" value="false" />
+    <option name="DETECT_NESTED_COPIES" value="true" />
+    <option name="IGNORE_SPACES_IN_ANNOTATE" value="true" />
+    <option name="SHOW_MERGE_SOURCES_IN_ANNOTATE" value="true" />
+    <myIsUseDefaultProxy>false</myIsUseDefaultProxy>
+  </component>
+  <component name="TaskManager">
+    <task active="true" id="Default" summary="Default task" />
+    <servers />
+  </component>
+  <component name="VcsManagerConfiguration">
+    <option name="OFFER_MOVE_TO_ANOTHER_CHANGELIST_ON_PARTIAL_COMMIT" value="true" />
+    <option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="true" />
+    <option name="PERFORM_UPDATE_IN_BACKGROUND" value="true" />
+    <option name="PERFORM_COMMIT_IN_BACKGROUND" value="true" />
+    <option name="PERFORM_EDIT_IN_BACKGROUND" value="true" />
+    <option name="PERFORM_CHECKOUT_IN_BACKGROUND" value="true" />
+    <option name="PERFORM_ADD_REMOVE_IN_BACKGROUND" value="true" />
+    <option name="PERFORM_ROLLBACK_IN_BACKGROUND" value="false" />
+    <option name="CHECK_LOCALLY_CHANGED_CONFLICTS_IN_BACKGROUND" value="false" />
+    <option name="ENABLE_BACKGROUND_PROCESSES" value="false" />
+    <option name="CHANGED_ON_SERVER_INTERVAL" value="60" />
+    <option name="FORCE_NON_EMPTY_COMMENT" value="false" />
+    <option name="LAST_COMMIT_MESSAGE" />
+    <option name="MAKE_NEW_CHANGELIST_ACTIVE" value="true" />
+    <option name="OPTIMIZE_IMPORTS_BEFORE_PROJECT_COMMIT" value="false" />
+    <option name="CHECK_FILES_UP_TO_DATE_BEFORE_COMMIT" value="false" />
+    <option name="REFORMAT_BEFORE_PROJECT_COMMIT" value="false" />
+    <option name="REFORMAT_BEFORE_FILE_COMMIT" value="false" />
+    <option name="FILE_HISTORY_DIALOG_COMMENTS_SPLITTER_PROPORTION" value="0.8" />
+    <option name="FILE_HISTORY_DIALOG_SPLITTER_PROPORTION" value="0.5" />
+    <option name="ACTIVE_VCS_NAME" />
+    <option name="UPDATE_GROUP_BY_PACKAGES" value="false" />
+    <option name="UPDATE_GROUP_BY_CHANGELIST" value="false" />
+    <option name="SHOW_FILE_HISTORY_AS_TREE" value="false" />
+    <option name="FILE_HISTORY_SPLITTER_PROPORTION" value="0.6" />
+  </component>
+  <component name="XDebuggerManager">
+    <breakpoint-manager />
+  </component>
+</project>
+
diff --git a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/IdeaPluginTest.groovy b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/IdeaPluginTest.groovy
new file mode 100644
index 0000000..32827d6
--- /dev/null
+++ b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/IdeaPluginTest.groovy
@@ -0,0 +1,134 @@
+/*
+ * 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.plugins.idea
+
+import org.gradle.api.JavaVersion
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.api.tasks.Delete
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+class IdeaPluginTest extends Specification {
+    private final DefaultProject project = HelperUtil.createRootProject()
+    private final Project childProject = HelperUtil.createChildProject(project, "child", new File("."))
+    private final IdeaPlugin ideaPlugin = new IdeaPlugin()
+
+    def addsIdeaProjectToRootProject() {
+        when:
+        applyPluginToProjects()
+
+        then:
+        assertThatCleanIdeaDependsOnDeleteTask(project, project.cleanIdeaProject)
+        IdeaProject ideaProjectTask = project.ideaProject
+        ideaProjectTask instanceof IdeaProject
+        ideaProjectTask.outputFile == new File(project.projectDir, project.name + ".ipr")
+        ideaProjectTask.subprojects == project.rootProject.allprojects
+        ideaProjectTask.javaVersion == JavaVersion.VERSION_1_6.toString()
+        ideaProjectTask.wildcards == ['!?*.java', '!?*.groovy'] as Set
+
+        childProject.tasks.findByName('ideaProject') == null
+        childProject.tasks.findByName('cleanIdeaProject') == null
+    }
+
+    def addsIdeaWorkspaceToRootProject() {
+        when:
+        applyPluginToProjects()
+
+        then:
+        project.ideaWorkspace instanceof IdeaWorkspace
+        assertThatCleanIdeaDependsOnDeleteTask(project, project.cleanIdeaWorkspace)
+
+        childProject.tasks.findByName('ideaWorkspace') == null
+        childProject.tasks.findByName('cleanIdeaWorkspace') == null
+    }
+
+    def addsIdeaModuleToProjects() {
+        when:
+        applyPluginToProjects()
+
+        then:
+        assertThatIdeaModuleIsProperlyConfigured(project)
+        assertThatIdeaModuleIsProperlyConfigured(childProject)
+    }
+
+    def addsSpecialConfigurationIfJavaPluginIsApplied() {
+        when:
+        applyPluginToProjects()
+        project.apply(plugin: 'java')
+
+        then:
+        project.ideaProject.javaVersion == project.sourceCompatibility.toString()
+
+        IdeaModule ideaModuleTask = project.ideaModule
+        ideaModuleTask.sourceDirs == project.sourceSets.main.allSource.sourceTrees.srcDirs.flatten() as Set
+        ideaModuleTask.testSourceDirs == project.sourceSets.test.allSource.sourceTrees.srcDirs.flatten() as Set
+        ideaModuleTask.outputDir == project.sourceSets.main.classesDir
+        ideaModuleTask.testOutputDir == project.sourceSets.test.classesDir
+        def configurations = project.configurations
+        ideaModuleTask.scopes == [
+                COMPILE: [plus: [configurations.compile], minus: []],
+                RUNTIME: [plus: [configurations.runtime], minus: [configurations.compile]],
+                TEST: [plus: [configurations.testRuntime], minus: [configurations.runtime]]
+        ]
+    }
+
+    void assertThatIdeaModuleIsProperlyConfigured(Project project) {
+        IdeaModule ideaModuleTask = project.ideaModule
+        assert ideaModuleTask instanceof IdeaModule
+        assert ideaModuleTask.outputFile == new File(project.projectDir, project.name + ".iml")
+        assert ideaModuleTask.moduleDir == project.projectDir
+        assert ideaModuleTask.sourceDirs == [] as Set
+        assert ideaModuleTask.testSourceDirs == [] as Set
+        assert ideaModuleTask.excludeDirs == [project.buildDir, project.file('.gradle')] as Set
+        assert ideaModuleTask.gradleCacheHome == new File(project.gradle.gradleUserHomeDir, '/cache')
+        assertThatCleanIdeaDependsOnDeleteTask(project, project.cleanIdeaModule)
+    }
+
+    void shouldPickUpLateChangesToBuildDir() {
+        when:
+        applyPluginToProjects()
+        project.apply(plugin: 'java')
+        project.buildDir = project.file('target')
+
+        then:
+        project.ideaModule.excludeDirs == [project.buildDir, project.file('.gradle')] as Set
+        project.ideaModule.outputDir == project.file('target/classes/main')
+    }
+
+    void assertThatCleanIdeaDependsOnDeleteTask(Project project, Task dependsOnTask) {
+        assert dependsOnTask instanceof Delete
+        assert project.cleanIdea.taskDependencies.getDependencies(project.cleanIdea).contains(dependsOnTask)
+    }
+
+    def addsCleanIdeaToProjects() {
+        when:
+        applyPluginToProjects()
+
+        then:
+        project.cleanIdea instanceof Task
+        childProject.cleanIdea instanceof Task
+    }
+
+    private def applyPluginToProjects() {
+        ideaPlugin.apply(project)
+        ideaPlugin.apply(childProject)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ModuleDependencyTest.groovy b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ModuleDependencyTest.groovy
new file mode 100644
index 0000000..8198027
--- /dev/null
+++ b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ModuleDependencyTest.groovy
@@ -0,0 +1,51 @@
+/*
+ * 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.plugins.idea.model
+
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+class ModuleDependencyTest extends Specification {
+    def equality() {
+        expect:
+        new ModuleDependency("a", null).equals(new ModuleDependency("a", "COMPILE"))
+        new ModuleDependency("a", "").equals(new ModuleDependency("a", "COMPILE"))
+        new ModuleDependency("a", "COMPILE").equals(new ModuleDependency("a", ""))
+        new ModuleDependency("a", "COMPILE").equals(new ModuleDependency("a", null))
+        new ModuleDependency("a", "").equals(new ModuleDependency("a", ""))
+        !new ModuleDependency("a", "").equals(new ModuleDependency("b", ""))
+        !new ModuleDependency("a", "RUNTIME").equals(new ModuleDependency("a", "COMPILE"))
+        !new ModuleDependency("a", "").equals(new ModuleDependency("a", "RUNTIME"))
+        !new ModuleDependency("a", "RUNTIME").equals(new ModuleDependency("a", ""))
+    }
+
+    def hash() {
+        expect:
+        new ModuleDependency("a", null).hashCode() == new ModuleDependency("a", "COMPILE").hashCode()
+        new ModuleDependency("a", "").hashCode() == new ModuleDependency("a", "COMPILE").hashCode()
+    }
+
+    def shouldExportForCompileAndRuntimeScope() {
+        expect:
+        new ModuleDependency("a", "COMPILE").exported
+        new ModuleDependency("a", "RUNTIME").exported
+        new ModuleDependency("a", "").exported
+        new ModuleDependency("a", null).exported
+        !(new ModuleDependency("a", "TEST").exported)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ModuleLibraryTest.groovy b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ModuleLibraryTest.groovy
new file mode 100644
index 0000000..4936ac3
--- /dev/null
+++ b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ModuleLibraryTest.groovy
@@ -0,0 +1,54 @@
+/*
+ * 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.plugins.idea.model
+
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+class ModuleLibraryTest extends Specification {
+    def equality() {
+        expect:
+        createModuleLibraryWithScope(null).equals(createModuleLibraryWithScope("COMPILE"))
+        createModuleLibraryWithScope("").equals(createModuleLibraryWithScope("COMPILE"))
+        createModuleLibraryWithScope("COMPILE").equals(createModuleLibraryWithScope(null))
+        createModuleLibraryWithScope("COMPILE").equals(createModuleLibraryWithScope(""))
+        createModuleLibraryWithScope("").equals(createModuleLibraryWithScope(""))
+        !createModuleLibraryWithScope("RUNTIME").equals(createModuleLibraryWithScope("COMPILE"))
+        !createModuleLibraryWithScope("").equals(createModuleLibraryWithScope("RUNTIME"))
+        !createModuleLibraryWithScope("RUNTIME").equals(createModuleLibraryWithScope(""))
+    }
+
+    def hash() {
+        expect:
+        createModuleLibraryWithScope(null).hashCode() == createModuleLibraryWithScope("COMPILE").hashCode()
+        createModuleLibraryWithScope("").hashCode() == createModuleLibraryWithScope("COMPILE").hashCode()
+    }
+
+    private ModuleLibrary createModuleLibraryWithScope(String scope) {
+        new ModuleLibrary([] as Set, [] as Set, [] as Set, [] as Set, scope)
+    }
+
+    def shouldExportForCompileAndRuntimeScope() {
+        expect:
+        createModuleLibraryWithScope("COMPILE").exported
+        createModuleLibraryWithScope("RUNTIME").exported
+        createModuleLibraryWithScope("").exported
+        createModuleLibraryWithScope(null).exported
+        !(createModuleLibraryWithScope("TEST").exported)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ModuleTest.groovy b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ModuleTest.groovy
new file mode 100644
index 0000000..8171347
--- /dev/null
+++ b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ModuleTest.groovy
@@ -0,0 +1,217 @@
+/*
+ * 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.plugins.idea.model
+
+import org.gradle.api.Action
+import org.gradle.listener.ListenerBroadcast
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+class ModuleTest extends Specification {
+    def static final CUSTOM_SOURCE_FOLDERS = [new Path('file://$MODULE_DIR$/src')] as LinkedHashSet
+    def static final CUSTOM_TEST_SOURCE_FOLDERS = [new Path('file://$MODULE_DIR$/srcTest')] as LinkedHashSet
+    def static final CUSTOM_EXCLUDE_FOLDERS = [new Path('file://$MODULE_DIR$/target')] as LinkedHashSet
+    def customDependencies = [
+            new ModuleLibrary([new Path('file://$MODULE_DIR$/gradle/lib')] as Set,
+                    [new Path('file://$MODULE_DIR$/gradle/javadoc')] as Set, [new Path('file://$MODULE_DIR$/gradle/src')] as Set,
+                    [] as Set, null),
+            new ModuleLibrary([new Path('file://$MODULE_DIR$/ant/lib'), new Path('jar://$GRADLE_CACHE$/gradle.jar!/')] as Set, [] as Set, [] as Set,
+                    [new JarDirectory(new Path('file://$MODULE_DIR$/ant/lib'), false)] as Set, "RUNTIME"),
+            new ModuleDependency('someModule', null)]
+
+    Module module
+
+//    def setup() {
+//        customDependencies.each { println it }
+//    }
+
+    def initWithReader() {
+        module = createModule(reader: customModuleReader)
+
+        expect:
+        module.javaVersion == "1.6"
+        module.sourceFolders == CUSTOM_SOURCE_FOLDERS
+        module.testSourceFolders == CUSTOM_TEST_SOURCE_FOLDERS
+        module.excludeFolders == CUSTOM_EXCLUDE_FOLDERS
+        module.outputDir == new Path('file://$MODULE_DIR$/out')
+        module.testOutputDir == new Path('file://$MODULE_DIR$/outTest')
+        (module.dependencies as List) == customDependencies
+    }
+
+    def initWithReaderAndDependencyVars() {
+        def replacable = '$GRADLE_CACHE$'
+        def replacer = '$MODULE_DIR$/../'
+        module = createModule(dependencyVariableReplacement: new VariableReplacement(replacable: replacable, replacer: replacer), reader: customModuleReader)
+
+        expect:
+        module.sourceFolders == CUSTOM_SOURCE_FOLDERS
+        module.testSourceFolders == CUSTOM_TEST_SOURCE_FOLDERS
+        module.excludeFolders == CUSTOM_EXCLUDE_FOLDERS
+        module.outputDir == new Path('file://$MODULE_DIR$/out')
+        module.testOutputDir == new Path('file://$MODULE_DIR$/outTest')
+        def expectedDependencies = customDependencies.collect { dependency ->
+            if (dependency instanceof ModuleLibrary) {
+                dependency.classes = dependency.classes.collect { Path path ->
+                    Path newPath = path
+                    if (path.url.contains(replacable)) {
+                        newPath = new Path(path.url.replace(replacable, replacer))
+                    }
+                    newPath
+                }
+            }
+            dependency
+        }
+        module.dependencies == (expectedDependencies as Set)
+    }
+
+    def initWithReaderAndValues_shouldBeMerged() {
+        def constructorSourceFolders = [new Path('a')] as Set
+        def constructorTestSourceFolders = [new Path('b')] as Set
+        def constructorExcludeFolders = [new Path('c')] as Set
+        def constructorOutputDir = new Path('someOut')
+        def constructorJavaVersion = '1.6'
+        def constructorTestOutputDir = new Path('someTestOut')
+        def constructorModuleDependencies = [
+                customDependencies[0],
+                new ModuleLibrary([new Path('x')], [], [], [new JarDirectory(new Path('y'), false)], null)] as LinkedHashSet
+        module = createModule(sourceFolders: constructorSourceFolders, testSourceFolders: constructorTestSourceFolders,
+                excludeFolders: constructorExcludeFolders, outputDir: constructorOutputDir, testOutputDir: constructorTestOutputDir,
+                moduleLibraries: constructorModuleDependencies, javaVersion: constructorJavaVersion, reader: customModuleReader)
+
+        expect:
+        module.sourceFolders == CUSTOM_SOURCE_FOLDERS + constructorSourceFolders
+        module.testSourceFolders == CUSTOM_TEST_SOURCE_FOLDERS + constructorTestSourceFolders
+        module.excludeFolders == CUSTOM_EXCLUDE_FOLDERS + constructorExcludeFolders
+        module.outputDir == constructorOutputDir
+        module.testOutputDir == constructorTestOutputDir
+        module.javaVersion == constructorJavaVersion
+        module.dependencies as LinkedHashSet == ((customDependencies as LinkedHashSet) + constructorModuleDependencies as LinkedHashSet) as LinkedHashSet
+    }
+
+    def initWithNullReader_shouldUseDefaultValuesAndMerge() {
+        def constructorSourceFolders = [new Path('a')] as Set
+        module = createModule(sourceFolders: constructorSourceFolders)
+
+        expect:
+        module.javaVersion == Module.INHERITED
+        module.sourceFolders == constructorSourceFolders
+        module.dependencies.size() == 0
+    }
+
+    def initWithNullReader_shouldUseDefaultSkeleton() {
+        when:
+        module = createModule([:])
+
+        then:
+        new XmlParser().parse(toXmlReader).toString() == module.xml.toString()
+    }
+
+    def toXml_shouldContainCustomValues() {
+        def constructorSourceFolders = [new Path('a')] as Set
+        def constructorOutputDir = new Path('someOut')
+        def constructorTestOutputDir = new Path('someTestOut')
+
+        when:
+        this.module = createModule(javaVersion: '1.6', sourceFolders: constructorSourceFolders, reader: defaultModuleReader,
+                outputDir: constructorOutputDir, testOutputDir: constructorTestOutputDir)
+        def module = createModule(reader: toXmlReader)
+
+        then:
+        this.module == module
+    }
+
+    def toXml_shouldContainSkeleton() {
+        when:
+        module = createModule(reader: customModuleReader)
+
+        then:
+        new XmlParser().parse(toXmlReader).toString() == module.xml.toString()
+    }
+
+    def beforeConfigured() {
+        def constructorSourceFolders = [new Path('a')] as Set
+        ListenerBroadcast beforeConfiguredActions = new ListenerBroadcast(Action)
+        beforeConfiguredActions.add("execute") { Module module ->
+            module.sourceFolders.clear()
+        }
+
+        when:
+        module = createModule(sourceFolders: constructorSourceFolders, reader: customModuleReader, beforeConfiguredActions: beforeConfiguredActions)
+
+        then:
+        createModule(reader: toXmlReader).sourceFolders == constructorSourceFolders
+    }
+
+    def whenConfigured() {
+        def constructorSourceFolder = new Path('a')
+        def configureActionSourceFolder = new Path("c")
+
+        ListenerBroadcast whenConfiguredActions = new ListenerBroadcast(Action)
+        whenConfiguredActions.add("execute") { Module module ->
+            assert module.sourceFolders.contains((CUSTOM_SOURCE_FOLDERS as List)[0])
+            assert module.sourceFolders.contains(constructorSourceFolder)
+            module.sourceFolders.add(configureActionSourceFolder)
+        }
+
+        when:
+        module = createModule(sourceFolders: [constructorSourceFolder] as Set, reader: customModuleReader,
+                whenConfiguredActions: whenConfiguredActions)
+
+        then:
+        createModule(reader: toXmlReader).sourceFolders == CUSTOM_SOURCE_FOLDERS + ([constructorSourceFolder, configureActionSourceFolder] as LinkedHashSet)
+    }
+
+    private StringReader getToXmlReader() {
+        StringWriter toXmlText = new StringWriter()
+        module.toXml(toXmlText)
+        return new StringReader(toXmlText.toString())
+    }
+
+    def withXml() {
+        ListenerBroadcast withXmlActions = new ListenerBroadcast(Action)
+        module = createModule(reader: customModuleReader, withXmlActions: withXmlActions)
+
+        when:
+        def modifiedVersion
+        withXmlActions.add("execute") { xml ->
+            xml. at version += 'x'
+            modifiedVersion = xml. at version
+        }
+
+        then:
+        new XmlParser().parse(toXmlReader). at version == modifiedVersion
+    }
+
+    private InputStreamReader getCustomModuleReader() {
+        return new InputStreamReader(getClass().getResourceAsStream('customModule.xml'))
+    }
+
+    private InputStreamReader getDefaultModuleReader() {
+        return new InputStreamReader(getClass().getResourceAsStream('defaultModule.xml'))
+    }
+
+    private Module createModule(Map customArgs) {
+        ListenerBroadcast dummyBroadcast = new ListenerBroadcast(Action)
+        Map args = [contentPath: null, sourceFolders: [] as Set, testSourceFolders: [] as Set, excludeFolders: [] as Set, outputDir: null, testOutputDir: null,
+                moduleLibraries: [] as Set, dependencyVariableReplacement: VariableReplacement.NO_REPLACEMENT, javaVersion: null, 
+                reader: null, beforeConfiguredActions: dummyBroadcast, whenConfiguredActions: dummyBroadcast, withXmlActions: dummyBroadcast] + customArgs
+        return new Module(args.contentPath, args.sourceFolders, args.testSourceFolders, args.excludeFolders, args.outputDir, args.testOutputDir,
+                args.moduleLibraries, args.dependencyVariableReplacement, args.javaVersion, args.reader,
+                args.beforeConfiguredActions, args.whenConfiguredActions, args.withXmlActions)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ProjectTest.groovy b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ProjectTest.groovy
new file mode 100644
index 0000000..4fedfdb
--- /dev/null
+++ b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/ProjectTest.groovy
@@ -0,0 +1,141 @@
+/*
+ * 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.plugins.idea.model
+
+import org.gradle.api.Action
+import org.gradle.listener.ListenerBroadcast
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+class ProjectTest extends Specification {
+    Project project
+
+    def initWithReaderAndNoJdkAndNoWildcards() {
+        project = createProject(javaVersion: "1.4", reader: customProjectReader)
+
+        expect:
+        project.modulePaths == [new ModulePath('file://$PROJECT_DIR$/gradle-idea-plugin.iml', '$PROJECT_DIR$/gradle-idea-plugin.iml')] as Set
+        project.wildcards == ["?*.gradle", "?*.grails"] as Set
+        project.jdk == new Jdk(true, false, "JDK_1_4", "1.4")
+    }
+
+    def initWithReaderAndJdkAndWildcards_shouldBeMerged() {
+        project = createProject(wildcards: ['?*.groovy'] as Set, reader: customProjectReader)
+
+        expect:
+        project.modulePaths == [new ModulePath('file://$PROJECT_DIR$/gradle-idea-plugin.iml', '$PROJECT_DIR$/gradle-idea-plugin.iml')] as Set
+        project.wildcards == ["?*.gradle", "?*.grails", "?*.groovy"] as Set
+        project.jdk == new Jdk("1.6")
+    }
+
+    def initWithNullReader_shouldUseDefaults() {
+        project = createProject(wildcards: ['!?*.groovy'] as Set)
+
+        expect:
+        project.modulePaths.size() == 0
+        project.wildcards == ["!?*.groovy"] as Set
+        project.jdk == new Jdk("1.6")
+    }
+
+    def toXml_shouldContainCustomValues() {
+        when:
+        project = createProject(wildcards: ['?*.groovy'] as Set, reader: customProjectReader)
+
+        then:
+        project == createProject(wildcards: ['?*.groovy'] as Set, reader: toXmlReader)
+    }
+
+    def toXml_shouldContainSkeleton() {
+        when:
+        project = createProject(reader: customProjectReader)
+
+        then:
+        new XmlParser().parse(toXmlReader).toString() == project.xml.toString()
+    }
+
+    def beforeConfigured() {
+        ListenerBroadcast beforeConfiguredActions = new ListenerBroadcast(Action)
+        beforeConfiguredActions.add("execute") { Project ideaProject ->
+            ideaProject.modulePaths.clear()
+        }
+        def modulePaths = [new ModulePath("a", "b")] as Set
+
+        when:
+        project = createProject(modulePaths: modulePaths, reader: customProjectReader, beforeConfiguredActions: beforeConfiguredActions)
+
+        then:
+        createProject(reader: toXmlReader).modulePaths == modulePaths
+    }
+
+    def whenConfigured() {
+        def moduleFromInitialXml = null
+        def moduleFromProjectConstructor = new ModulePath("a", "b")
+        def moduleAddedInWhenConfiguredAction = new ModulePath("c", "d")
+        ListenerBroadcast beforeConfiguredActions = new ListenerBroadcast(Action)
+        beforeConfiguredActions.add("execute") { Project ideaProject ->
+            moduleFromInitialXml = (ideaProject.modulePaths as List)[0]
+        }
+        ListenerBroadcast whenConfiguredActions = new ListenerBroadcast(Action)
+        whenConfiguredActions.add("execute") { Project ideaProject ->
+            assert ideaProject.modulePaths.contains(moduleFromInitialXml)
+            assert ideaProject.modulePaths.contains(moduleFromProjectConstructor)
+            ideaProject.modulePaths.add(moduleAddedInWhenConfiguredAction)
+        }
+
+        when:
+        project = createProject(modulePaths: [moduleFromProjectConstructor] as Set, reader: customProjectReader,
+                beforeConfiguredActions: beforeConfiguredActions,
+                whenConfiguredActions: whenConfiguredActions)
+
+        then:
+        createProject(reader: toXmlReader).modulePaths == [moduleFromInitialXml, moduleFromProjectConstructor, moduleAddedInWhenConfiguredAction] as Set
+    }
+
+    private StringReader getToXmlReader() {
+        StringWriter toXmlText = new StringWriter()
+        project.toXml(toXmlText)
+        return new StringReader(toXmlText.toString())
+    }
+
+    def withXml() {
+        ListenerBroadcast withXmlActions = new ListenerBroadcast(Action)
+        project = createProject(reader: customProjectReader, withXmlActions: withXmlActions)
+
+        when:
+        def modifiedVersion
+        withXmlActions.add("execute") { xml ->
+            xml. at version += 'x'
+            modifiedVersion = xml. at version
+        }
+
+        then:
+        new XmlParser().parse(toXmlReader). at version == modifiedVersion
+    }
+
+    private InputStreamReader getCustomProjectReader() {
+        return new InputStreamReader(getClass().getResourceAsStream('customProject.xml'))
+    }
+
+    private Project createProject(Map customArgs) {
+        ListenerBroadcast dummyBroadcast = new ListenerBroadcast(Action)
+        Map args = [modulePaths: [] as Set, javaVersion: "1.6", wildcards: [] as Set, reader: null,
+                beforeConfiguredActions: dummyBroadcast, whenConfiguredActions: dummyBroadcast, withXmlActions: dummyBroadcast] + customArgs
+        return new Project(args.modulePaths, args.javaVersion, args.wildcards, args.reader,
+                args.beforeConfiguredActions, args.whenConfiguredActions, args.withXmlActions)
+    }
+}
diff --git a/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/WorkspaceTest.groovy b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/WorkspaceTest.groovy
new file mode 100644
index 0000000..80c735c
--- /dev/null
+++ b/subprojects/gradle-idea/src/test/groovy/org/gradle/plugins/idea/model/WorkspaceTest.groovy
@@ -0,0 +1,79 @@
+/*
+ * 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.plugins.idea.model
+
+import org.gradle.api.Action
+import org.gradle.listener.ListenerBroadcast
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+
+class WorkspaceTest extends Specification {
+    Workspace workspace
+
+    def toXmlWithCustomReader() {
+        when:
+        workspace = createWorkspace(reader: customWorkspaceReader)
+
+        then:
+        new XmlParser().parse(toXmlReader).toString() == new XmlParser().parse(customWorkspaceReader).toString()
+    }
+
+    def toXmlWithNullReader() {
+        when:
+        workspace = createWorkspace([:])
+
+        then:
+        new XmlParser().parse(toXmlReader).toString() == new XmlParser().parse(defaultWorkspaceReader).toString()
+    }
+    
+    private StringReader getToXmlReader() {
+        StringWriter toXmlText = new StringWriter()
+        workspace.toXml(toXmlText)
+        return new StringReader(toXmlText.toString())
+    }
+
+    def withXml() {
+        ListenerBroadcast withXmlActions = new ListenerBroadcast(Action)
+        workspace = createWorkspace(reader: customWorkspaceReader, withXmlActions: withXmlActions)
+
+        when:
+        def modifiedVersion
+        withXmlActions.add("execute") { xml ->
+            xml. at version += 'x'
+            modifiedVersion = xml. at version
+        }
+
+        then:
+        new XmlParser().parse(toXmlReader). at version == modifiedVersion
+    }
+
+    private InputStreamReader getCustomWorkspaceReader() {
+        return new InputStreamReader(getClass().getResourceAsStream('customWorkspace.xml'))
+    }
+
+    private InputStreamReader getDefaultWorkspaceReader() {
+        return new InputStreamReader(getClass().getResourceAsStream('defaultWorkspace.xml'))
+    }
+
+    private Workspace createWorkspace(Map customArgs) {
+        ListenerBroadcast dummyBroadcast = new ListenerBroadcast(Action)
+        Map args = [reader: null, withXmlActions: dummyBroadcast] + customArgs
+        return new Workspace(args.reader, args.withXmlActions)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-idea/src/test/resources/org/gradle/plugins/idea/model/customModule.xml b/subprojects/gradle-idea/src/test/resources/org/gradle/plugins/idea/model/customModule.xml
new file mode 100644
index 0000000..ab84573
--- /dev/null
+++ b/subprojects/gradle-idea/src/test/resources/org/gradle/plugins/idea/model/customModule.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+    <component name="NewModuleRootManager">
+        <output url="file://$MODULE_DIR$/out"/>
+        <output-test url="file://$MODULE_DIR$/outTest"/>
+        <exclude-output/>
+        <orderEntry type="jdk" jdkName="1.6" jdkType="JavaSDK" />
+        <content url="file://$MODULE_DIR$">
+            <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false"/>
+            <sourceFolder url="file://$MODULE_DIR$/srcTest" isTestSource="true"/>
+            <excludeFolder url="file://$MODULE_DIR$/target"/>
+        </content>
+        <orderEntry type="sourceFolder" forTests="false"/>
+        <orderEntry type="module-library">
+            <library>
+                <CLASSES>
+                    <root url="file://$MODULE_DIR$/gradle/lib"/>
+                </CLASSES>
+                <JAVADOC>
+                    <root url="file://$MODULE_DIR$/gradle/javadoc"/>
+                </JAVADOC>
+                <SOURCES>
+                    <root url="file://$MODULE_DIR$/gradle/src"/>
+                </SOURCES>
+            </library>
+        </orderEntry>
+        <orderEntry type="module-library" scope="RUNTIME">
+            <library>
+                <CLASSES>
+                    <root url="file://$MODULE_DIR$/ant/lib"/>
+                    <root url="jar://$GRADLE_CACHE$/gradle.jar!/"/>
+                </CLASSES>
+                <JAVADOC/>
+                <SOURCES/>
+                <jarDirectory url="file://$MODULE_DIR$/ant/lib" recursive="false"/>
+            </library>
+        </orderEntry>
+        <orderEntry type="module" module-name="someModule" exported="" />
+    </component>
+    <component name="ModuleRootManager"/>
+</module>
\ No newline at end of file
diff --git a/subprojects/gradle-idea/src/test/resources/org/gradle/plugins/idea/model/customProject.xml b/subprojects/gradle-idea/src/test/resources/org/gradle/plugins/idea/model/customProject.xml
new file mode 100644
index 0000000..ec189ec
--- /dev/null
+++ b/subprojects/gradle-idea/src/test/resources/org/gradle/plugins/idea/model/customProject.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <option name="DEFAULT_COMPILER" value="Javac" />
+    <resourceExtensions>
+      <entry name=".+\.(properties|xml|html|dtd|tld)" />
+      <entry name=".+\.(gif|png|jpeg|jpg)" />
+    </resourceExtensions>
+    <wildcardResourcePatterns>
+      <entry name="?*.gradle" />
+      <entry name="?*.grails" />
+    </wildcardResourcePatterns>
+    <annotationProcessing enabled="false" useClasspath="true" />
+  </component>
+  <component name="CopyrightManager" default="">
+    <module2copyright />
+  </component>
+  <component name="DependencyValidationManager">
+    <option name="SKIP_IMPORT_STATEMENTS" value="false" />
+  </component>
+  <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
+  <component name="GradleUISettings">
+    <setting name="root" />
+  </component>
+  <component name="GradleUISettings2">
+    <setting name="root" />
+  </component>
+  <component name="IdProvider" IDEtalkID="11DA1DB66DD62DDA1ED602B7079FE97C" />
+  <component name="JavadocGenerationManager">
+    <option name="OUTPUT_DIRECTORY" />
+    <option name="OPTION_SCOPE" value="protected" />
+    <option name="OPTION_HIERARCHY" value="true" />
+    <option name="OPTION_NAVIGATOR" value="true" />
+    <option name="OPTION_INDEX" value="true" />
+    <option name="OPTION_SEPARATE_INDEX" value="true" />
+    <option name="OPTION_DOCUMENT_TAG_USE" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_VERSION" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true" />
+    <option name="OPTION_DEPRECATED_LIST" value="true" />
+    <option name="OTHER_OPTIONS" value="" />
+    <option name="HEAP_SIZE" />
+    <option name="LOCALE" />
+    <option name="OPEN_IN_BROWSER" value="true" />
+  </component>
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/gradle-idea-plugin.iml" filepath="$PROJECT_DIR$/gradle-idea-plugin.iml" />
+    </modules>
+  </component>
+  <component name="ProjectRootManager" version="2" assert-keyword="true" jdk-15="false" project-jdk-name="1.4">
+    <output url="file://$PROJECT_DIR$/out" />
+  </component>
+  <component name="SvnBranchConfigurationManager">
+    <option name="mySupportsUserInfoFilter" value="true" />
+  </component>
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>
diff --git a/subprojects/gradle-idea/src/test/resources/org/gradle/plugins/idea/model/customWorkspace.xml b/subprojects/gradle-idea/src/test/resources/org/gradle/plugins/idea/model/customWorkspace.xml
new file mode 100644
index 0000000..8672123
--- /dev/null
+++ b/subprojects/gradle-idea/src/test/resources/org/gradle/plugins/idea/model/customWorkspace.xml
@@ -0,0 +1,211 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ChangeListManager">
+    <option name="TRACKING_ENABLED" value="true" />
+    <option name="SHOW_DIALOG" value="false" />
+    <option name="HIGHLIGHT_CONFLICTS" value="true" />
+    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
+    <option name="LAST_RESOLUTION" value="IGNORE" />
+  </component>
+  <component name="ChangesViewManager" flattened_view="true" show_ignored="false" />
+  <component name="CreatePatchCommitExecutor">
+    <option name="PATCH_PATH" value="" />
+    <option name="REVERSE_PATCH" value="false" />
+  </component>
+  <component name="DaemonCodeAnalyzer">
+    <disable_hints />
+  </component>
+  <component name="DebuggerManager">
+    <breakpoint_any>
+      <breakpoint>
+        <option name="NOTIFY_CAUGHT" value="true" />
+        <option name="NOTIFY_UNCAUGHT" value="true" />
+        <option name="ENABLED" value="false" />
+        <option name="LOG_ENABLED" value="false" />
+        <option name="LOG_EXPRESSION_ENABLED" value="false" />
+        <option name="SUSPEND_POLICY" value="SuspendAll" />
+        <option name="COUNT_FILTER_ENABLED" value="false" />
+        <option name="COUNT_FILTER" value="0" />
+        <option name="CONDITION_ENABLED" value="false" />
+        <option name="CLASS_FILTERS_ENABLED" value="false" />
+        <option name="INSTANCE_FILTERS_ENABLED" value="false" />
+        <option name="CONDITION" value="" />
+        <option name="LOG_MESSAGE" value="" />
+      </breakpoint>
+      <breakpoint>
+        <option name="NOTIFY_CAUGHT" value="true" />
+        <option name="NOTIFY_UNCAUGHT" value="true" />
+        <option name="ENABLED" value="false" />
+        <option name="LOG_ENABLED" value="false" />
+        <option name="LOG_EXPRESSION_ENABLED" value="false" />
+        <option name="SUSPEND_POLICY" value="SuspendAll" />
+        <option name="COUNT_FILTER_ENABLED" value="false" />
+        <option name="COUNT_FILTER" value="0" />
+        <option name="CONDITION_ENABLED" value="false" />
+        <option name="CLASS_FILTERS_ENABLED" value="false" />
+        <option name="INSTANCE_FILTERS_ENABLED" value="false" />
+        <option name="CONDITION" value="" />
+        <option name="LOG_MESSAGE" value="" />
+      </breakpoint>
+    </breakpoint_any>
+    <breakpoint_rules />
+    <ui_properties />
+  </component>
+  <component name="ModuleEditorState">
+    <option name="LAST_EDITED_MODULE_NAME" />
+    <option name="LAST_EDITED_TAB_NAME" />
+  </component>
+  <component name="ProjectInspectionProfilesVisibleTreeState">
+    <entry key="Project Default">
+      <profile-state />
+    </entry>
+  </component>
+  <component name="ProjectLevelVcsManager">
+    <OptionsSetting value="true" id="Add" />
+    <OptionsSetting value="true" id="Remove" />
+    <OptionsSetting value="true" id="Checkout" />
+    <OptionsSetting value="true" id="Update" />
+    <OptionsSetting value="true" id="Status" />
+    <OptionsSetting value="true" id="Edit" />
+    <ConfirmationsSetting value="0" id="Add" />
+    <ConfirmationsSetting value="0" id="Remove" />
+  </component>
+  <component name="ProjectReloadState">
+    <option name="STATE" value="0" />
+  </component>
+  <component name="PropertiesComponent">
+    <property name="GoToFile.includeJavaFiles" value="false" />
+    <property name="GoToClass.toSaveIncludeLibraries" value="false" />
+    <property name="MemberChooser.sorted" value="false" />
+    <property name="MemberChooser.showClasses" value="true" />
+    <property name="GoToClass.includeLibraries" value="false" />
+    <property name="MemberChooser.copyJavadoc" value="false" />
+  </component>
+  <component name="RunManager">
+    <configuration default="true" type="Remote" factoryName="Remote">
+      <option name="USE_SOCKET_TRANSPORT" value="true" />
+      <option name="SERVER_MODE" value="false" />
+      <option name="SHMEM_ADDRESS" value="javadebug" />
+      <option name="HOST" value="localhost" />
+      <option name="PORT" value="5005" />
+      <method>
+        <option name="BuildArtifacts" enabled="false" />
+      </method>
+    </configuration>
+    <configuration default="true" type="Applet" factoryName="Applet">
+      <module name="" />
+      <option name="MAIN_CLASS_NAME" />
+      <option name="HTML_FILE_NAME" />
+      <option name="HTML_USED" value="false" />
+      <option name="WIDTH" value="400" />
+      <option name="HEIGHT" value="300" />
+      <option name="POLICY_FILE" value="$APPLICATION_HOME_DIR$/bin/appletviewer.policy" />
+      <option name="VM_PARAMETERS" />
+      <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+      <option name="ALTERNATIVE_JRE_PATH" />
+      <method>
+        <option name="BuildArtifacts" enabled="false" />
+        <option name="Make" enabled="true" />
+      </method>
+    </configuration>
+    <configuration default="true" type="Application" factoryName="Application">
+      <extension name="coverage" enabled="false" merge="false" />
+      <option name="MAIN_CLASS_NAME" />
+      <option name="VM_PARAMETERS" />
+      <option name="PROGRAM_PARAMETERS" />
+      <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
+      <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+      <option name="ALTERNATIVE_JRE_PATH" />
+      <option name="ENABLE_SWING_INSPECTOR" value="false" />
+      <option name="ENV_VARIABLES" />
+      <option name="PASS_PARENT_ENVS" value="true" />
+      <module name="" />
+      <envs />
+      <method>
+        <option name="BuildArtifacts" enabled="false" />
+        <option name="Make" enabled="true" />
+      </method>
+    </configuration>
+    <configuration default="true" type="JUnit" factoryName="JUnit">
+      <extension name="coverage" enabled="false" merge="false" />
+      <module name="" />
+      <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+      <option name="ALTERNATIVE_JRE_PATH" />
+      <option name="PACKAGE_NAME" />
+      <option name="MAIN_CLASS_NAME" />
+      <option name="METHOD_NAME" />
+      <option name="TEST_OBJECT" value="class" />
+      <option name="VM_PARAMETERS" />
+      <option name="PARAMETERS" />
+      <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
+      <option name="ENV_VARIABLES" />
+      <option name="PASS_PARENT_ENVS" value="true" />
+      <option name="TEST_SEARCH_SCOPE">
+        <value defaultName="moduleWithDependencies" />
+      </option>
+      <envs />
+      <method>
+        <option name="BuildArtifacts" enabled="false" />
+        <option name="Make" enabled="true" />
+      </method>
+    </configuration>
+    <list size="0" />
+    <configuration name="<template>" type="WebApp" default="true" selected="false">
+      <Host>localhost</Host>
+      <Port>5050</Port>
+    </configuration>
+  </component>
+  <component name="ShelveChangesManager" show_recycled="false" />
+  <component name="SvnConfiguration" maxAnnotateRevisions="500">
+    <option name="USER" value="" />
+    <option name="PASSWORD" value="" />
+    <option name="LAST_MERGED_REVISION" />
+    <option name="UPDATE_RUN_STATUS" value="false" />
+    <option name="MERGE_DRY_RUN" value="false" />
+    <option name="MERGE_DIFF_USE_ANCESTRY" value="true" />
+    <option name="UPDATE_LOCK_ON_DEMAND" value="false" />
+    <option name="IGNORE_SPACES_IN_MERGE" value="false" />
+    <option name="DETECT_NESTED_COPIES" value="true" />
+    <option name="IGNORE_SPACES_IN_ANNOTATE" value="true" />
+    <option name="SHOW_MERGE_SOURCES_IN_ANNOTATE" value="true" />
+    <myIsUseDefaultProxy>false</myIsUseDefaultProxy>
+  </component>
+  <component name="TaskManager">
+    <task active="true" id="Default" summary="Default task" />
+    <servers />
+  </component>
+  <component name="VcsManagerConfiguration">
+    <option name="OFFER_MOVE_TO_ANOTHER_CHANGELIST_ON_PARTIAL_COMMIT" value="true" />
+    <option name="CHECK_CODE_SMELLS_BEFORE_PROJECT_COMMIT" value="true" />
+    <option name="PERFORM_UPDATE_IN_BACKGROUND" value="true" />
+    <option name="PERFORM_COMMIT_IN_BACKGROUND" value="true" />
+    <option name="PERFORM_EDIT_IN_BACKGROUND" value="true" />
+    <option name="PERFORM_CHECKOUT_IN_BACKGROUND" value="true" />
+    <option name="PERFORM_ADD_REMOVE_IN_BACKGROUND" value="true" />
+    <option name="PERFORM_ROLLBACK_IN_BACKGROUND" value="false" />
+    <option name="CHECK_LOCALLY_CHANGED_CONFLICTS_IN_BACKGROUND" value="false" />
+    <option name="ENABLE_BACKGROUND_PROCESSES" value="false" />
+    <option name="CHANGED_ON_SERVER_INTERVAL" value="60" />
+    <option name="FORCE_NON_EMPTY_COMMENT" value="false" />
+    <option name="LAST_COMMIT_MESSAGE" />
+    <option name="MAKE_NEW_CHANGELIST_ACTIVE" value="true" />
+    <option name="OPTIMIZE_IMPORTS_BEFORE_PROJECT_COMMIT" value="false" />
+    <option name="CHECK_FILES_UP_TO_DATE_BEFORE_COMMIT" value="false" />
+    <option name="REFORMAT_BEFORE_PROJECT_COMMIT" value="false" />
+    <option name="REFORMAT_BEFORE_FILE_COMMIT" value="false" />
+    <option name="FILE_HISTORY_DIALOG_COMMENTS_SPLITTER_PROPORTION" value="0.8" />
+    <option name="FILE_HISTORY_DIALOG_SPLITTER_PROPORTION" value="0.5" />
+    <option name="ACTIVE_VCS_NAME" />
+    <option name="UPDATE_GROUP_BY_PACKAGES" value="false" />
+    <option name="UPDATE_GROUP_BY_CHANGELIST" value="false" />
+    <option name="SHOW_FILE_HISTORY_AS_TREE" value="false" />
+    <option name="FILE_HISTORY_SPLITTER_PROPORTION" value="0.6" />
+  </component>
+  <component name="XDebuggerManager">
+    <breakpoint-manager />
+  </component>
+  <component name="Git.Settings">
+    <option name="GIT_EXECUTABLE" value="/usr/local/git/bin/git" />
+  </component>
+</project>
+
diff --git a/subprojects/gradle-jetty/jetty.gradle b/subprojects/gradle-jetty/jetty.gradle
new file mode 100644
index 0000000..2278a0f
--- /dev/null
+++ b/subprojects/gradle-jetty/jetty.gradle
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+dependencies {
+    groovy libraries.groovy_depends
+
+    compile project(':core')
+    compile project(':plugins')
+
+    compile libraries.slf4j_api,
+            libraries.jetty_depends,
+            "org.mortbay.jetty:jetty-plus:6.1.22 at jar"
+
+    runtime "org.mortbay.jetty:jsp-api-2.1:6.1.14 at jar",
+            "org.mortbay.jetty:jsp-2.1:6.1.14 at jar",
+            "org.eclipse.jdt:core:3.1.1 at jar",
+            "org.mortbay.jetty:jetty-naming:6.1.22 at jar",
+            "org.mortbay.jetty:jetty-annotations:6.1.22 at jar"
+
+    testCompile project(path: ':core', configuration: 'testFixtures')
+    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+}
diff --git a/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/AbstractJettyRunTask.java b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/AbstractJettyRunTask.java
new file mode 100644
index 0000000..cb06120
--- /dev/null
+++ b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/AbstractJettyRunTask.java
@@ -0,0 +1,511 @@
+/*
+ * 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.jetty;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.plugins.jetty.internal.ConsoleScanner;
+import org.gradle.api.plugins.jetty.internal.JettyPluginServer;
+import org.gradle.api.plugins.jetty.internal.JettyPluginWebAppContext;
+import org.gradle.api.plugins.jetty.internal.Monitor;
+import org.gradle.api.tasks.*;
+import org.gradle.util.GFileUtils;
+import org.mortbay.jetty.Connector;
+import org.mortbay.jetty.RequestLog;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.security.UserRealm;
+import org.mortbay.util.Scanner;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.net.URLClassLoader;
+import java.util.*;
+
+public abstract class AbstractJettyRunTask extends ConventionTask {
+    private static Logger logger = LoggerFactory.getLogger(AbstractJettyRunTask.class);
+
+    private Iterable<File> additionalRuntimeJars = new ArrayList<File>();
+
+    /**
+     * The proxy for the Server object
+     */
+    private JettyPluginServer server;
+
+    /**
+     * The "virtual" webapp created by the plugin
+     */
+    private JettyPluginWebAppContext webAppConfig;
+
+    /**
+     * The context path for the webapp.
+     */
+    private String contextPath;
+
+    /**
+     * A webdefault.xml file to use instead of the default for the webapp. Optional.
+     */
+    private File webDefaultXml;
+
+    /**
+     * A web.xml file to be applied AFTER the webapp's web.xml file. Useful for applying different build profiles, eg
+     * test, production etc. Optional.
+     */
+    private File overrideWebXml;
+
+    /**
+     * The interval in seconds to scan the webapp for changes and restart the context if necessary. Ignored if reload is
+     * enabled. Disabled by default.
+     */
+    private int scanIntervalSeconds;
+
+    /**
+     * reload can be set to either 'automatic' or 'manual' <p/> if 'manual' then the context can be reloaded by a
+     * linefeed in the console if 'automatic' then traditional reloading on changed files is enabled.
+     */
+    protected String reload;
+
+    /**
+     * Location of a jetty xml configuration file whose contents will be applied before any plugin configuration.
+     * Optional.
+     */
+    private File jettyConfig;
+
+    /**
+     * Port to listen to stop jetty on executing -DSTOP.PORT=<stopPort> -DSTOP.KEY=<stopKey> -jar start.jar
+     * --stop
+     */
+    private Integer stopPort;
+
+    /**
+     * Key to provide when stopping jetty on executing java -DSTOP.KEY=<stopKey> -DSTOP.PORT=<stopPort> -jar
+     * start.jar --stop
+     */
+    private String stopKey;
+
+    /**
+     * <p> Determines whether or not the server blocks when started. The default behavior (daemon = false) will cause
+     * the server to pause other processes while it continues to handle web requests. This is useful when starting the
+     * server with the intent to work with it interactively. </p><p> Often, it is desirable to let the server start and
+     * continue running subsequent processes in an automated build environment. This can be facilitated by setting
+     * daemon to true. </p>
+     */
+    private boolean daemon;
+
+    private Integer httpPort;
+
+    /**
+     * List of connectors to use. If none are configured then we use a single SelectChannelConnector at port 8080
+     */
+    private Connector[] connectors;
+
+    /**
+     * List of security realms to set up. Optional.
+     */
+    private UserRealm[] userRealms;
+
+    /**
+     * A RequestLog implementation to use for the webapp at runtime. Optional.
+     */
+    private RequestLog requestLog;
+
+    /**
+     * A scanner to check for changes to the webapp
+     */
+    private Scanner scanner = new Scanner();
+
+    /**
+     * List of Listeners for the scanner
+     */
+    protected ArrayList scannerListeners;
+
+    /**
+     * A scanner to check ENTER hits on the console
+     */
+    protected Thread consoleScanner;
+
+    public static final String PORT_SYSPROPERTY = "jetty.port";
+
+    public abstract void validateConfiguration();
+
+    public abstract void configureScanner();
+
+    public abstract void applyJettyXml() throws Exception;
+
+    /**
+     * create a proxy that wraps a particular jetty version Server object
+     *
+     * @return The Jetty Plugin Server
+     */
+    public abstract JettyPluginServer createServer() throws Exception;
+
+    public abstract void finishConfigurationBeforeStart() throws Exception;
+
+    @TaskAction
+    protected void start() {
+        ClassLoader originalClassloader = Server.class.getClassLoader();
+        List<File> additionalClasspath = new ArrayList<File>();
+        for (File additionalRuntimeJar : getAdditionalRuntimeJars()) {
+            additionalClasspath.add(additionalRuntimeJar);
+        }
+        URLClassLoader jettyClassloader = new URLClassLoader(GFileUtils.toURLArray(additionalClasspath), originalClassloader);
+        try {
+            Thread.currentThread().setContextClassLoader(jettyClassloader);
+            startJetty();
+        } finally {
+            Thread.currentThread().setContextClassLoader(originalClassloader);
+        }
+    }
+
+    public JettyPluginServer getServer() {
+        return this.server;
+    }
+
+    public void setServer(JettyPluginServer server) {
+        this.server = server;
+    }
+
+    public void setScannerListeners(ArrayList listeners) {
+        this.scannerListeners = new ArrayList(listeners);
+    }
+
+    public ArrayList getScannerListeners() {
+        return this.scannerListeners;
+    }
+
+    public Scanner getScanner() {
+        return scanner;
+    }
+
+    public void startJetty() {
+        logger.info("Configuring Jetty for " + getProject());
+        validateConfiguration();
+        startJettyInternal();
+    }
+
+    public void startJettyInternal() {
+        try {
+            logger.debug("Starting Jetty Server ...");
+
+            setServer(createServer());
+
+            applyJettyXml();
+
+            JettyPluginServer plugin = getServer();
+
+            Object[] configuredConnectors = getConnectors();
+
+            plugin.setConnectors(configuredConnectors);
+            Object[] connectors = plugin.getConnectors();
+
+            if (connectors == null || connectors.length == 0) {
+                configuredConnectors = new Object[]{plugin.createDefaultConnector(getHttpPort())};
+                plugin.setConnectors(configuredConnectors);
+            }
+
+            //set up a RequestLog if one is provided
+            if (getRequestLog() != null) {
+                getServer().setRequestLog(getRequestLog());
+            }
+
+            //set up the webapp and any context provided
+            getServer().configureHandlers();
+            configureWebApplication();
+            getServer().addWebApplication(webAppConfig);
+
+            // set up security realms
+            Object[] configuredRealms = getUserRealms();
+            for (int i = 0; (configuredRealms != null) && i < configuredRealms.length; i++) {
+                logger.debug(configuredRealms[i].getClass().getName() + ": " + configuredRealms[i].toString());
+            }
+
+            plugin.setUserRealms(configuredRealms);
+
+            //do any other configuration required by the
+            //particular Jetty version
+            finishConfigurationBeforeStart();
+
+            // start Jetty
+            server.start();
+
+            logger.info("Started Jetty Server");
+
+            if (getStopPort() != null && getStopPort() > 0 && getStopKey() != null) {
+                Monitor monitor = new Monitor(getStopPort(), getStopKey(),
+                        new Server[]{(Server) server.getProxiedObject()}, !daemon);
+                monitor.start();
+            }
+
+            // start the scanner thread (if necessary) on the main webapp
+            configureScanner();
+            startScanner();
+
+            // start the new line scanner thread if necessary
+            startConsoleScanner();
+
+            // keep the thread going if not in daemon mode
+            if (!daemon) {
+                server.join();
+            }
+        } catch (Exception e) {
+            throw new GradleException("An error occurred starting the Jetty server.", e);
+        } finally {
+            if (!daemon) {
+                logger.info("Jetty server exiting.");
+            }
+        }
+    }
+
+    public abstract void restartWebApp(boolean reconfigureScanner) throws Exception;
+
+    /**
+     * Subclasses should invoke this to setup basic info on the webapp
+     */
+    public void configureWebApplication() throws Exception {
+        //use EITHER a <webAppConfig> element or the now deprecated <contextPath>, <webDefaultXml>, <overrideWebXml>
+        //way of doing things
+        if (webAppConfig == null) {
+            webAppConfig = new JettyPluginWebAppContext();
+        }
+        webAppConfig.setContextPath(getContextPath().startsWith("/") ? getContextPath() : "/" + getContextPath());
+        if (getTemporaryDir() != null) {
+            webAppConfig.setTempDirectory(getTemporaryDir());
+        }
+        if (getWebDefaultXml() != null) {
+            webAppConfig.setDefaultsDescriptor(getWebDefaultXml().getCanonicalPath());
+        }
+        if (getOverrideWebXml() != null) {
+            webAppConfig.setOverrideDescriptor(getOverrideWebXml().getCanonicalPath());
+        }
+
+        // Don't treat JCL or Log4j as system classes
+        Set<String> systemClasses = new LinkedHashSet<String>(Arrays.asList(webAppConfig.getSystemClasses()));
+        systemClasses.remove("org.apache.commons.logging.");
+        systemClasses.remove("org.apache.log4j.");
+        webAppConfig.setSystemClasses(systemClasses.toArray(new String[systemClasses.size()]));
+
+        webAppConfig.setParentLoaderPriority(false);
+
+        logger.info("Context path = " + webAppConfig.getContextPath());
+        logger.info("Tmp directory = " + " determined at runtime");
+        logger.info("Web defaults = " + (webAppConfig.getDefaultsDescriptor() == null ? " jetty default"
+                : webAppConfig.getDefaultsDescriptor()));
+        logger.info("Web overrides = " + (webAppConfig.getOverrideDescriptor() == null ? " none"
+                : webAppConfig.getOverrideDescriptor()));
+    }
+
+    /**
+     * Run a scanner thread on the given list of files and directories, calling stop/start on the given list of
+     * LifeCycle objects if any of the watched files change.
+     */
+    private void startScanner() {
+
+        // check if scanning is enabled
+        if (getScanIntervalSeconds() <= 0) {
+            return;
+        }
+
+        // check if reload is manual. It disables file scanning
+        if ("manual".equalsIgnoreCase(reload)) {
+            // issue a warning if both scanIntervalSeconds and reload
+            // are enabled
+            logger.warn("scanIntervalSeconds is set to " + scanIntervalSeconds
+                    + " but will be IGNORED due to manual reloading");
+            return;
+        }
+
+        scanner.setReportExistingFilesOnStartup(false);
+        scanner.setScanInterval(getScanIntervalSeconds());
+        scanner.setRecursive(true);
+        List listeners = getScannerListeners();
+        Iterator itor = listeners == null ? null : listeners.iterator();
+        while (itor != null && itor.hasNext()) {
+            scanner.addListener((Scanner.Listener) itor.next());
+        }
+        logger.info("Starting scanner at interval of " + getScanIntervalSeconds() + " seconds.");
+        scanner.start();
+    }
+
+    /**
+     * Run a thread that monitors the console input to detect ENTER hits.
+     */
+    protected void startConsoleScanner() {
+        if ("manual".equalsIgnoreCase(reload)) {
+            logger.info("Console reloading is ENABLED. Hit ENTER on the console to restart the context.");
+            consoleScanner = new ConsoleScanner(this);
+            consoleScanner.start();
+        }
+    }
+
+    /**
+     * Try and find a jetty-web.xml file, using some historical naming conventions if necessary.
+     *
+     * @return File object to the location of the jetty-web.xml
+     */
+    public File findJettyWebXmlFile(File webInfDir) {
+        if (webInfDir == null) {
+            return null;
+        }
+        if (!webInfDir.exists()) {
+            return null;
+        }
+
+        File f = new File(webInfDir, "jetty-web.xml");
+        if (f.exists()) {
+            return f;
+        }
+
+        //try some historical alternatives
+        f = new File(webInfDir, "web-jetty.xml");
+        if (f.exists()) {
+            return f;
+        }
+        f = new File(webInfDir, "jetty6-web.xml");
+        if (f.exists()) {
+            return f;
+        }
+
+        return null;
+    }
+
+    @InputFile
+    @Optional
+    public File getWebDefaultXml() {
+        return webDefaultXml;
+    }
+
+    public void setWebDefaultXml(File webDefaultXml) {
+        this.webDefaultXml = webDefaultXml;
+    }
+
+    @InputFile
+    @Optional
+    public File getOverrideWebXml() {
+        return overrideWebXml;
+    }
+
+    public void setOverrideWebXml(File overrideWebXml) {
+        this.overrideWebXml = overrideWebXml;
+    }
+
+    public int getScanIntervalSeconds() {
+        return scanIntervalSeconds;
+    }
+
+    public void setScanIntervalSeconds(int scanIntervalSeconds) {
+        this.scanIntervalSeconds = scanIntervalSeconds;
+    }
+
+    public String getContextPath() {
+        return contextPath;
+    }
+
+    public void setContextPath(String contextPath) {
+        this.contextPath = contextPath;
+    }
+
+    public JettyPluginWebAppContext getWebAppConfig() {
+        return webAppConfig;
+    }
+
+    public void setWebAppConfig(JettyPluginWebAppContext webAppConfig) {
+        this.webAppConfig = webAppConfig;
+    }
+
+    public String getReload() {
+        return reload;
+    }
+
+    public void setReload(String reload) {
+        this.reload = reload;
+    }
+
+    @InputFile
+    @Optional
+    public File getJettyConfig() {
+        return jettyConfig;
+    }
+
+    public void setJettyConfig(File jettyConfig) {
+        this.jettyConfig = jettyConfig;
+    }
+
+    public Integer getStopPort() {
+        return stopPort;
+    }
+
+    public void setStopPort(Integer stopPort) {
+        this.stopPort = stopPort;
+    }
+
+    public String getStopKey() {
+        return stopKey;
+    }
+
+    public void setStopKey(String stopKey) {
+        this.stopKey = stopKey;
+    }
+
+    public boolean isDaemon() {
+        return daemon;
+    }
+
+    public void setDaemon(boolean daemon) {
+        this.daemon = daemon;
+    }
+
+    public Integer getHttpPort() {
+        return httpPort;
+    }
+
+    public void setHttpPort(Integer httpPort) {
+        this.httpPort = httpPort;
+    }
+
+    public Connector[] getConnectors() {
+        return connectors;
+    }
+
+    public void setConnectors(Connector[] connectors) {
+        this.connectors = connectors;
+    }
+
+    public UserRealm[] getUserRealms() {
+        return userRealms;
+    }
+
+    public void setUserRealms(UserRealm[] userRealms) {
+        this.userRealms = userRealms;
+    }
+
+    public RequestLog getRequestLog() {
+        return requestLog;
+    }
+
+    public void setRequestLog(RequestLog requestLog) {
+        this.requestLog = requestLog;
+    }
+
+    @InputFiles
+    public Iterable<File> getAdditionalRuntimeJars() {
+        return additionalRuntimeJars;
+    }
+
+    public void setAdditionalRuntimeJars(Iterable<File> additionalRuntimeJars) {
+        this.additionalRuntimeJars = additionalRuntimeJars;
+    }
+}
diff --git a/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/AbstractJettyRunWarTask.java b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/AbstractJettyRunWarTask.java
new file mode 100644
index 0000000..e8fdd3f
--- /dev/null
+++ b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/AbstractJettyRunWarTask.java
@@ -0,0 +1,48 @@
+/*
+ * 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.plugins.jetty;
+
+import org.gradle.api.plugins.jetty.internal.Jetty6PluginServer;
+import org.mortbay.xml.XmlConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AbstractJettyRunWarTask extends AbstractJettyRunTask {
+    private static Logger logger = LoggerFactory.getLogger(AbstractJettyRunWarTask.class);
+
+    public void applyJettyXml() throws Exception {
+
+        if (getJettyConfig() == null) {
+            return;
+        }
+
+        logger.info("Configuring Jetty from xml configuration file = {}", getJettyConfig());
+        XmlConfiguration xmlConfiguration = new XmlConfiguration(getJettyConfig().toURI().toURL());
+        xmlConfiguration.configure(getServer().getProxiedObject());
+    }
+
+
+    /**
+     * @see AbstractJettyRunTask#createServer()
+     */
+    public org.gradle.api.plugins.jetty.internal.JettyPluginServer createServer() throws Exception {
+        return new Jetty6PluginServer();
+    }
+
+
+
+}
diff --git a/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/JettyPlugin.java b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/JettyPlugin.java
new file mode 100644
index 0000000..e7a2bd8
--- /dev/null
+++ b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/JettyPlugin.java
@@ -0,0 +1,166 @@
+/*
+ * 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.jetty;
+
+import org.gradle.api.Action;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.internal.IConventionAware;
+import org.gradle.api.plugins.*;
+import org.gradle.api.tasks.ConventionValue;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.bundling.War;
+
+import java.io.File;
+
+/**
+ * <p>A {@link Plugin} which extends the {@link WarPlugin} to add tasks which run the web application using an embedded
+ * Jetty web container.</p>
+ *
+ * @author Hans Dockter
+ */
+public class JettyPlugin implements Plugin<Project> {
+    public static final String JETTY_RUN = "jettyRun";
+    public static final String JETTY_RUN_WAR = "jettyRunWar";
+    public static final String JETTY_STOP = "jettyStop";
+
+    public static final String RELOAD_AUTOMATIC = "automatic";
+    public static final String RELOAD_MANUAL = "manual";
+
+    public void apply(Project project) {
+        project.getPlugins().apply(WarPlugin.class);
+        JettyPluginConvention jettyConvention = new JettyPluginConvention();
+        Convention convention = project.getConvention();
+        convention.getPlugins().put("jetty", jettyConvention);
+
+        configureMappingRules(project, jettyConvention);
+        configureJettyRun(project);
+        configureJettyRunWar(project);
+        configureJettyStop(project, jettyConvention);
+    }
+
+    private void configureMappingRules(final Project project, final JettyPluginConvention jettyConvention) {
+        project.getTasks().withType(AbstractJettyRunTask.class).whenTaskAdded(new Action<AbstractJettyRunTask>() {
+            public void execute(AbstractJettyRunTask abstractJettyRunTask) {
+                configureAbstractJettyTask(project, jettyConvention, abstractJettyRunTask);
+            }
+        });
+    }
+
+    private void configureJettyRunWar(final Project project) {
+        project.getTasks().withType(JettyRunWar.class).whenTaskAdded(new Action<JettyRunWar>() {
+            public void execute(JettyRunWar jettyRunWar) {
+                jettyRunWar.dependsOn(WarPlugin.WAR_TASK_NAME);
+                jettyRunWar.getConventionMapping().map("webApp", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return ((War) project.getTasks().getByName(WarPlugin.WAR_TASK_NAME)).getArchivePath();
+                    }
+                });
+            }
+        });
+
+        JettyRunWar jettyRunWar = project.getTasks().add(JETTY_RUN_WAR, JettyRunWar.class);
+        jettyRunWar.setDescription("Assembles the webapp into a war and deploys it to Jetty.");
+        jettyRunWar.setGroup(WarPlugin.WEB_APP_GROUP);
+    }
+
+    private void configureJettyStop(Project project, final JettyPluginConvention jettyConvention) {
+        JettyStop jettyStop = project.getTasks().add(JETTY_STOP, JettyStop.class);
+        jettyStop.setDescription("Stops Jetty.");
+        jettyStop.setGroup(WarPlugin.WEB_APP_GROUP);
+        jettyStop.getConventionMapping().map("stopPort", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return jettyConvention.getStopPort();
+            }
+        });
+        jettyStop.getConventionMapping().map("stopKey", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return jettyConvention.getStopKey();
+            }
+        });
+    }
+
+    private void configureJettyRun(final Project project) {
+        project.getTasks().withType(JettyRun.class).whenTaskAdded(new Action<JettyRun>() {
+            public void execute(JettyRun jettyRun) {
+                jettyRun.getConventionMapping().map("webXml", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return getWebXml(project);
+                    }
+                });
+                jettyRun.getConventionMapping().map("classpath", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return getJavaConvention(project).getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath();
+                    }
+                });
+                jettyRun.getConventionMapping().map("webAppSourceDirectory", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return getWarConvention(project).getWebAppDir();
+                    }
+                });
+            }
+        });
+
+        JettyRun jettyRun = project.getTasks().add(JETTY_RUN, JettyRun.class);
+        jettyRun.setDescription("Uses your files as and where they are and deploys them to Jetty.");
+        jettyRun.setGroup(WarPlugin.WEB_APP_GROUP);
+    }
+
+    private Object getWebXml(Project project) {
+        War war = (War) project.getTasks().getByName(WarPlugin.WAR_TASK_NAME);
+        File webXml;
+        if (war.getWebXml() != null) {
+            webXml = war.getWebXml();
+        } else {
+            webXml = new File(getWarConvention(project).getWebAppDir(), "WEB-INF/web.xml");
+        }
+        return webXml;
+    }
+
+    private void configureAbstractJettyTask(final Project project, final JettyPluginConvention jettyConvention, AbstractJettyRunTask jettyTask) {
+        jettyTask.setDaemon(false);
+        jettyTask.setReload(RELOAD_AUTOMATIC);
+        jettyTask.setScanIntervalSeconds(0);
+        jettyTask.getConventionMapping().map("contextPath", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return ((War) project.getTasks().getByName(WarPlugin.WAR_TASK_NAME)).getBaseName();
+            }
+        });
+        jettyTask.getConventionMapping().map("httpPort", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return jettyConvention.getHttpPort();
+            }
+        });
+        jettyTask.getConventionMapping().map("stopPort", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return jettyConvention.getStopPort();
+            }
+        });
+        jettyTask.getConventionMapping().map("stopKey", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return jettyConvention.getStopKey();
+            }
+        });
+    }
+
+    public JavaPluginConvention getJavaConvention(Project project) {
+        return project.getConvention().getPlugin(JavaPluginConvention.class);
+    }
+
+    public WarPluginConvention getWarConvention(Project project) {
+        return project.getConvention().getPlugin(WarPluginConvention.class);
+    }
+}
diff --git a/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/JettyPluginConvention.java b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/JettyPluginConvention.java
new file mode 100644
index 0000000..8e95c22
--- /dev/null
+++ b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/JettyPluginConvention.java
@@ -0,0 +1,49 @@
+/*
+ * 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.plugins.jetty;
+
+/**
+ * @author Hans Dockter
+ */
+public class JettyPluginConvention {
+    private Integer stopPort;
+    private String stopKey;
+    private Integer httpPort = 8080;
+
+    public Integer getHttpPort() {
+        return httpPort;
+    }
+
+    public void setHttpPort(Integer httpPort) {
+        this.httpPort = httpPort;
+    }
+
+    public Integer getStopPort() {
+        return stopPort;
+    }
+
+    public void setStopPort(Integer stopPort) {
+        this.stopPort = stopPort;
+    }
+
+    public String getStopKey() {
+        return stopKey;
+    }
+
+    public void setStopKey(String stopKey) {
+        this.stopKey = stopKey;
+    }
+}
diff --git a/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/JettyRun.java b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/JettyRun.java
new file mode 100644
index 0000000..32e4101
--- /dev/null
+++ b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/JettyRun.java
@@ -0,0 +1,457 @@
+/*
+ * 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.plugins.jetty;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.file.ConfigurableFileTree;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.plugins.jetty.internal.Jetty6PluginServer;
+import org.gradle.api.plugins.jetty.internal.JettyPluginServer;
+import org.gradle.api.tasks.InputDirectory;
+import org.gradle.api.tasks.InputFile;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.Optional;
+import org.mortbay.jetty.Handler;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.handler.ContextHandler;
+import org.mortbay.jetty.handler.ContextHandlerCollection;
+import org.mortbay.jetty.handler.HandlerCollection;
+import org.mortbay.resource.Resource;
+import org.mortbay.resource.ResourceCollection;
+import org.mortbay.util.Scanner;
+import org.mortbay.xml.XmlConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * <p>The {@code JettyRun} task deploys an exploded web application to an embedded Jetty web container, without first
+ * requiring that the web application be assembled into a war, saving time during the development cycle.</p>
+ *
+ * <p>Once started, the web container can be configured to run continuously, scanning for changes in the project and
+ * automatically performing a hot redeploy when necessary. This allows the developer to concentrate on coding changes to
+ * the project using their IDE of choice and have those changes immediately and transparently reflected in the running
+ * web container, eliminating development time that is wasted on rebuilding, reassembling and redeploying. </p>
+ *
+ * @author janb
+ */
+public class JettyRun extends AbstractJettyRunTask {
+    private static Logger logger = LoggerFactory.getLogger(JettyRun.class);
+
+    /**
+     * List of other contexts to set up. Optional.
+     */
+    private ContextHandler[] contextHandlers;
+
+    /**
+     * The location of a jetty-env.xml file. Optional.
+     */
+    private File jettyEnvXml;
+
+    /**
+     * The location of the web.xml file. If not set then it is assumed it is in ${basedir}/src/main/webapp/WEB-INF
+     */
+    private File webXml;
+
+    /**
+     * Root directory for all html/jsp etc files
+     */
+    private File webAppSourceDirectory;
+
+    /**
+     * List of files or directories to additionally periodically scan for changes. Optional.
+     */
+    private File[] scanTargets;
+
+    /**
+     * List of directories with ant-style <include> and <exclude> patterns for extra targets to periodically
+     * scan for changes. Can be used instead of, or in conjunction with <scanTargets>.Optional.
+     */
+    private ScanTargetPattern[] scanTargetPatterns;
+
+    /**
+     * jetty-env.xml as a File
+     */
+    private File jettyEnvXmlFile;
+
+    /**
+     * List of files on the classpath for the webapp
+     */
+    private List<File> classPathFiles;
+
+    /**
+     * Extra scan targets as a list
+     */
+    private List<File> extraScanTargets;
+
+    private FileCollection classpath;
+
+    public void validateConfiguration() {
+        // check the location of the static content/jsps etc
+        try {
+            if ((getWebAppSourceDirectory() == null) || !getWebAppSourceDirectory().exists()) {
+                throw new InvalidUserDataException("Webapp source directory "
+                        + (getWebAppSourceDirectory() == null ? "null" : getWebAppSourceDirectory().getCanonicalPath())
+                        + " does not exist");
+            } else {
+                logger.info("Webapp source directory = " + getWebAppSourceDirectory().getCanonicalPath());
+            }
+        } catch (IOException e) {
+            throw new InvalidUserDataException("Webapp source directory does not exist", e);
+        }
+
+        // check reload mechanic
+        if (!"automatic".equalsIgnoreCase(reload) && !"manual".equalsIgnoreCase(reload)) {
+            throw new InvalidUserDataException("invalid reload mechanic specified, must be 'automatic' or 'manual'");
+        } else {
+            logger.info("Reload Mechanic: " + reload);
+        }
+
+        // get the web.xml file if one has been provided, otherwise assume it is in the webapp src directory
+        if (getWebXml() == null) {
+            setWebXml(new File(new File(getWebAppSourceDirectory(), "WEB-INF"), "web.xml"));
+        }
+        logger.info("web.xml file = " + getWebXml());
+
+        //check if a jetty-env.xml location has been provided, if so, it must exist
+        if (getJettyEnvXml() != null) {
+            setJettyEnvXmlFile(jettyEnvXml);
+
+            try {
+                if (!getJettyEnvXmlFile().exists()) {
+                    throw new InvalidUserDataException("jetty-env.xml file does not exist at location " + jettyEnvXml);
+                } else {
+                    logger.info(" jetty-env.xml = " + getJettyEnvXmlFile().getCanonicalPath());
+                }
+            } catch (IOException e) {
+                throw new InvalidUserDataException("jetty-env.xml does not exist");
+            }
+        }
+
+        setExtraScanTargets(new ArrayList<File>());
+        if (scanTargets != null) {
+            for (File scanTarget : scanTargets) {
+                logger.info("Added extra scan target:" + scanTarget);
+                getExtraScanTargets().add(scanTarget);
+            }
+        }
+
+        if (scanTargetPatterns != null) {
+            for (ScanTargetPattern scanTargetPattern : scanTargetPatterns) {
+                ConfigurableFileTree files = getProject().fileTree(scanTargetPattern.getDirectory());
+                files.include(scanTargetPattern.getIncludes());
+                files.exclude(scanTargetPattern.getExcludes());
+                List<File> currentTargets = getExtraScanTargets();
+                if (currentTargets != null && !currentTargets.isEmpty()) {
+                    currentTargets.addAll(files.getFiles());
+                } else {
+                    setExtraScanTargets((List) files.asType(List.class));
+                }
+            }
+        }
+    }
+
+    public void configureWebApplication() throws Exception {
+        super.configureWebApplication();
+        setClassPathFiles(setUpClassPath());
+        if (getWebAppConfig().getWebXmlFile() == null) {
+            getWebAppConfig().setWebXmlFile(getWebXml());
+        }
+        if (getWebAppConfig().getJettyEnvXmlFile() == null) {
+            getWebAppConfig().setJettyEnvXmlFile(getJettyEnvXmlFile());
+        }
+        if (getWebAppConfig().getClassPathFiles() == null) {
+            getWebAppConfig().setClassPathFiles(getClassPathFiles());
+        }
+        if (getWebAppConfig().getWar() == null) {
+            getWebAppConfig().setWar(getWebAppSourceDirectory().getCanonicalPath());
+        }
+        logger.info("Webapp directory = " + getWebAppSourceDirectory().getCanonicalPath());
+
+        getWebAppConfig().configure();
+    }
+
+    public void configureScanner() {
+        // start the scanner thread (if necessary) on the main webapp
+        List<File> scanList = new ArrayList<File>();
+        scanList.add(getWebXml());
+        if (getJettyEnvXmlFile() != null) {
+            scanList.add(getJettyEnvXmlFile());
+        }
+        File jettyWebXmlFile = findJettyWebXmlFile(new File(getWebAppSourceDirectory(), "WEB-INF"));
+        if (jettyWebXmlFile != null) {
+            scanList.add(jettyWebXmlFile);
+        }
+        scanList.addAll(getExtraScanTargets());
+        scanList.add(getProject().getBuildFile());
+        scanList.addAll(getClassPathFiles());
+        getScanner().setScanDirs(scanList);
+        ArrayList listeners = new ArrayList();
+        listeners.add(new Scanner.BulkListener() {
+            public void filesChanged(List changes) {
+                try {
+                    boolean reconfigure = changes.contains(getProject().getBuildFile().getCanonicalPath());
+                    restartWebApp(reconfigure);
+                } catch (Exception e) {
+                    logger.error("Error reconfiguring/restarting webapp after change in watched files", e);
+                }
+            }
+        });
+        setScannerListeners(listeners);
+    }
+
+    public void restartWebApp(boolean reconfigureScanner) throws Exception {
+        logger.info("restarting " + getWebAppConfig());
+        logger.debug("Stopping webapp ...");
+        getWebAppConfig().stop();
+        logger.debug("Reconfiguring webapp ...");
+
+        validateConfiguration();
+        configureWebApplication();
+
+        // check if we need to reconfigure the scanner
+        if (reconfigureScanner) {
+            logger.info("Reconfiguring scanner ...");
+            List<File> scanList = new ArrayList<File>();
+            scanList.add(getWebXml());
+            if (getJettyEnvXmlFile() != null) {
+                scanList.add(getJettyEnvXmlFile());
+            }
+            scanList.addAll(getExtraScanTargets());
+            scanList.add(getProject().getBuildFile());
+            scanList.addAll(getClassPathFiles());
+            getScanner().setScanDirs(scanList);
+        }
+
+        logger.debug("Restarting webapp ...");
+        getWebAppConfig().start();
+        logger.info("Restart completed at " + new Date().toString());
+    }
+
+    private Set<File> getDependencyFiles() {
+        List<Resource> overlays = new ArrayList<Resource>();
+
+        Set<File> dependencies = getClasspath().getFiles();
+        logger.debug("Adding dependencies {} for WEB-INF/lib ", dependencies);
+
+        //todo incorporate overlays when our resolved dependencies provide type information
+//            if (artifact.getType().equals("war")) {
+//                try {
+//                    Resource r = Resource.newResource("jar:" + artifact.getFile().toURL().toString() + "!/");
+//                    overlays.add(r);
+//                    getExtraScanTargets().add(artifact.getFile());
+//                }
+//                catch (Exception e) {
+//                    throw new RuntimeException(e);
+//                }
+//                continue;
+//            }
+        if (!overlays.isEmpty()) {
+            try {
+                Resource resource = getWebAppConfig().getBaseResource();
+                ResourceCollection rc = new ResourceCollection();
+                if (resource == null) {
+                    // nothing configured, so we automagically enable the overlays                    
+                    int size = overlays.size() + 1;
+                    Resource[] resources = new Resource[size];
+                    resources[0] = Resource.newResource(getWebAppSourceDirectory().toURI().toURL());
+                    for (int i = 1; i < size; i++) {
+                        resources[i] = overlays.get(i - 1);
+                        logger.info("Adding overlay: " + resources[i]);
+                    }
+                    rc.setResources(resources);
+                } else {
+                    if (resource instanceof ResourceCollection) {
+                        // there was a preconfigured ResourceCollection ... append the artifact wars
+                        Resource[] old = ((ResourceCollection) resource).getResources();
+                        int size = old.length + overlays.size();
+                        Resource[] resources = new Resource[size];
+                        System.arraycopy(old, 0, resources, 0, old.length);
+                        for (int i = old.length, j = 0; i < size; i++, j++) {
+                            resources[i] = overlays.get(j);
+                            logger.info("Adding overlay: " + resources[i]);
+                        }
+                        rc.setResources(resources);
+                    } else {
+                        // baseResource was already configured w/c could be src/main/webapp
+                        if (!resource.isDirectory() && String.valueOf(resource.getFile()).endsWith(".war")) {
+                            // its a war                            
+                            resource = Resource.newResource("jar:" + resource.getURL().toString() + "!/");
+                        }
+                        int size = overlays.size() + 1;
+                        Resource[] resources = new Resource[size];
+                        resources[0] = resource;
+                        for (int i = 1; i < size; i++) {
+                            resources[i] = overlays.get(i - 1);
+                            logger.info("Adding overlay: " + resources[i]);
+                        }
+                        rc.setResources(resources);
+                    }
+                }
+                getWebAppConfig().setBaseResource(rc);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+        return dependencies;
+    }
+
+    private List<File> setUpClassPath() {
+        List<File> classPathFiles = new ArrayList<File>();
+
+        classPathFiles.addAll(getDependencyFiles());
+
+        if (logger.isDebugEnabled()) {
+            for (File classPathFile : classPathFiles) {
+                logger.debug("classpath element: " + classPathFile.getName());
+            }
+        }
+        return classPathFiles;
+    }
+
+    public void finishConfigurationBeforeStart() throws Exception {
+        Handler[] handlers = getConfiguredContextHandlers();
+        org.gradle.api.plugins.jetty.internal.JettyPluginServer plugin = getServer();
+        Server server = (Server) plugin.getProxiedObject();
+
+        HandlerCollection contexts = (HandlerCollection) server.getChildHandlerByClass(ContextHandlerCollection.class);
+        if (contexts == null) {
+            contexts = (HandlerCollection) server.getChildHandlerByClass(HandlerCollection.class);
+        }
+
+        for (int i = 0; (handlers != null) && (i < handlers.length); i++) {
+            contexts.addHandler(handlers[i]);
+        }
+    }
+
+    public void applyJettyXml() throws Exception {
+        if (getJettyConfig() == null) {
+            return;
+        }
+
+        logger.info("Configuring Jetty from xml configuration file = " + getJettyConfig());
+        XmlConfiguration xmlConfiguration = new XmlConfiguration(getJettyConfig().toURI().toURL());
+        xmlConfiguration.configure(getServer().getProxiedObject());
+    }
+
+    /**
+     * @see JettyRun#createServer()
+     */
+    public JettyPluginServer createServer() {
+        return new Jetty6PluginServer();
+    }
+
+    @InputFile
+    @Optional
+    public File getJettyEnvXml() {
+        return jettyEnvXml;
+    }
+
+    public void setJettyEnvXml(File jettyEnvXml) {
+        this.jettyEnvXml = jettyEnvXml;
+    }
+
+//    @InputFile @Optional
+
+    public File getWebXml() {
+        return webXml;
+    }
+
+    public void setWebXml(File webXml) {
+        this.webXml = webXml;
+    }
+
+    @InputDirectory
+    public File getWebAppSourceDirectory() {
+        return webAppSourceDirectory;
+    }
+
+    public void setWebAppSourceDirectory(File webAppSourceDirectory) {
+        this.webAppSourceDirectory = webAppSourceDirectory;
+    }
+
+    public File[] getScanTargets() {
+        return scanTargets;
+    }
+
+    public void setScanTargets(File[] scanTargets) {
+        this.scanTargets = scanTargets;
+    }
+
+    public List<File> getExtraScanTargets() {
+        return extraScanTargets;
+    }
+
+    public void setExtraScanTargets(List<File> extraScanTargets) {
+        this.extraScanTargets = extraScanTargets;
+    }
+
+    @InputFile
+    @Optional
+    public File getJettyEnvXmlFile() {
+        return jettyEnvXmlFile;
+    }
+
+    public void setJettyEnvXmlFile(File jettyEnvXmlFile) {
+        this.jettyEnvXmlFile = jettyEnvXmlFile;
+    }
+
+    public List<File> getClassPathFiles() {
+        return classPathFiles;
+    }
+
+    public void setClassPathFiles(List<File> classPathFiles) {
+        this.classPathFiles = classPathFiles;
+    }
+
+    public ScanTargetPattern[] getScanTargetPatterns() {
+        return scanTargetPatterns;
+    }
+
+    public void setScanTargetPatterns(ScanTargetPattern[] scanTargetPatterns) {
+        this.scanTargetPatterns = scanTargetPatterns;
+    }
+
+    /**
+     * @return Returns the contextHandlers.
+     */
+    public ContextHandler[] getConfiguredContextHandlers() {
+        return this.contextHandlers;
+    }
+
+    public void setContextHandlers(ContextHandler[] contextHandlers) {
+        this.contextHandlers = contextHandlers;
+    }
+
+    /**
+     * Returns the classpath for the web application.
+     */
+    @InputFiles
+    public FileCollection getClasspath() {
+        return classpath;
+    }
+
+    /**
+     * Set the classpath for the web application
+     */
+    public void setClasspath(FileCollection classpath) {
+        this.classpath = classpath;
+    }
+}
diff --git a/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/JettyRunWar.java b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/JettyRunWar.java
new file mode 100644
index 0000000..e2fc531
--- /dev/null
+++ b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/JettyRunWar.java
@@ -0,0 +1,117 @@
+/*
+ * 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.jetty;
+
+import org.mortbay.util.Scanner;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.gradle.api.tasks.InputFile;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>The {@code JettyRunWar} deploys a WAR to an embedded Jetty web container.</p>
+ *
+ * <p> Once started, the web container can be configured to run continuously, scanning for changes to the war file and
+ * automatically performing a hot redeploy when necessary. </p>
+ */
+public class JettyRunWar extends AbstractJettyRunWarTask {
+    private static Logger logger = LoggerFactory.getLogger(JettyRunWar.class);
+
+    /**
+     * The location of the war file.
+     */
+    private File webApp;
+
+    public void configureWebApplication() throws Exception {
+        super.configureWebApplication();
+
+        getWebAppConfig().setWar(getWebApp().getCanonicalPath());
+        getWebAppConfig().configure();
+    }
+
+
+    /**
+     * @see AbstractJettyRunTask#validateConfiguration()
+     */
+    public void validateConfiguration() {
+    }
+
+    /* (non-Javadoc)
+    * @see org.mortbay.jetty.plugin.util.AbstractJettyTask#configureScanner()
+    */
+    public void configureScanner() {
+        List<File> scanList = new ArrayList<File>();
+        scanList.add(getProject().getBuildFile());
+        scanList.add(getWebApp());
+        getScanner().setScanDirs(scanList);
+
+        ArrayList listeners = new ArrayList();
+        listeners.add(new Scanner.BulkListener() {
+            public void filesChanged(List changes) {
+                try {
+                    boolean reconfigure = changes.contains(getProject().getBuildFile().getCanonicalPath());
+                    restartWebApp(reconfigure);
+                }
+                catch (Exception e) {
+                    logger.error("Error reconfiguring/restarting webapp after change in watched files", e);
+                }
+            }
+        });
+        setScannerListeners(listeners);
+    }
+
+    public void restartWebApp(boolean reconfigureScanner) throws Exception {
+        logger.info("Restarting webapp ...");
+        logger.debug("Stopping webapp ...");
+        getWebAppConfig().stop();
+        logger.debug("Reconfiguring webapp ...");
+
+        validateConfiguration();
+
+        // check if we need to reconfigure the scanner
+        if (reconfigureScanner) {
+            logger.info("Reconfiguring scanner ...");
+            List<File> scanList = new ArrayList<File>();
+            scanList.add(getProject().getBuildFile());
+            scanList.add(getWebApp());
+            getScanner().setScanDirs(scanList);
+        }
+
+        logger.debug("Restarting webapp ...");
+        getWebAppConfig().start();
+        logger.info("Restart completed.");
+    }
+
+
+    /**
+     * @see AbstractJettyRunTask#finishConfigurationBeforeStart()
+     */
+    public void finishConfigurationBeforeStart() {
+    }
+
+    @InputFile
+    public File getWebApp() {
+        return webApp;
+    }
+
+    public void setWebApp(File webApp) {
+        this.webApp = webApp;
+    }
+}
diff --git a/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/JettyStop.java b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/JettyStop.java
new file mode 100644
index 0000000..fa213c0
--- /dev/null
+++ b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/JettyStop.java
@@ -0,0 +1,91 @@
+/*
+ * 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.plugins.jetty;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.internal.ConventionTask;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.OutputStream;
+import java.net.ConnectException;
+import java.net.InetAddress;
+import java.net.Socket;
+
+public class JettyStop extends ConventionTask {
+    private static Logger logger = LoggerFactory.getLogger(JettyStop.class);
+
+    private Integer stopPort;
+
+    private String stopKey;
+
+    @TaskAction
+    public void stop() {
+        if (getStopPort() == null) {
+            throw new InvalidUserDataException("Please specify a valid port");
+        }
+        if (getStopKey() == null) {
+            throw new InvalidUserDataException("Please specify a valid stopKey");
+        }
+
+        try {
+            Socket s = new Socket(InetAddress.getByName("127.0.0.1"), getStopPort());
+            s.setSoLinger(false, 0);
+
+            OutputStream out = s.getOutputStream();
+            out.write((getStopKey() + "\r\nstop\r\n").getBytes());
+            out.flush();
+            s.close();
+        } catch (ConnectException e) {
+            logger.info("Jetty not running!");
+        } catch (Exception e) {
+            logger.error("Exception during stopping", e);
+        }
+    }
+
+    /**
+     * Returns port to listen to stop jetty on sending stop command
+     */
+    public Integer getStopPort() {
+        return stopPort;
+    }
+
+    /**
+     * Sets port to listen to stop jetty on sending stop command
+     */
+    public void setStopPort(Integer stopPort) {
+        this.stopPort = stopPort;
+    }
+
+    /**
+     * Returns stop key.
+     *
+     * @see #setStopKey(String)
+     */
+    public String getStopKey() {
+        return stopKey;
+    }
+
+    /**
+     * Sets key to provide when stopping jetty on executing java -DSTOP.KEY=<stopKey> -DSTOP.PORT=<stopPort>
+     * -jar start.jar --stop
+     */
+    public void setStopKey(String stopKey) {
+        this.stopKey = stopKey;
+    }
+}
diff --git a/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/ScanTargetPattern.java b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/ScanTargetPattern.java
new file mode 100644
index 0000000..d7fe1b9
--- /dev/null
+++ b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/ScanTargetPattern.java
@@ -0,0 +1,65 @@
+/*
+ * 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.plugins.jetty;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+public class ScanTargetPattern
+{
+    private File directory;
+    private List includes = Collections.EMPTY_LIST;
+    private List excludes = Collections.EMPTY_LIST;
+
+    /**
+     * @return the _directory
+     */
+    public File getDirectory()
+    {
+        return directory;
+    }
+
+    /**
+     * @param directory the _directory to set
+     */
+    public void setDirectory(File directory)
+    {
+        this.directory = directory;
+    }
+    
+    public void setIncludes (List includes)
+    {
+        this.includes = includes;
+    }
+    
+    public void setExcludes(List excludes)
+    {
+        this.excludes = excludes;
+    }
+    
+    public List getIncludes()
+    {
+        return includes;
+    }
+    
+    public List getExcludes()
+    {
+        return excludes;
+    }
+
+}
diff --git a/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/ConsoleScanner.java b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/ConsoleScanner.java
new file mode 100644
index 0000000..e08d3fc
--- /dev/null
+++ b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/ConsoleScanner.java
@@ -0,0 +1,99 @@
+/*
+ * 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.plugins.jetty.internal;
+
+import java.io.IOException;
+
+import org.gradle.api.plugins.jetty.AbstractJettyRunTask;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ConsoleScanner extends Thread {
+    private static Logger logger = LoggerFactory.getLogger(ConsoleScanner.class);
+
+    private final AbstractJettyRunTask task;
+
+    public ConsoleScanner(AbstractJettyRunTask task) {
+        this.task = task;
+        setName("Console scanner");
+        setDaemon(true);
+    }
+
+    public void run() {
+        try {
+            while (true) {
+                checkSystemInput();
+                getSomeSleep();
+            }
+        } catch (IOException e) {
+            logger.warn("Error when checking console input.", e);
+        }
+    }
+
+    private void getSomeSleep() {
+        try {
+            Thread.sleep(500);
+        } catch (InterruptedException e) {
+            logger.debug("Error while sleeping.", e);
+        }
+    }
+
+    private void checkSystemInput() throws IOException {
+        while (System.in.available() > 0) {
+            int inputByte = System.in.read();
+            if (inputByte >= 0) {
+                char c = (char) inputByte;
+                if (c == '\n') {
+                    restartWebApp();
+                }
+            }
+        }
+    }
+
+    /**
+     * Skip buffered bytes of system console.
+     */
+    private void clearInputBuffer() {
+        try {
+            while (System.in.available() > 0) {
+                // System.in.skip doesn't work properly. I don't know why
+                long available = System.in.available();
+                for (int i = 0; i < available; i++) {
+                    if (System.in.read() == -1) {
+                        break;
+                    }
+                }
+            }
+        } catch (IOException e) {
+            logger.warn("Error discarding console input buffer", e);
+        }
+    }
+
+    private void restartWebApp() {
+        try {
+            task.restartWebApp(false);
+            // Clear input buffer to discard anything entered on the console
+            // while the application was being restarted.
+            clearInputBuffer();
+        } catch (Exception e) {
+            logger.error("Error reconfiguring/restarting webapp after a new line on the console", e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/Jetty6PluginServer.java b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/Jetty6PluginServer.java
new file mode 100644
index 0000000..708c164
--- /dev/null
+++ b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/Jetty6PluginServer.java
@@ -0,0 +1,164 @@
+/*
+ * 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.plugins.jetty.internal;
+
+import org.mortbay.jetty.Connector;
+import org.mortbay.jetty.Handler;
+import org.mortbay.jetty.RequestLog;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.handler.ContextHandlerCollection;
+import org.mortbay.jetty.handler.DefaultHandler;
+import org.mortbay.jetty.handler.HandlerCollection;
+import org.mortbay.jetty.handler.RequestLogHandler;
+import org.mortbay.jetty.nio.SelectChannelConnector;
+import org.mortbay.jetty.security.UserRealm;
+import org.mortbay.jetty.webapp.WebAppContext;
+import org.mortbay.resource.Resource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Jetty6PluginServer <p/> Jetty6 version of a wrapper for the Server class.
+ */
+public class Jetty6PluginServer implements JettyPluginServer {
+    private static final Logger LOGGER = LoggerFactory.getLogger(Jetty6PluginServer.class);
+
+    public static final int DEFAULT_MAX_IDLE_TIME = 30000;
+    private Server server;
+    private ContextHandlerCollection contexts; //the list of ContextHandlers
+    HandlerCollection handlers; //the list of lists of Handlers
+    private RequestLogHandler requestLogHandler; //the request log handler
+    private DefaultHandler defaultHandler; //default handler
+
+    private RequestLog requestLog; //the particular request log implementation
+
+    public Jetty6PluginServer() {
+        this.server = new Server();
+        this.server.setStopAtShutdown(true);
+        //make sure Jetty does not use URLConnection caches with the plugin
+        Resource.setDefaultUseCaches(false);
+    }
+
+    /**
+     * @see Jetty6PluginServer#setConnectors(Object[])
+     */
+    public void setConnectors(Object[] connectors) {
+        if (connectors == null || connectors.length == 0) {
+            return;
+        }
+
+        for (int i = 0; i < connectors.length; i++) {
+            Connector connector = (Connector) connectors[i];
+            LOGGER.debug("Setting Connector: " + connector.getClass().getName() + " on port " + connector.getPort());
+            this.server.addConnector(connector);
+        }
+    }
+
+    /**
+     * @see org.gradle.api.plugins.jetty.internal.JettyPluginServer#getConnectors()
+     */
+    public Object[] getConnectors() {
+        return this.server.getConnectors();
+    }
+
+    /**
+     * @see Jetty6PluginServer#setUserRealms(Object[])
+     */
+    public void setUserRealms(Object[] realms) throws Exception {
+        if (realms == null) {
+            return;
+        }
+
+        for (int i = 0; i < realms.length; i++) {
+            this.server.addUserRealm((UserRealm) realms[i]);
+        }
+    }
+
+    /**
+     * @see org.gradle.api.plugins.jetty.internal.JettyPluginServer#getUserRealms()
+     */
+    public Object[] getUserRealms() {
+        return this.server.getUserRealms();
+    }
+
+    public void setRequestLog(Object requestLog) {
+        this.requestLog = (RequestLog) requestLog;
+    }
+
+    public Object getRequestLog() {
+        return this.requestLog;
+    }
+
+    /**
+     * @see org.gradle.api.plugins.jetty.internal.JettyPluginServer#start()
+     */
+    public void start() throws Exception {
+        LOGGER.info("Starting jetty " + this.server.getClass().getPackage().getImplementationVersion() + " ...");
+        this.server.start();
+    }
+
+    /**
+     * @see org.gradle.api.plugins.jetty.internal.Proxy#getProxiedObject()
+     */
+    public Object getProxiedObject() {
+        return this.server;
+    }
+
+    /**
+     * @see Jetty6PluginServer#addWebApplication
+     */
+    public void addWebApplication(WebAppContext webapp) throws Exception {
+        contexts.addHandler(webapp);
+    }
+
+    /**
+     * Set up the handler structure to receive a webapp. Also put in a DefaultHandler so we get a nice page than a 404
+     * if we hit the root and the webapp's context isn't at root.
+     */
+    public void configureHandlers() throws Exception {
+        this.defaultHandler = new DefaultHandler();
+        this.requestLogHandler = new RequestLogHandler();
+        if (this.requestLog != null) {
+            this.requestLogHandler.setRequestLog(this.requestLog);
+        }
+
+        this.contexts = (ContextHandlerCollection) server.getChildHandlerByClass(ContextHandlerCollection.class);
+        if (this.contexts == null) {
+            this.contexts = new ContextHandlerCollection();
+            this.handlers = (HandlerCollection) server.getChildHandlerByClass(HandlerCollection.class);
+            if (this.handlers == null) {
+                this.handlers = new HandlerCollection();
+                this.server.setHandler(handlers);
+                this.handlers.setHandlers(new Handler[]{this.contexts, this.defaultHandler, this.requestLogHandler});
+            } else {
+                this.handlers.addHandler(this.contexts);
+            }
+        }
+    }
+
+    public Object createDefaultConnector(int port) throws Exception {
+        SelectChannelConnector connector = new SelectChannelConnector();
+        connector.setPort(port);
+        connector.setMaxIdleTime(DEFAULT_MAX_IDLE_TIME);
+
+        return connector;
+    }
+
+    public void join() throws Exception {
+        this.server.getThreadPool().join();
+    }
+}
diff --git a/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/JettyConfiguration.java b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/JettyConfiguration.java
new file mode 100644
index 0000000..a8ed54a
--- /dev/null
+++ b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/JettyConfiguration.java
@@ -0,0 +1,138 @@
+/*
+ * 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.plugins.jetty.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Iterator;
+import java.util.List;
+
+import org.mortbay.jetty.plus.annotation.InjectionCollection;
+import org.mortbay.jetty.plus.annotation.LifeCycleCallbackCollection;
+import org.mortbay.jetty.plus.annotation.RunAsCollection;
+import org.mortbay.jetty.plus.webapp.Configuration;
+import org.mortbay.jetty.servlet.FilterHolder;
+import org.mortbay.jetty.servlet.ServletHolder;
+import org.mortbay.jetty.webapp.WebAppContext;
+import org.mortbay.jetty.webapp.WebAppClassLoader;
+import org.mortbay.log.Log;
+import org.mortbay.util.LazyList;
+
+public class JettyConfiguration extends Configuration {
+    private List classPathFiles;
+    private File webXmlFile;
+
+    public JettyConfiguration() {
+        super();
+    }
+
+    public void setClassPathConfiguration(List classPathFiles) {
+        this.classPathFiles = classPathFiles;
+    }
+
+    public void setWebXml(File webXmlFile) {
+        this.webXmlFile = webXmlFile;
+    }
+
+    /**
+     * Set up the classloader for the webapp, using the various parts of the Maven project
+     *
+     * @see org.mortbay.jetty.webapp.Configuration#configureClassLoader()
+     */
+    public void configureClassLoader() throws Exception {
+        if (classPathFiles != null) {
+            Log.debug("Setting up classpath ...");
+
+            //put the classes dir and all dependencies into the classpath
+            Iterator itor = classPathFiles.iterator();
+            while (itor.hasNext()) {
+                ((WebAppClassLoader) getWebAppContext().getClassLoader()).addClassPath(
+                        ((File) itor.next()).getCanonicalPath());
+            }
+
+            if (Log.isDebugEnabled()) {
+                Log.debug("Classpath = " + LazyList.array2List(
+                        ((URLClassLoader) getWebAppContext().getClassLoader()).getURLs()));
+            }
+        } else {
+            super.configureClassLoader();
+        }
+    }
+
+    protected URL findWebXml() throws IOException {
+        //if an explicit web.xml file has been set (eg for jetty:run) then use it
+        if (webXmlFile != null && webXmlFile.exists()) {
+            return webXmlFile.toURI().toURL();
+        }
+
+        //if we haven't overridden location of web.xml file, use the
+        //standard way of finding it
+        Log.debug("Looking for web.xml file in WEB-INF");
+        return super.findWebXml();
+    }
+
+    public void parseAnnotations() throws Exception {
+        String v = System.getProperty("java.version");
+        String[] version = v.split("\\.");
+        if (version == null) {
+            Log.info("Unable to determine jvm version, annotations will not be supported");
+            return;
+        }
+        int major = Integer.parseInt(version[0]);
+        int minor = Integer.parseInt(version[1]);
+        if ((major >= 1) && (minor >= 5)) {
+            //TODO it would be nice to be able to re-use the parseAnnotations() method on 
+            //the org.mortbay.jetty.annotations.Configuration class, but it's too difficult?
+
+            //able to use annotations on on jdk1.5 and above
+            Class annotationParserClass = Thread.currentThread().getContextClassLoader().loadClass(
+                    "org.mortbay.jetty.annotations.AnnotationParser");
+            Method parseAnnotationsMethod = annotationParserClass.getMethod("parseAnnotations", WebAppContext.class,
+                    Class.class, RunAsCollection.class, InjectionCollection.class, LifeCycleCallbackCollection.class);
+
+            //look thru _servlets
+            Iterator itor = LazyList.iterator(_servlets);
+            while (itor.hasNext()) {
+                ServletHolder holder = (ServletHolder) itor.next();
+                Class servlet = getWebAppContext().loadClass(holder.getClassName());
+                parseAnnotationsMethod.invoke(null, getWebAppContext(), servlet, _runAsCollection, _injections,
+                        _callbacks);
+            }
+
+            //look thru _filters
+            itor = LazyList.iterator(_filters);
+            while (itor.hasNext()) {
+                FilterHolder holder = (FilterHolder) itor.next();
+                Class filter = getWebAppContext().loadClass(holder.getClassName());
+                parseAnnotationsMethod.invoke(null, getWebAppContext(), filter, null, _injections, _callbacks);
+            }
+
+            //look thru _listeners
+            itor = LazyList.iterator(_listeners);
+            while (itor.hasNext()) {
+                Object listener = itor.next();
+                parseAnnotationsMethod.invoke(null, getWebAppContext(), listener.getClass(), null, _injections,
+                        _callbacks);
+            }
+        } else {
+            Log.info("Annotations are not supported on jvms prior to jdk1.5");
+        }
+    }
+}
diff --git a/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/JettyPluginServer.java b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/JettyPluginServer.java
new file mode 100644
index 0000000..e1f5f30
--- /dev/null
+++ b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/JettyPluginServer.java
@@ -0,0 +1,49 @@
+/*
+ * 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.plugins.jetty.internal;
+
+import org.mortbay.jetty.webapp.WebAppContext;
+
+/**
+ * JettyPluginServer
+ *
+ *
+ * Type to hide differences in API for different versions of Jetty for Server class.
+ */
+public interface JettyPluginServer extends Proxy {
+    public void setRequestLog(Object requestLog);
+
+    public Object getRequestLog();
+
+    public void setConnectors(Object[] connectors) throws Exception;
+
+    public Object[] getConnectors();
+
+    public void setUserRealms(Object[] realms) throws Exception;
+
+    public Object[] getUserRealms();
+
+    public void configureHandlers() throws Exception;
+
+    public void addWebApplication(WebAppContext webapp) throws Exception;
+
+    public void start() throws Exception;
+
+    public Object createDefaultConnector(int port) throws Exception;
+
+    public void join() throws Exception;
+}
diff --git a/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/JettyPluginWebAppContext.java b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/JettyPluginWebAppContext.java
new file mode 100644
index 0000000..8aa8c8a
--- /dev/null
+++ b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/JettyPluginWebAppContext.java
@@ -0,0 +1,121 @@
+/*
+ * 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.plugins.jetty.internal;
+
+import java.io.File;
+import java.util.List;
+
+import org.mortbay.jetty.plus.webapp.EnvConfiguration;
+import org.mortbay.jetty.webapp.Configuration;
+import org.mortbay.jetty.webapp.JettyWebXmlConfiguration;
+import org.mortbay.jetty.webapp.TagLibConfiguration;
+import org.mortbay.jetty.webapp.WebAppContext;
+import org.mortbay.jetty.webapp.WebInfConfiguration;
+
+/**
+ * Jetty6PluginWebAppContext
+ */
+public class JettyPluginWebAppContext extends WebAppContext {
+    private List classpathFiles;
+    private File jettyEnvXmlFile;
+    private File webXmlFile;
+    private WebInfConfiguration webInfConfig = new WebInfConfiguration();
+    private EnvConfiguration envConfig = new EnvConfiguration();
+    private JettyConfiguration mvnConfig = new JettyConfiguration();
+    private JettyWebXmlConfiguration jettyWebConfig = new JettyWebXmlConfiguration();
+    private TagLibConfiguration tagConfig = new TagLibConfiguration();
+    private Configuration[] configs = new Configuration[]{
+            webInfConfig, envConfig, mvnConfig, jettyWebConfig, tagConfig
+    };
+
+    public JettyPluginWebAppContext() {
+        super();
+        setConfigurations(configs);
+    }
+
+    public void setClassPathFiles(List classpathFiles) {
+        this.classpathFiles = classpathFiles;
+    }
+
+    public List getClassPathFiles() {
+        return this.classpathFiles;
+    }
+
+    public void setWebXmlFile(File webXmlFile) {
+        this.webXmlFile = webXmlFile;
+    }
+
+    public File getWebXmlFile() {
+        return this.webXmlFile;
+    }
+
+    public void setJettyEnvXmlFile(File jettyEnvXmlFile) {
+        this.jettyEnvXmlFile = jettyEnvXmlFile;
+    }
+
+    public File getJettyEnvXmlFile() {
+        return this.jettyEnvXmlFile;
+    }
+
+    public void configure() {
+        setConfigurations(configs);
+        mvnConfig.setClassPathConfiguration(classpathFiles);
+        mvnConfig.setWebXml(webXmlFile);
+        try {
+            if (this.jettyEnvXmlFile != null) {
+                envConfig.setJettyEnvXml(this.jettyEnvXmlFile.toURI().toURL());
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        /*
+        Configuration[] configurations = getConfigurations();
+        for (int i=0;i<configurations.length; i++)
+        {
+            if (configurations[i] instanceof JettyConfiguration)
+            {
+                ((JettyConfiguration)configurations[i]).setClassPathConfiguration (classpathFiles);
+                ((JettyConfiguration)configurations[i]).setWebXml (webXmlFile);
+            }
+            else if (configurations[i] instanceof EnvConfiguration)
+            {
+                try
+                {
+                    if (this.jettyEnvXmlFile != null)
+                        ((EnvConfiguration)configurations[i]).setJettyEnvXml(this.jettyEnvXmlFile.toURL());
+                }
+                catch (Exception e)
+                {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+        */
+    }
+
+    public void doStart() throws Exception {
+        setShutdown(false);
+        super.doStart();
+    }
+
+    public void doStop() throws Exception {
+        setShutdown(true);
+        //just wait a little while to ensure no requests are still being processed
+        Thread.sleep(500L);
+        super.doStop();
+    }
+}
diff --git a/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/Monitor.java b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/Monitor.java
new file mode 100644
index 0000000..238bc70
--- /dev/null
+++ b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/Monitor.java
@@ -0,0 +1,123 @@
+/*
+ * 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.plugins.jetty.internal;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import org.mortbay.jetty.Server;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Monitor <p/> Listens for stop commands eg via mvn jetty:stop and causes jetty to stop either by exiting the jvm, or
+ * by stopping the Server instances. The choice of behaviour is controlled by either passing true (exit jvm) or false
+ * (stop Servers) in the constructor.
+ */
+public class Monitor extends Thread {
+    private static final Logger LOGGER = LoggerFactory.getLogger(Monitor.class);
+
+    private String key;
+    private Server[] servers;
+
+    ServerSocket serverSocket;
+    boolean kill;
+
+    public Monitor(int port, String key, Server[] servers, boolean kill) throws IOException {
+        if (port <= 0) {
+            throw new IllegalStateException("Bad stop port");
+        }
+        if (key == null) {
+            throw new IllegalStateException("Bad stop key");
+        }
+
+        this.key = key;
+        this.servers = servers;
+        this.kill = kill;
+        setDaemon(true);
+        setName("StopJettyPluginMonitor");
+        serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
+        serverSocket.setReuseAddress(true);
+    }
+
+    public void run() {
+        while (serverSocket != null) {
+            Socket socket = null;
+            try {
+                socket = serverSocket.accept();
+                socket.setSoLinger(false, 0);
+                LineNumberReader lin = new LineNumberReader(new InputStreamReader(socket.getInputStream()));
+
+                String key = lin.readLine();
+                if (!this.key.equals(key)) {
+                    continue;
+                }
+                String cmd = lin.readLine();
+                if ("stop".equals(cmd)) {
+                    try {
+                        socket.close();
+                    } catch (Exception e) {
+                        LOGGER.debug("Exception when stopping server", e);
+                    }
+                    try {
+                        socket.close();
+                    } catch (Exception e) {
+                        LOGGER.debug("Exception when stopping server", e);
+                    }
+                    try {
+                        serverSocket.close();
+                    } catch (Exception e) {
+                        LOGGER.debug("Exception when stopping server", e);
+                    }
+
+                    serverSocket = null;
+
+                    if (kill) {
+                        LOGGER.info("Killing Jetty");
+                        System.exit(0);
+                    } else {
+                        for (int i = 0; servers != null && i < servers.length; i++) {
+                            try {
+                                LOGGER.info("Stopping server " + i);
+                                servers[i].stop();
+                            } catch (Exception e) {
+                                LOGGER.error("Exception when stopping server", e);
+                            }
+                        }
+                    }
+                } else {
+                    LOGGER.info("Unsupported monitor operation");
+                }
+            } catch (Exception e) {
+                LOGGER.error("Exception during monitoring Server", e);
+            } finally {
+                if (socket != null) {
+                    try {
+                        socket.close();
+                    } catch (Exception e) {
+                        LOGGER.debug("Exception when stopping server", e);
+                    }
+                }
+                socket = null;
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/Proxy.java b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/Proxy.java
new file mode 100644
index 0000000..3a82b32
--- /dev/null
+++ b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/internal/Proxy.java
@@ -0,0 +1,27 @@
+/*
+ * 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.plugins.jetty.internal;
+
+/**
+ * Proxy
+ *
+ * Provides untyped access to an object of a particular jetty version.
+ */
+public interface Proxy {
+
+    public Object getProxiedObject();
+}
diff --git a/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/package-info.java b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/package-info.java
new file mode 100644
index 0000000..cbb61db
--- /dev/null
+++ b/subprojects/gradle-jetty/src/main/java/org/gradle/api/plugins/jetty/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * The Jetty {@link org.gradle.api.Plugin} implementation.
+ */
+package org.gradle.api.plugins.jetty;
\ No newline at end of file
diff --git a/subprojects/gradle-jetty/src/main/resources/META-INF/gradle-plugins/jetty.properties b/subprojects/gradle-jetty/src/main/resources/META-INF/gradle-plugins/jetty.properties
new file mode 100644
index 0000000..e6bbbdc
--- /dev/null
+++ b/subprojects/gradle-jetty/src/main/resources/META-INF/gradle-plugins/jetty.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+implementation-class=org.gradle.api.plugins.jetty.JettyPlugin
diff --git a/subprojects/gradle-jetty/src/test/groovy/org/gradle/api/plugins/jetty/JettyPluginTest.groovy b/subprojects/gradle-jetty/src/test/groovy/org/gradle/api/plugins/jetty/JettyPluginTest.groovy
new file mode 100644
index 0000000..219ba03
--- /dev/null
+++ b/subprojects/gradle-jetty/src/test/groovy/org/gradle/api/plugins/jetty/JettyPluginTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * 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.plugins.jetty
+
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.plugins.WarPlugin
+import org.gradle.util.HelperUtil
+import org.junit.Test
+import static org.gradle.util.Matchers.*
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+public class JettyPluginTest {
+    private final Project project = HelperUtil.createRootProject()
+
+    @Test
+    public void appliesWarPluginAndAddsConventionToProject() {
+        new JettyPlugin().apply(project)
+
+        assertTrue(project.getPlugins().hasPlugin(WarPlugin))
+
+        assertThat(project.convention.plugins.jetty, instanceOf(JettyPluginConvention))
+    }
+    
+    @Test
+    public void addsTasksToProject() {
+        new JettyPlugin().apply(project)
+
+        def task = project.tasks[JettyPlugin.JETTY_RUN]
+        assertThat(task, instanceOf(JettyRun))
+        assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
+        assertThat(task.httpPort, equalTo(project.httpPort))
+
+        task = project.tasks[JettyPlugin.JETTY_RUN_WAR]
+        assertThat(task, instanceOf(JettyRunWar))
+        assertThat(task, dependsOn(WarPlugin.WAR_TASK_NAME))
+        assertThat(task.httpPort, equalTo(project.httpPort))
+
+        task = project.tasks[JettyPlugin.JETTY_STOP]
+        assertThat(task, instanceOf(JettyStop))
+        assertThat(task.stopPort, equalTo(project.stopPort))
+    }
+
+    @Test
+    public void addsMappingToNewJettyTasks() {
+        new JettyPlugin().apply(project)
+
+        def task = project.tasks.add('customRun', JettyRun)
+        assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
+        assertThat(task.httpPort, equalTo(project.httpPort))
+
+        task = project.tasks.add('customWar', JettyRunWar)
+        assertThat(task, dependsOn(WarPlugin.WAR_TASK_NAME))
+        assertThat(task.httpPort, equalTo(project.httpPort))
+    }
+}
diff --git a/subprojects/gradle-launcher/launcher.gradle b/subprojects/gradle-launcher/launcher.gradle
new file mode 100644
index 0000000..23ddf02
--- /dev/null
+++ b/subprojects/gradle-launcher/launcher.gradle
@@ -0,0 +1,33 @@
+import org.gradle.build.startscripts.StartScriptsGenerator
+
+dependencies {
+    groovy libraries.groovy_depends
+
+    compile project(':core')
+    compile project(':ui')
+
+    compile libraries.slf4j_api
+
+    testCompile project(path: ':core', configuration: 'testFixtures')
+    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+}
+
+jar.manifest.mainAttributes('Main-Class': "org.gradle.launcher.GradleMain")
+jar.doFirst {
+    jar.manifest.mainAttributes('Class-Path': "${project(':core').jar.archivePath.name}")
+}
+
+task startScripts << {
+    ant.mkdir(dir: startScriptsDir)
+    StartScriptsGenerator.generate(jar.archiveName, startScriptsDir, 'gradle')
+}
+
+ideaModule {
+    whenConfigured { module ->
+        def runtimeProjects = rootProject.groovyProjects() - ([project] +
+                configurations.runtime.getAllDependencies(ProjectDependency).collect { it.dependencyProject })
+        runtimeProjects.each { groovyProject ->
+            module.dependencies.add(new org.gradle.plugins.idea.model.ModuleDependency(groovyProject.name, "RUNTIME"))
+        }
+    }
+}
diff --git a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/GradleMain.java b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/GradleMain.java
new file mode 100644
index 0000000..0d05514
--- /dev/null
+++ b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/GradleMain.java
@@ -0,0 +1,64 @@
+/*
+ * 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.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.DefaultClassPathRegistry;
+
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
+
+/**
+ * @author Steven Devijver, Hans Dockter
+ */
+public class GradleMain {
+
+    public static void main(String[] args) throws Exception {
+        String bootStrapDebugValue = System.getProperty("gradle.bootstrap.debug");
+        boolean bootStrapDebug = bootStrapDebugValue != null && !bootStrapDebugValue.toUpperCase().equals("FALSE");
+
+        processGradleUserHome(bootStrapDebug);
+
+        ClassPathRegistry classPathRegistry = new DefaultClassPathRegistry();
+        URL[] classpath = classPathRegistry.getClassPathUrls("GRADLE_RUNTIME");
+        ClassLoader parentClassloader = ClassLoader.getSystemClassLoader().getParent();
+        if (bootStrapDebug) {
+            System.out.println("Parent Classloader of new context classloader is: " + parentClassloader);
+            System.out.println("Adding the following files to new lib classloader: " + Arrays.toString(classpath));
+        }
+        URLClassLoader libClassLoader = new URLClassLoader(classpath, parentClassloader);
+        Thread.currentThread().setContextClassLoader(libClassLoader);
+        Class mainClass = libClassLoader.loadClass("org.gradle.launcher.Main");
+        Method mainMethod = mainClass.getMethod("main", String[].class);
+        mainMethod.invoke(null, new Object[]{args});
+    }
+
+    private static void processGradleUserHome(boolean bootStrapDebug) {
+        String gradleUserHome = System.getProperty("gradle.user.home");
+        if (gradleUserHome == null) {
+            gradleUserHome = System.getenv("GRADLE_USER_HOME");
+            if (gradleUserHome != null) {
+                System.setProperty("gradle.user.home", gradleUserHome);
+                if (bootStrapDebug) {
+                    System.out.println("Gradle User Home is declared by environment variable GRADLE_USER_HOME to: " + gradleUserHome);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/Main.java b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/Main.java
new file mode 100644
index 0000000..28d79e2
--- /dev/null
+++ b/subprojects/gradle-launcher/src/main/java/org/gradle/launcher/Main.java
@@ -0,0 +1,113 @@
+/*
+ * 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;
+
+import org.gradle.*;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.gradleplugin.userinterface.swing.standalone.BlockingApplication;
+import org.gradle.initialization.CommandLine2StartParameterConverter;
+import org.gradle.initialization.DefaultCommandLine2StartParameterConverter;
+import org.gradle.util.Clock;
+import org.gradle.util.GradleVersion;
+
+/**
+ * @author Hans Dockter
+ */
+public class Main {
+    private static Logger logger = Logging.getLogger(Main.class);
+
+    private final String[] args;
+    private BuildCompleter buildCompleter = new ProcessExitBuildCompleter();
+    private CommandLine2StartParameterConverter parameterConverter = new DefaultCommandLine2StartParameterConverter();
+
+    public Main(String[] args) {
+        this.args = args;
+    }
+
+    public static void main(String[] args) throws Throwable {
+        new Main(args).execute();
+    }
+
+    void setBuildCompleter(BuildCompleter buildCompleter) {
+        this.buildCompleter = buildCompleter;
+    }
+
+    public void setParameterConverter(CommandLine2StartParameterConverter parameterConverter) {
+        this.parameterConverter = parameterConverter;
+    }
+
+    public void execute() throws Exception {
+        Clock buildTimeClock = new Clock();
+
+        StartParameter startParameter = null;
+
+        try {
+            startParameter = parameterConverter.convert(args);
+        } catch (Exception e) {
+            System.err.println(e.getMessage());
+            parameterConverter.showHelp(System.err);
+            buildCompleter.exit(e);
+        }
+
+        if (startParameter.isShowHelp()) {
+            parameterConverter.showHelp(System.out);
+            buildCompleter.exit(null);
+        }
+
+        if (startParameter.isShowVersion()) {
+            System.out.println(new GradleVersion().prettyPrint());
+            buildCompleter.exit(null);
+        }
+
+        if (startParameter.isLaunchGUI()) {
+            try {
+                BlockingApplication.launchAndBlock();
+            } catch (Throwable e) {
+                logger.error("Failed to run the UI.", e);
+                buildCompleter.exit(e);
+            }
+
+            buildCompleter.exit(null);
+        }
+
+        BuildListener resultLogger = new BuildLogger(logger, buildTimeClock, startParameter);
+        try {
+            GradleLauncher gradleLauncher = GradleLauncher.newInstance(startParameter);
+
+            gradleLauncher.useLogger(resultLogger);
+
+            BuildResult buildResult = gradleLauncher.run();
+            if (buildResult.getFailure() != null) {
+                buildCompleter.exit(buildResult.getFailure());
+            }
+        } catch (Throwable e) {
+            resultLogger.buildFinished(new BuildResult(null, e));
+            buildCompleter.exit(e);
+        }
+        buildCompleter.exit(null);
+    }
+
+    public interface BuildCompleter {
+        void exit(Throwable failure);
+    }
+
+    private static class ProcessExitBuildCompleter implements BuildCompleter {
+        public void exit(Throwable failure) {
+            System.exit(failure == null ? 0 : 1);
+        }
+    }
+}
diff --git a/subprojects/gradle-launcher/src/test/groovy/org/gradle/launcher/MainTest.java b/subprojects/gradle-launcher/src/test/groovy/org/gradle/launcher/MainTest.java
new file mode 100644
index 0000000..281a556
--- /dev/null
+++ b/subprojects/gradle-launcher/src/test/groovy/org/gradle/launcher/MainTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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;
+
+import org.gradle.*;
+import org.gradle.initialization.CommandLine2StartParameterConverter;
+import org.gradle.util.HelperUtil;
+import static org.hamcrest.Matchers.*;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class MainTest {
+    private static final String[] TEST_ARGS = { "arg1", "arg2" };
+    private JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+
+    private void setUpGradle(final BuildResult buildResult, final StartParameter startParameter) {
+        final GradleLauncher gradleMockLauncher = context.mock(GradleLauncher.class);
+        final GradleLauncherFactory gradleLauncherFactoryMock = context.mock(GradleLauncherFactory.class);
+
+        GradleLauncher.injectCustomFactory(gradleLauncherFactoryMock);
+        context.checking(new Expectations() {{
+            one(gradleLauncherFactoryMock).newInstance(startParameter); will(returnValue(gradleMockLauncher));
+            one(gradleMockLauncher).useLogger(with(any(BuildLogger.class)));
+            one(gradleMockLauncher).run(); will(returnValue(buildResult));
+        }});
+    }
+
+    private void setUpMain(Main main, final StartParameter startParameter) {
+        final CommandLine2StartParameterConverter commandLine2StartParameterConverterStub =
+                context.mock(CommandLine2StartParameterConverter.class);
+        main.setParameterConverter(commandLine2StartParameterConverterStub);
+        main.setBuildCompleter(new Main.BuildCompleter() {
+            public void exit(Throwable failure) {
+                throw new BuildCompletedError(failure);
+            }
+        });
+        context.checking(new Expectations() {{
+            allowing(commandLine2StartParameterConverterStub).convert(TEST_ARGS);
+            will(returnValue(startParameter));    
+        }});
+    }
+
+
+    @Test
+    public void runBuild() throws Exception {
+        Main main = new Main(TEST_ARGS);
+        BuildResult buildResult = HelperUtil.createBuildResult(null);
+        StartParameter startParameter = new StartParameter();
+        setUpMain(main, startParameter);
+        setUpGradle(buildResult, startParameter);
+        try {
+            main.execute();
+            fail();
+        } catch (BuildCompletedError e) {
+            assertThat(e.getCause(), nullValue());
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+    }
+
+    @Test
+    public void runBuildWithFailure() throws Exception {
+        Main main = new Main(TEST_ARGS);
+        RuntimeException exception = new RuntimeException();
+        BuildResult buildResult = HelperUtil.createBuildResult(exception);
+        StartParameter startParameter = new StartParameter();
+        setUpMain(main, startParameter);
+        setUpGradle(buildResult, startParameter);
+        try {
+            main.execute();
+            fail();
+        } catch (BuildCompletedError e) {
+            assertThat((RuntimeException) (e.getCause().getCause()), sameInstance(exception));
+        }
+    }
+
+    @Test
+    public void showHelp() throws Exception {
+        final CommandLine2StartParameterConverter commandLine2StartParameterConverterMock =
+                context.mock(CommandLine2StartParameterConverter.class, "helpMock");
+        Main main = new Main(TEST_ARGS);
+        final StartParameter startParameter = new StartParameter();
+        startParameter.setShowHelp(true);
+        setUpMain(main, startParameter);
+        main.setParameterConverter(commandLine2StartParameterConverterMock);
+        context.checking(new Expectations() {{
+            one(commandLine2StartParameterConverterMock).convert(TEST_ARGS); will(returnValue(startParameter));
+            one(commandLine2StartParameterConverterMock).showHelp(System.out);
+        }});
+        try {
+            main.execute();
+            fail();
+        } catch (BuildCompletedError e) {
+            assertThat(e.getCause(), nullValue());
+        }
+    }
+
+    @Test
+    public void showVersion() throws Exception {
+        // This tests just that showVersion does not lead to running a build or throwing of an exception.
+        Main main = new Main(TEST_ARGS);
+        StartParameter startParameter = new StartParameter();
+        startParameter.setShowVersion(true);
+        setUpMain(main, startParameter);
+        try {
+            main.execute();
+            fail();
+        } catch (BuildCompletedError e) {
+            assertThat(e.getCause(), nullValue());
+        }
+    }
+
+    @Test
+    public void illegalCommandLineArgs() throws Exception {
+        final CommandLine2StartParameterConverter commandLine2StartParameterConverterMock =
+                context.mock(CommandLine2StartParameterConverter.class, "exceptionMock");
+        Main main = new Main(TEST_ARGS);
+        final CommandLineArgumentException conversionException = new CommandLineArgumentException("fail");
+        final StartParameter startParameter = new StartParameter();
+        setUpMain(main, startParameter);
+        main.setParameterConverter(commandLine2StartParameterConverterMock);
+        context.checking(new Expectations() {{
+            allowing(commandLine2StartParameterConverterMock).convert(TEST_ARGS); will(throwException(conversionException));
+            one(commandLine2StartParameterConverterMock).showHelp(System.err);
+        }});
+        try {
+            main.execute();
+            fail();
+        } catch (BuildCompletedError e) {
+            assertThat((CommandLineArgumentException) e.getCause(), sameInstance(conversionException));
+        }
+    }
+
+
+    private class BuildCompletedError extends Error {
+        public BuildCompletedError(Throwable failure) {
+            super(failure);
+        }
+    }
+}
+
diff --git a/subprojects/gradle-maven/maven.gradle b/subprojects/gradle-maven/maven.gradle
new file mode 100644
index 0000000..e853ad2
--- /dev/null
+++ b/subprojects/gradle-maven/maven.gradle
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+dependencies {
+    groovy libraries.groovy_depends
+
+    compile project(':core')
+    compile project(':plugins')
+    compile libraries.slf4j_api
+
+    testCompile project(path: ':core', configuration: 'testFixtures')
+    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+}
diff --git a/subprojects/gradle-maven/src/main/groovy/org/gradle/api/plugins/MavenPlugin.java b/subprojects/gradle-maven/src/main/groovy/org/gradle/api/plugins/MavenPlugin.java
new file mode 100644
index 0000000..d17fd92
--- /dev/null
+++ b/subprojects/gradle-maven/src/main/groovy/org/gradle/api/plugins/MavenPlugin.java
@@ -0,0 +1,127 @@
+/*
+ * 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.plugins;
+
+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.ResolverContainer;
+import org.gradle.api.artifacts.dsl.RepositoryHandler;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.internal.IConventionAware;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.tasks.ConventionValue;
+import org.gradle.api.tasks.Upload;
+import org.gradle.util.GUtil;
+import org.gradle.util.WrapUtil;
+
+import java.util.Map;
+
+/**
+ * <p>A {@link org.gradle.api.Plugin} which allows project artifacts to be deployed to a Maven repository, or installed
+ * to the local Maven cache.</p>
+ *
+ * @author Hans Dockter
+ */
+public class MavenPlugin implements Plugin<Project> {
+    public static final int COMPILE_PRIORITY = 300;
+    public static final int RUNTIME_PRIORITY = 200;
+    public static final int TEST_COMPILE_PRIORITY = 150;
+    public static final int TEST_RUNTIME_PRIORITY = 100;
+
+    public static final int PROVIDED_COMPILE_PRIORITY = COMPILE_PRIORITY + 100;
+    public static final int PROVIDED_RUNTIME_PRIORITY = COMPILE_PRIORITY + 150;
+
+    public static final String INSTALL_TASK_NAME = "install";
+
+    public void apply(final Project project) {
+        setConventionMapping(project);
+        addConventionObject(project);
+        PluginContainer plugins = project.getPlugins();
+        plugins.withType(JavaPlugin.class).allPlugins(new Action<JavaPlugin>() {
+            public void execute(JavaPlugin javaPlugin) {
+                configureJavaScopeMappings(project.getRepositories(), project.getConfigurations());
+                configureInstall(project);
+            }
+        });
+        plugins.withType(WarPlugin.class).allPlugins(new Action<WarPlugin>() {
+            public void execute(WarPlugin warPlugin) {
+                configureWarScopeMappings(project.getRepositories(), project.getConfigurations());
+            }
+        });
+    }
+
+    private void setConventionMapping(final Project project) {
+        Map mapping = GUtil.map(
+                "mavenPomDir", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return convention.getPlugin(MavenPluginConvention.class).getPomDir();
+                    }
+                },
+                "configurationContainer", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return project.getConfigurations();
+                    }
+                },
+                "fileResolver", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return ((ProjectInternal) project).getFileResolver();
+                    }
+                },
+                "mavenScopeMappings", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return convention.getPlugin(MavenPluginConvention.class).getConf2ScopeMappings();
+                    }
+                });
+        ((IConventionAware) project.getRepositories()).getConventionMapping().map(mapping);
+    }
+
+    private void addConventionObject(Project project) {
+        MavenPluginConvention mavenConvention = new MavenPluginConvention((ProjectInternal) project);
+        Convention convention = project.getConvention();
+        convention.getPlugins().put("maven", mavenConvention);
+    }
+
+    private void configureJavaScopeMappings(ResolverContainer resolverFactory, ConfigurationContainer configurations) {
+        resolverFactory.getMavenScopeMappings().addMapping(COMPILE_PRIORITY, configurations.getByName(JavaPlugin.COMPILE_CONFIGURATION_NAME),
+                Conf2ScopeMappingContainer.COMPILE);
+        resolverFactory.getMavenScopeMappings().addMapping(RUNTIME_PRIORITY, configurations.getByName(JavaPlugin.RUNTIME_CONFIGURATION_NAME),
+                Conf2ScopeMappingContainer.RUNTIME);
+        resolverFactory.getMavenScopeMappings().addMapping(TEST_COMPILE_PRIORITY, configurations.getByName(JavaPlugin.TEST_COMPILE_CONFIGURATION_NAME),
+                Conf2ScopeMappingContainer.TEST);
+        resolverFactory.getMavenScopeMappings().addMapping(TEST_RUNTIME_PRIORITY, configurations.getByName(JavaPlugin.TEST_RUNTIME_CONFIGURATION_NAME),
+                Conf2ScopeMappingContainer.TEST);
+    }
+
+    private void configureWarScopeMappings(ResolverContainer resolverContainer, ConfigurationContainer configurations) {
+        resolverContainer.getMavenScopeMappings().addMapping(PROVIDED_COMPILE_PRIORITY, configurations.getByName(WarPlugin.PROVIDED_COMPILE_CONFIGURATION_NAME),
+                Conf2ScopeMappingContainer.PROVIDED);
+        resolverContainer.getMavenScopeMappings().addMapping(PROVIDED_RUNTIME_PRIORITY, configurations.getByName(WarPlugin.PROVIDED_RUNTIME_CONFIGURATION_NAME),
+                Conf2ScopeMappingContainer.PROVIDED);
+    }
+
+    private void configureInstall(Project project) {
+        Upload installUpload = project.getTasks().add(INSTALL_TASK_NAME, Upload.class);
+        Configuration configuration = project.getConfigurations().getByName(Dependency.ARCHIVES_CONFIGURATION);
+        installUpload.dependsOn(configuration.getBuildArtifacts());
+        installUpload.setConfiguration(configuration);
+        installUpload.getRepositories().mavenInstaller(WrapUtil.toMap("name", RepositoryHandler.DEFAULT_MAVEN_INSTALLER_NAME));
+        installUpload.setDescription("Does a maven install of the archives artifacts into the local .m2 cache.");
+    }
+}
diff --git a/subprojects/gradle-maven/src/main/groovy/org/gradle/api/plugins/MavenPluginConvention.java b/subprojects/gradle-maven/src/main/groovy/org/gradle/api/plugins/MavenPluginConvention.java
new file mode 100644
index 0000000..8456a7f
--- /dev/null
+++ b/subprojects/gradle-maven/src/main/groovy/org/gradle/api/plugins/MavenPluginConvention.java
@@ -0,0 +1,76 @@
+/*
+ * 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.plugins;
+
+import groovy.lang.Closure;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.artifacts.maven.MavenPom;
+import org.gradle.api.internal.artifacts.publish.maven.DefaultMavenPom;
+import org.gradle.api.internal.artifacts.publish.maven.dependencies.DefaultConf2ScopeMappingContainer;
+import org.gradle.api.internal.artifacts.publish.maven.dependencies.DefaultExcludeRuleConverter;
+import org.gradle.api.internal.artifacts.publish.maven.dependencies.DefaultPomDependenciesConverter;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.util.ConfigureUtil;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class MavenPluginConvention {
+    private ProjectInternal project;
+    private String pomDirName = "poms";
+    private Conf2ScopeMappingContainer conf2ScopeMappings = new DefaultConf2ScopeMappingContainer();
+
+    public MavenPluginConvention(ProjectInternal project) {
+        this.project = project;
+    }
+
+    public String getPomDirName() {
+        return pomDirName;
+    }
+
+    public void setPomDirName(String pomDirName) {
+        this.pomDirName = pomDirName;
+    }
+
+    public Conf2ScopeMappingContainer getConf2ScopeMappings() {
+        return conf2ScopeMappings;
+    }
+
+    public void setConf2ScopeMappings(Conf2ScopeMappingContainer conf2ScopeMappings) {
+        this.conf2ScopeMappings = conf2ScopeMappings;
+    }
+
+    public File getPomDir() {
+        return project.getFileResolver().withBaseDir(project.getBuildDir()).resolve(pomDirName);
+    }
+
+    public MavenPom pom() {
+        return pom(null);
+    }
+
+    public MavenPom pom(Closure configureClosure) {
+        DefaultMavenPom pom = new DefaultMavenPom(project.getConfigurations(),
+                new DefaultConf2ScopeMappingContainer(conf2ScopeMappings.getMappings()),
+                new DefaultPomDependenciesConverter(new DefaultExcludeRuleConverter()),
+                ((ProjectInternal) project).getFileResolver());
+        pom.setGroupId(project.getGroup().toString());
+        pom.setArtifactId(project.getName());
+        pom.setVersion(project.getVersion().toString());
+        return ConfigureUtil.configure(configureClosure, pom);
+    }
+}
diff --git a/subprojects/gradle-maven/src/main/resources/META-INF/gradle-plugins/maven.properties b/subprojects/gradle-maven/src/main/resources/META-INF/gradle-plugins/maven.properties
new file mode 100644
index 0000000..8985224
--- /dev/null
+++ b/subprojects/gradle-maven/src/main/resources/META-INF/gradle-plugins/maven.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+implementation-class=org.gradle.api.plugins.MavenPlugin
diff --git a/subprojects/gradle-maven/src/test/groovy/org/gradle/api/plugins/MavenPluginConventionTest.groovy b/subprojects/gradle-maven/src/test/groovy/org/gradle/api/plugins/MavenPluginConventionTest.groovy
new file mode 100644
index 0000000..27f529d
--- /dev/null
+++ b/subprojects/gradle-maven/src/test/groovy/org/gradle/api/plugins/MavenPluginConventionTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * 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.internal.artifacts.publish.maven.DefaultMavenPom
+import org.gradle.api.internal.artifacts.publish.maven.dependencies.DefaultConf2ScopeMappingContainer
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+class MavenPluginConventionTest extends Specification {
+    DefaultProject project = HelperUtil.createRootProject()
+    MavenPluginConvention mavenPluginConvention = new MavenPluginConvention(project)
+
+    def pomShouldCreateMavenPom() {
+        mavenPluginConvention.conf2ScopeMappings = new DefaultConf2ScopeMappingContainer();
+        project.group = 'someGroup'
+        project.version = '1.0'
+        DefaultMavenPom mavenPom = mavenPluginConvention.pom()
+
+        expect:
+        !mavenPluginConvention.conf2ScopeMappings.is(mavenPom.scopeMappings)
+        mavenPluginConvention.conf2ScopeMappings == mavenPom.scopeMappings
+        mavenPom.mavenProject != null
+        mavenPom.pomDependenciesConverter != null
+        mavenPom.configurations.is(project.getConfigurations())
+        mavenPom.fileResolver = project.fileResolver
+        mavenPom.groupId == project.group
+        mavenPom.artifactId == project.name
+        mavenPom.version == project.version
+    }
+
+    def pomShouldCreateAndConfigureMavenPom() {
+        mavenPluginConvention.conf2ScopeMappings = new DefaultConf2ScopeMappingContainer();
+        DefaultMavenPom mavenPom = mavenPluginConvention.pom {
+            project {
+                inceptionYear '1999'
+            }
+        }
+
+        expect:
+        mavenPom.mavenProject.inceptionYear = '1999'
+
+    }
+}
diff --git a/subprojects/gradle-maven/src/test/groovy/org/gradle/api/plugins/MavenPluginTest.java b/subprojects/gradle-maven/src/test/groovy/org/gradle/api/plugins/MavenPluginTest.java
new file mode 100644
index 0000000..07f7568
--- /dev/null
+++ b/subprojects/gradle-maven/src/test/groovy/org/gradle/api/plugins/MavenPluginTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.plugins;
+
+import org.gradle.api.Task;
+import org.gradle.api.artifacts.ConfigurationContainer;
+import org.gradle.api.artifacts.maven.Conf2ScopeMapping;
+import org.gradle.api.artifacts.maven.Conf2ScopeMappingContainer;
+import org.gradle.api.internal.project.DefaultProject;
+import org.gradle.util.HelperUtil;
+import static org.gradle.util.WrapUtil.toSet;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class MavenPluginTest {
+    private final DefaultProject project = HelperUtil.createRootProject();
+    private final MavenPlugin mavenPlugin = new MavenPlugin();
+
+    @org.junit.Test
+    public void applyWithWarPlugin() {
+        project.getPlugins().apply(WarPlugin.class);
+        mavenPlugin.apply(project);
+        assertHasConfigurationAndMapping(project, WarPlugin.PROVIDED_COMPILE_CONFIGURATION_NAME, Conf2ScopeMappingContainer.PROVIDED,
+                MavenPlugin.PROVIDED_COMPILE_PRIORITY);
+        assertHasConfigurationAndMapping(project, WarPlugin.PROVIDED_RUNTIME_CONFIGURATION_NAME, Conf2ScopeMappingContainer.PROVIDED,
+                MavenPlugin.PROVIDED_RUNTIME_PRIORITY);
+
+        Task task = project.getTasks().getByName(MavenPlugin.INSTALL_TASK_NAME);
+        Set dependencies = task.getTaskDependencies().getDependencies(task);
+        assertThat(dependencies, equalTo((Set) toSet(project.getTasks().getByName(WarPlugin.WAR_TASK_NAME))));
+    }
+
+    private void assertHasConfigurationAndMapping(DefaultProject project, String configurationName, String scope, int priority) {
+        Conf2ScopeMappingContainer scopeMappingContainer = project.getRepositories().getMavenScopeMappings();
+        ConfigurationContainer configurationContainer = project.getConfigurations();
+        Conf2ScopeMapping mapping = scopeMappingContainer.getMappings().get(configurationContainer.getByName(configurationName));
+        assertThat(mapping.getScope(), equalTo(scope));
+        assertThat(mapping.getPriority(), equalTo(priority));
+    }
+
+    @org.junit.Test
+    public void applyWithJavaPlugin() {
+        project.getPlugins().apply(JavaPlugin.class);
+        mavenPlugin.apply(project);
+        assertHasConfigurationAndMapping(project, JavaPlugin.COMPILE_CONFIGURATION_NAME, Conf2ScopeMappingContainer.COMPILE,
+                MavenPlugin.COMPILE_PRIORITY);
+        assertHasConfigurationAndMapping(project, JavaPlugin.RUNTIME_CONFIGURATION_NAME, Conf2ScopeMappingContainer.RUNTIME,
+                MavenPlugin.RUNTIME_PRIORITY);
+        assertHasConfigurationAndMapping(project, JavaPlugin.TEST_COMPILE_CONFIGURATION_NAME, Conf2ScopeMappingContainer.TEST,
+                MavenPlugin.TEST_COMPILE_PRIORITY);
+        assertHasConfigurationAndMapping(project, JavaPlugin.TEST_RUNTIME_CONFIGURATION_NAME, Conf2ScopeMappingContainer.TEST,
+                MavenPlugin.TEST_RUNTIME_PRIORITY);
+
+        Task task = project.getTasks().getByName(MavenPlugin.INSTALL_TASK_NAME);
+        Set dependencies = task.getTaskDependencies().getDependencies(task);
+        assertEquals(dependencies, toSet(project.getTasks().getByName(JavaPlugin.JAR_TASK_NAME)));
+    }
+
+    @org.junit.Test
+    public void applyWithoutWarPlugin() {
+        mavenPlugin.apply(project);
+        assertThat(project.getConfigurations().findByName(WarPlugin.PROVIDED_COMPILE_CONFIGURATION_NAME),
+                nullValue());
+    }
+
+    @org.junit.Test
+    public void applyWithoutJavaPlugin() {
+        mavenPlugin.apply(project);
+        assertThat(project.getConfigurations().findByName(JavaPlugin.COMPILE_CONFIGURATION_NAME),
+                nullValue());
+    }
+}
diff --git a/subprojects/gradle-open-api/open-api.gradle b/subprojects/gradle-open-api/open-api.gradle
new file mode 100644
index 0000000..9fef018
--- /dev/null
+++ b/subprojects/gradle-open-api/open-api.gradle
@@ -0,0 +1,52 @@
+apply plugin: 'groovy'
+
+dependencies {
+    groovy libraries.groovy_depends
+    testCompile project(path: ':core', configuration: 'testFixtures')
+    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+
+    testCompile project(path: ':core', configuration: 'integTestFixtures')
+    testRuntime project(path: ':core', configuration: 'integTestFixturesRuntime')
+}
+
+//define our integration tests
+configurations {
+    integTestCompile {
+        extendsFrom testCompile
+    }
+    integTestRuntime {
+        extendsFrom integTestCompile, testRuntime
+    }
+}
+
+//define the source sets for our integration tests
+sourceSets {
+    integTest {
+        compileClasspath = sourceSets.test.classes + sourceSets.main.classes + configurations.integTestCompile + project(':ui').sourceSets.main.classes
+        runtimeClasspath = classes + compileClasspath + configurations.integTestRuntime
+    }
+}
+
+//create a task for running the integration tests
+task integTest(type: Test, dependsOn: [ rootProject.intTestImage ]) {
+    systemProperties['integTest.gradleHomeDir'] = rootProject.intTestImage.integTestGradleHome.absolutePath
+    systemProperties['integTest.srcDir'] = file('src').absolutePath
+    systemProperties['integTest.gradleUserHomeDir'] = rootProject.integTest.integTestUserDir.absolutePath
+    testClassesDir = sourceSets.integTest.classesDir
+    classpath = sourceSets.integTest.runtimeClasspath + configurations.integTestRuntime
+
+    doFirst { task -> systemProperties['integTest.gradleHomeDir'] = rootProject.intTestImage.integTestGradleHome.absolutePath }
+}
+
+ideaModule {
+    testSourceDirs.addAll(project.sourceSets.integTest.groovy.srcDirs)
+    whenConfigured { module ->
+        module.dependencies.add(new org.gradle.plugins.idea.model.ModuleDependency('ui', null))
+    }
+}
+
+eclipseClasspath {
+    whenConfigured { classpath ->
+        classpath.entries.add(new org.gradle.plugins.eclipse.model.ProjectDependency('/ui', true, null, [] as Set))
+    }
+}
diff --git a/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/GradleRunnerTest.groovy b/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/GradleRunnerTest.groovy
new file mode 100644
index 0000000..de3a16e
--- /dev/null
+++ b/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/GradleRunnerTest.groovy
@@ -0,0 +1,277 @@
+/*
+ * 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.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.openapi.external.runner.GradleRunnerFactory
+import org.gradle.openapi.external.runner.GradleRunnerInteractionVersion1
+import org.gradle.openapi.external.runner.GradleRunnerVersion1
+import org.gradle.openapi.wrappers.RunnerWrapperFactory
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+ at RunWith(DistributionIntegrationTestRunner.class)
+class GradleRunnerTest {
+
+  static final String JAVA_PROJECT_NAME = 'javaproject'
+  static final String SHARED_NAME = 'shared'
+  static final String API_NAME = 'api'
+  static final String WEBAPP_NAME = 'webservice'
+  static final String SERVICES_NAME = 'services'
+  static final String WEBAPP_PATH = "$SERVICES_NAME/$WEBAPP_NAME" as String
+
+  private File javaprojectDir
+
+  @Rule public final GradleDistribution dist = new GradleDistribution()
+  @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+  @Rule public final Sample sample = new Sample('java/quickstart')
+
+  @Before
+  void setUp() {
+      javaprojectDir = sample.dir
+  }
+
+  /**
+   * We just want to make sure we can instantiate a GradleRunner here. That's all
+  */
+  @Test
+  public void testInstantiation()
+  {
+    TestGradleRunnerInteractionVersion1 interaction = new TestGradleRunnerInteractionVersion1(javaprojectDir)
+
+    GradleRunnerVersion1 runner = GradleRunnerFactory.createGradleRunner(getClass().getClassLoader(), dist.getGradleHomeDir(), interaction, true)
+
+    Assert.assertNotNull( "Failed to instantiate runner", runner )
+  }
+
+  /**
+   * This does a basic execution. It also checks to make sure that the notifications were fired
+   * correctly.
+  */
+  @Test
+  public void testExecution()
+  {
+    TestGradleRunnerInteractionVersion1 interaction = new TestGradleRunnerInteractionVersion1( javaprojectDir )
+
+    GradleRunnerVersion1 runner = GradleRunnerFactory.createGradleRunner(getClass().getClassLoader(), dist.getGradleHomeDir(), interaction, true)
+
+    Assert.assertNotNull( "Failed to instantiate runner", runner )
+
+    runner.executeCommand( "clean build" )
+
+        //wait for it to complete
+    int totalWaitTime = 0;
+    int maximumWaitTime = 80
+    while ( !interaction.executionFinished && totalWaitTime <= maximumWaitTime ) {
+        try {
+            Thread.sleep(1000);
+        }
+        catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        totalWaitTime += 1;
+    }
+
+    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.\nCurrent project directory: '" + interaction.getWorkingDirectory() + "'\nOutput:\n" + interaction.output.toString() )
+    }
+
+    //now make sure we were notified of things correctly:
+
+    //it should have fired a message that execution has started
+    Assert.assertTrue( "Execution did not report started", interaction.executionStarted )
+    
+    //it should have finished
+    Assert.assertTrue( "Execution did not report finished", interaction.executionFinished )
+
+    //it should have been successful
+    Assert.assertTrue( "Did not execute command successfully", interaction.wasSuccessful )
+
+    //we should have output
+    Assert.assertTrue( "Missing output", interaction.output.length() > 0 )
+
+    //we should have a message when we finished (basically the full output)
+    Assert.assertTrue( "Missing finish message", interaction.finishMessage != null )
+
+    //there should have been multiple tasks to execute
+    Assert.assertTrue( "Not enough tasks executed. Expected multiple. Found " + interaction.numberOfTasksToExecute, interaction.numberOfTasksToExecute > 1 )
+
+    //we should have been notified that tasks started and completed (we're not interested in tracking how many times or specific tasks as that might change too often with releases of gradle.
+    Assert.assertTrue( "No tasks reported started", interaction.taskStarted )
+    Assert.assertTrue( "No tasks reported completed", interaction.taskCompleted )
+  }
+
+  /**
+   * This tests killing a task. We'll start a build task then kill it after it starts executing.
+   * Note: the kill interaction actually kills execution. It waits for a certain number of tasks
+   * to be executed.
+  */
+  @Test
+  public void testKill()
+  {
+    KillTestInteraction interaction = new KillTestInteraction(javaprojectDir)
+
+    GradleRunnerVersion1 runner = GradleRunnerFactory.createGradleRunner(getClass().getClassLoader(), dist.getGradleHomeDir(), interaction, true)
+
+    interaction.runner = runner
+
+    Assert.assertNotNull( "Failed to instantiate runner", runner )
+
+    runner.executeCommand( "build" )
+
+        //wait for it to complete
+    int totalWaitTime = 0;
+    int maximumWaitTime = 80
+    while ( !interaction.executionFinished && totalWaitTime <= maximumWaitTime ) {
+        try {
+            Thread.sleep(1000);
+        }
+        catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        totalWaitTime += 1;
+    }
+
+    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.\nCurrent project directory: '" + interaction.getWorkingDirectory() + "'\nOutput:\n" + interaction.output.toString() )
+    }
+
+    //make sure we tried to kill the task
+    Assert.assertTrue( "Did not attempt to kill execution", interaction.killedTask )
+
+    //now make sure we were notified of things correctly:
+
+    //it should NOT have been successful
+    Assert.assertFalse( "Erroneously executed successfully (was not killed)", interaction.wasSuccessful )
+
+    //it should have fired a message that execution has started
+    Assert.assertTrue( "Execution did not report started", interaction.executionStarted )
+
+    //it should have finished
+    Assert.assertTrue( "Execution did not report finished", interaction.executionFinished )
+  }
+   /**
+    * This directly verifies that our wrapper factory is working. We just want to use it to
+    * instantiate a runner.
+   */
+  @Test
+  public void testRunnerWrapperFactory()
+  {
+    TestGradleRunnerInteractionVersion1 interaction = new TestGradleRunnerInteractionVersion1( javaprojectDir )
+
+    GradleRunnerVersion1 runner = RunnerWrapperFactory.createGradleRunner( dist.getGradleHomeDir(), interaction, true)
+
+    Assert.assertNotNull( "Failed to instantiate runner", runner )
+  }
+}
+
+  //Inner class used to track what has been called
+  public class TestGradleRunnerInteractionVersion1 implements GradleRunnerInteractionVersion1
+  {
+    private File workingDirectory
+    private StringBuilder output = new StringBuilder()
+    private String finishMessage
+    boolean wasSuccessful
+    boolean executionStarted
+    int numberOfTasksToExecute
+    boolean executionFinished
+    boolean taskCompleted
+    boolean taskStarted
+
+
+    public TestGradleRunnerInteractionVersion1(File workingDirectory) {
+      this.workingDirectory = workingDirectory;
+    }
+
+    def TestGradleRunnerInteractionVersion1() {
+    }
+
+    File getWorkingDirectory() { return workingDirectory }
+
+    GradleRunnerInteractionVersion1.LogLevel getLogLevel() { return GradleRunnerInteractionVersion1.LogLevel.Lifecycle }
+
+    GradleRunnerInteractionVersion1.StackTraceLevel getStackTraceLevel() { return GradleRunnerInteractionVersion1.StackTraceLevel.InternalExceptions }
+
+    void reportExecutionStarted() { executionStarted = true }
+    void reportNumberOfTasksToExecute(int size) { numberOfTasksToExecute = size }
+
+    //both of these will be fired often. We're not going to try to track that.
+    void reportTaskStarted(String currentTaskName, float percentComplete) { taskStarted = true; }
+    void reportTaskComplete(String currentTaskName, float percentComplete) { taskCompleted = true }
+
+    void reportLiveOutput(String output) { this.output.append( output ) }
+
+    void reportExecutionFinished(boolean wasSuccessful, String message, Throwable throwable) {
+      this.executionFinished = true;
+      this.wasSuccessful = wasSuccessful
+      this.finishMessage = message;
+    }
+
+    File getCustomGradleExecutable() { return null; }
+  }
+
+
+  //class to track that has class was started and then kills it. 
+  private class KillTestInteraction implements GradleRunnerInteractionVersion1
+  {
+    private GradleRunnerVersion1 runner
+    int tasks = 0
+    boolean killedTask
+
+    //after at least 2 tasks start, try to kill the process. This simulates someone killing it while
+    //its in the middle of running
+    def void reportTaskStarted(String currentTaskName, float percentComplete) {
+      tasks++
+      if( tasks == 2 ) {
+        killedTask = true
+        runner.killProcess();
+      }
+    }
+    private File workingDirectory
+    boolean wasSuccessful
+    boolean executionStarted
+    boolean executionFinished
+
+    public KillTestInteraction(File workingDirectory) {
+      this.workingDirectory = workingDirectory;
+    }
+
+    File getWorkingDirectory() { return workingDirectory }
+
+    GradleRunnerInteractionVersion1.LogLevel getLogLevel() { return GradleRunnerInteractionVersion1.LogLevel.Lifecycle }
+
+    GradleRunnerInteractionVersion1.StackTraceLevel getStackTraceLevel() { return GradleRunnerInteractionVersion1.StackTraceLevel.InternalExceptions }
+
+    void reportExecutionStarted() { executionStarted = true }
+    void reportNumberOfTasksToExecute(int size) { }
+
+    void reportTaskComplete(String currentTaskName, float percentComplete) {}
+    void reportLiveOutput(String output) {}
+    File getCustomGradleExecutable() { return null; }
+
+    void reportExecutionFinished(boolean wasSuccessful, String message, Throwable throwable) {
+      this.executionFinished = true;
+      this.wasSuccessful = wasSuccessful
+    }
+  }
\ No newline at end of file
diff --git a/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/OpenApiUiTest.groovy b/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/OpenApiUiTest.groovy
new file mode 100644
index 0000000..5728219
--- /dev/null
+++ b/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/OpenApiUiTest.groovy
@@ -0,0 +1,1288 @@
+/*
+ * 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 java.awt.BorderLayout
+import java.awt.Color
+import java.awt.Component
+import java.awt.event.HierarchyEvent
+import java.awt.event.HierarchyListener
+import javax.swing.JFrame
+import javax.swing.JLabel
+import javax.swing.JPanel
+import javax.swing.SwingUtilities
+import junit.framework.AssertionFailedError
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.openapi.external.ExternalUtility
+import org.gradle.openapi.external.foundation.favorites.FavoriteTaskVersion1
+import org.gradle.openapi.external.foundation.favorites.FavoritesEditorVersion1
+import org.gradle.openapi.wrappers.UIWrapperFactory
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.gradle.openapi.external.foundation.*
+import org.gradle.openapi.external.ui.*
+
+/**
+ * This tests numerous aspects of the Open API UI. This is how the Idea plugin extracts the UI from
+ * Gradle.
+ */
+ at RunWith(DistributionIntegrationTestRunner.class)
+public class OpenApiUiTest {
+  static final String JAVA_PROJECT_NAME = 'javaproject'
+  static final String SHARED_NAME = 'shared'
+  static final String API_NAME = 'api'
+  static final String WEBAPP_NAME = 'webservice'
+  static final String SERVICES_NAME = 'services'
+  static final String WEBAPP_PATH = "$SERVICES_NAME/$WEBAPP_NAME" as String
+
+  private File javaprojectDir
+
+  @Rule public final GradleDistribution dist = new GradleDistribution()
+  @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+  @Rule public final Sample sample = new Sample('java/quickstart')
+
+  @Before
+  void setUp() {
+      javaprojectDir = sample.dir
+  }
+
+     /**
+      This tests to see if we can call the UIFactory to create a single pane UI.
+      This is only testing that extracting the UI returns something without giving
+      errors and that it has a component. This is just a good general-case test
+      to make sure teh basics are working.
+      */
+    @Test
+    public void testSinglePaneBasic()
+    {
+        SinglePaneUIVersion1 singlePane = createSinglePaneUI()
+
+        //make sure we got something
+        Assert.assertNotNull( singlePane )
+
+        //tell it we're about to show it, so it'll create a component
+        singlePane.aboutToShow();
+
+        //make sure we now have that component
+        Assert.assertNotNull( singlePane.getComponent() )
+    }
+
+  /**
+   * This is the same as testSinglePaneBasic but uses the UIWrapperFactory directly;
+   * which is what the UIFactory uses via reflection to instantiate the UI. Just
+   * making sure this is working normally and not throwing any exceptions.
+   */
+   @Test
+   public void testUIWrapperSinglePane()
+   {
+     TestSingleDualPaneUIInteractionVersion1 testSingleDualPaneUIInteractionVersion1 = new TestSingleDualPaneUIInteractionVersion1( new TestAlternateUIInteractionVersion1(), new TestSettingsNodeVersion1() )
+     SinglePaneUIVersion1 singlePane = UIWrapperFactory.createSinglePaneUI(testSingleDualPaneUIInteractionVersion1, false)
+
+     //tell it we're about to show it, so it'll create a component
+     singlePane.aboutToShow();
+
+     //make sure we now have that component
+     Assert.assertNotNull( singlePane.getComponent() )
+   }
+
+  /**
+   * This tests to see if we can call the UIFactory to create a dual pane UI.
+      This is only testing that extracting the UI returns something without giving
+      errors and that it has its dual components. This is just a good general-case test
+      to make sure teh basics are working.
+   */
+    @Test
+    public void testDualPaneBasic()
+    {
+        DualPaneUIVersion1 dualPane = createDualPaneUI()
+
+        //make sure we got something
+        Assert.assertNotNull( dualPane )
+
+        //tell it we're about to show it, so it'll create a component
+        dualPane.aboutToShow();
+
+        //make sure we now have the main component
+        Assert.assertNotNull( dualPane.getMainComponent() )
+
+        //and the output component
+        Assert.assertNotNull( dualPane.getOutputPanel() )
+    }
+
+  /**
+   * This is the same as testDualPaneBasic but uses the UIWrapperFactory directly;
+   * which is what the UIFactory uses via reflection to instantiate the UI. Just
+   * making sure this is working normally and not throwing any exceptions.
+   */
+   @Test
+   public void testUIWrapperDualPane()
+   {
+     TestSingleDualPaneUIInteractionVersion1 testSingleDualPaneUIInteractionVersion1 = new TestSingleDualPaneUIInteractionVersion1( new TestAlternateUIInteractionVersion1(), new TestSettingsNodeVersion1() )
+     DualPaneUIVersion1 dualPane = UIWrapperFactory.createDualPaneUI(testSingleDualPaneUIInteractionVersion1, false)
+
+     //tell it we're about to show it, so it'll create a component
+     dualPane.aboutToShow();
+
+     //make sure we now have that component
+     Assert.assertNotNull( dualPane.getMainComponent() )
+     Assert.assertNotNull( dualPane.getOutputPanel() )
+   }
+
+   /**
+   * Helper function that creates a single pane UI
+   */
+   private SinglePaneUIVersion1 createSinglePaneUI()
+   {
+     TestSingleDualPaneUIInteractionVersion1 testSingleDualPaneUIInteractionVersion1 = new TestSingleDualPaneUIInteractionVersion1( new TestAlternateUIInteractionVersion1(), new TestSettingsNodeVersion1() )
+     SinglePaneUIVersion1 singlePane = UIFactory.createSinglePaneUI(getClass().getClassLoader(), dist.getGradleHomeDir(), testSingleDualPaneUIInteractionVersion1, false )
+
+     //make sure we got something
+     Assert.assertNotNull( singlePane )
+
+     singlePane.setCurrentDirectory( javaprojectDir )
+
+     return singlePane
+   }
+
+  /**
+   * Helper function that creates a dual pane UI
+   */
+   private DualPaneUIVersion1 createDualPaneUI()
+   {
+     TestSingleDualPaneUIInteractionVersion1 testSingleDualPaneUIInteractionVersion1 = new TestSingleDualPaneUIInteractionVersion1( new TestAlternateUIInteractionVersion1(), new TestSettingsNodeVersion1() )
+     DualPaneUIVersion1 dualPane = UIFactory.createDualPaneUI(getClass().getClassLoader(), dist.getGradleHomeDir(), testSingleDualPaneUIInteractionVersion1, false )
+
+     //make sure we got something
+     Assert.assertNotNull( dualPane )
+
+     dualPane.setCurrentDirectory( javaprojectDir )
+
+     return dualPane
+   }
+
+    /**
+    * This verifies that favorites are working for some basics. We're going to test this with both
+     * the single and dual pane UIs (they actually use the same editor then for other tests we'll
+     * assume they're same).
+    */
+    @Test
+    public void testFavoritesBasic()
+    {
+      SinglePaneUIVersion1 singlePane = createSinglePaneUI()
+      checkFavoritesBasic( singlePane )
+
+      DualPaneUIVersion1 dualPane = createDualPaneUI()
+      checkFavoritesBasic( dualPane )
+    }
+
+    /**
+    * This verifies that we favorites are basically working based on the given UI. We're going to add one, then
+    * do some 'gets' to find the just-added favorite.
+    */
+    private void checkFavoritesBasic( BasicGradleUIVersion1 basicGradleUI )
+    {
+      FavoritesEditorVersion1 editor = basicGradleUI.getFavoritesEditor()
+
+      //there should be no favorites as of yet
+      Assert.assertTrue( editor.getFavoriteTasks().isEmpty() )
+
+      //add one (doesn't really matter what it is)
+      def fullCommandLine = "-t -S"
+      def displayName = "Task List With Stack trace"
+      FavoriteTaskVersion1 favorite = editor.addFavorite( fullCommandLine, displayName, true )
+
+      //make sure something was added
+      Assert.assertEquals( 1, editor.getFavoriteTasks().size() )
+
+      //get the newly-added favorite by command line.
+      FavoriteTaskVersion1 matchingFavorite1 = editor.getFavorite( fullCommandLine )
+      Assert.assertEquals( favorite, matchingFavorite1 )
+
+      //get the newly-added favorite by displayName.
+      FavoriteTaskVersion1 matchingFavorite2 = editor.getFavoriteByDisplayName( displayName )
+      Assert.assertEquals( favorite, matchingFavorite2 )
+
+      Assert.assertTrue( matchingFavorite2.alwaysShowOutput() )
+    }
+
+    /**
+    * This verifies that we can edit favorites. We're going to add a favorite then edit its
+     * command line, display name, and 'show output' setting.
+    */
+    @Test
+    public void testEditingFavorites()
+    {
+      SinglePaneUIVersion1 singlePane = createSinglePaneUI()
+      FavoritesEditorVersion1 editor = singlePane.getFavoritesEditor()
+
+      def originalFullCommandLine = "-t -S"
+      def originalDisplayName = "Task List With Stack trace"
+      FavoriteTaskVersion1 addedFavorite = editor.addFavorite(originalFullCommandLine, originalDisplayName, true)
+
+      //make sure we can find the just-added favorite
+      Assert.assertNotNull( editor.getFavorite( originalFullCommandLine ) )
+      Assert.assertNotNull( editor.getFavoriteByDisplayName( originalDisplayName ) )
+
+      String newFullCommandLine = "-t -S -d"
+      String newDisplayName = "new task list"
+      String error = editor.editFavorite( addedFavorite, newFullCommandLine, newDisplayName, false )
+      Assert.assertNull( error )  //we should get no error
+
+      //now we shouldn't be able to find the favorite using the original values. This is part of verifying the values were in fact changed.
+      Assert.assertNull( editor.getFavorite( originalFullCommandLine ) )
+      Assert.assertNull( editor.getFavoriteByDisplayName( originalDisplayName ) )
+
+      //make sure we can find it using the new values. This is part of verifying the values were in fact changed.
+      Assert.assertNotNull( editor.getFavorite( newFullCommandLine ) )
+      Assert.assertNotNull( editor.getFavoriteByDisplayName( newDisplayName ) )
+
+      //there should just be 1 favorite
+      Assert.assertEquals( 1, editor.getFavoriteTasks().size() )
+    }
+
+    /**
+    * This verifies that we can remove favorites. We're going to add some favorites then remove them
+     * verifying that they've gone.
+    */
+    @Test
+    public void testRemovingFavorites()
+    {
+      SinglePaneUIVersion1 singlePane = createSinglePaneUI()
+      FavoritesEditorVersion1 editor = singlePane.getFavoritesEditor()
+
+      //there should be no favorites as of yet
+      Assert.assertTrue( editor.getFavoriteTasks().isEmpty() )
+
+      //add one (doesn't really matter what it is)
+      String command1 = "build"
+      FavoriteTaskVersion1 favorite1 = editor.addFavorite( command1, command1, true )
+
+      //make sure it was added
+      Assert.assertNotNull( editor.getFavorite( command1 ) )
+      Assert.assertEquals( 1, editor.getFavoriteTasks().size() )
+
+      //add another one
+      String command2 = "build -xtest"
+      FavoriteTaskVersion1 favorite2 = editor.addFavorite( command2, command2, true )
+
+      //make sure it was added
+      Assert.assertNotNull( editor.getFavorite( command2 ) )
+      Assert.assertEquals( 2, editor.getFavoriteTasks().size() )
+
+      String command3 = "clean"
+      FavoriteTaskVersion1 favorite3 = editor.addFavorite( command3, command3, true )
+
+      //make sure it was added
+      Assert.assertNotNull( editor.getFavorite( command3 ) )
+      Assert.assertEquals( 3, editor.getFavoriteTasks().size() )
+
+      String command4 = "docs"
+      FavoriteTaskVersion1 favorite4 = editor.addFavorite( command4, command4, true )
+
+      //make sure it was added
+      Assert.assertNotNull( editor.getFavorite( command4 ) )
+      Assert.assertEquals( 4, editor.getFavoriteTasks().size() )
+
+      //now remove one of them
+      List removed1 = [ favorite2 ]
+      editor.removeFavorites( removed1 )
+
+      //make sure it was removed
+      Assert.assertNull( editor.getFavorite( command2 ) )
+      Assert.assertEquals( 3, editor.getFavoriteTasks().size() )
+
+      //now remove multiples
+      List removed2 = [ favorite1, favorite4 ]
+      editor.removeFavorites( removed2 )
+
+      //make sure they were both removed
+      Assert.assertNull( editor.getFavorite( command1 ) )
+      Assert.assertNull( editor.getFavorite( command4 ) )
+      Assert.assertEquals( 1, editor.getFavoriteTasks().size() )
+    }
+
+    /**
+   * This tests executing multiple favorites at once. We'll add two favorites, then execute both of them
+   * via GradleInterfaceVersion1.executeFavorites(). This should execute both as a single command
+   * concatenating them together.
+   */
+    @Test
+    public void testExecutingFavorites()
+    {
+      SinglePaneUIVersion1 singlePane = createSinglePaneUI()
+      FavoritesEditorVersion1 editor = singlePane.getFavoritesEditor()
+
+      //this starts the execution queue
+      singlePane.aboutToShow()
+
+      //there should be no favorites as of yet
+      Assert.assertTrue( editor.getFavoriteTasks().isEmpty() )
+
+      //add one (doesn't really matter what it is)
+      String command1 = "build"
+      FavoriteTaskVersion1 favorite1 = editor.addFavorite( command1, command1, true )
+
+      //make sure it was added
+      Assert.assertNotNull( editor.getFavorite( command1 ) )
+      Assert.assertEquals( 1, editor.getFavoriteTasks().size() )
+
+      //add another one
+      String command2 = "clean"
+      FavoriteTaskVersion1 favorite2 = editor.addFavorite( command2, command2, true )
+
+      //make sure it was added
+      Assert.assertNotNull( editor.getFavorite( command2 ) )
+      Assert.assertEquals( 2, editor.getFavoriteTasks().size() )
+
+      //add a request observer so we can observe when the command is finished. This allows us to
+      //see what was actually executed.
+      TestRequestObserver testRequestObserver = new TestRequestObserver( RequestVersion1.EXECUTION_TYPE )
+      ((GradleInterfaceVersion2)singlePane.getGradleInterfaceVersion1()).addRequestObserver( testRequestObserver )
+
+      //now execute both favorites
+      List<FavoriteTaskVersion1> favorites = [ favorite1, favorite2 ]
+      RequestVersion1 request = ( (GradleInterfaceVersion2) singlePane.getGradleInterfaceVersion1() ).executeFavorites(favorites)
+
+      Assert.assertNotNull( request )
+
+      //verify that the actual command that was executed is a concatenation of both favorites
+      Assert.assertEquals( "build clean", request.getFullCommandLine() )
+    }
+
+    /**
+    * This tests getting projects and tasks from gradle. It then goes through a series of tests
+    * related to projects and tasks (such as making sure their description is returned, their
+    * parent is returned, etc).
+    */
+    @Test
+    public void testProjectsAndTasks()
+    {
+      DualPaneUIVersion1 dualPane = createDualPaneUI()
+      GradleInterfaceVersion2 gradleInterface = (GradleInterfaceVersion2) dualPane.getGradleInterfaceVersion1()
+
+      //make sure our samples directory exists
+      if( !gradleInterface.getCurrentDirectory().exists() ) {
+        throw new AssertionFailedError('sample project missing. Expected it at: ' + gradleInterface.getCurrentDirectory())
+      }
+
+      //add a request observer so we can observe when the command is finished. This allows us to
+      //see what was actually executed.
+      TestRequestObserver testRequestObserver = new TestRequestObserver( RequestVersion1.REFRESH_TYPE )
+      gradleInterface.addRequestObserver( testRequestObserver )
+
+      //this starts the execution queue
+      dualPane.aboutToShow()
+
+      gradleInterface.refreshTaskTree()
+
+      //now we'll wait up to x seconds (arbitrary) for the refresh to occur. This is ugly, but its just a test.
+      int maximumWaitTime = 40;
+      int totalWaitTime = 0;
+      while ( testRequestObserver.request == null && totalWaitTime <= maximumWaitTime ) {
+          try {
+              Thread.sleep(1000);
+          }
+          catch (InterruptedException e) {
+              e.printStackTrace();
+          }
+
+          totalWaitTime += 1;
+      }
+
+      if( totalWaitTime > maximumWaitTime ) {
+        throw new AssertionFailedError("Waited " + totalWaitTime + " seconds and failed to get root projects. This is taking too long, so assuming something is wrong.\nCurrent project directory: '" + gradleInterface.getCurrentDirectory() + "'\ngradle home: '" + gradleInterface.getGradleHomeDirectory() + "'")
+      }
+
+      Assert.assertEquals( "Execution Failed: " + testRequestObserver.output, 0, testRequestObserver.result)
+
+      List<ProjectVersion1> rootProjects = gradleInterface.getRootProjects();
+      Assert.assertFalse( rootProjects.isEmpty() );   //do we have any root projects?
+
+      ProjectVersion1 rootProject = rootProjects.get( 0 );
+      Assert.assertNotNull( rootProject );
+
+      //Quick check to make sure there are tasks on each of the sub projects.
+      //The exact task names will change over time, so I don't want to try
+      //to test for those. I'll just make sure there are several.
+      Iterator<ProjectVersion1> iterator = rootProjects.get(0).getSubProjects().iterator();
+      while( iterator.hasNext() )
+      {
+         ProjectVersion1 projectVersion1 = iterator.next();
+         Assert.assertTrue( projectVersion1.getTasks().size() > 4 );
+      }
+
+      //there should be a 'services' project
+      ProjectVersion1 servicesProject = rootProjects.get(0).getSubProject("services" );
+      Assert.assertNotNull( servicesProject );
+
+      //and it contains a 'webservice' sub project
+      ProjectVersion1 webserviceProject = servicesProject.getSubProject("webservice");
+      Assert.assertNotNull( webserviceProject );
+
+      ProjectVersion1 apiProject = rootProjects.get(0).getSubProject("api");
+      Assert.assertNotNull( apiProject );
+
+      //verify the parent project is set correctly
+      Assert.assertEquals( servicesProject, webserviceProject.getParentProject() )
+
+      //verify its full name is correct (this might should be prefixed with a colon)
+      Assert.assertEquals( "services:webservice", webserviceProject.getFullProjectName() )
+
+      //verify getSubProjectFromFullPath works
+      ProjectVersion1 foundProject = rootProject.getSubProjectFromFullPath("services:webservice")
+      Assert.assertNotNull( "Failed to find services:webservice", foundProject )
+      Assert.assertEquals( webserviceProject, foundProject )
+
+      //verify that are multiple tasks here (we know their should be)
+      Assert.assertTrue( webserviceProject.getTasks().size() > 4 );
+
+      //verify getTaskFromFullPath works
+      TaskVersion1 apiBuildTask = rootProject.getTaskFromFullPath(":api:build")
+      Assert.assertNotNull( "Failed to find :api:build", apiBuildTask )
+
+      Assert.assertEquals( apiProject, apiBuildTask.getProject() )
+
+      //then make sure it has a description
+      Assert.assertNotNull( apiBuildTask.getDescription() )
+
+      //and that its not marked as the default (we need a task to be the default so we can verify it returns true)
+      Assert.assertFalse( apiBuildTask.isDefault() )
+
+      //there are no default tasks here
+      Assert.assertTrue( apiProject.getDefaultTasks().isEmpty() )
+
+      //this build task is a child of the api project. Should be the same task we got earlier
+      TaskVersion1 buildTask = apiProject.getTask("build")
+      Assert.assertNotNull( "Failed to find build task", buildTask )
+      Assert.assertEquals( apiBuildTask, buildTask )
+    }
+
+   /**
+    * This verifies that the GradleInterfaceVersion1.refreshTaskTree that takes
+    * additional arguments works. We're not really interested in what those additional
+    * arguments are, just that it passes them along.
+    */
+    @Test
+    public void testRefreshWithArguments()
+    {
+      DualPaneUIVersion1 dualPane = createDualPaneUI()
+      GradleInterfaceVersion2 gradleInterface = (GradleInterfaceVersion2) dualPane.getGradleInterfaceVersion1()
+
+      //make sure our samples directory exists
+      if( !gradleInterface.getCurrentDirectory().exists() ) {
+        throw new AssertionFailedError('sample project missing. Expected it at: ' + gradleInterface.getCurrentDirectory())
+      }
+
+      //this starts the execution queue
+      dualPane.aboutToShow()
+
+      //add a request observer so we can observe when the command is finished. This allows us to
+      //see what was actually executed.
+      TestRequestObserver testRequestObserver = new TestRequestObserver( RequestVersion1.REFRESH_TYPE )
+      gradleInterface.addRequestObserver( testRequestObserver )
+
+      RequestVersion1 request = gradleInterface.refreshTaskTree2("-xtest")
+
+      //make sure that the actual request is the normal refresh request with our
+      //(this line is really what we're trying to test)
+      Assert.assertEquals( "-t -xtest", request.getFullCommandLine() )
+
+      //now we'll wait up to x seconds (arbitrary) for the refresh to occur. This is ugly, but its just a test.
+      int maximumWaitTime = 20;
+      int totalWaitTime = 0;
+      while ( testRequestObserver.request == null && totalWaitTime <= maximumWaitTime ) {
+          try {
+              Thread.sleep(1000);
+          }
+          catch (InterruptedException e) {
+              e.printStackTrace();
+          }
+
+          totalWaitTime += 1;
+      }
+
+      if( totalWaitTime > maximumWaitTime ) {
+        throw new AssertionFailedError("Waited " + totalWaitTime + " seconds and failed to complete refresh. This is taking too long, so assuming something is wrong.\nCurrent project directory: '" + gradleInterface.getCurrentDirectory() + "'\ngradle home: '" + gradleInterface.getGradleHomeDirectory() + "'")
+      }
+
+      Assert.assertEquals( "Execution Failed: " + testRequestObserver.output, 0, testRequestObserver.result)
+
+      Assert.assertEquals( "Not our request", request, testRequestObserver.request );
+    }
+
+    /**
+    * This verifies that you can add custom stuff to the setup tab. This is a UI test and is kinda tricky. We're going
+    * to use a HierarchyListener to see if our component is made visible. This will confirm if it was added or not
+    * because it must be added to be made visible. To do this, however, we'll need to actually show the UI. All we're
+    * really doing here, is adding a 'custom' component to the UI, then adding the UI to a frame, then showing the frame,
+    * so we can verify that our component was shown.
+    */
+    @Test
+    public void testAddingComponentToSetupTab()
+    {
+      if ( java.awt.GraphicsEnvironment.isHeadless() ) {
+        return;  // Can't run this test in headless mode!
+       }
+
+      JLabel label = new JLabel("Testing Testing 123")
+      TestVisibilityHierarchyListener hierarchyAdapter = new TestVisibilityHierarchyListener()
+      label.addHierarchyListener( hierarchyAdapter )
+
+      SinglePaneUIVersion1 singlePane = createSinglePaneUI()
+
+      //make sure we haven't been told the component was shown or hidden yet
+      Assert.assertFalse( hierarchyAdapter.componentWasShown )
+      Assert.assertFalse( hierarchyAdapter.componentWasHidden )
+
+      singlePane.aboutToShow();
+
+      singlePane.setCustomPanelToSetupTab( label )
+
+      //this still should not show the component (at this point, we're probably more testing that our hierarchyAdapter is working)
+      Assert.assertFalse( hierarchyAdapter.componentWasShown )
+      Assert.assertFalse( hierarchyAdapter.componentWasHidden )
+
+      //now create a frame, place the UI in it, then show it briefly
+      JFrame frame = createTestFrame( singlePane.getComponent(), null ) //null because the single pane is entirely self contained. There is no output panel.
+
+      //set the Setup tab as the current tab. This is required to actually show the component.
+      int setupTabIndex = singlePane.getGradleTabIndex( "Setup" );
+      Assert.assertTrue( "Failed to get index of setup tab", setupTabIndex != -1 )
+      singlePane.setCurrentGradleTab( setupTabIndex );
+
+      //still should not show the component (its not yet visible, but is about to be)
+      Assert.assertFalse( hierarchyAdapter.componentWasShown )
+      Assert.assertFalse( hierarchyAdapter.componentWasHidden )
+
+
+      //This shows and hides the UI, giving it time to actually show itself and empty the event dispatch
+      //queue. This is required for the setup tab to become current as well as show the custom component we added.
+      showFrameEmptyEventQueueHide( frame )
+
+      Assert.assertEquals( "The setup tab was not selected", setupTabIndex, singlePane.getCurrentGradleTab() )
+
+      //now the label should have been made visible then invisible
+      Assert.assertTrue( hierarchyAdapter.componentWasShown )
+      Assert.assertTrue( hierarchyAdapter.componentWasHidden )
+    }
+
+    /**
+    * This verifies that you can add a custom tab to the UI. This is a UI test and is kinda tricky. We're going
+    * to use a HierarchyListener to see if our tab component is made visible. This will confirm if it was added or not
+    * because it must be added to be made visible. To do this, however, we'll need to actually show the UI. All we're
+    * really doing here, is adding a tab to the UI, then adding the UI to a frame, then showing the frame so we can
+    * then verify that our tab was shown (actually using our tab's component).
+    */
+    @Test
+    public void testAddingCustomTab()
+    {
+      if ( java.awt.GraphicsEnvironment.isHeadless() ) {
+        return;  // Can't run this test in headless mode!
+      }
+
+      TestTab testTab = new TestTab()
+
+      SinglePaneUIVersion1 singlePane = createSinglePaneUI()
+
+      //make sure we haven't been told the component was shown or hidden yet
+      Assert.assertFalse( testTab.hierarchyAdapter.componentWasShown )
+      Assert.assertFalse( testTab.hierarchyAdapter.componentWasHidden )
+
+      //make sure things are initialized properly. These should all be false
+      Assert.assertFalse( testTab.nameRetrieved );
+      Assert.assertFalse( testTab.informedAboutToShow );
+      Assert.assertFalse( testTab.componentCreated );
+
+      int originalCount = singlePane.getGradleTabCount();
+
+      singlePane.addTab( 99, testTab ) //I don't really care about the index. It should accept a number that is too large and handle it appropriately.
+
+      singlePane.aboutToShow()
+
+      //this still should not show the component (at this point, we're probably more testing that our hierarchyAdapter is working)
+      Assert.assertFalse( testTab.hierarchyAdapter.componentWasShown )
+      Assert.assertFalse( testTab.hierarchyAdapter.componentWasHidden )
+
+      //now create a frame, place the UI in it, then show it briefly
+      JFrame frame = createTestFrame( singlePane.getComponent(), null ) //null because the single pane is entirely self contained. There is no output panel.
+
+      String testTabName = "Test Tab"
+
+      //set the test tab as the current tab. This is required to actually show the component.
+      int testTabIndex = singlePane.getGradleTabIndex( testTabName )
+      Assert.assertTrue( "Failed to get index of test tab", testTabIndex != -1 )
+      singlePane.setCurrentGradleTab( testTabIndex )
+
+      //just to test getGradleTabName, make sure it returns our tab name
+      Assert.assertEquals( testTabName, singlePane.getGradleTabName( testTabIndex ) )
+
+      //to test getGradleTabCount, make sure the tab count went up by 1
+      Assert.assertEquals( originalCount + 1, singlePane.getGradleTabCount() )
+
+      //still should not show the component (its not yet visible, but is about to be)
+      Assert.assertFalse( testTab.hierarchyAdapter.componentWasShown )
+      Assert.assertFalse( testTab.hierarchyAdapter.componentWasHidden )
+
+      //This shows and hides the UI, giving it time to actually show itself and empty the event dispatch
+      //queue. This is required for the test tab to become current.
+      showFrameEmptyEventQueueHide( frame )
+
+      Assert.assertEquals( "The test tab was not selected", testTabIndex, singlePane.getCurrentGradleTab() )
+
+      //now the label should have been made visible then invisible
+      Assert.assertTrue( testTab.hierarchyAdapter.componentWasShown )
+      Assert.assertTrue( testTab.hierarchyAdapter.componentWasHidden )
+
+      //at the end, the name should have been queried, we should have been told we were about to shown, and the component should be created
+      Assert.assertTrue( testTab.nameRetrieved );
+      Assert.assertTrue( testTab.informedAboutToShow );
+      Assert.assertTrue( testTab.componentCreated );
+
+      //reset the test tab (resets the listener so we can remove the tab and verify that it no longer shows up, as well as some of our test variables)
+      testTab.reset()
+      singlePane.removeTab( testTab )
+
+      //I'm going to set the current tab, but this shouldn't do anything because the tab was removed
+      singlePane.setCurrentGradleTab( testTabIndex );
+
+      //part of showing the UI is telling it its about to be shown. In this case, nothing should happen
+      //related to the test tab. It has been removed
+      singlePane.aboutToShow()
+
+      //This shows and hides the UI, giving it time to actually show itself and empty the event
+      //dispatch queue. This is required for the test tab to become current (were it still present).
+      showFrameEmptyEventQueueHide( frame )
+
+      //try to get the test tab
+      testTabIndex = singlePane.getGradleTabIndex( "Test Tab" );
+      Assert.assertTrue( "Erroneously got index of test tab. It was removed", testTabIndex == -1 )
+
+      //we've removed it, so it shouldn't have been polled about or informed of anything
+      Assert.assertFalse( testTab.nameRetrieved );
+      Assert.assertFalse( testTab.informedAboutToShow );
+      Assert.assertFalse( testTab.componentCreated );
+
+      //It was not shown after the reset, these should both be false
+      Assert.assertFalse( testTab.hierarchyAdapter.componentWasShown )
+      Assert.assertFalse( testTab.hierarchyAdapter.componentWasHidden )
+    }
+
+  /**
+   * This creates a frame with the specified main component (presumably the Gradle UI).
+   * This also shows large text explaining what this so developers seeing this won't
+   * freak out too much when running tests.
+   * @param mainComponent the component that goes in the center of our frame
+   * @param outputComponent an optional 'output pane' component that goes along the bottom  
+   */
+    private JFrame createTestFrame( Component mainComponent, Component outputComponent )
+    {
+      JFrame frame = new JFrame()
+      frame.setSize( 650, 500 )
+      JPanel panel = new JPanel( new BorderLayout() )
+      frame.getContentPane().add( panel )
+
+      //add a large red label explaining this
+      JLabel label2 = new JLabel("Performing Open API Integration Test!")
+      label2.setForeground( Color.red )
+      label2.setFont( label2.getFont().deriveFont( 26f ) );
+      panel.add( label2, BorderLayout.NORTH )
+
+      //add the main UI to the frame
+      panel.add( mainComponent, BorderLayout.CENTER )
+
+      if( outputComponent != null ) {
+        panel.add( outputComponent, BorderLayout.SOUTH )
+      }
+
+      return frame;
+    }
+
+    /*
+     * This shows the specified frame for a moment so the event queue can be emptied. This
+     * ensures Swing events a processed
+     * Don't place anything between the following three lines (especially something that might
+     * throw an exception). This shows and hides the UI, giving it time to actually show itself
+     * and empty the event dispatch queue.
+     */
+    private void showFrameEmptyEventQueueHide( JFrame frame )
+    {
+      SwingUtilities.invokeAndWait( {frame.setVisible( true ) } )
+      Thread.currentThread().sleep( 500 );
+      SwingUtilities.invokeAndWait( {frame.setVisible( false ) } )
+    }
+
+    /**
+    * We want to make sure the settings are working correctly here. This is the mechanism that
+     * handles saving/restoring the values within the UI and can be stored in different ways
+     * depending on how the UI integrated with its parent (its up to whoever implements
+     * SettingsNodeVersion1). Here, to spot check that the basics are working, we'll create a
+     * UI, set a value, close it, then recreate it using the same settings object. The values
+     * should be saved upon close and then restored.
+    */
+    @Test
+    public void testSettings()
+    {
+      TestSettingsNodeVersion1 settingsNode = new TestSettingsNodeVersion1();
+
+      TestSingleDualPaneUIInteractionVersion1 testSingleDualPaneUIInteractionVersion1 = new TestSingleDualPaneUIInteractionVersion1( new TestAlternateUIInteractionVersion1(), settingsNode );
+        SinglePaneUIVersion1 singlePane = null;
+        try {
+            singlePane = UIFactory.createSinglePaneUI(getClass().getClassLoader(), dist.getGradleHomeDir(), testSingleDualPaneUIInteractionVersion1, false );
+        } catch (Exception e) {
+            throw new AssertionFailedError( "Failed to extract single pane: Caused by " + e.getMessage() )
+        }
+
+        File illegalDirectory = createTempDirectory( "non-existant" );
+        try {
+            if( illegalDirectory.equals( singlePane.getCurrentDirectory() ) ) {
+              throw new AssertionFailedError( "Directory already set to 'test' directory. The test is not setup correctly." );
+            }
+
+            //this is required to get the ball rolling
+            singlePane.aboutToShow();
+
+            //set the current directory after calling aboutToShow (otherwise, it'll stomp over us when it restores its default settings)
+            singlePane.setCurrentDirectory( illegalDirectory );
+
+            //close the UI. This saves the current settings.
+            singlePane.close();
+
+            //now instantiate it again
+            testSingleDualPaneUIInteractionVersion1 = new TestSingleDualPaneUIInteractionVersion1( new TestAlternateUIInteractionVersion1(), settingsNode );
+            try {
+                singlePane = UIFactory.createSinglePaneUI(getClass().getClassLoader(), dist.getGradleHomeDir(), testSingleDualPaneUIInteractionVersion1, false );
+            } catch (Exception e) {
+                throw new AssertionFailedError( "Failed to extract single pane (second time): Caused by " + e.getMessage() )
+            }
+
+            //this should restore the previous settings
+            singlePane.aboutToShow();
+
+            Assert.assertEquals( illegalDirectory, singlePane.getCurrentDirectory() );
+        } finally {
+            deleteTempDirectory( illegalDirectory );
+        }
+    }
+
+    /**
+      Call this to create a temporary directory with the specified name.
+
+      <!      Name       Description>
+      @param  baseName   the base name. We may append a number to it.
+      @return the directory.
+      @author mhunsicker
+   */
+   public static File createTempDirectory( String baseName )
+   {
+      String systemTemporaryDirectory = System.getProperty( "java.io.tmpdir" );
+
+      File file = new File( systemTemporaryDirectory, baseName );
+      int index = 2;
+      while( file.exists() )
+      {
+         file = new File( systemTemporaryDirectory, baseName + "_" + index );
+         index++;
+      }
+
+      file.mkdirs(); //create it
+      return file;
+   }
+
+   /**
+      This deletes the contents of a temporary directory (created with the above
+      function) and hopefully the directory itself. BUt this doesn't work very
+      well on Windows. Sun says it's officially a Windows issue. I tend to agree
+      since Windows mysteriously locks files and won't let you delete them and
+      only Explorer has a handle to them (verified using process explorer).
+
+      @author mhunsicker
+   */
+   public static void deleteTempDirectory( File temporaryDirectory )
+   {
+      File[] files = temporaryDirectory.listFiles();
+      if( files != null ) {
+        for (int index = 0; index < files.length; index++) {
+          File file = files[index];
+          file.delete();
+        }
+      }
+
+
+     temporaryDirectory.deleteOnExit();
+   }
+
+    /**
+     * This tests that the command line altering mechanism works. This adds additional
+     * things to the command line being executed. This is used by gradle build systems
+     * that need custom, system-specific arguments passed to it that aren't known by gradle
+     * proper. For example: you're working with a large project made of MANY subprojects, a
+     * custom IDE plugin can track which projects you're focusing on and pass that information
+     * to the build system via this mechanism, so it only builds appropriate projects.
+     * This is awkward to test because its real use requires a customized build system,
+     * so I'm going to pass an argument that is illegal by itself -- meaning no tasks
+     * are specified. Then I'll alter the command line by adding an actual task. Then
+     * wait for it to complete and verify what was executed
+    */
+    @Test
+    public void testCommandLineAlteringListener()
+    {
+      DualPaneUIVersion1 dualPane = createDualPaneUI()
+      GradleInterfaceVersion2 gradleInterface = (GradleInterfaceVersion2) dualPane.getGradleInterfaceVersion1()
+
+      //this starts the execution queue. This also initiates a refresh that we'll ignore later.
+      dualPane.aboutToShow()
+
+      //add a request observer so we can observe when the command is finished. This allows us to
+      //see what was actually executed.
+      TestRequestObserver testRequestObserver = new TestRequestObserver( RequestVersion1.EXECUTION_TYPE )
+      gradleInterface.addRequestObserver( testRequestObserver )
+
+      //now that we know that command is illegal by itself, try it again but the listener will append 'build'
+      //to the command line which makes it legal (again, we don't really care what we execute.
+      TestCommandLineArgumentAlteringListenerVersion1 commandLineArgumentAlteringListener = new TestCommandLineArgumentAlteringListenerVersion1("build")
+      gradleInterface.addCommandLineArgumentAlteringListener( commandLineArgumentAlteringListener )
+
+      //execute this before we do our test. This is not legal by itself. It should fail. That means our
+      //test is setup correctly. For example: if someone adds a default task to this project, this will
+      //generate NO error and thus, our test will prove nothing. If you get a test failure here, you
+      //can try changing the command line to something that's illegal by itself (we don't care what).
+      RequestVersion1 request = gradleInterface.executeCommand2("-s", "test command")
+
+      //now we'll wait up to x seconds (arbitrary) for the task to execute. This is ugly, but its just a test.
+      int maximumWaitTime = 80;
+      int totalWaitTime = 0;
+      while ( testRequestObserver.request == null && totalWaitTime <= maximumWaitTime ) {
+          try {
+              Thread.sleep(1000);
+          }
+          catch (InterruptedException e) {
+              e.printStackTrace();
+          }
+
+          totalWaitTime += 1;
+      }
+
+      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.\nCurrent project directory: '" + gradleInterface.getCurrentDirectory() + "'\ngradle home: '" + gradleInterface.getGradleHomeDirectory() + "'\nOutput:\n" + testRequestObserver.output)
+      }
+
+      Assert.assertEquals( "Incorrect request", "-s build", testRequestObserver.request.getFullCommandLine() )
+
+      //make sure it completed execution correctly
+      Assert.assertEquals( "Execution failed with return code: " + testRequestObserver.result + "\nOutput:\n" + testRequestObserver.output , 0, testRequestObserver.result )
+
+      //the request that was executed should be equal to our original command with our 'altered' command added to it
+      Assert.assertNotNull( "Missing 'execution completed' request", testRequestObserver.request )
+
+      //just to be paranoid, let's make sure it was actually our request. If this fails, it probably represents something
+      //fundamentally flawed with the request or request wrapper mechanism.
+      Assert.assertEquals( request, testRequestObserver.request )
+
+      gradleInterface.removeRequestObserver( testRequestObserver )
+      gradleInterface.removeCommandLineArgumentAlteringListener( commandLineArgumentAlteringListener )
+    }
+
+   /**
+    * This tests that getVersion returns the same thing as the jar's suffix.
+    * We'll get the gradle jar, then strip off its extension and verify that
+    * the jar's name ends with the version number.
+    */
+  @Test
+  public void testVersion()
+  {
+    SinglePaneUIVersion1 singlePane = createSinglePaneUI()
+    String version = ( (GradleInterfaceVersion2) singlePane.getGradleInterfaceVersion1()).getVersion()
+
+    Assert.assertNotNull( "null version number", version )
+
+    Assert.assertFalse( "Empty version number", version.trim().equals( "" ) )       //shouldn't be empty
+
+    File gradleJar = ExternalUtility.getGradleJar(dist.gradleHomeDir)
+
+    Assert.assertNotNull( "Missing gradle jar", gradleJar )                         //we should have a gradle jar
+
+    int indexOfExtension = gradleJar.getName().toLowerCase().lastIndexOf( ".jar" )  //get the index of its extension
+
+    Assert.assertTrue( "Has no '.jar' extension", indexOfExtension != -1 )          //it had better have an extension
+
+    String name = gradleJar.getName().substring( 0, indexOfExtension )              //get its name minus the extension
+
+    Assert.assertTrue( "Jar name doesn't end with version", name.endsWith( version ) )  //the name (minus extension) should end with the version
+  }
+
+  /**
+   * This is just a spot check that getGradleHomeDirectory works. Its based off
+   * of the gradle you're running.
+   */
+  @Test
+  public void testGradleHomeDirectory()
+  {
+    SinglePaneUIVersion1 singlePane = createSinglePaneUI()
+
+    Assert.assertEquals( dist.gradleHomeDir, singlePane.getGradleHomeDirectory() )
+  }
+
+
+  /**
+   * This is just a spot check that we can get an instance of the OutputUILord.
+   * Other tests cover its functionality more thoroughly. This is just to make sure
+   * its working when accessed via the Open API.
+   */
+  @Test
+  public void testOutputUILord()
+  {
+    SinglePaneUIVersion1 singlePane = createSinglePaneUI()
+    OutputUILordVersion1 outputUILord = singlePane.getOutputLord()
+    Assert.assertNotNull( outputUILord )
+  }
+
+  /**
+   * This tests that you can correctly obtain the number of output tabs from a
+   * dual pane UI. This
+   */
+  @Test
+  public void testDualPaneOutputPaneNumber()
+  {
+    if ( java.awt.GraphicsEnvironment.isHeadless() ) {
+      return;  // Can't run this test in headless mode!
+    }
+
+    DualPaneUIVersion1 dualPane = createDualPaneUI()
+
+    //now create a frame, place the UI in it, then show it briefly
+    JFrame frame = createTestFrame( dualPane.getMainComponent(), dualPane.getOutputPanel() )
+
+    //make sure we got something
+    Assert.assertNotNull( dualPane )
+
+    //tell it we're about to show it, so it'll create a component
+    dualPane.aboutToShow()
+
+    dualPane.refreshTaskTree()
+
+    showFrameEmptyEventQueueHide( frame )
+
+    //there should be one opened output tab for the refresh
+    Assert.assertEquals( 1, dualPane.getNumberOfOpenedOutputTabs() )
+
+    dualPane.executeCommand( "build", "build" )
+
+    showFrameEmptyEventQueueHide( frame )
+
+    //there should be 2 opened output tabs. One for refresh, one for build
+    Assert.assertEquals( 2, dualPane.getNumberOfOpenedOutputTabs() )
+  }
+
+   /**
+  * This tests whether or not a the UI is considered busy. Its busy if its
+    * executing a command. To test this, we'll execute a command and verify
+    * we're busy. When it finishes, we'll verify we're not longer busy.
+    * We'll also check that canClose works properly. If we're busy, calling
+    * canClose should prompt the user to confirm closing and return their
+    * answer. If we're not busy, it should not prompt the user and return
+    * true.
+   */
+  @Test
+  public void testBusy()
+  {
+      DualPaneUIVersion1 dualPane = createDualPaneUI()
+      GradleInterfaceVersion2 gradleInterface = (GradleInterfaceVersion2) dualPane.getGradleInterfaceVersion1()
+
+      //this starts the execution queue. This also initiates a refresh that we'll ignore later.
+      dualPane.aboutToShow()
+
+      //add a request observer so we can observe when the command is finished.
+      TestRequestObserver testRequestObserver = new TestRequestObserver( RequestVersion1.EXECUTION_TYPE )
+      gradleInterface.addRequestObserver( testRequestObserver )
+
+      gradleInterface.executeCommand("build", "test command")
+
+      //now that there's a real command in the queue, we should be considered busy
+      Assert.assertTrue( dualPane.isBusy() )
+      Assert.assertTrue( gradleInterface.isBusy() )
+
+      //we're busy, we shouldn't be able to close
+      TestCloseInteraction testCloseInteraction = new TestCloseInteraction( false )
+      Assert.assertFalse( dualPane.canClose( testCloseInteraction ) )
+
+      //since we just asked to close and we're busy, make sure we prompted the user
+      Assert.assertTrue( testCloseInteraction.wasPromptedToConfirmClose )
+
+      //now we'll wait up to x seconds (arbitrary) for the task to execute. This is ugly, but its just a test.
+      int maximumWaitTime = 80;
+      int totalWaitTime = 0;
+      while ( testRequestObserver.request == null && totalWaitTime <= maximumWaitTime ) {
+          try {
+              Thread.sleep(1000);
+          }
+          catch (InterruptedException e) {
+              e.printStackTrace();
+          }
+
+          totalWaitTime += 1;
+      }
+
+      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.\nCurrent project directory: '" + gradleInterface.getCurrentDirectory() + "'\ngradle home: '" + gradleInterface.getGradleHomeDirectory() + "'\nOutput:\n" + testRequestObserver.output)
+      }
+
+      Assert.assertEquals( "Incorrect request", "build", testRequestObserver.request.getFullCommandLine() )
+
+      //make sure it completed execution correctly
+      Assert.assertEquals( "Execution failed with return code: " + testRequestObserver.result + "\nOutput:\n" + testRequestObserver.output , 0, testRequestObserver.result )
+
+      //make sure we're not longer considered busy
+      Assert.assertFalse( dualPane.isBusy() )
+      Assert.assertFalse( gradleInterface.isBusy() )
+
+      //make sure we can close now
+      testCloseInteraction = new TestCloseInteraction( false )
+      Assert.assertTrue( dualPane.canClose( testCloseInteraction ) )
+
+      //since we just asked to close and we're NOT busy, make sure we did NOT prompt the user
+      Assert.assertFalse( testCloseInteraction.wasPromptedToConfirmClose )
+
+      gradleInterface.removeRequestObserver( testRequestObserver )
+  }
+
+   /**
+   * This tests that we can set a custom gradle executor.
+    */
+    @Test
+    public void testSettingCustomGradleExecutor()
+    {
+       DualPaneUIVersion1 dualPane = createDualPaneUI()
+      GradleInterfaceVersion2 gradleInterface = (GradleInterfaceVersion2) dualPane.getGradleInterfaceVersion1()
+
+      //it should be null by default
+      Assert.assertNull( gradleInterface.getCustomGradleExecutable() )
+
+      //now let's set it to a custom gradle executable. Actually, we're not going to really get
+      //a custom one; we'll use the normal one. Why? Because a real custom one would probably
+      //become a pain for maintaining this test. Here, we're interested that the basics are working
+      //from an open-api standpoint.
+      File gradleExecutor = getCustomGradleExecutable()
+
+      gradleInterface.setCustomGradleExecutable( gradleExecutor )
+
+      //make sure it was set
+      Assert.assertEquals( gradleExecutor, gradleInterface.getCustomGradleExecutable() )
+      Assert.assertEquals( gradleExecutor, dualPane.getCustomGradleExecutable() ) //just another way to get it
+
+      //add a request observer so we can observe when the command is finished.
+      TestRequestObserver testRequestObserver = new TestRequestObserver( RequestVersion1.REFRESH_TYPE )
+      gradleInterface.addRequestObserver( testRequestObserver )
+
+      //this starts the execution queue
+      dualPane.aboutToShow()
+
+      dualPane.refreshTaskTree()
+
+      //now we'll wait up to x seconds (arbitrary) for the task to execute. This is ugly, but its just a test.
+      int maximumWaitTime = 80;
+      int totalWaitTime = 0;
+      while ( testRequestObserver.request == null && totalWaitTime <= maximumWaitTime ) {
+          try {
+              Thread.sleep(1000);
+          }
+          catch (InterruptedException e) {
+              e.printStackTrace();
+          }
+
+          totalWaitTime += 1;
+      }
+
+      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.\nCurrent project directory: '" + gradleInterface.getCurrentDirectory() + "'\ngradle home: '" + gradleInterface.getGradleHomeDirectory() + "'\nOutput:\n" + testRequestObserver.output)
+      }
+
+      //make sure it completed execution correctly
+      Assert.assertEquals( "Execution failed with return code: " + testRequestObserver.result + "\nOutput:\n" + testRequestObserver.output , 0, testRequestObserver.result )
+
+      gradleInterface.removeRequestObserver( testRequestObserver )
+    }
+
+
+   /**
+    * This gets a gradle executable. That is, a way to launch gradle (shell script or batch file).
+    */
+    private File getCustomGradleExecutable()
+    {
+      //now let's set it to a custom gradle executable. We'll just point it to the regular
+      //gradle file (but it'll be the custom one.
+      String name;
+      String osName = System.getProperty("os.name").toLowerCase(Locale.US);
+      if (osName.toLowerCase().indexOf("windows") > -1)
+      {
+        name = "gradle.bat"
+      }
+      else
+      {
+        name = "gradle"
+      }
+
+      File gradleExecutor = new File( dist.getGradleHomeDir(), "bin/" + name )
+
+      //make sure the executable exists
+      Assert.assertTrue( "Missing gradle executable at: " + gradleExecutor, gradleExecutor.exists() )
+
+      return gradleExecutor
+    }
+}
+
+  /**
+   * Inner class for tracking a component's visiblity has changed.
+   * A HierarchyListener is how Swing notifies you that a component's visibility has changed.
+   * We'll use it to track if the component was shown and then hidden.
+   */
+    private class TestVisibilityHierarchyListener implements HierarchyListener
+    {
+      private boolean componentWasShown = false;
+      private boolean componentWasHidden = false;
+
+       public void hierarchyChanged(HierarchyEvent e)
+       {
+         if((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED)!=0)
+         {
+            if( e.getComponent().isShowing() ) {
+              componentWasShown = true;
+            }
+            else {
+              componentWasHidden = true;
+            }
+         }
+       }
+     }
+
+
+
+ /**
+ * A class that manages a dummy gradle tab. It just consists of a label,
+ *  but tracks that certain fields were called.
+  */
+  public class TestTab implements GradleTabVersion1
+  {
+    private JLabel label = new JLabel("Testing Testing 123")
+    private TestVisibilityHierarchyListener hierarchyAdapter = new TestVisibilityHierarchyListener()
+    private boolean nameRetrieved
+    private boolean informedAboutToShow
+    private boolean componentCreated
+
+    def TestTab() {
+      label.addHierarchyListener( hierarchyAdapter )
+    }
+
+    private void reset() {
+      label.removeHierarchyListener( hierarchyAdapter )         //remove the existing listener
+      hierarchyAdapter = new TestVisibilityHierarchyListener()  //create a new one
+      label.addHierarchyListener( hierarchyAdapter )            //add it
+
+      nameRetrieved = false
+      informedAboutToShow = false
+      componentCreated = false
+    }
+
+    String getName() {
+      nameRetrieved = true;
+      return "Test Tab";
+    }
+
+    Component createComponent() {
+      componentCreated = true;
+      return label;
+    }
+
+    void aboutToShow() {
+      informedAboutToShow = true;
+    }
+  }
+
+  /**
+  * This allows us to get a copy of hte request that was executed so we can inspect it when its done
+    */
+  private class TestRequestObserver implements RequestObserverVersion1
+  {
+    private String typeOfInterest;  //either RequestVersion1.EXECUTION_TYPE or RequestVersion1.REFRESH_TYPE
+    private RequestVersion1 request
+    private int result = -98 //means it hasn't been set to anything. 0 means success, so we have to initialize it to something else
+    private String output
+
+
+    def TestRequestObserver(typeOfInterest) {
+      this.typeOfInterest = typeOfInterest;
+    }
+
+    void executionRequestAdded(RequestVersion1 request) { }
+    void refreshRequestAdded(RequestVersion1 request) { }
+    void aboutToExecuteRequest(RequestVersion1 request) { }
+
+    void requestExecutionComplete(RequestVersion1 request, int result, String output) {
+      if( this.typeOfInterest.equals( request.getType() ) ) {  //refreshes will come through here. We're ignoring those
+        this.request = request
+        this.result = result
+        this.output = output
+      }
+    }
+
+  }
+
+  /**
+   * Class that tracks whether we were prompted to confirm close. It also returns a specific
+   * value to that prompt.
+   */
+    private class TestCloseInteraction implements BasicGradleUIVersion1.CloseInteraction
+    {
+      boolean wasPromptedToConfirmClose
+      boolean promptResult
+
+
+
+      def TestCloseInteraction(promptResult) {
+        this.promptResult = promptResult;
+      }
+
+      boolean promptUserToConfirmClosingWhileBusy() {
+        wasPromptedToConfirmClose = true
+        return promptResult
+      }
+    }
+
+  /**
+   * This appends a specified string to the command line when executing a command. 
+   */
+  private class TestCommandLineArgumentAlteringListenerVersion1 implements CommandLineArgumentAlteringListenerVersion1
+  {
+    private String additionalArguments;
+
+    def TestCommandLineArgumentAlteringListenerVersion1(additionalArguments) {
+      this.additionalArguments = additionalArguments;
+    }
+
+    String getAdditionalCommandLineArguments(String commandLineArguments) {
+      if( commandLineArguments.equals( "-s" ) ) {  //we're only interested in altering this one command
+        return additionalArguments;
+      }
+
+      return null;
+    }
+  }
+
+
diff --git a/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/OutputUILordTest.groovy b/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/OutputUILordTest.groovy
new file mode 100644
index 0000000..6aeb90c
--- /dev/null
+++ b/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/OutputUILordTest.groovy
@@ -0,0 +1,212 @@
+/*
+ * 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 java.awt.Font
+import javax.swing.UIManager
+import junit.framework.AssertionFailedError
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.openapi.external.foundation.GradleInterfaceVersion1
+import org.gradle.openapi.external.foundation.RequestObserverVersion1
+import org.gradle.openapi.external.foundation.RequestVersion1
+import org.gradle.openapi.external.ui.OutputUILordVersion1
+import org.gradle.openapi.external.ui.SinglePaneUIVersion1
+import org.gradle.openapi.external.ui.UIFactory
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests aspects of the OutputUILord in OpenAPI
+ *
+ * @author mhunsicker
+ */
+ at RunWith(DistributionIntegrationTestRunner.class)
+public class OutputUILordTest  {
+  static final String JAVA_PROJECT_NAME = 'javaproject'
+  static final String SHARED_NAME = 'shared'
+  static final String API_NAME = 'api'
+  static final String WEBAPP_NAME = 'webservice'
+  static final String SERVICES_NAME = 'services'
+  static final String WEBAPP_PATH = "$SERVICES_NAME/$WEBAPP_NAME" as String
+
+  private File javaprojectDir
+
+  @Rule public final GradleDistribution dist = new GradleDistribution()
+  @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+  @Rule public final Sample sample = new Sample('java/quickstart')
+
+  @Before
+  void setUp() {
+      javaprojectDir = sample.dir
+  }
+
+  /**
+   * Helper function that creates a single pane UI
+   */
+   private SinglePaneUIVersion1 createSinglePaneUI()
+   {
+     TestSingleDualPaneUIInteractionVersion1 testSingleDualPaneUIInteractionVersion1 = new TestSingleDualPaneUIInteractionVersion1( new TestAlternateUIInteractionVersion1(), new TestSettingsNodeVersion1() )
+     SinglePaneUIVersion1 singlePane = UIFactory.createSinglePaneUI(getClass().getClassLoader(), dist.getGradleHomeDir(), testSingleDualPaneUIInteractionVersion1, false )
+
+     //make sure we got something
+     Assert.assertNotNull( singlePane )
+
+     singlePane.setCurrentDirectory( javaprojectDir )
+
+     return singlePane
+   }
+
+  /**
+  * This verifies that you can add file extension to the output lord. This is for
+  * highlighting file links in the output. Here, we're just interested in whether
+  * or not the functions work via/exists in the Open API. The actual functionality
+  * is tested elsewhere.
+  */
+  @Test
+  public void testAddingFileExtension()
+  {
+    SinglePaneUIVersion1 singlePane = createSinglePaneUI()
+    OutputUILordVersion1 outputUILord = singlePane.getOutputLord()
+
+    outputUILord.addFileExtension( '.txt', ':' )
+    List extensions = outputUILord.getFileExtensions()
+    Assert.assertTrue( extensions.contains( '.txt' ) )
+  }
+
+  /**
+  * This verifies that you can add prefixed file extensions to the output lord. This
+  * is for highlighting file links in the output. Here, we're just interested in whether
+  * or not the functions work via/exists in the Open API. The actual functionality is tested elsewhere.
+  */
+  @Test
+  public void testAddingPrefixedFileLink()
+  {
+    SinglePaneUIVersion1 singlePane = createSinglePaneUI()
+    OutputUILordVersion1 outputUILord = singlePane.getOutputLord()
+
+    outputUILord.addPrefixedFileLink( "Error Text", "The error is:", ".txt", ":" )
+  }
+
+  /**
+  * This tests setting the font. There's not much here to do other than set it and then
+  * get it, making sure its the same. This isn't worried so much about the font itself as
+  * much as the open API doesn't have a problem with setting the font.
+  */
+  @Test
+  public void testFont()
+  {
+    if ( java.awt.GraphicsEnvironment.isHeadless() ) {
+       return;  // Can't run this test in headless mode!
+    }
+
+    SinglePaneUIVersion1 singlePane = createSinglePaneUI()
+    OutputUILordVersion1 outputUILord = singlePane.getOutputLord()
+    Font font = UIManager.getFont( "Button.font" )  //this specific font is not important
+
+    //make sure that the above font doesn't happen to be the default font for the output lord. If it
+    //is, this test will silently succeed even if it should fail.
+    Assert.assertNotSame( "Fonts are the same. This test is not setup correctly.", font, outputUILord.getOutputTextFont() )
+
+    //now set the new font and then make sure it worked
+    outputUILord.setOutputTextFont( font )
+  }
+
+  /**
+  *
+  */
+  @Test
+  public void testReExecute()
+  {
+    SinglePaneUIVersion1 singlePane = createSinglePaneUI()
+    OutputUILordVersion1 outputUILord = singlePane.getOutputLord()
+
+    //this starts the execution queue. This also initiates a refresh that we'll ignore later.
+    singlePane.aboutToShow()
+
+    TestRequestObserver2 testRequestObserver = new TestRequestObserver2()
+    singlePane.getGradleInterfaceVersion1().addRequestObserver( testRequestObserver )
+
+    //now execute a command
+    singlePane.executeCommand( "build", "test build")
+
+    //wait for it to complete
+    waitForCompletion( testRequestObserver, singlePane.getGradleInterfaceVersion1(), 80 )
+
+    //now the single command we're trying to test
+    outputUILord.reExecuteLastCommand();
+
+    //wait again for it exit
+    waitForCompletion( testRequestObserver, singlePane.getGradleInterfaceVersion1(), 80 )
+
+    //make sure it executed the correct request
+    Assert.assertEquals( "Incorrect request", "build", testRequestObserver.executionRequest.getFullCommandLine() )
+  }
+
+  private void waitForCompletion( TestRequestObserver2 testRequestObserver, GradleInterfaceVersion1 gradleInterface, int maximumWaitTime )
+  {
+    int totalWaitTime = 0;
+    while ( testRequestObserver.executionRequest == null && totalWaitTime <= maximumWaitTime ) {
+        try {
+            Thread.sleep(1000);
+        }
+        catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+
+        totalWaitTime += 1;
+    }
+
+    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.\nCurrent project directory: '" + gradleInterface.getCurrentDirectory() + "'\ngradle home: '" + gradleInterface.getGradleHomeDirectory() + "'\nOutput:\n" + testRequestObserver.output)
+    }
+  }
+}
+
+    /**
+      * This allows us to get a copy of the request that was executed so we can inspect it when its done
+      */
+    private class TestRequestObserver2 implements RequestObserverVersion1
+    {
+      public RequestVersion1 executionRequest
+      public int result = -98 //means it hasn't been set to anything. 0 means success, so we have to initialize it to something else
+      public String output
+
+      void executionRequestAdded(RequestVersion1 request) { }
+      void refreshRequestAdded(RequestVersion1 request) { }
+      void aboutToExecuteRequest(RequestVersion1 request) { }
+
+      void requestExecutionComplete(RequestVersion1 request, int result, String output) {
+        if( RequestVersion1.EXECUTION_TYPE.equals( request.getType() ) ) {  //refreshes will come through here. We're ignoring those
+          this.executionRequest = request
+          this.result = result
+          this.output = output
+        }
+      }
+
+      public void reset() {
+        this.executionRequest = null;
+        this.result = -98;
+        this.output = null;
+      }
+
+    }
+
diff --git a/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/TestAlternateUIInteractionVersion1.java b/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/TestAlternateUIInteractionVersion1.java
new file mode 100644
index 0000000..ef5d2c2
--- /dev/null
+++ b/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/TestAlternateUIInteractionVersion1.java
@@ -0,0 +1,54 @@
+/*
+ * 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.openapi.external.ui.AlternateUIInteractionVersion1;
+
+import java.io.File;
+
+/**
+ * Implementation of AlternateUIInteractionVersion1 for testing purposes.
+ * This would lend itself well for mocking. 
+ *
+ * @author mhunsicker
+  */
+public class TestAlternateUIInteractionVersion1 implements AlternateUIInteractionVersion1 {
+
+    private boolean supportsEditingOpeningFiles;
+
+    public TestAlternateUIInteractionVersion1() {
+    }
+
+    public TestAlternateUIInteractionVersion1(boolean supportsEditingOpeningFiles) {
+        this.supportsEditingOpeningFiles = supportsEditingOpeningFiles;
+    }
+
+    public void openFile(File file, int line) {
+
+    }
+
+    public void editFile(File file, int line) {
+
+    }
+
+    public boolean doesSupportEditingOpeningFiles() {
+        return supportsEditingOpeningFiles;
+    }
+
+    public void aboutToExecuteCommand(String fullCommandLine) {
+
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/TestSettingsNodeVersion1.java b/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/TestSettingsNodeVersion1.java
new file mode 100644
index 0000000..62032c4
--- /dev/null
+++ b/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/TestSettingsNodeVersion1.java
@@ -0,0 +1,247 @@
+/*
+ * 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.openapi.external.ui.SettingsNodeVersion1;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Implementation of settings node. It basically mirrors a DOM.
+ * @author mhunsicker
+ */
+public class TestSettingsNodeVersion1 implements SettingsNodeVersion1 {
+
+    private String name;
+    private String value;
+    private HashMap<String,String> attributes = new HashMap<String, String>();
+    private SettingsNodeVersion1 parent;
+    private List<SettingsNodeVersion1> children = new ArrayList<SettingsNodeVersion1>();
+
+
+    public TestSettingsNodeVersion1() {
+        //this creates a root settings node.
+    }
+
+    public TestSettingsNodeVersion1(SettingsNodeVersion1 parent) {
+        this.parent = parent;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValueOfChild(String name, String value) {
+        SettingsNodeVersion1 settingsNode = addChildIfNotPresent(name);
+        settingsNode.setValue(value);
+    }
+
+    public String getValueOfChild(String name, String defaultValue) {
+        SettingsNodeVersion1 settingsNode = getChildNode(name);
+        if (settingsNode != null) {
+            String value = settingsNode.getValue();
+            if (value != null) {
+                return value;
+            }
+        }
+        return defaultValue;
+    }
+
+    public SettingsNodeVersion1 getChildNode(String name) {
+        Iterator<SettingsNodeVersion1> iterator = children.iterator();
+        while (iterator.hasNext()) {
+            SettingsNodeVersion1 childNode = iterator.next();
+            if (name.equals(childNode.getName() )) {
+                return childNode;
+            }
+        }
+        return null;
+    }
+
+    public List<SettingsNodeVersion1> getChildNodes() {
+        return children;
+    }
+
+    public List<SettingsNodeVersion1> getChildNodes(String name) {
+        List<SettingsNodeVersion1> children = new ArrayList<SettingsNodeVersion1>();
+
+        Iterator<SettingsNodeVersion1> iterator = children.iterator();
+        while (iterator.hasNext()) {
+            SettingsNodeVersion1 childNode = iterator.next();
+            if (name.equals(childNode.getName()) ) {
+                children.add(childNode);
+            }
+        }
+
+        return children;
+    }
+
+    public int getValueOfChildAsInt(String name, int defaultValue) {
+        SettingsNodeVersion1 settingsNode = getChildNode(name);
+        if (settingsNode != null) {
+            String value = settingsNode.getValue();
+
+            try {
+                if (value != null) {
+                    return Integer.parseInt(value);
+                }
+            } catch (NumberFormatException e) {
+                //we couldn't parse it. Just return the default.
+            }
+        }
+        return defaultValue;
+    }
+
+    public void setValueOfChildAsInt(String name, int value) {
+        setValueOfChild(name, Integer.toString(value));
+    }
+
+    public long getValueOfChildAsLong(String name, long defaultValue) {
+        SettingsNodeVersion1 settingsNode = getChildNode(name);
+        if (settingsNode != null) {
+            String value = settingsNode.getValue();
+
+            try {
+                if (value != null) {
+                    return Long.parseLong(value);
+                }
+            } catch (NumberFormatException e) {
+                //we couldn't parse it. Just return the default.
+            }
+        }
+        return defaultValue;
+    }
+
+    public void setValueOfChildAsLong(String name, long value) {
+        setValueOfChild(name, Long.toString(value));
+    }
+
+    public boolean getValueOfChildAsBoolean(String name, boolean defaultValue) {
+        SettingsNodeVersion1 settingsNode = getChildNode(name);
+        if (settingsNode != null) {
+            String value = settingsNode.getValue();
+
+            //I'm not calling 'Boolean.parseBoolean( value )' because it will return false if the value isn't true/false
+            //and we want it to return whatever the default is if its not a boolean.
+            if (value != null) {
+                if ("true".equalsIgnoreCase(value)) {
+                    return true;
+                }
+
+                if ("false".equalsIgnoreCase(value)) {
+                    return false;
+                }
+            }
+        }
+
+        return defaultValue;
+    }
+
+    public void setValueOfChildAsBoolean(String name, boolean value) {
+        setValueOfChild(name, Boolean.toString(value));
+    }
+
+    public SettingsNodeVersion1 addChild(String name) {
+        SettingsNodeVersion1 childNode = new TestSettingsNodeVersion1( this );
+        childNode.setName( name );
+
+        children.add( childNode );
+        return childNode;
+    }
+
+    public SettingsNodeVersion1 addChildIfNotPresent(String name) {
+        SettingsNodeVersion1 settingsNode = getChildNode(name);
+        if (settingsNode == null) {
+            settingsNode = addChild(name);
+        }
+
+        return settingsNode;
+    }
+
+    public SettingsNodeVersion1 getNodeAtPath(String... pathPortions) {
+        if (pathPortions == null || pathPortions.length == 0) {
+            return null;
+        }
+
+        String firstPathPortion = pathPortions[0];
+
+        SettingsNodeVersion1 currentNode = getChildNode(firstPathPortion);
+
+        int index = 1; //Skip the first one. we've already used that one.
+        while (index < pathPortions.length && currentNode != null) {
+            String pathPortion = pathPortions[index];
+            currentNode = currentNode.getChildNode(pathPortion);
+            index++;
+        }
+
+        return currentNode;
+    }
+
+    private SettingsNodeVersion1 getNodeAtPathCreateIfNotFound(String... pathPortions) {
+        if (pathPortions == null || pathPortions.length == 0) {
+            return null;
+        }
+
+        String firstPathPortion = pathPortions[0];
+
+        SettingsNodeVersion1 currentNode = getChildNode(firstPathPortion);
+        if (currentNode == null) {
+            currentNode = addChild(firstPathPortion);
+        }
+
+        int index = 1;
+        while (index < pathPortions.length) {
+            String pathPortion = pathPortions[index];
+            currentNode = currentNode.getChildNode(pathPortion);
+            if (currentNode == null) {
+                currentNode = addChild(firstPathPortion);
+            }
+
+            index++;
+        }
+
+        return currentNode;
+    }
+
+    public void removeFromParent() {
+        ((TestSettingsNodeVersion1) this.parent).children.remove( this );
+        this.parent = null;
+    }
+
+    public void removeAllChildren() {
+        children.clear();
+    }
+
+    @Override
+    public String toString() {
+        return getName() + "='" + getValue() + "' " + children.size() + " children";
+    }
+}
diff --git a/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/TestSingleDualPaneUIInteractionVersion1.java b/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/TestSingleDualPaneUIInteractionVersion1.java
new file mode 100644
index 0000000..bf5ad6a
--- /dev/null
+++ b/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/TestSingleDualPaneUIInteractionVersion1.java
@@ -0,0 +1,45 @@
+/*
+ * 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.openapi.external.ui.AlternateUIInteractionVersion1;
+import org.gradle.openapi.external.ui.DualPaneUIInteractionVersion1;
+import org.gradle.openapi.external.ui.SettingsNodeVersion1;
+import org.gradle.openapi.external.ui.SinglePaneUIInteractionVersion1;
+
+/**
+ * This is a test implementation of both SinglePaneUIInteractionVersion1 and DualPaneUIInteractionVersion1.
+ * This is really just a container to hand off more complex interactions to the UI when asked for.
+ * @author mhunsicker
+ */
+public class TestSingleDualPaneUIInteractionVersion1 implements SinglePaneUIInteractionVersion1, DualPaneUIInteractionVersion1 {
+
+    private AlternateUIInteractionVersion1 alternateUIInteractionVersion1;
+    private SettingsNodeVersion1 settingsNodeVersion1;
+
+    public TestSingleDualPaneUIInteractionVersion1(AlternateUIInteractionVersion1 alternateUIInteractionVersion1, SettingsNodeVersion1 settingsNodeVersion1) {
+        this.alternateUIInteractionVersion1 = alternateUIInteractionVersion1;
+        this.settingsNodeVersion1 = settingsNodeVersion1;
+    }
+
+    public AlternateUIInteractionVersion1 instantiateAlternateUIInteraction() {
+        return alternateUIInteractionVersion1;
+    }
+
+    public SettingsNodeVersion1 instantiateSettings() {
+        return settingsNodeVersion1;
+    }
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/foundation/BootstrapLoader.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/foundation/BootstrapLoader.java
new file mode 100644
index 0000000..26468af
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/foundation/BootstrapLoader.java
@@ -0,0 +1,193 @@
+/*
+ * 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.foundation;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/*
+ This handles the work of loading gradle dynamically. Due to jar version issues,
+ you can't just load all jar files.
+
+ This does NOT require any system or environment variables to be set.
+
+ To use this, instantiate this, then call one of the initialize functions.
+ Now you can get the class loader to load whatever classes you like.
+
+ @author mhunsicker
+  */
+public class BootstrapLoader
+{
+   private URLClassLoader libClassLoader;
+
+    public void initialize(File gradleHome, boolean bootStrapDebug) throws Exception {
+        initialize(ClassLoader.getSystemClassLoader().getParent(), gradleHome, false, true, bootStrapDebug);
+    }
+
+    /*
+      Call this to initialize gradle.
+      @param  parentClassloader    a parent class loader. Probably whatever class loader
+                                   is used by the caller.
+      @param  gradleHome           the root directory where gradle is installed. This
+                                   directory should have a 'bin' child directory.
+      @param  useParentLastClassLoader true to use a class loader that will delegate
+                                   to the parent only if it can't find it locally. This
+                                   should only be true if you're trying to load gradle
+                                   dynamically from another application.
+      @param  loadOpenAPI          True to load the gradle open API, false not to.
+                                   If you're calling this from a tool using the OpenAPI,
+                                   then you've probably already loaded it, so pass in false
+                                   here, otherwise, pass in true.
+      @param  bootStrapDebug       true to output debug information about the loading
+                                   process.
+      @throws Exception            if something goes wrong.
+      @author mhunsicker
+   */
+   public void initialize(ClassLoader parentClassloader, File gradleHome, boolean useParentLastClassLoader,
+                          boolean loadOpenAPI, boolean bootStrapDebug) throws Exception {
+       if (gradleHome == null || !gradleHome.exists()) {
+           throw new RuntimeException("Gradle home not defined!");
+       }
+
+       if (bootStrapDebug) {
+           System.out.println(
+                   "Gradle Home is declared by system property gradle.home to: " + gradleHome.getAbsolutePath());
+       }
+
+       System.setProperty("gradle.home", gradleHome.getAbsolutePath());
+
+       List<URL> loggingJars = toUrl(getLoggingJars());
+
+       List<File> nonLoggingJarFiles = getNonLoggingJars();
+       removeUnwantedJarFiles(nonLoggingJarFiles, loadOpenAPI);
+       List<URL> nonLoggingJars = toUrl(nonLoggingJarFiles);
+
+       if (bootStrapDebug) {
+           System.out.println("Parent Classloader of new context classloader is: " + parentClassloader);
+           System.out.println("Adding the following files to new logging classloader: " + loggingJars);
+           System.out.println("Adding the following files to new lib classloader: " + nonLoggingJars);
+       }
+
+       URLClassLoader loggingClassLoader = new URLClassLoader(loggingJars.toArray(new URL[loggingJars.size()]),
+               parentClassloader);
+
+       if (useParentLastClassLoader) {
+           libClassLoader = new ParentLastClassLoader(nonLoggingJars.toArray(new URL[nonLoggingJars.size()]),
+                   loggingClassLoader);
+       } else {
+           libClassLoader = new URLClassLoader(nonLoggingJars.toArray(new URL[nonLoggingJars.size()]),
+                   loggingClassLoader);
+       }
+
+       if (bootStrapDebug) {
+           System.out.println("Logging class loader: " + loggingClassLoader);
+           System.out.println("Lib class loader: " + libClassLoader);
+       }
+   }
+
+    public static File[] getGradleHomeLibClasspath() {
+        File gradleHomeLib = new File(System.getProperty("gradle.home") + "/lib");
+        if (gradleHomeLib.isDirectory()) {
+            return gradleHomeLib.listFiles();
+        }
+        return new File[0];
+    }
+
+    public static List<File> getNonLoggingJars() {
+        List<File> pathElements = new ArrayList<File>();
+        for (File file : getGradleClasspath()) {
+            if (!isLogLib(file)) {
+                pathElements.add(file);
+            }
+        }
+        return pathElements;
+    }
+
+    public static List<File> getLoggingJars() {
+        List<File> pathElements = new ArrayList<File>();
+        for (File file : getGradleClasspath()) {
+            if (isLogLib(file)) {
+                pathElements.add(file);
+            }
+        }
+        return pathElements;
+    }
+
+    private static boolean isLogLib(File file) {
+        return file.getName().startsWith("logback") || file.getName().startsWith("slf4j");
+    }
+
+    public static List<File> getGradleClasspath() {
+        File customGradleBin = null;
+        List<File> pathElements = new ArrayList<File>();
+        if (System.getProperty("gradle.bootstrap.gradleBin") != null) {
+            customGradleBin = new File(System.getProperty("gradle.bootstrap.gradleBin"));
+            pathElements.add(customGradleBin);
+        }
+        for (File homeLibFile : getGradleHomeLibClasspath()) {
+            if (homeLibFile.isFile() && !(customGradleBin != null && homeLibFile.getName().startsWith("gradle-"))) {
+                pathElements.add(homeLibFile);
+            }
+        }
+        return pathElements;
+    }
+
+    /*
+      This removes unwanted jar files. At the time of this writing, we're only
+      interested in the open api jar.
+
+      @param  nonLoggingJarFiles a list of jar files
+      @param  loadOpenAPI        true to keep the open api jar, false to remove it.
+      @author mhunsicker
+   */
+    private void removeUnwantedJarFiles(List<File> nonLoggingJarFiles, boolean loadOpenAPI) {
+        if (loadOpenAPI) {
+            return;
+        }
+
+        Iterator<File> iterator = nonLoggingJarFiles.iterator();
+        while (iterator.hasNext()) {
+            File file = iterator.next();
+            if (file.getName().startsWith("gradle-open-api-")) {
+                iterator.remove();
+            }
+        }
+    }
+
+    /*
+      Call this to get the class loader you can use to load gradle classes.
+      @return a URLClassLoader
+      @author mhunsicker
+   */
+   public URLClassLoader getClassLoader() { return libClassLoader; }
+
+    public Class load(String classPath) throws Exception {
+        return libClassLoader.loadClass(classPath);
+    }
+
+    private static List<URL> toUrl(List<File> files) throws MalformedURLException {
+        List<URL> result = new ArrayList<URL>();
+        for (File file : files) {
+            result.add(file.toURI().toURL());
+        }
+        return result;
+    }
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/foundation/ParentLastClassLoader.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/foundation/ParentLastClassLoader.java
new file mode 100644
index 0000000..0976d35
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/foundation/ParentLastClassLoader.java
@@ -0,0 +1,88 @@
+/*
+ * 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.foundation;
+
+import java.net.URLClassLoader;
+import java.net.URL;
+import java.net.URLStreamHandlerFactory;
+
+/**
+
+ This class loader delegates to the parent class loader ONLY if it cannot find
+ it itself. This is meant to solve classloading issues when running something
+ as, say, a plugin inside an application that may have already loaded a different
+ version of some required jars. This makes sure it looks locally first. This is
+ the opposite of a ClassLoader's typical behavior, but it necessary when you
+ can't control the environment in which you're running.
+
+ Using this class can be very dangerous. You must carefully make sure you
+ understand the ramifications of using this. You should also probably make this
+ the first class loader between your plugin and the plugin's owner.
+
+ @author mhunsicker
+  */
+public class ParentLastClassLoader extends URLClassLoader
+{
+   public ParentLastClassLoader( URL[] urls, ClassLoader parent )
+   {
+      super( urls, parent );
+   }
+
+   public ParentLastClassLoader( URL[] urls )
+   {
+      super( urls );
+   }
+
+   public ParentLastClassLoader( URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory )
+   {
+      super( urls, parent, factory );
+   }
+
+   /*
+    This has been overridden to look at the parent class loader last.
+    @author mhunsicker
+    */
+   @Override
+   public Class<?> loadClass( String name ) throws ClassNotFoundException
+   {
+      // First check whether it's already been loaded, if so use it
+      Class loadedClass = findLoadedClass( name );
+
+      // Not loaded, try to load it
+      if( loadedClass == null )
+      {
+         try
+         {
+            // Ignore parent delegation and just try to load locally
+            loadedClass = findClass( name );
+         }
+         catch( ClassNotFoundException e )
+         {
+            // Swallow exception - does not exist locally
+         }
+
+         // If not found locally, use normal parent delegation in URLClassloader
+         if( loadedClass == null )
+         {
+            // throws ClassNotFoundException if not found in delegation hierarchy at all
+            loadedClass = super.loadClass( name );
+         }
+      }
+      // will never return null (ClassNotFoundException will be thrown)
+      return loadedClass;
+   }
+}
+
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ExternalUtility.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ExternalUtility.java
new file mode 100644
index 0000000..e000b6b
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ExternalUtility.java
@@ -0,0 +1,194 @@
+/*
+ * 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.openapi.external;
+
+import org.gradle.foundation.BootstrapLoader;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.regex.Pattern;
+
+/**
+
+ Utility functions required by the OpenAPI
+
+ @author mhunsicker
+  */
+public class ExternalUtility
+{
+    private static final Pattern GRADLE_CORE_PATTERN = Pattern.compile("^gradle-core-\\d.*\\.jar$");
+
+   /*
+      Call this to get a classloader that has loaded gradle.
+
+      @param  parentClassLoader    Your classloader. Probably the classloader
+                                   of whatever class is calling this.
+      @param  gradleHomeDirectory  the root directory of a gradle installation
+      @param  showDebugInfo        true to show some additional information that
+                                   may be helpful diagnosing problems is this
+                                   fails
+      @return a classloader that has loaded gradle and all of its dependencies.
+      @author mhunsicker
+   */
+
+    public static ClassLoader getGradleClassloader(ClassLoader parentClassLoader, File gradleHomeDirectory,
+                                                   boolean showDebugInfo) throws Exception {
+        File gradleJarFile = getGradleJar(gradleHomeDirectory);
+        if (gradleJarFile == null) {
+            throw new RuntimeException(
+                    "Not a valid gradle home directory '" + gradleHomeDirectory.getAbsolutePath() + "'");
+        }
+
+        System.setProperty("gradle.home", gradleHomeDirectory.getAbsolutePath());
+
+        BootstrapLoader bootstrapLoader = new BootstrapLoader();
+        bootstrapLoader.initialize(parentClassLoader, gradleHomeDirectory, true, false, showDebugInfo);
+        return bootstrapLoader.getClassLoader();
+    }
+
+    /*
+       This locates the gradle jar. We do NOT want the gradle-wrapper jar.
+
+       @param  gradleHomeDirectory the root directory of a gradle installation.
+                                   We're expecting this to have a child directory
+                                   named 'lib'.
+       @return the gradle jar file. Null if we didn't find it.
+       @author mhunsicker
+    */
+
+    public static File getGradleJar(File gradleHomeDirectory) {
+        File libDirectory = new File(gradleHomeDirectory, "lib");
+        if (!libDirectory.exists()) {
+            return null;
+        }
+
+        //try to get the gradle.jar. It'll be "gradle-[version].jar"
+        File[] files = libDirectory.listFiles(new FileFilter() {
+            public boolean accept(File file) {
+                return GRADLE_CORE_PATTERN.matcher(file.getName()).matches();
+            }
+        });
+
+        if (files == null || files.length == 0) {
+            return null;
+        }
+
+        //if they've given us a directory with multiple gradle jars, tell them. We won't know which one to use.
+        if (files.length > 1) {
+            throw new RuntimeException(
+                    "Installation has multiple gradle jars. Cannot determine which one to use. Found files: " + createFileNamesString(files));
+        }
+
+        return files[0];
+    }
+
+    private static StringBuilder createFileNamesString(File[] files) {
+        StringBuilder fileNames = new StringBuilder();
+        for (File f : files)
+        {
+            fileNames.append(f.getName() + ", ");
+        }
+        fileNames.delete(fileNames.length() - 2, fileNames.length()); // Remove the trailing ', '
+        return fileNames;
+    }
+
+    //just a function to help debugging. If we can't find the constructor we want, this dumps out what is available.
+
+    public static String dumpConstructors(Class classInQuestion) {
+        StringBuilder builder = new StringBuilder();
+        Constructor[] constructors = classInQuestion.getConstructors();
+        for (int index = 0; index < constructors.length; index++) {
+            Constructor constructor = constructors[index];
+            builder.append(constructor).append('\n');
+        }
+
+        return builder.toString();
+    }
+
+
+    public static String dumpMethods(Class classInQuestion) {
+        StringBuilder builder = new StringBuilder();
+
+        Method[] methods = classInQuestion.getMethods();
+        for (int index = 0; index < methods.length; index++) {
+            Method method = methods[index];
+            builder.append(method).append('\n');
+        }
+
+        return builder.toString();
+    }
+
+    /**
+     * This attempts to load the a class from the specified gradle home directory.
+     * @param classToLoad  the full path to the class to load
+     * @param  parentClassLoader    Your classloader. Probably the classloader
+     *                              of whatever class is calling this.
+     * @param  gradleHomeDirectory  the root directory of a gradle installation
+     * @param  showDebugInfo        true to show some additional information that
+     *                              may be helpful diagnosing problems is this
+     *                              fails
+     */
+    public static Class loadGradleClass( String classToLoad, ClassLoader parentClassLoader, File gradleHomeDirectory, boolean showDebugInfo ) throws Exception
+    {
+       ClassLoader bootStrapClassLoader = getGradleClassloader( parentClassLoader, gradleHomeDirectory, showDebugInfo );
+       Thread.currentThread().setContextClassLoader(bootStrapClassLoader);
+
+       //load the class in gradle that wraps our return interface and handles versioning issues.
+      try
+      {
+         return bootStrapClassLoader.loadClass( classToLoad );
+      }
+      catch( NoClassDefFoundError e )
+      {  //might be a version mismatch
+         e.printStackTrace();
+         return null;
+      }
+      catch( Throwable e )
+      {  //might be a version mismatch
+         e.printStackTrace();
+         return null;
+      }
+    }
+
+     /**
+     * This wraps up invoking a static method into a single call.
+     * @param classToInvoke the class that has the method
+     * @param methodName    the name of the method to invoke
+     * @param argumentsClasses the classes of the arguments (we can't determine this from the argumentValues
+     *                          because they can be of class A, but implement class B and B is be the argument
+     *                          type of the method in question
+     * @param argumentValues   the values of the arguments.
+     * @return the return value of invoking the method.
+     * @throws Exception
+     */
+    public static Object invokeStaticMethod( Class classToInvoke, String methodName, Class[] argumentsClasses, Object ... argumentValues) throws Exception
+    {
+      Method method = null;
+      try
+      {
+         method = classToInvoke.getDeclaredMethod( methodName, argumentsClasses );
+      }
+      catch( NoSuchMethodException e )
+      {
+         e.printStackTrace();
+         System.out.println( "Dumping available methods on " + classToInvoke.getName() + "\n" + ExternalUtility.dumpMethods( classToInvoke ) );
+         throw e;
+      }
+      return method.invoke( null, argumentValues);
+    }
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/GradleInterfaceVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/GradleInterfaceVersion1.java
new file mode 100644
index 0000000..857d6f6
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/GradleInterfaceVersion1.java
@@ -0,0 +1,99 @@
+/*
+ * 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.openapi.external.foundation;
+
+import org.gradle.openapi.external.ui.CommandLineArgumentAlteringListenerVersion1;
+import java.io.File;
+import java.util.List;
+
+/**
+ * This is an abstraction from Gradle that allows you to retrieve projects
+ * and views from it.
+ *
+ * This is a mirror of GradlePluginLord inside Gradle, but this is meant
+ * to aid backward and forward compatibility by shielding you from direct
+ * changes within gradle.
+ *
+ * @author mhunsicker
+
+ */
+public interface GradleInterfaceVersion1 {
+
+    /**
+     * @return the root projects. It probably only has one.
+     */
+    public List<ProjectVersion1> getRootProjects();
+
+    /**
+    This refreshes the projects and task list.
+    */
+   public void refreshTaskTree();
+
+   /**
+      Determines if commands are currently being executed or not.
+      @return true if we're busy, false if not.
+   */
+   public boolean isBusy();
+
+    /*
+      Call this to execute the given gradle command.
+
+      @param  commandLineArguments the command line arguments to pass to gradle.
+      @param displayName           the name displayed in the UI for this command
+      @author mhunsicker
+   */
+   public void executeCommand( String commandLineArguments, String displayName );
+
+   /*
+      @return the root directory of your gradle project.
+      @author mhunsicker
+   */
+   public File getCurrentDirectory();
+
+   /*
+      @param  currentDirectory the new root directory of your gradle project.
+      @author mhunsicker
+   */
+   public void setCurrentDirectory( File currentDirectory );
+
+   /*
+      @return the gradle home directory. Where gradle is installed.
+      @author mhunsicker
+   */
+   public File getGradleHomeDirectory();
+
+   /*
+      This is called to get a custom gradle executable file. If you don't run
+      gradle.bat or gradle shell script to run gradle, use this to specify
+      what you do run. Note: we're going to pass it the arguments that we would
+      pass to gradle so if you don't like that, see alterCommandLineArguments.
+      Normally, this should return null.
+      @return the Executable to run gradle command or null to use the default
+      @author mhunsicker
+   */
+   public File getCustomGradleExecutable();
+
+   /*
+      This allows you to add a listener that can add additional command line
+      arguments whenever gradle is executed. This is useful if you've customized
+      your gradle build and need to specify, for example, an init script.
+
+      @param  listener   the listener that modifies the command line arguments.
+      @author mhunsicker
+   */
+   public void addCommandLineArgumentAlteringListener( CommandLineArgumentAlteringListenerVersion1 listener );
+   public void removeCommandLineArgumentAlteringListener( CommandLineArgumentAlteringListenerVersion1 listener );
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/GradleInterfaceVersion2.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/GradleInterfaceVersion2.java
new file mode 100644
index 0000000..4120eb0
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/GradleInterfaceVersion2.java
@@ -0,0 +1,95 @@
+/*
+ * 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.openapi.external.foundation;
+
+import org.gradle.openapi.external.foundation.favorites.FavoriteTaskVersion1;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * This is an abstraction from Gradle that allows you to retrieve projects
+ * and views from it.
+ *
+ * This is a mirror of GradlePluginLord inside Gradle, but this is meant
+ * to aid backward and forward compatibility by shielding you from direct
+ * changes within gradle.
+ *
+ * @author mhunsicker
+
+ */
+public interface GradleInterfaceVersion2 extends GradleInterfaceVersion1 {
+
+    /**
+     * @return the version of gradle being run. This is basically the version from the jar file.
+     */
+    public String getVersion();
+
+    /**
+    This refreshes the projects and task list.
+    */
+   public RequestVersion1 refreshTaskTree2();
+
+   /**
+    This refreshes the task tree. Useful if you know you've changed something behind
+    gradle's back or when first displaying this UI.
+    @param additionalCommandLineArguments additional command line arguments to be passed to gradle when
+                                          refreshing the task tree.
+    @return the request object. Useful if you want to track its completion via a RequestObserver
+    */
+   public RequestVersion1 refreshTaskTree2( String additionalCommandLineArguments );
+
+    /**
+      Call this to execute the given gradle command.
+
+      @param  commandLineArguments the command line arguments to pass to gradle.
+      @param displayName           the name displayed in the UI for this command
+      @return the request object. Useful if you want to track its completion via a RequestObserver
+      @author mhunsicker
+   */
+   public RequestVersion1 executeCommand2( String commandLineArguments, String displayName );
+
+    /**
+     * Executes several favorites commands at once as a single command. This has the affect
+     * of simply concatenating all the favorite command lines into a single line.
+     * @param favorites a list of favorites. If just one favorite, it executes it normally.
+     *                  If multiple favorites, it executes them all at once as a single command.
+     *                  This blindly concatenates them so it may wind up with duplicate tasks on
+     *                  the command line.
+     * @return the request object. Useful if you want to track its completion via a RequestObserver
+     */
+    public RequestVersion1 executeFavorites(List<FavoriteTaskVersion1> favorites);
+
+    /**
+     * Sets a custom gradle executable. See getCustomGradleExecutable
+     * @param customGradleExecutor the path to an executable (or script/batch file)
+     */
+   public void setCustomGradleExecutable(File customGradleExecutor);
+
+    /**
+     * Adds an observer that is notified when Gradle commands are executed and
+     * completed.
+     * @param observer the observer that is notified
+     */
+   public void addRequestObserver( RequestObserverVersion1 observer );
+
+    /**
+     * Removes a request observer when you no longer wish to receive notifications
+     * about Gradle command being executed.
+     * @param observer the observer to remove
+     */
+   public void removeRequestObserver( RequestObserverVersion1 observer );
+}
\ No newline at end of file
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/ProjectVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/ProjectVersion1.java
new file mode 100644
index 0000000..874d03e
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/ProjectVersion1.java
@@ -0,0 +1,89 @@
+/*
+ * 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.openapi.external.foundation;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * This is an abstraction of a Gradle project
+ *
+ * This is a mirror of ProjectView inside Gradle, but this is meant
+ * to aid backward and forward compatibility by shielding you from direct
+ * changes within gradle. 
+ *
+ * @author mhunsicker
+
+ */
+public interface ProjectVersion1 {
+
+    /**
+     * @return the name of this project
+     */
+    public String getName();
+
+    /**
+     * @return The full project name. This is just the project name if its off of the root.
+     * Otherwise, its all of its ancestors separated by colons with this project being last.
+     */
+    public String getFullProjectName();
+
+    /**
+     * @return the TaskVersion1 objects associated with this project
+     */
+    public List<TaskVersion1> getTasks();
+
+    /**
+     * @return the .gradle file this project is defined in
+     */
+    public File getFile();
+
+
+    /**
+     * @return the sub projects of this project
+     */
+    public List<ProjectVersion1> getSubProjects();
+
+    /**
+     * @return the parent of this project if this is a sub project. Otherwise, null
+     */
+    public ProjectVersion1 getParentProject();
+
+    /**
+     * @return a list of projects that this project depends on.
+     */
+    public List<ProjectVersion1> getDependantProjects();
+
+    public ProjectVersion1 getSubProject( String name );
+
+    public ProjectVersion1 getSubProjectFromFullPath(String fullProjectName);
+
+    public TaskVersion1 getTask(String name);
+
+    /**
+     * Builds a list of default tasks. These are defined by specifying
+     *
+     * defaultTasks 'task name'
+     *
+     * in the gradle file. There can be multiple default tasks. This only returns default tasks directly for this
+     * project and does not return them for subprojects.
+     *
+     * @return a list of default tasks or an empty list if none exist
+     */
+    public List<TaskVersion1> getDefaultTasks();
+
+    public TaskVersion1 getTaskFromFullPath(String fullTaskName);
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/RequestObserverVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/RequestObserverVersion1.java
new file mode 100644
index 0000000..72d3f86
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/RequestObserverVersion1.java
@@ -0,0 +1,58 @@
+/*
+ * 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.openapi.external.foundation;
+
+/**
+ * This allows you to observer when Gradle commands are executed/complete. It is
+ * an abstraction of a GradlePluginLord.RequestObserver.
+ *
+ * This is a mirror of GradlePluginLord.RequestObserver inside Gradle, but
+ * this is meant to aid backward and forward compatibility by shielding you
+ * from direct changes within gradle.
+ *
+ * @author mhunsicker
+ */
+public interface RequestObserverVersion1 {
+
+    /**
+     * Notification that an execution request was added to the queue. This is
+     * the normal request that initiates a gradle command.
+     * @param request the request that was added
+     */
+  public void executionRequestAdded( RequestVersion1 request );
+
+    /**
+     * Notification that a refresh request was added to the queue. This type
+     * of request updates the task tree.
+     * @param request
+     */
+  public void refreshRequestAdded( RequestVersion1 request );
+
+  /**
+   * Notification that a command is about to be executed. This is mostly useful
+   * for IDE's that may need to save their files. This is always called after
+   * a request has been added to the queue.
+  */
+  public void aboutToExecuteRequest( RequestVersion1 request );
+
+  /**
+   * Notification that a request has completed execution.
+   * @param request the original request containing the command that was executed
+   * @param result the result of the command
+   * @param output the output from gradle executing the command
+   */
+  public void requestExecutionComplete( RequestVersion1 request, int result, String output );
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/RequestVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/RequestVersion1.java
new file mode 100644
index 0000000..938e6d9
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/RequestVersion1.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.openapi.external.foundation;
+
+/**
+ * This represents an execution or refresh request sent to Gradle. Execution requests are
+ * just Gradle commands (what would be the command line arguments). A refresh request is
+ * what updates the task tree.
+ * 
+ * This is a mirror of Request inside Gradle, but this is meant to aid backward and forward compatibility by shielding you
+ * from direct changes within gradle.
+ *
+ * @author mhunsicker
+ */
+public interface RequestVersion1 {
+
+    /**
+     * @return the full gradle command line of this request
+     */
+    public String getFullCommandLine();
+
+    /**
+     * @return the display name of this request. Often this is the same as the full
+     * command line, but favorites may specify something more user-friendly.
+     */
+    public String getDisplayName();
+
+    /**
+     * @return whether or not output should always be shown. If false, only show it when
+     * errors occur.
+     */
+    public boolean forceOutputToBeShown();
+
+    /**
+    * Cancels this request.
+    */
+    public boolean cancel();
+
+    public static final String EXECUTION_TYPE = "execution";
+    public static final String REFRESH_TYPE = "refresh";
+    public static final String UNKNOWN_TYPE_PREFIX = "[unknown]:";
+
+    /**
+     * @return the type of the request. Either EXECUTION, REFRESH, or something that
+     * starts with UNKNOWN_TYPE_PREFIX (followed by an internal identifier) if its not
+     * listed above.
+     */
+    public String getType();
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/TaskVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/TaskVersion1.java
new file mode 100644
index 0000000..e430b51
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/TaskVersion1.java
@@ -0,0 +1,61 @@
+/*
+ * 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.openapi.external.foundation;
+
+/**
+ * This is an abstraction of a gradle task
+ *
+ * This is a mirror of TaskView inside Gradle, but this is meant
+ * to aid backward and forward compatibility by shielding you from direct
+ * changes within gradle.
+ *
+ * @author mhunsicker
+ */
+public interface TaskVersion1 {
+
+    /**
+     * @return the project this task is associated with
+     */
+    public ProjectVersion1 getProject();
+
+    /**
+     * @return the name of this task
+     */
+    public String getName();
+
+    /**
+     * @return this tasks description
+     */
+    public String getDescription();
+
+    /**
+     * returns whether or not this is a default task for its parent project. These are defined by specifying
+     *
+     * defaultTasks 'task name'
+     *
+     * in the gradle file. There can be multiple default tasks.
+     *
+     * @return true if its a default task, false if not.
+     */
+    public boolean isDefault();
+
+    /**
+     * This generates this task's full name. This is a colon-separated string of this task and its parent projects.
+     *
+     * Example: root_project:sub_project:sub_sub_project:task.
+     */
+    public String getFullTaskName();
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/favorites/FavoriteTaskVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/favorites/FavoriteTaskVersion1.java
new file mode 100644
index 0000000..d7b239a
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/favorites/FavoriteTaskVersion1.java
@@ -0,0 +1,49 @@
+/*
+ * 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.openapi.external.foundation.favorites;
+
+/**
+ * This is an abstraction from Gradle that allows you to work with a 'favorite' task.
+ *
+ * This is a mirror of FavoriteTask inside Gradle, but this is meant
+ * to aid backward and forward compatibility by shielding you from direct
+ * changes within gradle.
+ *
+ * You should not implement this yourself. Only use an implementation coming from Gradle.
+ *
+ * @author mhunsicker
+ */
+public interface FavoriteTaskVersion1
+{
+    /**<!====== getFullCommandLine ============================================>
+       @return the command line that is executed
+       @author mhunsicker
+    <!=======================================================================>*/
+    public String getFullCommandLine();
+
+    /**<!====== getDisplayName ================================================>
+       @return a display name for this command
+       @author mhunsicker
+    <!=======================================================================>*/
+    public String getDisplayName();
+
+    /**<!====== alwaysShowOutput ==============================================>
+       @return true if executing this command should always show the output, false
+               to only show output if an error occurs.
+       @author mhunsicker
+    <!=======================================================================>*/
+    public boolean alwaysShowOutput();
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/favorites/FavoritesEditorVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/favorites/FavoritesEditorVersion1.java
new file mode 100644
index 0000000..77e8bda
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/foundation/favorites/FavoritesEditorVersion1.java
@@ -0,0 +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.openapi.external.foundation.favorites;
+
+import org.gradle.openapi.external.foundation.TaskVersion1;
+
+import java.awt.*;
+import java.util.List;
+
+/**
+ * This is an abstraction from Gradle that allows you to obtain and edit favorites.
+ *
+ * This is a mirror of FavoritesEditor inside Gradle, but this is meant
+ * to aid backward and forward compatibility by shielding you from direct
+ * changes within gradle.
+ *
+ * @author mhunsicker
+
+ */
+public interface FavoritesEditorVersion1 {
+
+    /**
+     * Adds the specified favorite.
+     *
+     * @param fullCommandLine the command line that this favorite executes
+     * @param displayName  a more user-friendly name for the command
+     * @param alwaysShowOutput true to always show output when this favorite is executed. False to only show output when errors occur.
+     * @return the favorite added
+     */
+    public FavoriteTaskVersion1 addFavorite(String fullCommandLine, String displayName, boolean alwaysShowOutput);
+
+    /**
+     * Sets new values on the specified favorite task. This provides a simple way to programmatically edit favorite tasks.
+     * @param favoriteTask the favorite to edit
+     * @param newFullCommandLine the new command line
+     * @param newDisplayName the new display name
+     * @param newAlwaysShowOutput the new value for whether or not to always show output (vs only showing it when an error occurs).
+     * @returns null if successful otherwise, an error suitable for displaying to the user.
+     */
+    public String editFavorite( FavoriteTaskVersion1 favoriteTask, String newFullCommandLine, String newDisplayName, boolean newAlwaysShowOutput );
+
+    /**
+     * @return a list of all favorites in the system
+     */
+    public List<FavoriteTaskVersion1> getFavoriteTasks();
+
+    /**
+     * Returns the favorite with the specified command line
+     * @param fullCommandLine the command line of the sought favorite
+     * @return the matching favorite or null if no match found.
+     */
+    public FavoriteTaskVersion1 getFavorite(String fullCommandLine);
+
+    /**
+     * Returns the favorite with the specified display name
+     * @param displayName the display name of the sought favorite
+     * @return the matching favorite or null if no match found.
+     */
+    public FavoriteTaskVersion1 getFavoriteByDisplayName(String displayName);
+
+    /**
+     * Returns the favorite with the specified task
+     * @param task the task of the sought favorite
+     * @return the matching favorite or null if no match found.
+     */
+    public FavoriteTaskVersion1 getFavorite( TaskVersion1 task);
+
+    /**
+     * Display a Swing dialog prompting the user to enter a favorite.
+     * @param parent the parent window of the dialog.
+     * @return the favorite that was added or null if the user canceled
+     */
+    public FavoriteTaskVersion1 promptUserToAddFavorite( Window parent );
+
+    /**
+     * Display a Swing dialog prompting the user to edit the specified favorite
+     * @param parent the parent window of the dialog
+     * @param favorite the favorite to edit
+     * @return true if the user made changes and accepted them, false if the user canceled.
+     */
+    public boolean promptUserToEditFavorite( Window parent, FavoriteTaskVersion1 favorite );
+
+    /**
+     * Removes the specified favorites.
+     * @param favoritesToRemove the favorites to remove
+     */
+    public void removeFavorites( List<FavoriteTaskVersion1> favoritesToRemove);
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerFactory.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerFactory.java
new file mode 100644
index 0000000..e925d97
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerFactory.java
@@ -0,0 +1,156 @@
+/*
+ * 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.openapi.external.runner;
+
+import org.gradle.openapi.external.ExternalUtility;
+
+import java.io.File;
+import java.lang.reflect.Constructor;
+
+/**
+
+ This provides a simple way to execute gradle commands from an external
+ process. call createGradleRunner to instantiate a gradle runner. You can then
+ use that to execute commands.
+
+ @author mhunsicker
+  */
+public class GradleRunnerFactory
+{
+   /*
+      Call this to instantiate an object that you can use to execute gradle
+      commands directly.
+
+      Note: this function is meant to be backward and forward compatible. So
+      this signature should not change at all, however, it may take and return
+      objects that implement ADDITIONAL interfaces. That is, it will always
+      return a GradleRunnerVersion1, but it may also be an object that implements
+      GradleRunnerVersion2 (notice the 2). The caller will need to dynamically
+      determine that. The GradleRunnerInteractionVersion1 may take an object
+      that also implements GradleRunnerInteractionVersion2. If so, we'll
+      dynamically determine that and handle it. Of course, this all depends on
+      what happens in the future.
+
+      @param  parentClassLoader    Your classloader. Probably the classloader
+                                   of whatever class is calling this.
+      @param  gradleHomeDirectory  the root directory of a gradle installation
+      @param  interaction          this is how we interact with the caller.
+      @param  showDebugInfo        true to show some additional information that
+                                   may be helpful diagnosing problems is this
+                                   fails
+      @return a gradle runner
+      @author mhunsicker
+   */
+   public static GradleRunnerVersion1 createGradleRunner( ClassLoader parentClassLoader, File gradleHomeDirectory, GradleRunnerInteractionVersion1 interaction, boolean showDebugInfo ) throws Exception
+   {
+       //much of this function is exception handling so if we can't obtain it via the newer factory method, then
+       //we'll try the old way, but we want to report the original exception if we can't do it either way.
+       Exception viaFactoryException = null;
+       GradleRunnerVersion1 gradleRunner = null;
+
+       //first, try it the new way
+       try {
+           gradleRunner = createGradleRunnerViaFactory(parentClassLoader, gradleHomeDirectory, interaction, showDebugInfo);
+       } catch (Exception e) {
+           //we might ignore this. It means we're probably using an older version of gradle. That case is handled below.
+           //If not, this exception will be thrown at the end.
+           viaFactoryException = e;
+       }
+
+       //try it the old way
+       if( gradleRunner == null ) {
+           gradleRunner = createGradleRunnerOldWay(parentClassLoader, gradleHomeDirectory, interaction, showDebugInfo);
+       }
+
+       //if we still don't have a gradle runner and we have an exception from using the factory, throw it. If we
+       //got an exception using the 'old way', it would have been thrown already and we wouldn't be here.
+       if( gradleRunner == null && viaFactoryException != null ) {
+           throw viaFactoryException;
+       }
+
+       return gradleRunner;
+   }
+
+    /**
+     * This function uses a factory to instantiate a GradleRunner. The factory is located with the version of gradle
+     * pointed to by gradleHomeDirectory and thus allows the version of gradle being loaded to make decisions
+     * about how to instantiate the runner. This is needed as multiple versions of the runner are being used.
+     */
+   private static GradleRunnerVersion1 createGradleRunnerViaFactory( ClassLoader parentClassLoader, File gradleHomeDirectory, GradleRunnerInteractionVersion1 interaction, boolean showDebugInfo ) throws Exception
+   {
+      //load the class in gradle that wraps our return interface and handles versioning issues.
+      Class soughtClass = ExternalUtility.loadGradleClass( "org.gradle.openapi.wrappers.RunnerWrapperFactory", parentClassLoader, gradleHomeDirectory, showDebugInfo );
+      if( soughtClass == null )
+      {
+         return null;
+      }
+
+      Class[] argumentClasses = new Class[ ] { File.class, GradleRunnerInteractionVersion1.class, boolean.class };
+
+      Object gradleRunner = ExternalUtility.invokeStaticMethod( soughtClass, "createGradleRunner", argumentClasses, gradleHomeDirectory, interaction, showDebugInfo );
+      return (GradleRunnerVersion1) gradleRunner;
+   }
+
+   /**
+     * This function uses an early way (early 0.9 pre-release and sooner) of instantiating the GradleRunner and
+     * should no longer be used. It unfortunately is tied to a single wrapper class instance (which it tries to
+     * directly instantiate). This doesn't allow the GradleRunner to adaptively determine what to instantiate.
+     */
+   private static GradleRunnerVersion1 createGradleRunnerOldWay( ClassLoader parentClassLoader, File gradleHomeDirectory, GradleRunnerInteractionVersion1 interaction, boolean showDebugInfo ) throws Exception
+   {
+      ClassLoader bootStrapClassLoader = ExternalUtility.getGradleClassloader( parentClassLoader, gradleHomeDirectory, showDebugInfo );
+      Thread.currentThread().setContextClassLoader(bootStrapClassLoader);
+
+      //load the class in gradle that wraps our return interface and handles versioning issues.
+      Class soughtClass = null;
+      try
+      {
+         soughtClass = bootStrapClassLoader.loadClass( "org.gradle.openapi.wrappers.runner.GradleRunnerWrapper" );
+      }
+      catch( NoClassDefFoundError e )
+      {  //might be a version mismatch
+         e.printStackTrace();
+         return null;
+      }
+      catch( ClassNotFoundException e )
+      {  //might be a version mismatch
+         e.printStackTrace();
+         return null;
+      }
+
+      if( soughtClass == null ) {
+         return null;
+      }
+
+      //instantiate it.
+      Constructor constructor = null;
+      try
+      {
+         constructor = soughtClass.getDeclaredConstructor( File.class, GradleRunnerInteractionVersion1.class );
+      }
+      catch( NoSuchMethodException e )
+      {
+         e.printStackTrace();
+         System.out.println( "Dumping available constructors on " + soughtClass.getName() + "\n" + ExternalUtility.dumpConstructors( soughtClass ) );
+
+         throw e;
+      }
+
+      Object gradleRunner = constructor.newInstance( gradleHomeDirectory, interaction );
+
+      return (GradleRunnerVersion1) gradleRunner;
+   }
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerInteractionVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerInteractionVersion1.java
new file mode 100644
index 0000000..1e3efb4
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerInteractionVersion1.java
@@ -0,0 +1,99 @@
+/*
+ * 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.openapi.external.runner;
+
+import java.io.File;
+
+/**
+
+ .
+
+ @author mhunsicker
+  */
+public interface GradleRunnerInteractionVersion1
+{
+   /*
+      @return The root directory of your gradle project. The same directory
+      Where you would run gradle from the command.
+      @author mhunsicker
+   */
+   public File getWorkingDirectory();
+
+   public enum LogLevel { Quiet, Lifecycle, Debug };
+   public enum StackTraceLevel { InternalExceptions, Always, AlwaysFull };
+
+   /*
+      @return the log level. This determines the detail level of information
+      reported via reportLiveOutput and reportExecutionFinished.
+      @author mhunsicker
+   */
+   public LogLevel getLogLevel();
+
+   /*
+      @return the stack trace level. This determines the detail level of any
+      stack traces should an exception occur.
+      @author mhunsicker
+   */
+   public StackTraceLevel getStackTraceLevel();
+
+   /*
+      Notification that overall execution has been started. This is only called
+      once at the end.
+      @author mhunsicker
+   */
+   public void reportExecutionStarted();
+
+   /**
+      Notification of the total number of tasks that will be executed. This is
+      called after reportExecutionStarted and before any tasks are executed.
+      @param size the total number of tasks.
+   */
+   public void reportNumberOfTasksToExecute( int size );
+
+   /*
+      Notification that a single task has completed. Note: the task you kicked
+      off probably executes other tasks and this notifies you of those tasks
+      and provides completion progress.
+
+      @param  currentTaskName the task being executed
+      @param  percentComplete the percent complete of all the tasks that make
+                              up the task you requested.
+      @author mhunsicker
+   */
+   public void reportTaskStarted( String currentTaskName, float percentComplete );
+
+   public void reportTaskComplete( String currentTaskName, float percentComplete );
+
+   /*
+      Report real-time output from gradle and its subsystems (such as ant).
+      @param  output     a single line of text to show.
+      @author mhunsicker
+   */
+   public void reportLiveOutput( String output );
+
+   public void reportExecutionFinished( boolean wasSuccessful, String message, Throwable throwable );
+
+   /*
+      This is called to get a custom gradle executable file. If you don't run
+      gradle.bat or gradle shell script to run gradle, use this to specify
+      what you do run. Note: we're going to pass it the arguments that we would
+      pass to gradle so if you don't like that, see alterCommandLineArguments.
+      Normaly, this should return null.
+      @return the Executable to run gradle command or null to use the default
+      @author mhunsicker
+   */
+   public File getCustomGradleExecutable();
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerVersion1.java
new file mode 100644
index 0000000..0680290
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/runner/GradleRunnerVersion1.java
@@ -0,0 +1,39 @@
+/*
+ * 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.openapi.external.runner;
+
+/*
+ This executes gradle commands in an external process.
+
+ @author mhunsicker
+  */
+public interface GradleRunnerVersion1
+{
+   /*
+      Call this to execute the specified command line.
+
+      @param  commandLine the command to execute
+      @author mhunsicker
+   */
+   public void executeCommand( String commandLine );
+
+   /*
+      Call this to stop the gradle command. This is killing the process, not
+      gracefully exiting.
+      @author mhunsicker
+   */
+   public void killProcess();
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/AlternateUIInteractionVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/AlternateUIInteractionVersion1.java
new file mode 100644
index 0000000..676f62f
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/AlternateUIInteractionVersion1.java
@@ -0,0 +1,69 @@
+/*
+ * 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.openapi.external.ui;
+
+import java.io.File;
+
+/*
+ This is how the gradle UI panel interacts with the UI that is holding it.
+
+ This is a mirror of AlternateUIInteraction inside Gradle, but this is meant
+ to aid backward and forward compatibility by shielding you from direct
+ changes within gradle.
+
+ @author mhunsicker
+ */
+public interface AlternateUIInteractionVersion1
+{
+   /**
+    Notification that you should open the specified file and go to the specified line. Its up to the
+    application to determine if this file should be opened for editing or simply displayed. The difference
+    comes into play for things like xml or html files where a user may want to open them in a browser vs
+    a source code file where they may want to open it directly in an IDE.
+
+    @param file the file to edit
+    @param line the line to go to. -1 if no line is specified.
+    */
+   public void openFile( File file, int line );
+
+   /*
+      This is called when we should open the specified file for editing. This version explicitly wants them
+      edited versus just opened.
+
+      @param  file      the file to open
+      @param line the line to go to. -1 if no line is specified.
+      @author mhunsicker
+   */
+   public void editFile( File file, int line );
+
+   /*
+      Determines if we can call editFiles or openFile. This is not a dynamic answer
+      and should always return either true of false. If you want to change the
+      answer, return true and then handle the files differently in editFiles.
+      @return true if support editing files, false otherwise.
+      @author mhunsicker
+   */
+   public boolean doesSupportEditingOpeningFiles();
+
+   /**
+    Notification that a command is about to be executed. This is mostly useful
+    for IDE's that may need to save their files.
+
+    @param fullCommandLine the command that's about to be executed.
+    @author mhunsicker
+    */
+   public void aboutToExecuteCommand( String fullCommandLine );
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/BasicGradleUIVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/BasicGradleUIVersion1.java
new file mode 100644
index 0000000..f5f2982
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/BasicGradleUIVersion1.java
@@ -0,0 +1,222 @@
+/*
+ * 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.openapi.external.ui;
+
+import org.gradle.openapi.external.foundation.GradleInterfaceVersion1;
+import org.gradle.openapi.external.foundation.favorites.FavoritesEditorVersion1;
+import javax.swing.*;
+import java.io.File;
+
+/*
+ This represents a basic gradle UI
+
+ To use this, you'll want to get an instance of this from Gradle. Then setup
+ your UI and add this to it via getComponent. Then call aboutToShow before
+ you display your UI. Call close before you hide your UI. You'll need to set
+ the current directory (at any time) so gradle knows where your project is
+ located.
+
+ @author mhunsicker
+  */
+public interface BasicGradleUIVersion1
+{
+   /*
+      Call this whenever you're about to show this panel. We'll do whatever
+      initialization is necessary.
+      @author mhunsicker
+   */
+   public void aboutToShow();
+
+   //
+            public interface CloseInteraction
+            {
+               /*
+                  This is called if gradle tasks are being executed and you want to know if
+                  we can close. Ask the user.
+                  @return true if the user confirms cancelling the current tasks. False if not.
+                  @author mhunsicker
+               */
+               public boolean promptUserToConfirmClosingWhileBusy();
+            }
+
+   /*
+      Call this to determine if you can close this pane. if we're busy, we'll
+      ask the user if they want to close.
+
+      @param  closeInteraction allows us to interact with the user
+      @return true if we can close, false if not.
+      @author mhunsicker
+   */
+   public boolean canClose( CloseInteraction closeInteraction );
+
+   /*
+      Call this before you close the pane. This gives it an opportunity to do
+      cleanup. You probably should call canClose before this. It gives the
+      app a chance to cancel if its busy.
+      @author mhunsicker
+   */
+   public void close();
+
+   /*
+      @return the root directory of your gradle project.
+      @author mhunsicker
+   */
+   public File getCurrentDirectory();
+
+   /*
+      @param  currentDirectory the new root directory of your gradle project.
+      @author mhunsicker
+   */
+   public void setCurrentDirectory( File currentDirectory );
+
+   /*
+      @return the gradle home directory. Where gradle is installed.
+      @author mhunsicker
+   */
+   public File getGradleHomeDirectory();
+
+   /*
+      This is called to get a custom gradle executable file. If you don't run
+      gradle.bat or gradle shell script to run gradle, use this to specify
+      what you do run. Note: we're going to pass it the arguments that we would
+      pass to gradle so if you don't like that, see alterCommandLineArguments.
+      Normally, this should return null.
+      @return the Executable to run gradle command or null to use the default
+      @author mhunsicker
+   */
+   public File getCustomGradleExecutable();
+
+   /*
+      Call this to add an additional tab to the gradle UI. You can call this
+      at any time.
+
+      @param  index             the index of where to add the tab.
+      @param  gradleTabVersion1 the tab to add.
+      @author mhunsicker
+   */
+   public void addTab( int index, GradleTabVersion1 gradleTabVersion1 );
+
+   /*
+      Call this to remove one of your own tabs from this.
+      @param  gradleTabVersion1 the tab to remove
+      @author mhunsicker
+   */
+   public void removeTab( GradleTabVersion1 gradleTabVersion1 );
+
+   /*
+      @return the total number of tabs.
+      @author mhunsicker
+   */
+   public int getGradleTabCount();
+
+   /*
+      @param  index      the index of the tab
+      @return the name of the tab at the specified index.
+      @author mhunsicker
+   */
+   public String getGradleTabName( int index );
+
+    /**
+     * Returns the index of the gradle tab with the specified name.
+     * @param name the name of the tab
+     * @return the index of the tab or -1 if not found
+     */
+    public int getGradleTabIndex( String name );
+
+    /**
+     * @return the currently selected tab
+     */
+    public int getCurrentGradleTab();
+
+    /**
+     * Makes the specified tab the current tab.
+     * @param index the index of the tab.
+     */
+    public void setCurrentGradleTab( int index );
+
+   /*
+      This allows you to add a listener that can add additional command line
+      arguments whenever gradle is executed. This is useful if you've customized
+      your gradle build and need to specify, for example, an init script.
+
+      @param  listener   the listener that modifies the command line arguments.
+      @author mhunsicker
+   */
+   public void addCommandLineArgumentAlteringListener( CommandLineArgumentAlteringListenerVersion1 listener );
+
+   public void removeCommandLineArgumentAlteringListener( CommandLineArgumentAlteringListenerVersion1 listener );
+
+   /*
+      Call this to execute the given gradle command.
+
+      @param  commandLineArguments the command line arguments to pass to gradle.
+      @param displayName           the name displayed in the UI for this command
+      @author mhunsicker
+   */
+   public void executeCommand( String commandLineArguments, String displayName );
+
+   /**
+    This refreshes the task tree. Useful if you know you've changed something behind
+    gradle's back or when first displaying this UI.
+    */
+   public void refreshTaskTree();
+
+    /**
+     * @return the output lord which shows the live output of all commands being executed. You can add observers to this as well as alter how it finds file links. 
+     */
+   public OutputUILordVersion1 getOutputLord();
+
+    //these were moved to OutputUILordVersion1, but remain here for backward compatibility
+   public void addOutputObserver( OutputObserverVersion1 outputObserverVersion1 );
+   public void removeOutputObserver( OutputObserverVersion1 outputObserverVersion1 );    
+
+   /**
+      Determines if commands are currently being executed or not.
+      @return true if we're busy, false if not.
+   */
+   public boolean isBusy();
+
+   /**
+    Determines whether output is shown only when errors occur or always
+    @return true to only show output if errors occur, false to show it always.
+    */
+   public boolean getOnlyShowOutputOnErrors();
+
+   /**
+    This adds the specified component to the setup panel. It is added below the last
+    'default' item. You can only add 1 component here, so if you need to add multiple
+    things, you'll have to handle adding that to yourself to the one component.
+    @param component the component to add.
+    */
+   public void setCustomPanelToSetupTab( JComponent component );
+
+    /**
+     * This returns an object that works with lower level gradle and contains the
+     * current projects and tasks. You can also execute tasks from it and perform
+     * certain setup.
+     * @return a GradleInterfaceVersion1 object. It may also be GradleInterfaceVersion2 or
+     * a future version. You can check its type and then cast it as appropriate.
+     * This allows the caller to be backward compatible.
+     */
+   public GradleInterfaceVersion1 getGradleInterfaceVersion1();
+
+    /**
+     * Returns a FavoritesEditor. This is useful for getting a list of all favorites or
+     * modifying them.
+     * @return a FavoritesEditorVersion1. Use this to interact with the favorites.
+     */
+   public FavoritesEditorVersion1 getFavoritesEditor();
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/CommandLineArgumentAlteringListenerVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/CommandLineArgumentAlteringListenerVersion1.java
new file mode 100644
index 0000000..cac7d5e
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/CommandLineArgumentAlteringListenerVersion1.java
@@ -0,0 +1,36 @@
+/*
+ * 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.openapi.external.ui;
+
+/**
+ This allows you to add a listener that can add additional command line
+ arguments whenever gradle is executed. This is useful if you've customized
+ your gradle build and need to specify, for example, an init script.
+
+ @author mhunsicker
+  */
+public interface CommandLineArgumentAlteringListenerVersion1
+{
+   /*
+      This is called when you can add additional command line arguments. Return
+      any additional arguments to add. This doesn't modify the existing commands.
+
+      @param  commandLineArguments the command line to execute.
+      @return any command lines to add or null to leave it alone
+      @author mhunsicker
+   */
+   public String getAdditionalCommandLineArguments( String commandLineArguments );
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/DualPaneUIInteractionVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/DualPaneUIInteractionVersion1.java
new file mode 100644
index 0000000..ff16c62
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/DualPaneUIInteractionVersion1.java
@@ -0,0 +1,29 @@
+/*
+ * 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.openapi.external.ui;
+
+/**
+   This interface holds onto our options and allows us to interact with the
+   caller. This is meant to interact with the Gradle UI across class loader
+   and version boundaries. That is, the open API has a single entry point
+   that shouldn't change across versions. New interfaces can be expected, but
+   we'll always allow 'version1'. This is to provide backward/forward compatibility.
+
+   @author mhunsicker
+*/
+public interface DualPaneUIInteractionVersion1 extends GradleUIInteractionVersion1
+{
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/DualPaneUIVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/DualPaneUIVersion1.java
new file mode 100644
index 0000000..ac176f3
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/DualPaneUIVersion1.java
@@ -0,0 +1,57 @@
+/*
+ * 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.openapi.external.ui;
+
+import javax.swing.JComponent;
+import java.awt.Component;
+
+/**
+This is a gradle UI that is broken into two panels: one contains a tabbed pane
+ of tasks, favorites, command line, etc. The other pane contains the output.
+ This is meant to simplify how an IDE plugin can interact with gradle. Specifically,
+ this allows the 'main' pane to be vertical and the output pane to be horizontal.
+
+ To use this, you'll want to get an instance of this from Gradle. Then setup
+ your UI and add this to it via getComponent. Then call aboutToShow before
+ you display your UI. Call close before you hide your UI. You'll need to set
+ the current directory (at any time) so gradle knows where your project is
+ located.
+ */
+public interface DualPaneUIVersion1 extends BasicGradleUIVersion1
+{
+   /**
+      Returns a component that shows the task tree tab, favorites tab, etc.
+      suitable for inserting in your UI.
+      @return the main component
+    */
+   public JComponent getMainComponent();
+
+   /**
+      Returns a component that shows the output of the tasks being executed.
+      This is suitable for inserting in your UI.
+      @return the output component
+   */
+   public Component getOutputPanel();
+
+    /**
+     * This gets the number of opened output tabs. This is used by the Idea plugin
+     * to determine if it should close the entire output pane when a tab is closed
+     * This doesn't determine whether or not the tabs are busy. See
+     * GradleInterfaceVersion1.isBusy for that 
+     * @return the number of opened output tabs.
+     */
+   public int getNumberOfOpenedOutputTabs();
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/GradleTabVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/GradleTabVersion1.java
new file mode 100644
index 0000000..c381e2a
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/GradleTabVersion1.java
@@ -0,0 +1,52 @@
+/*
+ * 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.openapi.external.ui;
+
+import java.awt.Component;
+
+/**
+
+ This represents a tab that the caller can add to the gradle UI.
+
+  This is a mirror of GradleTab inside Gradle, but this is meant to aid
+  backward and forward compatibility by shielding you from direct changes
+  within gradle.
+
+ @author mhunsicker
+  */
+public interface GradleTabVersion1
+{
+   /*
+      @return the name of this tab
+      @author mhunsicker
+   */
+   public String getName();
+
+   /*
+      This is where we should create your component.
+
+      @return the component
+      @author mhunsicker
+   */
+   public Component createComponent();
+
+   /*
+      Notification that this component is about to be shown. Do whatever
+      initialization you choose.
+      @author mhunsicker
+   */
+   public void aboutToShow();
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/GradleUIInteractionVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/GradleUIInteractionVersion1.java
new file mode 100644
index 0000000..b19173e
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/GradleUIInteractionVersion1.java
@@ -0,0 +1,43 @@
+/*
+ * 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.openapi.external.ui;
+
+/**
+ This interface holds onto our options and allows us to interact with the
+ caller. This is meant to interact with the Gradle UI across class loader
+ and version boundaries. That is, the open API has a single entry point
+ that shouldn't change across versions. New interfaces can be expected, but
+ we'll always allow 'version1'. This is to provide backward/forward compatibility.
+
+ @author mhunsicker
+*/
+public interface GradleUIInteractionVersion1
+{
+   /*
+      This is only called once and is how we get ahold of the AlternateUIInteraction.
+      @return an AlternateUIInteraction object. This cannot be null.
+      @author mhunsicker
+   */
+   public AlternateUIInteractionVersion1 instantiateAlternateUIInteraction();
+
+   /*
+      This is only called once and is how we get ahold of how the owner wants
+      to store preferences.
+      @return a settings object. This cannot be null.
+      @author mhunsicker
+   */
+   public SettingsNodeVersion1 instantiateSettings();
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/OutputObserverVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/OutputObserverVersion1.java
new file mode 100644
index 0000000..effb733
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/OutputObserverVersion1.java
@@ -0,0 +1,68 @@
+/*
+ * 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.openapi.external.ui;
+
+/**
+ This interface informs you when the output pane is displaying requests. This is NOT
+ for general output of gradle commands.
+ This is a mirror of OutputUILord.OutputObserver inside Gradle, but this is meant to aid
+ backward and forward compatibility by shielding you from direct changes within
+ gradle.
+  */
+public interface OutputObserverVersion1
+{
+   /**
+      Notification that a request was added to the output. This means we've got some output
+      that is useful to display.
+      <!      Name             Description>
+      @param  requestID        an ID you can use to identify this request when it is complete.
+      @param  fullCommandLine  the command line for the request that was added
+      @param  displayName      the display name of this command (often the same as the full command line)
+      @param  forceOutputToBeShown true if this request wants to force its output to be shown
+   */
+   public void executionRequestAdded( long requestID, String fullCommandLine, String displayName, boolean forceOutputToBeShown );
+
+   /**
+    Notification that a refresh task list request was added to the output. This means we've got some output
+    that is useful to display.
+
+    @param requestID        an ID you can use to identify this request when it is complete.
+    @param forceOutputToBeShown true if this request wants to force its output to be shown
+    */
+   public void refreshRequestAdded( long requestID, boolean forceOutputToBeShown );
+
+   /**
+    Notification that a request is complete. Note: if its canceled, you'll just get an
+    outputTabClosed notification.
+
+    @param requestID     the ID of the request that is complete. It is given to you in
+                         executionRequestAdded or refreshRequestAdded.
+    @param wasSuccessful true if was successful, false if not or was cancelled.
+    */
+   public void requestComplete( long requestID, boolean wasSuccessful );
+
+   /**
+    Notification that an output tab was closed, possibly because it was canceled. You might want to
+    know this if you want to close your IDE output window when all tabs are closed.
+
+    @param requestID     the ID of the request associated with this tab. It is given to you in
+                         executionRequestAdded or refreshRequestAdded.
+    */
+   public void outputTabClosed( long requestID );
+
+
+   
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/OutputUILordVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/OutputUILordVersion1.java
new file mode 100644
index 0000000..e37fffe
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/OutputUILordVersion1.java
@@ -0,0 +1,72 @@
+/*
+ * 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.openapi.external.ui;
+
+import java.util.List;
+import java.awt.Font;
+
+/**
+ Provides access to aspects of gradle's output
+ * @author mhunsicker
+ */
+public interface OutputUILordVersion1 {
+
+   public void setOutputTextFont( Font font );
+   public Font getOutputTextFont();
+
+    /**
+     Call this to add file extensions to look for in the output. The files will be highlighted
+     and are clickable by the user. This results in AlternateUIInteractionVersion1.editFile
+     or openFile being called. This assumes the file path is the first thing on the line.
+     @param extension the file extension
+     @param lineNumberDelimiter optional delimiter text for line number. Whatever is after
+             this will be assumed to be a line number. We'll only parse the numbers after
+             this so there can be other stuff after the line number. Pass in null to ignore.
+     */
+    public void addFileExtension( String extension, String lineNumberDelimiter );
+
+    /**
+     Creates a file link definition to find file paths in the output that have a known prefix and extension.
+     The files will be highlighted and are clickable by the user. This results in
+     AlternateUIInteractionVersion1.editFile or openFile being called.
+     It also allows for an optional line number after a delimiter. This is useful if you know a certain
+     message always precedes a file path.
+     @param name  the name of this file link definition. Used by tests mostly.
+     @param prefix  the text that is before the file path. It should be enough to make it fairly unique
+     @param extension  the expected file extension. If we don't find this extension, we do not consider
+             the text a file's path. If there are multiple extensions, you'll have to add multiples of these.
+     @param lineNumberDelimiter optional delimiter text for line number. Whatever is after
+             this will be assumed to be a line number. We'll only parse the numbers after
+             this so there can be other stuff after the line number. Pass in null to ignore.
+
+     */
+    public void addPrefixedFileLink( String name, String prefix, String extension, String lineNumberDelimiter );
+
+
+    /**
+     * @return a list of file extensions that are highlighted in the output
+     */
+    public List<String> getFileExtensions();
+
+   public void addOutputObserver( OutputObserverVersion1 outputObserverVersion1 );
+   public void removeOutputObserver( OutputObserverVersion1 outputObserverVersion1 );
+
+    /*
+    This re-executes the last execution command (ignores refresh commands).
+    This is potentially useful for IDEs to hook into (hotkey to execute last command).
+     */
+    public void reExecuteLastCommand();
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/SettingsNodeVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/SettingsNodeVersion1.java
new file mode 100644
index 0000000..b46e414
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/SettingsNodeVersion1.java
@@ -0,0 +1,63 @@
+/*
+ * 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.openapi.external.ui;
+
+import java.util.List;
+
+/**
+
+ Abstraction of how settings are stored. If you're implementing this, see
+ SettingsNode for more information.
+
+ This is a mirror of SettingsNode inside Gradle, but this is meant to aid
+ backward and forward compatibility by shielding you from direct changes within
+ gradle.
+
+ @author mhunsicker
+  */
+public interface SettingsNodeVersion1
+{
+   public void setName( String name );
+   public String getName();
+
+   public void setValue( String value );
+   public String getValue();
+
+   public void setValueOfChild( String name, String value );
+   public String getValueOfChild( String name, String defaultValue );
+
+   public int getValueOfChildAsInt( String name, int defaultValue );
+   public void setValueOfChildAsInt( String name, int value );
+
+   public boolean getValueOfChildAsBoolean( String name, boolean defaultValue );
+   public void setValueOfChildAsBoolean( String name, boolean value );
+
+   public long getValueOfChildAsLong( String name, long defaultValue );
+   public void setValueOfChildAsLong( String name, long value );
+
+
+   public List<SettingsNodeVersion1> getChildNodes();
+   public List<SettingsNodeVersion1> getChildNodes( String name );
+
+   public SettingsNodeVersion1 addChild( String name );
+   public SettingsNodeVersion1 addChildIfNotPresent( String name );
+   public SettingsNodeVersion1 getChildNode( String name );
+
+   public SettingsNodeVersion1 getNodeAtPath( String ... pathPortions );
+
+   public void removeFromParent();
+   public void removeAllChildren();
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/SinglePaneUIInteractionVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/SinglePaneUIInteractionVersion1.java
new file mode 100644
index 0000000..28eb3a0
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/SinglePaneUIInteractionVersion1.java
@@ -0,0 +1,29 @@
+/*
+ * 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.openapi.external.ui;
+
+/*
+   This interface holds onto our options and allows us to interact with the
+   caller. This is meant to interact with the Gradle UI across class loader
+   and version boundaries. That is, the open API has a single entry point
+   that shouldn't change across versions. New interfaces can be expected, but
+   we'll always allow 'version1'. This is to provide backward/forward compatibility.
+
+   @author mhunsicker
+*/
+public interface SinglePaneUIInteractionVersion1 extends GradleUIInteractionVersion1
+{
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/SinglePaneUIVersion1.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/SinglePaneUIVersion1.java
new file mode 100644
index 0000000..79d503d
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/SinglePaneUIVersion1.java
@@ -0,0 +1,40 @@
+/*
+ * 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.openapi.external.ui;
+
+import javax.swing.JComponent;
+
+/*
+ This is a gradle UI that is entirely within a single panel (and only a panel;
+ no dialog or frame). This is meant to simplify how a plugin can interact with
+ gradle.
+
+ To use this, you'll want to get an instance of this from Gradle. Then setup
+ your UI and add this to it via getComponent. Then call aboutToShow before
+ you display your UI. Call close before you hide your UI. You'll need to set
+ the current directory (at any time) so gradle knows where your project is
+ located.
+
+ @author mhunsicker
+  */
+public interface SinglePaneUIVersion1 extends BasicGradleUIVersion1
+{
+   /**
+   Returns this panel as a Swing object suitable for inserting in your UI.
+   @return the main component
+      */
+   public JComponent getComponent();
+}
diff --git a/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/UIFactory.java b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/UIFactory.java
new file mode 100644
index 0000000..c5553bb
--- /dev/null
+++ b/subprojects/gradle-open-api/src/main/groovy/org/gradle/openapi/external/ui/UIFactory.java
@@ -0,0 +1,283 @@
+/*
+ * 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.openapi.external.ui;
+
+import org.gradle.openapi.external.ExternalUtility;
+
+import java.io.File;
+import java.lang.reflect.Constructor;
+
+/*
+ This loads up the main gradle UI. This is intended to be used as a plugin
+ inside another application (like an IDE) in a dynamic fashion. If you're
+ always going to ship the entire plugin with the entire Gradle dist, you don't
+ need to use this. This is meant to dynamically load Gradle from its dist. The
+ idea is that you point your plugin to a Gradle dist and then can always load
+ the latest version.
+
+ @author mhunsicker
+  */
+public class UIFactory
+{
+    private static final String UIWRAPPER_FACTORY_CLASS_NAME = "org.gradle.openapi.wrappers.UIWrapperFactory";
+    /*
+       Call this to instantiate a self-contained gradle UI. That is, everything in
+       the UI is in a single panel (versus 2 panels one for the tasks and one
+       for the output). This will load gradle via reflection, instantiate the UI
+       and all required gradle-related classes.
+
+       Note: this function is meant to be backward and forward compatible. So
+       this signature should not change at all, however, it may take and return
+       objects that implement ADDITIONAL interfaces. That is, it will always
+       return SinglePaneUIVersion1, but it may also be an object that implements
+       SinglePaneUIVersion2 (notice the 2). The caller will need to dynamically
+       determine that. The SinglePaneUIInteractionVersion1 may take an object
+       that also implements SinglePaneUIInteractionVersion2. If so, we'll
+       dynamically determine that and handle it. Of course, this all depends on
+       what happens in the future.
+       @param  parentClassLoader    Your classloader. Probably the classloader
+                                    of whatever class is calling this.
+       @param  gradleHomeDirectory  the root directory of a gradle installation
+       @param  singlePaneUIArguments this is how we interact with the caller.
+       @param  showDebugInfo        true to show some additional information that
+                                    may be helpful diagnosing problems is this
+                                    fails
+       @return the UI object.
+       @author mhunsicker
+    */
+   public static SinglePaneUIVersion1 createSinglePaneUI( ClassLoader parentClassLoader, File gradleHomeDirectory, final SinglePaneUIInteractionVersion1 interaction, boolean showDebugInfo ) throws Exception
+   {
+       //much of this function is exception handling so if we can't obtain it via the newer factory method, then
+       //we'll try the old way, but we want to report the original exception if we can't do it either way.
+       Exception viaFactoryException = null;
+       SinglePaneUIVersion1 gradleUI = null;
+
+       //first, try it the new way
+       try {
+           gradleUI = createSinglePaneUIViaFactory( parentClassLoader, gradleHomeDirectory, interaction, showDebugInfo );
+       } catch (Exception e) {
+           //we might ignore this. It means we're probably using an older version of gradle. That case is handled below.
+           //If not, this exception will be thrown at the end.
+           viaFactoryException = e;
+       }
+
+       //try it the old way
+       if( gradleUI == null ) {
+          gradleUI = createSinglePaneUIOldWay(parentClassLoader, gradleHomeDirectory, interaction, showDebugInfo );
+       }
+
+       //if we still don't have a gradle ui and we have an exception from using the factory, throw it. If we
+       //got an exception using the 'old way', it would have been thrown already and we wouldn't be here.
+       if( gradleUI == null && viaFactoryException != null ) {
+           throw viaFactoryException;
+       }
+
+       return gradleUI;
+   }
+
+    /**
+     * This function uses a factory to instantiate the UI. The factory is located with the version of gradle
+     * pointed to by gradleHomeDirectory and thus allows the version of gradle being loaded to make decisions
+     * about how to instantiate the UI. This is needed as multiple versions of the UI are being used.
+     */
+   private static SinglePaneUIVersion1 createSinglePaneUIViaFactory( ClassLoader parentClassLoader, File gradleHomeDirectory, final SinglePaneUIInteractionVersion1 interaction, boolean showDebugInfo ) throws Exception
+   {
+      //load the class in gradle that wraps our return interface and handles versioning issues.
+      Class soughtClass = ExternalUtility.loadGradleClass(UIWRAPPER_FACTORY_CLASS_NAME, parentClassLoader, gradleHomeDirectory, showDebugInfo );
+      if( soughtClass == null )
+      {
+         return null;
+      }
+
+      Class[] argumentClasses = new Class[ ] { SinglePaneUIInteractionVersion1.class, boolean.class };
+
+      Object gradleUI = ExternalUtility.invokeStaticMethod( soughtClass, "createSinglePaneUI", argumentClasses, interaction, showDebugInfo );
+      return (SinglePaneUIVersion1) gradleUI;
+   }
+
+    /**
+     * This function uses an early way (early 0.9 pre-release and sooner) of instantiating the UI and should
+     * no longer be used. It unfortunately is tied to a single wrapper class instance (which it tries to
+     * directly instantiate). This doesn't allow the Gradle UI to adaptively determine what to instantiate.
+     */
+   private static SinglePaneUIVersion1 createSinglePaneUIOldWay( ClassLoader parentClassLoader, File gradleHomeDirectory, final SinglePaneUIInteractionVersion1 singlePaneUIArguments, boolean showDebugInfo ) throws Exception
+   {
+      ClassLoader bootStrapClassLoader = ExternalUtility.getGradleClassloader( parentClassLoader, gradleHomeDirectory, showDebugInfo );
+      Thread.currentThread().setContextClassLoader(bootStrapClassLoader);
+
+      //load the class in gradle that wraps our return interface and handles versioning issues.
+      Class soughtClass = null;
+      try
+      {
+         soughtClass = bootStrapClassLoader.loadClass( "org.gradle.openapi.wrappers.ui.SinglePaneUIWrapper" );
+      }
+      catch( NoClassDefFoundError e )
+      {  //might be a version mismatch
+         e.printStackTrace();
+         return null;
+      }
+      catch( ClassNotFoundException e )
+      {  //might be a version mismatch
+         e.printStackTrace();
+      }
+      if( soughtClass == null )
+      {
+         return null;
+      }
+
+      //instantiate it.
+      Constructor constructor = null;
+      try
+      {
+         constructor = soughtClass.getDeclaredConstructor( SinglePaneUIInteractionVersion1.class );
+      }
+      catch( NoSuchMethodException e )
+      {
+         e.printStackTrace();
+         System.out.println( "Dumping available constructors on " + soughtClass.getName() + "\n" + ExternalUtility.dumpConstructors( soughtClass ) );
+
+         throw e;
+      }
+      Object singlePaneUI = constructor.newInstance( singlePaneUIArguments );
+      return (SinglePaneUIVersion1) singlePaneUI;
+   }
+
+   /*
+      Call this to instantiate a gradle UI that contains the main tab control
+      separate from the output panel. This allows you to position the output
+      however you like. For example: you can place the main pane along the side
+      going vertically and you can place the output pane along the bottom going
+      horizontally.
+      This will load gradle via reflection, instantiate the UI and all required
+      gradle-related classes.
+
+      Note: this function is meant to be backward and forward compatible. So
+      this signature should not change at all, however, it may take and return
+      objects that implement ADDITIONAL interfaces. That is, it will always
+      return SinglePaneUIVersion1, but it may also be an object that implements
+      SinglePaneUIVersion2 (notice the 2). The caller will need to dynamically
+      determine that. The SinglePaneUIInteractionVersion1 may take an object
+      that also implements SinglePaneUIInteractionVersion2. If so, we'll
+      dynamically determine that and handle it. Of course, this all depends on
+      what happens in the future.
+      @param  parentClassLoader    Your classloader. Probably the classloader
+                                   of whatever class is calling this.
+      @param  gradleHomeDirectory  the root directory of a gradle installation
+      @param  interaction          this is how we interact with the caller.
+      @param  showDebugInfo        true to show some additional information that
+                                   may be helpful diagnosing problems is this
+                                   fails
+      @return the UI object.
+      @author mhunsicker
+   */
+   public static DualPaneUIVersion1 createDualPaneUI( ClassLoader parentClassLoader, File gradleHomeDirectory, final DualPaneUIInteractionVersion1 interaction, boolean showDebugInfo ) throws Exception
+   {
+       //much of this function is exception handling so if we can't obtain it via the newer factory method, then
+       //we'll try the old way, but we want to report the original exception if we can't do it either way.
+       Exception viaFactoryException = null;
+       DualPaneUIVersion1 gradleUI = null;
+
+       //first, try it the new way
+       try {
+           gradleUI = createDualPaneUIViaFactory( parentClassLoader, gradleHomeDirectory, interaction, showDebugInfo );
+       } catch (Exception e) {
+           //we might ignore this. It means we're probably using an older version of gradle. That case is handled below.
+           //If not, this exception will be thrown at the end.
+           viaFactoryException = e;
+       }
+
+       //try it the old way
+       if( gradleUI == null ) {
+          gradleUI = createDualPaneUIOldWay(parentClassLoader, gradleHomeDirectory, interaction, showDebugInfo );
+       }
+
+       //if we still don't have a gradle ui and we have an exception from using the factory, throw it. If we
+       //got an exception using the 'old way', it would have been thrown already and we wouldn't be here.
+       if( gradleUI == null && viaFactoryException != null ) {
+           throw viaFactoryException;
+       }
+
+       return gradleUI;
+   }
+
+    /**
+     * This function uses a factory to instantiate the UI. The factory is located with the version of gradle
+     * pointed to by gradleHomeDirectory and thus allows the version of gradle being loaded to make decisions
+     * about how to instantiate the UI. This is needed as multiple versions of the UI are being used.
+     */
+   public static DualPaneUIVersion1 createDualPaneUIViaFactory( ClassLoader parentClassLoader, File gradleHomeDirectory, final DualPaneUIInteractionVersion1 interaction, boolean showDebugInfo ) throws Exception
+   {
+      //load the class in gradle that wraps our return interface and handles versioning issues.
+      Class soughtClass = ExternalUtility.loadGradleClass(UIWRAPPER_FACTORY_CLASS_NAME, parentClassLoader, gradleHomeDirectory, showDebugInfo );
+      if( soughtClass == null )
+      {
+         return null;
+      }
+
+      Class[] argumentClasses = new Class[ ] { DualPaneUIInteractionVersion1.class, boolean.class };
+
+      Object gradleUI = ExternalUtility.invokeStaticMethod( soughtClass, "createDualPaneUI", argumentClasses, interaction, showDebugInfo );
+      return (DualPaneUIVersion1) gradleUI;
+   }
+
+    /**
+     * This function uses an early way (early 0.9 pre-release and sooner) of instantiating the UI and should no
+     * longer be used. It unfortunately is tied to a single wrapper class instance (which it tries to directly
+     * instantiate). This doesn't allow the Gradle UI to adaptively determine what to instantiate.
+     */
+   private static DualPaneUIVersion1 createDualPaneUIOldWay( ClassLoader parentClassLoader, File gradleHomeDirectory, final DualPaneUIInteractionVersion1 interaction, boolean showDebugInfo ) throws Exception
+   {
+      ClassLoader bootStrapClassLoader = ExternalUtility.getGradleClassloader( parentClassLoader, gradleHomeDirectory, showDebugInfo );
+      Thread.currentThread().setContextClassLoader(bootStrapClassLoader);
+
+      //load the class in gradle that wraps our return interface and handles versioning issues.
+      Class soughtClass = null;
+      try
+      {
+         soughtClass = bootStrapClassLoader.loadClass( "org.gradle.openapi.wrappers.ui.DualPaneUIWrapper" );
+      }
+      catch( NoClassDefFoundError e )
+      {  //might be a version mismatch
+         e.printStackTrace();
+         return null;
+      }
+      catch( ClassNotFoundException e )
+      {  //might be a version mismatch
+         e.printStackTrace();
+      }
+      if( soughtClass == null )
+      {
+         return null;
+      }
+
+      //instantiate it.
+      Constructor constructor = null;
+      try
+      {
+         constructor = soughtClass.getDeclaredConstructor( DualPaneUIInteractionVersion1.class );
+      }
+      catch( NoSuchMethodException e )
+      {
+         e.printStackTrace();
+         System.out.println( "Dumping available constructors on " + soughtClass.getName() + "\n" + ExternalUtility.dumpConstructors( soughtClass ) );
+
+         throw e;
+      }
+      Object gradleUI = constructor.newInstance( interaction );
+      return (DualPaneUIVersion1) gradleUI;
+   }
+
+}
diff --git a/subprojects/gradle-open-api/src/test/groovy/org/gradle/openapi/external/ExternalUtilityTest.groovy b/subprojects/gradle-open-api/src/test/groovy/org/gradle/openapi/external/ExternalUtilityTest.groovy
new file mode 100644
index 0000000..7132da3
--- /dev/null
+++ b/subprojects/gradle-open-api/src/test/groovy/org/gradle/openapi/external/ExternalUtilityTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * 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.openapi.external
+
+import spock.lang.Specification
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import org.gradle.util.TestFile
+
+public class ExternalUtilityTest extends Specification {
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    def getTheRightGradleCore() {
+        TestFile libDir = tmpDir.dir.file('lib')
+
+        when:
+        libDir.deleteDir()
+        libDir.createDir().files('gradle-core-0.9.jar', 'gradle-core-worker-0.9.jar', 'gradle-open-api-0.9.jar')*.touch()
+
+        then:
+        ExternalUtility.getGradleJar(tmpDir.dir).absolutePath == new File(libDir, 'gradle-core-0.9.jar').absolutePath
+
+        when:
+        libDir.deleteDir()
+        libDir.createDir().files('gradle-core-0.9-20100315080959+0100.jar', 'gradle-core-worker-0.9-20100315080959+0100.jar', 'gradle-open-api-0.9-20100315080959+0100.jar')*.touch()
+
+        then:
+        ExternalUtility.getGradleJar(tmpDir.dir).absolutePath == new File(libDir, 'gradle-core-0.9-20100315080959+0100.jar').absolutePath
+    }
+
+    def failWithMultipleGradleCore() {
+        tmpDir.dir.file('lib').createDir().files('gradle-core-0.9.jar', 'gradle-core-0.10.jar', 'gradle-open-api-0.9.jar')*.touch()
+
+        when:
+        ExternalUtility.getGradleJar(tmpDir.dir)
+
+        then:
+        RuntimeException e = thrown()
+        println e.message
+        e.message.contains('gradle-core-0.9.jar')
+        e.message.contains('gradle-core-0.10.jar')
+    }
+
+    def returnNullWitNonExistingGradleCore() {
+        tmpDir.dir.file('lib').createDir().files('gradle-open-api-0.9.jar')*.touch()
+
+        expect:
+        ExternalUtility.getGradleJar(tmpDir.dir) == null
+    }
+
+    def failWitNonExistingGradleHome() {
+        expect:
+        ExternalUtility.getGradleJar(tmpDir.dir) == null
+    }
+
+}
diff --git a/subprojects/gradle-osgi/osgi.gradle b/subprojects/gradle-osgi/osgi.gradle
new file mode 100644
index 0000000..bd6713b
--- /dev/null
+++ b/subprojects/gradle-osgi/osgi.gradle
@@ -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.
+ */
+repositories {
+    mavenRepo urls: 'http://www.aQute.biz/repo'
+}
+
+dependencies {
+    groovy libraries.groovy_depends
+
+    compile project(':core')
+    compile project(':plugins')
+    compile libraries.slf4j_api
+
+    compile 'biz.aQute:bndlib:0.0.384 at jar'
+
+    testCompile project(path: ':core', configuration: 'testFixtures')
+    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+}
+
diff --git a/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/AnalyzerFactory.java b/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/AnalyzerFactory.java
new file mode 100644
index 0000000..a08117d
--- /dev/null
+++ b/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/AnalyzerFactory.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+/**
+ * @author Hans Dockter
+ */
+public interface AnalyzerFactory {
+    ContainedVersionAnalyzer createAnalyzer();
+}
diff --git a/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/ContainedVersionAnalyzer.java b/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/ContainedVersionAnalyzer.java
new file mode 100644
index 0000000..55746c9
--- /dev/null
+++ b/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/ContainedVersionAnalyzer.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.api.internal.plugins.osgi;
+
+import aQute.lib.osgi.Analyzer;
+
+public class ContainedVersionAnalyzer extends Analyzer {
+}
\ No newline at end of file
diff --git a/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultAnalyzerFactory.java b/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultAnalyzerFactory.java
new file mode 100644
index 0000000..ea46e18
--- /dev/null
+++ b/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultAnalyzerFactory.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.api.internal.plugins.osgi;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultAnalyzerFactory implements AnalyzerFactory {
+    public ContainedVersionAnalyzer createAnalyzer() {
+        return new ContainedVersionAnalyzer();
+    }
+}
diff --git a/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifest.java b/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifest.java
new file mode 100644
index 0000000..d2dfd1b
--- /dev/null
+++ b/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifest.java
@@ -0,0 +1,206 @@
+/*
+ * 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.plugins.osgi;
+
+import aQute.lib.osgi.Analyzer;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.java.archives.internal.DefaultManifest;
+import org.gradle.api.plugins.osgi.OsgiManifest;
+import org.gradle.util.GUtil;
+import org.gradle.util.UncheckedException;
+import org.gradle.util.WrapUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+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 AnalyzerFactory analyzerFactory = new DefaultAnalyzerFactory();
+
+    private Map<String, List<String>> instructions = new HashMap<String, List<String>>();
+
+    private FileCollection classpath;
+
+    public DefaultOsgiManifest(FileResolver fileResolver) {
+        super(fileResolver);
+    }
+
+    @Override
+    public DefaultManifest getEffectiveManifest() {
+        ContainedVersionAnalyzer analyzer = analyzerFactory.createAnalyzer();
+        DefaultManifest effectiveManifest = new DefaultManifest(null);
+        try {
+            setAnalyzerProperties(analyzer);
+            Manifest osgiManifest = analyzer.calcManifest();
+            for (Map.Entry<Object, Object> entry : osgiManifest.getMainAttributes().entrySet()) {
+                effectiveManifest.attributes(WrapUtil.toMap(entry.getKey().toString(), (String) entry.getValue()));
+            }
+        } catch (Exception e) {
+            throw UncheckedException.asUncheckedException(e);
+        }
+        return getEffectiveManifestInternal(effectiveManifest);
+    }
+
+    private void setAnalyzerProperties(Analyzer analyzer) throws IOException {
+        for (String instructionName : instructions.keySet()) {
+            analyzer.setProperty(instructionName, createPropertyStringFromList(instructionValue(instructionName)));
+        }
+        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;
+        }
+        analyzer.setProperty(key, value);
+    }
+
+    public List<String> instructionValue(String instructionName) {
+        return instructions.get(instructionName);
+    }
+
+    public OsgiManifest instruction(String name, String... values) {
+        if (instructions.get(name) == null) {
+            instructions.put(name, new ArrayList<String>());
+        }
+        instructions.get(name).addAll(Arrays.asList(values));
+        return this;
+    }
+
+    public OsgiManifest instructionFirst(String name, String... values) {
+        if (instructions.get(name) == null) {
+            instructions.put(name, new ArrayList<String>());
+        }
+        instructions.get(name).addAll(0, Arrays.asList(values));
+        return this;
+    }
+
+    public Map<String, List<String>> getInstructions() {
+        return instructions;
+    }
+
+    public void setInstructions(Map<String, List<String>> instructions) {
+        this.instructions = instructions;
+    }
+
+    private String createPropertyStringFromList(List<String> valueList) {
+        return GUtil.join(valueList, ",");
+    }
+
+    public String getSymbolicName() {
+        return symbolicName;
+    }
+
+    public void setSymbolicName(String symbolicName) {
+        this.symbolicName = symbolicName;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getVersion() {
+        return version;
+    }
+
+    public void setVersion(String version) {
+        this.version = version;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public String getLicense() {
+        return license;
+    }
+
+    public void setLicense(String license) {
+        this.license = license;
+    }
+
+    public String getVendor() {
+        return vendor;
+    }
+
+    public void setVendor(String vendor) {
+        this.vendor = vendor;
+    }
+
+    public String getDocURL() {
+        return docURL;
+    }
+
+    public void setDocURL(String docURL) {
+        this.docURL = docURL;
+    }
+
+    public File getClassesDir() {
+        return classesDir;
+    }
+
+    public void setClassesDir(File classesDir) {
+        this.classesDir = classesDir;
+    }
+
+    public FileCollection getClasspath() {
+        return classpath;
+    }
+
+    public void setClasspath(FileCollection classpath) {
+        this.classpath = classpath;
+    }
+
+    public AnalyzerFactory getAnalyzerFactory() {
+        return analyzerFactory;
+    }
+
+    public void setAnalyzerFactory(AnalyzerFactory analyzerFactory) {
+        this.analyzerFactory = analyzerFactory;
+    }
+}
diff --git a/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/OsgiHelper.java b/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/OsgiHelper.java
new file mode 100644
index 0000000..114d812
--- /dev/null
+++ b/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/internal/plugins/osgi/OsgiHelper.java
@@ -0,0 +1,196 @@
+/*
+ * 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 org.gradle.api.Project;
+import org.gradle.api.plugins.BasePluginConvention;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author Hans Dockter
+ */
+public class OsgiHelper {
+    /**
+     * Bundle-Version must match this pattern
+     */
+    private static final Pattern OSGI_VERSION_PATTERN = Pattern
+            .compile("[0-9]+\\.[0-9]+\\.[0-9]+(\\.[0-9A-Za-z_-]+)?");
+
+    /** pattern used to change - to . */
+    // private static final Pattern P_VERSION = Pattern.compile("([0-9]+(\\.[0-9])*)-(.*)");
+    /**
+     * pattern that matches strings that contain only numbers
+     */
+    private static final Pattern ONLY_NUMBERS = Pattern.compile("[0-9]+");
+    private static final Pattern DATED_SNAPSHOT = Pattern.compile("([0-9])(\\.([0-9]))?(\\.([0-9]))?\\-([0-9]{8}\\.[0-9]{6}\\-[0-9]*)");
+    private static final Pattern DOTS_IN_QUALIFIER = Pattern.compile("([0-9])(\\.[0-9])?\\.([0-9A-Za-z_-]+)\\.([0-9A-Za-z_-]+)");
+    private static final Pattern NEED_TO_FILL_ZEROS = Pattern.compile("([0-9])(\\.([0-9]))?(\\.([0-9A-Za-z_-]+))?");
+
+    private String getBundleSymbolicName(String groupId, String artifactId) {
+        return groupId + "." + artifactId;
+    }
+
+    /**
+     * Get the symbolic name as group + "." + archivesBaseName, with the following exceptions
+     * <ul>
+     * <li>if group has only one section (no dots) and archivesBaseName is not null then the
+     * first package name with classes is returned. eg. commons-logging:commons-logging ->
+     * org.apache.commons.logging</li>
+     * <li>if archivesBaseName is equal to last section of group then group is returned. eg.
+     * org.gradle:gradle -> org.gradle</li>
+     * <li>if archivesBaseName starts with last section of group that portion is removed. eg.
+     * org.gradle:gradle-core -> org.gradle.core</li>
+     * </ul>
+     */
+    public String getBundleSymbolicName(Project project) {
+
+        String group = (String) project.property("group");
+        int i = group.lastIndexOf('.');
+
+        String lastSection = group.substring(++i);
+        String archiveBaseName = project.getConvention().getPlugin(BasePluginConvention.class).getArchivesBaseName();
+        if (archiveBaseName.equals(lastSection)) {
+            return group;
+        }
+        if (archiveBaseName.startsWith(lastSection)) {
+            String artifactId = archiveBaseName.substring(lastSection.length());
+            if (Character.isLetterOrDigit(artifactId.charAt(0))) {
+                return getBundleSymbolicName(group, artifactId);
+            } else {
+                return getBundleSymbolicName(group, artifactId.substring(1));
+            }
+        }
+        return getBundleSymbolicName(group, archiveBaseName);
+    }
+    
+    public String getVersion(String version) {
+        String osgiVersion;
+
+        // Matcher m = P_VERSION.matcher(version);
+        // if (m.matches()) {
+        // osgiVersion = m.group(1) + "." + m.group(3);
+        // }
+
+        /* TODO need a regexp guru here */
+
+        Matcher m;
+
+        /* if it's already OSGi compliant don't touch it */
+        m = OSGI_VERSION_PATTERN.matcher(version);
+        if (m.matches()) {
+            return version;
+        }
+
+        osgiVersion = version;
+
+        /* check for dated snapshot versions with only major or major and minor */
+        m = DATED_SNAPSHOT.matcher(osgiVersion);
+        if (m.matches()) {
+            String major = m.group(1);
+            String minor = (m.group(3) != null) ? m.group(3) : "0";
+            String service = (m.group(5) != null) ? m.group(5) : "0";
+            String qualifier = m.group(6).replaceAll("-", "_").replaceAll("\\.", "_");
+            osgiVersion = major + "." + minor + "." + service + "." + qualifier;
+        }
+
+        /* else transform first - to . and others to _ */
+        osgiVersion = osgiVersion.replaceFirst("-", "\\.");
+        osgiVersion = osgiVersion.replaceAll("-", "_");
+        m = OSGI_VERSION_PATTERN.matcher(osgiVersion);
+        if (m.matches()) {
+            return osgiVersion;
+        }
+
+        /* remove dots in the middle of the qualifier */
+        m = DOTS_IN_QUALIFIER.matcher(osgiVersion);
+        if (m.matches()) {
+            String s1 = m.group(1);
+            String s2 = m.group(2);
+            String s3 = m.group(3);
+            String s4 = m.group(4);
+
+            Matcher qualifierMatcher = ONLY_NUMBERS.matcher(s3);
+            /*
+             * if last portion before dot is only numbers then it's not in the middle of the
+             * qualifier
+             */
+            if (!qualifierMatcher.matches()) {
+                osgiVersion = s1 + s2 + "." + s3 + "_" + s4;
+            }
+        }
+
+        /* convert
+         * 1.string   -> 1.0.0.string
+         * 1.2.string -> 1.2.0.string
+         * 1          -> 1.0.0
+         * 1.1        -> 1.1.0
+         */
+        //Pattern NEED_TO_FILL_ZEROS = Pattern.compile( "([0-9])(\\.([0-9]))?\\.([0-9A-Za-z_-]+)" );
+        m = NEED_TO_FILL_ZEROS.matcher(osgiVersion);
+        if (m.matches()) {
+            String major = m.group(1);
+            String minor = m.group(3);
+            String service = null;
+            String qualifier = m.group(5);
+
+            /* if there's no qualifier just fill with 0s */
+            if (qualifier == null) {
+                osgiVersion = getVersion(major, minor, service, qualifier);
+            } else {
+                /* if last portion is only numbers then it's not a qualifier */
+                Matcher qualifierMatcher = ONLY_NUMBERS.matcher(qualifier);
+                if (qualifierMatcher.matches()) {
+                    if (minor == null) {
+                        minor = qualifier;
+                    } else {
+                        service = qualifier;
+                    }
+                    osgiVersion = getVersion(major, minor, service, null);
+                } else {
+                    osgiVersion = getVersion(major, minor, service, qualifier);
+                }
+            }
+        }
+
+        m = OSGI_VERSION_PATTERN.matcher(osgiVersion);
+        /* if still its not OSGi version then add everything as qualifier */
+        if (!m.matches()) {
+            String major = "0";
+            String minor = "0";
+            String service = "0";
+            String qualifier = osgiVersion.replaceAll("\\.", "_");
+            osgiVersion = major + "." + minor + "." + service + "." + qualifier;
+        }
+
+        return osgiVersion;
+    }
+
+    private String getVersion(String major, String minor, String service, String qualifier) {
+        StringBuffer sb = new StringBuffer();
+        sb.append(major != null ? major : "0");
+        sb.append('.');
+        sb.append(minor != null ? minor : "0");
+        sb.append('.');
+        sb.append(service != null ? service : "0");
+        if (qualifier != null) {
+            sb.append('.');
+            sb.append(qualifier);
+        }
+        return sb.toString();
+    }
+}
diff --git a/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiManifest.java b/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiManifest.java
new file mode 100644
index 0000000..70c5098
--- /dev/null
+++ b/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiManifest.java
@@ -0,0 +1,196 @@
+/*
+ * 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.plugins.osgi;
+
+import org.gradle.api.file.FileCollection;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Hans Dockter
+ */
+public interface OsgiManifest extends org.gradle.api.java.archives.Manifest {
+    /**
+     * Returns the list of arguments for a particular instruction.
+     *
+     * @param instructionName
+     * @return The list of arguments
+     * @see #instruction(String, String...)
+     */
+    List<String> instructionValue(String instructionName);
+
+    /**
+     * Adds arguments to an instruction. If the instruction does not exists, it is created. If it does exists, the
+     * arguments are appended to the existing arguments.
+     * 
+     * @param name
+     * @param values
+     * @return this
+     * @see #instructionFirst(String, String...)
+     */
+    OsgiManifest instruction(String name, String... values);
+
+    /**
+     * Adds arguments to an instruction. If the instruction does not exists, it is created. If it does exists, the
+     * arguments are inserted before the existing arguments.
+     *
+     * @param name
+     * @param values
+     * @return this
+     * @see #instructionFirst(String, String...)
+     */
+    OsgiManifest instructionFirst(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.
+     */
+    Map<String, List<String>> getInstructions();
+
+    /**
+     * Returns the symbolic name.
+     *
+     * @see #setSymbolicName(String) 
+     */
+    String getSymbolicName();
+
+    /**
+     * A convenient method for setting a Bundle-SymbolicName instruction.
+     *
+     * @param symbolicName the symbolicName to set
+     */
+    void setSymbolicName(String symbolicName);
+
+    /**
+     * Returns the name
+     *
+     * @see #setName(String)
+     */
+    String getName();
+
+    /**
+     * A convenient method for setting a Bundle-Name instruction.
+     *
+     * @param name the name to set
+     */
+    void setName(String name);
+
+    /**
+     * Returns the version
+     *
+     * @see #setVersion(String) 
+     */
+    String getVersion();
+
+    /**
+     * A convenient method for setting a Bundle-Version instruction.
+     *
+     * @param version the version to set
+     */
+    void setVersion(String version);
+
+    /**
+     * Returns the description.
+     *
+     * @see #setDescription(String) 
+     */
+    String getDescription();
+
+    /**
+     * A convenient method for setting a Bundle-Description instruction.
+     *
+     * @param description the description to set
+     */
+    void setDescription(String description);
+
+    /**
+     * Returns the license
+     * @see #setLicense(String) 
+     */
+    String getLicense();
+
+    /**
+     * A convenient method for setting a Bundle-License instruction.
+     *
+     * @param license The license to set
+     */
+    void setLicense(String license);
+
+    /**
+     * Returns the vendor.
+     *
+     * @see #setVendor(String) 
+     */
+    String getVendor();
+
+    /**
+     * A convenient method for setting a Bundle-Vendor instruction.
+     *
+     * @param vendor The vendor to set
+     */
+    void setVendor(String vendor);
+
+    /**
+     * Returns the docURL value.
+     *
+     * @see #setDocURL(String)
+     */
+    String getDocURL();
+
+    /**
+     * A convenient method for setting a Bundle-DocURL instruction.
+     *
+     * @param docURL the docURL to set.
+     */
+    void setDocURL(String docURL);
+
+    /**
+     * Returns the classes dir.
+     *
+     * @see #setClassesDir(java.io.File) 
+     */
+    File getClassesDir();
+
+    /**
+     * Sets the classes dir. This directory is the major source of input for generation the OSGi manifest. All classes
+     * are analyzed for its packages and package dependencies. Based on this the Import-Package value is set.
+     * This auto generated value can be overwritten by explicitly setting an instruction.
+     *
+     * @param classesDir
+     * 
+     * @see #instruction(String, String...)
+     */
+    void setClassesDir(File classesDir);
+
+    /**
+     * Returns the classpath.
+     *
+     * @see #setClasspath(org.gradle.api.file.FileCollection) 
+     */
+    FileCollection getClasspath();
+
+    /**
+     * A convenient method for setting a Bundle-Classpath instruction. The information of the classpath elements are only
+     * used if they are OSGi bundles. In this case for example the version information provided by the bundle is used in the Import-Package of the generated
+     * OSGi bundle.
+     *
+     * @param classpath The classpath elements
+     */
+    void setClasspath(FileCollection classpath);
+}
diff --git a/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPlugin.groovy b/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPlugin.groovy
new file mode 100644
index 0000000..b0a65ef
--- /dev/null
+++ b/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPlugin.groovy
@@ -0,0 +1,46 @@
+/*
+ * 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.plugins.osgi;
+
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.tasks.SourceSet
+
+/**
+ * <p>A {@link Plugin} which extends the {@link JavaPlugin} to add OSGi meta-information to the project JARs.</p>
+ *
+ * @author Hans Dockter
+ */
+public class OsgiPlugin implements Plugin<Project> {
+    public void apply(Project project) {
+        project.getPlugins().apply(JavaBasePlugin.class);
+
+        OsgiPluginConvention osgiConvention = new OsgiPluginConvention(project);
+        project.convention.plugins.osgi = osgiConvention
+
+        project.plugins.withType(JavaPlugin.class).allPlugins {javaPlugin ->
+            OsgiManifest osgiManifest = osgiConvention.osgiManifest {
+                from project.manifest
+                classesDir = project.convention.plugins.java.sourceSets[SourceSet.MAIN_SOURCE_SET_NAME].classesDir
+                classpath = project.configurations[JavaPlugin.RUNTIME_CONFIGURATION_NAME]
+            }
+            project.jar.manifest = osgiManifest
+        }
+    }
+}
diff --git a/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPluginConvention.java b/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPluginConvention.java
new file mode 100644
index 0000000..71b683e
--- /dev/null
+++ b/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/plugins/osgi/OsgiPluginConvention.java
@@ -0,0 +1,78 @@
+/*
+ * 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.osgi;
+
+import groovy.lang.Closure;
+import org.gradle.api.Project;
+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.util.ConfigureUtil;
+
+/**
+ * Is mixed in into the project when applying the  {@link org.gradle.api.plugins.osgi.OsgiPlugin} .
+ *
+ * @author Hans Dockter
+ */
+public class OsgiPluginConvention {
+    private Project project;
+
+    public OsgiPluginConvention(Project project) {
+        this.project = project;
+    }
+
+    /**
+     * Returns a new instance of {@link org.gradle.api.plugins.osgi.OsgiManifest}. The returned object is preconfigured with:
+     * <blockquote>
+     * <pre>
+     * version: project.version
+     * name: project.archivesBaseName
+     * symbolicName: project.group + "." + project.archivesBaseName (see below for exceptions to this rule)
+     * </pre></blockquote>
+     *
+     * The symbolic name is usually the group + "." + archivesBaseName, with the following exceptions
+     * <ul>
+     * <li>if group has only one section (no dots) and archivesBaseName is not null then the
+     * first package name with classes is returned. eg. commons-logging:commons-logging ->
+     * org.apache.commons.logging</li>
+     * <li>if archivesBaseName is equal to last section of group then group is returned. eg.
+     * org.gradle:gradle -> org.gradle</li>
+     * <li>if archivesBaseName starts with last section of group that portion is removed. eg.
+     * org.gradle:gradle-core -> org.gradle.core</li>
+     * </ul>
+     */
+    public OsgiManifest osgiManifest() {
+        return osgiManifest(null);
+    }
+
+    /**
+     * Returns a new instance of an  {@link org.gradle.api.plugins.osgi.OsgiManifest} . The closure configures
+     * the new manifest instance before it is returned.
+     */
+    public OsgiManifest osgiManifest(Closure closure) {
+        return ConfigureUtil.configure(closure, createDefaultOsgiManifest(project));
+    }
+
+    private OsgiManifest createDefaultOsgiManifest(Project project) {
+        OsgiHelper osgiHelper = new OsgiHelper();
+        OsgiManifest osgiManifest = new DefaultOsgiManifest(((ProjectInternal) project).getFileResolver());
+        osgiManifest.setVersion(osgiHelper.getVersion((String) project.property("version")));
+        osgiManifest.setName(project.getConvention().getPlugin(BasePluginConvention.class).getArchivesBaseName());
+        osgiManifest.setSymbolicName(osgiHelper.getBundleSymbolicName(project));
+        return osgiManifest;
+    }
+}
diff --git a/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/plugins/osgi/package-info.java b/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/plugins/osgi/package-info.java
new file mode 100644
index 0000000..df7a16b
--- /dev/null
+++ b/subprojects/gradle-osgi/src/main/groovy/org/gradle/api/plugins/osgi/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * The OSGi {@link org.gradle.api.Plugin} implementation.
+ */
+package org.gradle.api.plugins.osgi;
\ No newline at end of file
diff --git a/subprojects/gradle-osgi/src/main/resources/META-INF/gradle-plugins/osgi.properties b/subprojects/gradle-osgi/src/main/resources/META-INF/gradle-plugins/osgi.properties
new file mode 100644
index 0000000..153189d
--- /dev/null
+++ b/subprojects/gradle-osgi/src/main/resources/META-INF/gradle-plugins/osgi.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+implementation-class=org.gradle.api.plugins.osgi.OsgiPlugin
diff --git a/subprojects/gradle-osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultAnalyzerFactoryTest.java b/subprojects/gradle-osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultAnalyzerFactoryTest.java
new file mode 100644
index 0000000..fe1054b
--- /dev/null
+++ b/subprojects/gradle-osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultAnalyzerFactoryTest.java
@@ -0,0 +1,29 @@
+/*
+ * 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 org.junit.Test;import static org.junit.Assert.assertNotNull;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultAnalyzerFactoryTest {
+    @Test
+    public void create() {
+        ContainedVersionAnalyzer analyzer = new DefaultAnalyzerFactory().createAnalyzer();
+        assertNotNull(analyzer);
+    }
+}
diff --git a/subprojects/gradle-osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.java b/subprojects/gradle-osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.java
new file mode 100644
index 0000000..85e03d1
--- /dev/null
+++ b/subprojects/gradle-osgi/src/test/groovy/org/gradle/api/internal/plugins/osgi/DefaultOsgiManifestTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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.api.internal.file.FileResolver;
+import org.gradle.api.java.archives.internal.DefaultManifest;
+import org.gradle.util.GUtil;
+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.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.jar.Manifest;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class DefaultOsgiManifestTest {
+    private DefaultOsgiManifest osgiManifest;
+    private AnalyzerFactory analyzerFactoryMock;
+    private ContainedVersionAnalyzer analyzerMock;
+
+    private JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+    private FileResolver fileResolver = context.mock(FileResolver.class);
+
+    @Before
+    public void setUp() {
+        osgiManifest = new DefaultOsgiManifest(fileResolver);
+        analyzerFactoryMock = context.mock(AnalyzerFactory.class);
+        analyzerMock = context.mock(ContainedVersionAnalyzer.class);
+        context.checking(new Expectations() {{
+            allowing(analyzerFactoryMock).createAnalyzer();
+            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 expectedManifest = new DefaultManifest(fileResolver).attributes(getDefaultManifestWithOsgiValues().getAttributes());
+        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 expectedManifest = new DefaultManifest(fileResolver);
+        expectedManifest.attributes(getDefaultManifestWithOsgiValues().getAttributes());
+        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, new String[]{"pack1", "pack2"});
+        osgiManifest.instruction(Analyzer.IMPORT_PACKAGE, new String[]{"pack3", "pack4"});
+        osgiManifest.setClasspath(fileCollection);
+        osgiManifest.setClassesDir(new File("someDir"));
+    }
+
+    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).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), ","));
+        return manifest;
+    }
+}
diff --git a/subprojects/gradle-osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginConventionTest.groovy b/subprojects/gradle-osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginConventionTest.groovy
new file mode 100644
index 0000000..6c4f183
--- /dev/null
+++ b/subprojects/gradle-osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginConventionTest.groovy
@@ -0,0 +1,59 @@
+/*
+ * 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.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
+
+/**
+ * @author Hans Dockter
+ */
+class OsgiPluginConventionTest extends Specification {
+    DefaultProject project = HelperUtil.createRootProject()
+    OsgiPluginConvention osgiPluginConvention = new OsgiPluginConvention(project)
+
+    def setup() {
+        new JavaBasePlugin().apply(project)
+    }
+
+    def osgiManifestWithNoClosure() {
+        OsgiManifest osgiManifest = osgiPluginConvention.osgiManifest()
+
+        expect:
+        matchesExpectedConfig(osgiManifest)
+    }
+
+    def osgiManifestWithClosure() {
+        OsgiManifest osgiManifest = osgiPluginConvention.osgiManifest {
+            description = 'myDescription'    
+        }
+
+        expect:
+        matchesExpectedConfig(osgiManifest)
+        osgiManifest.description = 'myDescription'
+    }
+
+    void matchesExpectedConfig(DefaultOsgiManifest osgiManifest) {
+        OsgiHelper osgiHelper = new OsgiHelper();
+        assert osgiManifest.version == osgiHelper.getVersion((String) project.version)
+        assert osgiManifest.name == project.archivesBaseName
+        assert osgiManifest.symbolicName == osgiHelper.getBundleSymbolicName(project)
+    }
+}
diff --git a/subprojects/gradle-osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginTest.groovy b/subprojects/gradle-osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginTest.groovy
new file mode 100644
index 0000000..794cde2
--- /dev/null
+++ b/subprojects/gradle-osgi/src/test/groovy/org/gradle/api/plugins/osgi/OsgiPluginTest.groovy
@@ -0,0 +1,47 @@
+/*
+ * 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.plugins.osgi;
+
+
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaPlugin
+import org.gradle.util.HelperUtil
+import spock.lang.Specification
+import org.gradle.api.tasks.SourceSet
+
+public class OsgiPluginTest extends Specification {
+    private final Project project = HelperUtil.createRootProject();
+    private final OsgiPlugin osgiPlugin = new OsgiPlugin();
+    
+    public void appliesTheJavaPlugin() {
+        osgiPlugin.apply(project);
+
+        expect:
+        project.plugins.hasPlugin('java-base')
+        project.convention.plugins.osgi instanceof OsgiPluginConvention
+    }
+
+    public void addsAnOsgiManifestToTheDefaultJar() {
+        project.apply(plugin: 'java')
+        osgiPlugin.apply(project);
+        
+        expect:
+        OsgiManifest osgiManifest = project.jar.manifest
+        osgiManifest.mergeSpecs[0].mergePaths[0] == project.manifest
+        osgiManifest.classpath == project.configurations."$JavaPlugin.RUNTIME_CONFIGURATION_NAME"
+        osgiManifest.classesDir == project.sourceSets."$SourceSet.MAIN_SOURCE_SET_NAME".classesDir
+    }
+}
diff --git a/subprojects/gradle-plugins/plugins.gradle b/subprojects/gradle-plugins/plugins.gradle
new file mode 100644
index 0000000..673ca04
--- /dev/null
+++ b/subprojects/gradle-plugins/plugins.gradle
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+configurations {
+    testFixtures
+}
+
+dependencies {
+    groovy libraries.groovy_depends
+
+    compile project(':core')
+
+    compile libraries.slf4j_api,
+            libraries.commons_lang,
+            libraries.asm_all,
+            libraries.junit,
+            libraries.ant_junit,
+            libraries.ant,
+            'org.testng:testng:5.12.1'
+
+    testCompile libraries.xmlunit
+
+    testCompile project(path: ':core', configuration: 'testFixtures')
+    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+    testFixtures sourceSets.test.classes
+}
+
+test {
+    exclude 'org/gradle/api/internal/tasks/testing/junit/ATestClass*.*'
+    jvmArgs '-Xms128m', '-Xmx256m', '-XX:+HeapDumpOnOutOfMemoryError'
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultGroovySourceSet.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultGroovySourceSet.java
new file mode 100644
index 0000000..f215490
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultGroovySourceSet.java
@@ -0,0 +1,57 @@
+/*
+ * 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.tasks;
+
+import org.gradle.api.tasks.GroovySourceSet;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.internal.file.UnionFileTree;
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.tasks.util.PatternFilterable;
+import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.util.ConfigureUtil;
+import groovy.lang.Closure;
+
+public class DefaultGroovySourceSet implements GroovySourceSet {
+    private final SourceDirectorySet groovy;
+    private final UnionFileTree allGroovy;
+    private final PatternFilterable groovyPatterns = new PatternSet();
+
+    public DefaultGroovySourceSet(String displayName, FileResolver fileResolver) {
+        groovy = new DefaultSourceDirectorySet(String.format("%s Groovy source", displayName), fileResolver);
+        groovy.getFilter().include("**/*.java", "**/*.groovy");
+        groovyPatterns.include("**/*.groovy");
+        allGroovy = new UnionFileTree(String.format("%s Groovy source", displayName), groovy.matching(groovyPatterns));
+    }
+
+    public SourceDirectorySet getGroovy() {
+        return groovy;
+    }
+
+    public GroovySourceSet groovy(Closure configureClosure) {
+        ConfigureUtil.configure(configureClosure, getGroovy());
+        return this;
+    }
+
+    public PatternFilterable getGroovySourcePatterns() {
+        return groovyPatterns;
+    }
+
+    public FileTree getAllGroovy() {
+        return allGroovy;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSet.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSet.java
new file mode 100644
index 0000000..2d0a70b
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSet.java
@@ -0,0 +1,180 @@
+/*
+ * 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;
+
+import groovy.lang.Closure;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.file.FileTreeElement;
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.PathResolvingFileCollection;
+import org.gradle.api.internal.file.UnionFileTree;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.specs.Spec;
+import org.gradle.util.ConfigureUtil;
+import org.gradle.util.GUtil;
+import org.apache.commons.lang.StringUtils;
+
+import java.io.File;
+import java.util.concurrent.Callable;
+
+public class DefaultSourceSet implements SourceSet {
+    private final String name;
+    private final FileResolver fileResolver;
+    private File classesDir;
+    private FileCollection compileClasspath;
+    private FileCollection runtimeClasspath;
+    private final SourceDirectorySet javaSource;
+    private final UnionFileTree allJavaSource;
+    private final SourceDirectorySet resources;
+    private final PathResolvingFileCollection classes;
+    private final String displayName;
+    private final UnionFileTree allSource;
+
+    public DefaultSourceSet(String name, FileResolver fileResolver, TaskResolver taskResolver) {
+        this.name = name;
+        this.fileResolver = fileResolver;
+        displayName = GUtil.toWords(this.name);
+
+        String javaSrcDisplayName = String.format("%s Java source", displayName);
+        javaSource = new DefaultSourceDirectorySet(javaSrcDisplayName, fileResolver);
+        javaSource.getFilter().include("**/*.java");
+
+        allJavaSource = new UnionFileTree(javaSrcDisplayName, javaSource.matching(javaSource.getFilter()));
+
+        String resourcesDisplayName = String.format("%s resources", displayName);
+        resources = new DefaultSourceDirectorySet(resourcesDisplayName, fileResolver);
+        resources.getFilter().exclude(new Spec<FileTreeElement>() {
+            public boolean isSatisfiedBy(FileTreeElement element) {
+                return javaSource.contains(element.getFile());
+            }
+        });
+
+        String allSourceDisplayName = String.format("%s source", displayName);
+        allSource = new UnionFileTree(allSourceDisplayName, resources, javaSource);
+
+        String classesDisplayName = String.format("%s classes", displayName);
+        classes = new PathResolvingFileCollection(classesDisplayName, fileResolver, taskResolver, new Callable() {
+            public Object call() throws Exception {
+                return getClassesDir();
+            }
+        });
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("source set %s", getDisplayName());
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public String getClassesTaskName() {
+        return getTaskName(null, "classes");
+    }
+
+    public String getCompileTaskName(String language) {
+        return getTaskName("compile", language);
+    }
+
+    public String getCompileJavaTaskName() {
+        return getCompileTaskName("java");
+    }
+
+    public String getProcessResourcesTaskName() {
+        return getTaskName("process", "resources");
+    }
+
+    public String getTaskName(String verb, String target) {
+        if (verb == null) {
+            return StringUtils.uncapitalize(String.format("%s%s", getTaskBaseName(), StringUtils.capitalize(target)));
+        }
+        if (target == null) {
+            return StringUtils.uncapitalize(String.format("%s%s", verb, GUtil.toCamelCase(name)));
+        }
+        return StringUtils.uncapitalize(String.format("%s%s%s", verb, getTaskBaseName(), StringUtils.capitalize(target)));
+    }
+
+    private String getTaskBaseName() {
+        return name.equals(SourceSet.MAIN_SOURCE_SET_NAME) ? "" : GUtil.toCamelCase(name);
+    }
+
+    public File getClassesDir() {
+        return classesDir;
+    }
+
+    public void setClassesDir(File classesDir) {
+        this.classesDir = fileResolver.resolve(classesDir);
+    }
+
+    public FileCollection getClasses() {
+        return classes;
+    }
+
+    public SourceSet compiledBy(Object... taskPaths) {
+        classes.builtBy(taskPaths);
+        return this;
+    }
+
+    public FileCollection getCompileClasspath() {
+        return compileClasspath;
+    }
+
+    public FileCollection getRuntimeClasspath() {
+        return runtimeClasspath;
+    }
+
+    public void setCompileClasspath(FileCollection classpath) {
+        compileClasspath = classpath;
+    }
+
+    public void setRuntimeClasspath(FileCollection classpath) {
+        runtimeClasspath = classpath;
+    }
+
+    public SourceDirectorySet getJava() {
+        return javaSource;
+    }
+
+    public SourceSet java(Closure configureClosure) {
+        ConfigureUtil.configure(configureClosure, getJava());
+        return this;
+    }
+
+    public FileTree getAllJava() {
+        return allJavaSource;
+    }
+
+    public SourceDirectorySet getResources() {
+        return resources;
+    }
+
+    public SourceSet resources(Closure configureClosure) {
+        ConfigureUtil.configure(configureClosure, getResources());
+        return this;
+    }
+
+    public FileTree getAllSource() {
+        return allSource;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainer.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainer.java
new file mode 100644
index 0000000..0301001
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainer.java
@@ -0,0 +1,40 @@
+/*
+ * 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.tasks;
+
+import org.gradle.api.internal.AutoCreateDomainObjectContainer;
+import org.gradle.api.internal.ClassGenerator;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.SourceSetContainer;
+
+public class DefaultSourceSetContainer extends AutoCreateDomainObjectContainer<SourceSet> implements SourceSetContainer {
+    private final FileResolver fileResolver;
+    private final TaskResolver taskResolver;
+    private final ClassGenerator generator;
+
+    public DefaultSourceSetContainer(FileResolver fileResolver, TaskResolver taskResolver, ClassGenerator classGenerator) {
+        super(SourceSet.class, classGenerator);
+        this.fileResolver = fileResolver;
+        this.taskResolver = taskResolver;
+        this.generator = classGenerator;
+    }
+
+    @Override
+    protected SourceSet create(String name) {
+        return generator.newInstance(DefaultSourceSet.class, name, fileResolver, taskResolver);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntDependsStaleClassCleaner.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntDependsStaleClassCleaner.groovy
new file mode 100644
index 0000000..4e9c84c
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntDependsStaleClassCleaner.groovy
@@ -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.api.internal.tasks.compile
+
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.project.AntBuilderFactory
+import org.gradle.api.tasks.compile.AntDepend
+
+class AntDependsStaleClassCleaner extends StaleClassCleaner {
+    private final AntBuilderFactory antBuilderFactory
+    File dependencyCacheDir
+
+    def AntDependsStaleClassCleaner(AntBuilderFactory antBuilderFactory) {
+        this.antBuilderFactory = antBuilderFactory;
+    }
+
+    void execute() {
+        Map dependArgs = [
+                destDir: destinationDir
+        ]
+
+        Map dependOptions = dependArgs + compileOptions.dependOptions.optionMap()
+        if (compileOptions.dependOptions.useCache) {
+            dependOptions['cache'] = dependencyCacheDir
+        }
+
+        def ant = antBuilderFactory.createAntBuilder()
+        ant.project.addTaskDefinition('gradleDepend', AntDepend.class)
+        ant.gradleDepend(dependOptions) {
+            source.addToAntBuilder(ant, 'src', FileCollection.AntType.MatchingTask)
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntGroovyCompiler.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntGroovyCompiler.groovy
new file mode 100644
index 0000000..7749b52
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntGroovyCompiler.groovy
@@ -0,0 +1,90 @@
+/*
+ * 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.compile
+
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.project.IsolatedAntBuilder
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.gradle.api.internal.ClassPathRegistry
+
+import org.gradle.api.tasks.WorkResult
+import org.gradle.api.tasks.compile.GroovyCompileOptions
+import org.gradle.api.tasks.compile.CompileOptions
+
+/**
+ * Please note: includeAntRuntime=false is ignored if groovyc is used in non fork mode. In this case the runtime classpath is
+ * added to the compile classpath.
+ * See: http://jira.codehaus.org/browse/GROOVY-2717
+ *
+ * @author Hans Dockter
+ */
+class AntGroovyCompiler implements GroovyJavaJointCompiler {
+    private static Logger logger = LoggerFactory.getLogger(AntGroovyCompiler)
+
+    private final IsolatedAntBuilder ant
+    private final ClassPathRegistry classPathRegistry
+    FileCollection source
+    File destinationDir
+    Iterable<File> classpath
+    String sourceCompatibility
+    String targetCompatibility
+    GroovyCompileOptions groovyCompileOptions = new GroovyCompileOptions()
+    CompileOptions compileOptions = new CompileOptions()
+    Iterable<File> groovyClasspath
+
+    List nonGroovycJavacOptions = ['verbose', 'deprecation', 'includeJavaRuntime', 'includeAntRuntime', 'optimize', 'fork', 'failonerror', 'listfiles', 'nowarn', 'depend']
+
+    def AntGroovyCompiler(IsolatedAntBuilder ant, ClassPathRegistry classPathRegistry) {
+        this.ant = ant;
+        this.classPathRegistry = classPathRegistry;
+    }
+
+    public WorkResult execute() {
+        int numFilesCompiled;
+
+        // Add in commons-cli, as the Groovy POM does not (for some versions of Groovy)
+        Collection antBuilderClasspath = (groovyClasspath as List) + classPathRegistry.getClassPathFiles("COMMONS_CLI")
+        
+        ant.withGroovy(antBuilderClasspath).execute {
+            taskdef(name: 'groovyc', classname: 'org.codehaus.groovy.ant.Groovyc')
+            def task = groovyc([includeAntRuntime: false, destdir: destinationDir, classpath: ((classpath as List) + antBuilderClasspath).join(File.pathSeparator)]
+                    + groovyCompileOptions.optionMap()) {
+                source.addToAntBuilder(delegate, 'src', FileCollection.AntType.MatchingTask)
+                javac([source: sourceCompatibility, target: targetCompatibility] + filterNonGroovycOptions(compileOptions)) {
+                    compileOptions.compilerArgs.each {value ->
+                        compilerarg(value: value)
+                    }
+                }
+            }
+            numFilesCompiled = task.fileList.length
+        }
+
+        return { numFilesCompiled > 0 } as WorkResult
+    }
+
+    private Map filterNonGroovycOptions(CompileOptions options) {
+        // todo check if groupBy allows a more concise solution
+        Map result = [:]
+        options.optionMap().each {String key, Object value ->
+            if (!nonGroovycJavacOptions.contains(key)) {
+                result[key] = value
+            }
+        }
+        result
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntJavaCompiler.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntJavaCompiler.groovy
new file mode 100644
index 0000000..3096740
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/AntJavaCompiler.groovy
@@ -0,0 +1,84 @@
+/*
+ * 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.compile
+
+import org.gradle.api.AntBuilder
+import org.gradle.api.file.FileCollection
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+import org.gradle.api.tasks.WorkResult
+import org.gradle.api.tasks.compile.CompileOptions
+import org.gradle.api.internal.project.AntBuilderFactory
+
+/**
+ * @author Hans Dockter
+ */
+class AntJavaCompiler implements JavaCompiler {
+    private static Logger logger = LoggerFactory.getLogger(AntJavaCompiler)
+    static final String CLASSPATH_ID = 'compile.classpath'
+    FileCollection source;
+    File destinationDir;
+    Iterable<File> classpath;
+    String sourceCompatibility;
+    String targetCompatibility;
+    CompileOptions compileOptions = new CompileOptions()
+    final AntBuilderFactory antBuilderFactory
+
+    def AntJavaCompiler(AntBuilderFactory antBuilderFactory) {
+        this.antBuilderFactory = antBuilderFactory
+    }
+
+    void setDependencyCacheDir(File dir) {
+        // don't care
+    }
+
+    WorkResult execute() {
+        def ant = antBuilderFactory.createAntBuilder()
+        
+        createAntClassPath(ant, classpath)
+        Map otherArgs = [
+                includeAntRuntime: false,
+                destdir: destinationDir,
+                classpathref: CLASSPATH_ID,
+                sourcepath: '',
+                target: targetCompatibility,
+                source: sourceCompatibility
+        ]
+
+        Map options = otherArgs + compileOptions.optionMap()
+        logger.debug("Running ant javac with the following options {}", options)
+        def task = ant.javac(options) {
+            source.addToAntBuilder(ant, 'src', FileCollection.AntType.MatchingTask)
+            compileOptions.compilerArgs.each {value ->
+                compilerarg(value: value)
+            }
+        }
+
+        int numFilesCompiled = task.fileList.length;
+        return { numFilesCompiled > 0 } as WorkResult
+    }
+
+    private void createAntClassPath(AntBuilder ant, Iterable classpath) {
+        ant.path(id: CLASSPATH_ID) {
+            classpath.each {
+                logger.debug("Add {} to Ant classpath!", it)
+                pathelement(location: it)
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/Compiler.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/Compiler.java
new file mode 100644
index 0000000..601cb87
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/Compiler.java
@@ -0,0 +1,31 @@
+/*
+ * 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.compile;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.tasks.WorkResult;
+
+import java.io.File;
+
+public interface Compiler {
+    void setSource(FileCollection source);
+
+    void setDestinationDir(File destinationDir);
+
+    void setClasspath(Iterable<File> classpath);
+
+    WorkResult execute();
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyCompiler.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyCompiler.java
new file mode 100644
index 0000000..57523e4
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyCompiler.java
@@ -0,0 +1,26 @@
+/*
+ * 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.compile;
+
+import org.gradle.api.tasks.compile.GroovyCompileOptions;
+
+import java.io.File;
+
+public interface GroovyCompiler extends Compiler {
+    GroovyCompileOptions getGroovyCompileOptions();
+
+    void setGroovyClasspath(Iterable<File> classpath);
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyJavaJointCompiler.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyJavaJointCompiler.java
new file mode 100644
index 0000000..95a8756
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/GroovyJavaJointCompiler.java
@@ -0,0 +1,19 @@
+/*
+ * 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.compile;
+
+public interface GroovyJavaJointCompiler extends GroovyCompiler, JavaSourceCompiler {
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalGroovyCompiler.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalGroovyCompiler.java
new file mode 100644
index 0000000..fa8e089
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalGroovyCompiler.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.api.internal.tasks.compile;
+
+import org.gradle.api.internal.TaskOutputsInternal;
+import org.gradle.api.tasks.compile.GroovyCompileOptions;
+
+import java.io.File;
+
+public class IncrementalGroovyCompiler extends IncrementalJavaSourceCompiler<GroovyJavaJointCompiler> implements GroovyJavaJointCompiler {
+    private final TaskOutputsInternal taskOutputs;
+
+    public IncrementalGroovyCompiler(GroovyJavaJointCompiler compiler, TaskOutputsInternal taskOutputs) {
+        super(compiler);
+        this.taskOutputs = taskOutputs;
+    }
+
+    public GroovyCompileOptions getGroovyCompileOptions() {
+        return getCompiler().getGroovyCompileOptions();
+    }
+
+    public void setGroovyClasspath(Iterable<File> classpath) {
+        getCompiler().setGroovyClasspath(classpath);
+    }
+
+    @Override
+    protected StaleClassCleaner createCleaner() {
+        return new SimpleStaleClassCleaner(taskOutputs);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompiler.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompiler.java
new file mode 100644
index 0000000..7894cdf
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaCompiler.java
@@ -0,0 +1,49 @@
+/*
+ * 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.compile;
+
+import org.gradle.api.internal.TaskOutputsInternal;
+import org.gradle.api.internal.project.AntBuilderFactory;
+
+import java.io.File;
+
+public class IncrementalJavaCompiler extends IncrementalJavaSourceCompiler<JavaCompiler> implements JavaCompiler {
+    private final AntBuilderFactory antBuilderFactory;
+    private final TaskOutputsInternal taskOutputs;
+    private File dependencyCacheDir;
+
+    public IncrementalJavaCompiler(JavaCompiler compiler, AntBuilderFactory antBuilderFactory,
+                                    TaskOutputsInternal taskOutputs) {
+        super(compiler);
+        this.antBuilderFactory = antBuilderFactory;
+        this.taskOutputs = taskOutputs;
+    }
+
+    public void setDependencyCacheDir(File dir) {
+        dependencyCacheDir = dir;
+        getCompiler().setDependencyCacheDir(dir);
+    }
+
+    protected StaleClassCleaner createCleaner() {
+        if (getCompileOptions().isUseDepend()) {
+            AntDependsStaleClassCleaner cleaner = new AntDependsStaleClassCleaner(antBuilderFactory);
+            cleaner.setDependencyCacheDir(dependencyCacheDir);
+            return cleaner;
+        } else {
+            return new SimpleStaleClassCleaner(taskOutputs);
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaSourceCompiler.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaSourceCompiler.java
new file mode 100644
index 0000000..ce8b918
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaSourceCompiler.java
@@ -0,0 +1,77 @@
+/*
+ * 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.compile;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.api.tasks.compile.CompileOptions;
+
+import java.io.File;
+
+/**
+ * A dumb incremental compiler. Deletes stale classes before invoking the actual compiler
+ */
+public abstract class IncrementalJavaSourceCompiler<T extends JavaSourceCompiler> implements JavaSourceCompiler {
+    private final T compiler;
+    private FileCollection source;
+    private File destinationDir;
+
+    public IncrementalJavaSourceCompiler(T compiler) {
+        this.compiler = compiler;
+    }
+
+    public T getCompiler() {
+        return compiler;
+    }
+
+    public CompileOptions getCompileOptions() {
+        return compiler.getCompileOptions();
+    }
+
+    public void setSourceCompatibility(String sourceCompatibility) {
+        compiler.setSourceCompatibility(sourceCompatibility);
+    }
+
+    public void setTargetCompatibility(String targetCompatibility) {
+        compiler.setTargetCompatibility(targetCompatibility);
+    }
+
+    public void setSource(FileCollection source) {
+        this.source = source;
+        compiler.setSource(source);
+    }
+
+    public void setDestinationDir(File destinationDir) {
+        this.destinationDir = destinationDir;
+        compiler.setDestinationDir(destinationDir);
+    }
+
+    public void setClasspath(Iterable<File> classpath) {
+        compiler.setClasspath(classpath);
+    }
+
+    public WorkResult execute() {
+        StaleClassCleaner cleaner = createCleaner();
+        cleaner.setDestinationDir(destinationDir);
+        cleaner.setSource(source);
+        cleaner.setCompileOptions(compiler.getCompileOptions());
+        cleaner.execute();
+
+        return compiler.execute();
+    }
+
+    protected abstract StaleClassCleaner createCleaner();
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaCompiler.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaCompiler.java
new file mode 100644
index 0000000..f12c2cb
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaCompiler.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.api.internal.tasks.compile;
+
+import java.io.File;
+
+public interface JavaCompiler extends JavaSourceCompiler {
+    void setDependencyCacheDir(File dir);
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaSourceCompiler.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaSourceCompiler.java
new file mode 100644
index 0000000..b411b45
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/JavaSourceCompiler.java
@@ -0,0 +1,26 @@
+/*
+ * 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.compile;
+
+import org.gradle.api.tasks.compile.CompileOptions;
+
+public interface JavaSourceCompiler extends Compiler {
+    CompileOptions getCompileOptions();
+
+    void setSourceCompatibility(String sourceCompatibility);
+
+    void setTargetCompatibility(String targetCompatibility);
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/SimpleStaleClassCleaner.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/SimpleStaleClassCleaner.java
new file mode 100644
index 0000000..82e3554
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/SimpleStaleClassCleaner.java
@@ -0,0 +1,38 @@
+/*
+ * 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.compile;
+
+import org.gradle.api.internal.TaskOutputsInternal;
+
+import java.io.File;
+
+public class SimpleStaleClassCleaner extends StaleClassCleaner {
+    private final TaskOutputsInternal taskOutputs;
+
+    public SimpleStaleClassCleaner(TaskOutputsInternal taskOutputs) {
+        this.taskOutputs = taskOutputs;
+    }
+
+    @Override
+    public void execute() {
+        String prefix = getDestinationDir().getAbsolutePath() + File.separator;
+        for (File f : taskOutputs.getPreviousFiles()) {
+            if (f.getAbsolutePath().startsWith(prefix)) {
+                f.delete();
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/StaleClassCleaner.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/StaleClassCleaner.java
new file mode 100644
index 0000000..688f374
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/compile/StaleClassCleaner.java
@@ -0,0 +1,53 @@
+/*
+ * 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.compile;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.tasks.compile.CompileOptions;
+
+import java.io.File;
+
+public abstract class StaleClassCleaner {
+    private File destinationDir;
+    CompileOptions compileOptions;
+    FileCollection source;
+
+    public abstract void execute();
+
+    public CompileOptions getCompileOptions() {
+        return compileOptions;
+    }
+
+    public void setCompileOptions(CompileOptions compileOptions) {
+        this.compileOptions = compileOptions;
+    }
+
+    public FileCollection getSource() {
+        return source;
+    }
+
+    public void setSource(FileCollection source) {
+        this.source = source;
+    }
+
+    public void setDestinationDir(File destinationDir) {
+        this.destinationDir = destinationDir;
+    }
+
+    public File getDestinationDir() {
+        return destinationDir;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/AbstractTestDescriptor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/AbstractTestDescriptor.java
new file mode 100644
index 0000000..f104c5a
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/AbstractTestDescriptor.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+import java.io.Serializable;
+
+public abstract class AbstractTestDescriptor implements TestDescriptorInternal, Serializable {
+    private final Object id;
+    private final String name;
+
+    public AbstractTestDescriptor(Object id, String name) {
+        this.id = id;
+        this.name = name;
+    }
+
+    public Object getId() {
+        return id;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getClassName() {
+        return null;
+    }
+
+    public TestDescriptorInternal getParent() {
+        return null;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DecoratingTestDescriptor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DecoratingTestDescriptor.java
new file mode 100644
index 0000000..e06bf88
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DecoratingTestDescriptor.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+import org.gradle.api.tasks.testing.TestDescriptor;
+
+public class DecoratingTestDescriptor implements TestDescriptorInternal {
+    private final TestDescriptorInternal descriptor;
+    private final TestDescriptorInternal parent;
+
+    public DecoratingTestDescriptor(TestDescriptorInternal descriptor, TestDescriptorInternal parent) {
+        this.descriptor = descriptor;
+        this.parent = parent;
+    }
+
+    @Override
+    public String toString() {
+        return descriptor.toString();
+    }
+
+    public TestDescriptor getParent() {
+        return parent;
+    }
+
+    public Object getId() {
+        return descriptor.getId();
+    }
+
+    public String getClassName() {
+        return descriptor.getClassName();
+    }
+
+    public String getName() {
+        return descriptor.getName();
+    }
+
+    public boolean isComposite() {
+        return descriptor.isComposite();
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestClassDescriptor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestClassDescriptor.java
new file mode 100644
index 0000000..ee94597
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestClassDescriptor.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+public class DefaultTestClassDescriptor extends DefaultTestSuiteDescriptor {
+    public DefaultTestClassDescriptor(Object id, String className) {
+        super(id, className);
+    }
+
+    @Override
+    public String getClassName() {
+        return getName();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("test class %s", getClassName());
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestClassRunInfo.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestClassRunInfo.java
new file mode 100644
index 0000000..10dfd54
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestClassRunInfo.java
@@ -0,0 +1,37 @@
+/*
+ * 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.tasks.testing;
+
+import org.apache.commons.lang.StringUtils;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class DefaultTestClassRunInfo implements TestClassRunInfo {
+    private String testClassName;
+
+    public DefaultTestClassRunInfo(String testClassName) {
+        if (StringUtils.isEmpty(testClassName)) {
+            throw new IllegalArgumentException("testClassName is empty!");
+        }
+
+        this.testClassName = testClassName;
+    }
+
+    public String getTestClassName() {
+        return testClassName;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestDescriptor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestDescriptor.java
new file mode 100644
index 0000000..327bb1e
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestDescriptor.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+import java.io.Serializable;
+
+public class DefaultTestDescriptor extends AbstractTestDescriptor implements Serializable {
+    private final String className;
+
+    public DefaultTestDescriptor(Object id, String className, String name) {
+        super(id, name);
+        this.className = className;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("test %s(%s)", getName(), className);
+    }
+
+    public boolean isComposite() {
+        return false;
+    }
+
+    public String getClassName() {
+        return className;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestMethodDescriptor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestMethodDescriptor.java
new file mode 100644
index 0000000..b7dd451
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestMethodDescriptor.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+public class DefaultTestMethodDescriptor extends DefaultTestDescriptor {
+    public DefaultTestMethodDescriptor(Object id, String className, String methodName) {
+        super(id, className, methodName);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("test method %s(%s)", getName(), getClassName());
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestSuiteDescriptor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestSuiteDescriptor.java
new file mode 100644
index 0000000..dcb4e42
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/DefaultTestSuiteDescriptor.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+import java.io.Serializable;
+
+public class DefaultTestSuiteDescriptor extends AbstractTestDescriptor implements Serializable {
+    public DefaultTestSuiteDescriptor(Object id, String name) {
+        super(id, name);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("test '%s'", getName());
+    }
+
+    public boolean isComposite() {
+        return true;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/SuiteTestClassProcessor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/SuiteTestClassProcessor.java
new file mode 100644
index 0000000..649ea40
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/SuiteTestClassProcessor.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.api.internal.tasks.testing;
+
+import org.gradle.api.internal.tasks.testing.results.AttachParentTestResultProcessor;
+import org.gradle.util.TimeProvider;
+
+public class SuiteTestClassProcessor implements TestClassProcessor {
+    private final TestClassProcessor processor;
+    private final TimeProvider timeProvider;
+    private final TestDescriptorInternal suiteDescriptor;
+    private TestResultProcessor resultProcessor;
+
+    public SuiteTestClassProcessor(TestDescriptorInternal suiteDescriptor, TestClassProcessor processor,
+                                   TimeProvider timeProvider) {
+        this.suiteDescriptor = suiteDescriptor;
+        this.processor = processor;
+        this.timeProvider = timeProvider;
+    }
+
+    public void startProcessing(TestResultProcessor testResultProcessor) {
+        resultProcessor = new AttachParentTestResultProcessor(testResultProcessor);
+        resultProcessor.started(suiteDescriptor, new TestStartEvent(timeProvider.getCurrentTime()));
+
+        try {
+            processor.startProcessing(resultProcessor);
+        } catch (Throwable t) {
+            resultProcessor.failure(suiteDescriptor.getId(), new TestSuiteExecutionException(String.format(
+                    "Could not start %s.", suiteDescriptor), t));
+        }
+    }
+
+    public void processTestClass(TestClassRunInfo testClass) {
+        try {
+            processor.processTestClass(testClass);
+        } catch (Throwable t) {
+            resultProcessor.failure(suiteDescriptor.getId(), new TestSuiteExecutionException(String.format(
+                    "Could not execute test class '%s'.", testClass.getTestClassName()), t));
+        }
+    }
+
+    public void stop() {
+        try {
+            processor.stop();
+        } catch (Throwable t) {
+            resultProcessor.failure(suiteDescriptor.getId(), new TestSuiteExecutionException(String.format(
+                    "Could not complete execution for %s.", suiteDescriptor), t));
+        } finally {
+            resultProcessor.completed(suiteDescriptor.getId(), new TestCompleteEvent(timeProvider.getCurrentTime()));
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassProcessor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassProcessor.java
new file mode 100644
index 0000000..cfefa2e
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassProcessor.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import org.gradle.messaging.concurrent.Stoppable;
+
+/**
+ * A processor for executing tests. Implementations are not required to be thread-safe.
+ *
+ * @author Tom Eyckmans
+ */
+public interface TestClassProcessor extends Stoppable {
+    /**
+     * Performs any initialisation which this processor needs to perform.
+     *
+     * @param resultProcessor The processor to send results to during test execution.
+     */
+    void startProcessing(TestResultProcessor resultProcessor);
+
+    /**
+     * Accepts the given test class for processing. May execute synchronously, asynchronously, or defer execution for
+     * later.
+     *
+     * @param testClass The test class.
+     */
+    void processTestClass(TestClassRunInfo testClass);
+
+    /**
+     * Completes any pending or asynchronous processing. Blocks until all processing is complete. The processor should
+     * not use the result processor provided to {@link #startProcessing(TestResultProcessor)} after this method has
+     * returned.
+     */
+    void stop();
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassProcessorFactory.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassProcessorFactory.java
new file mode 100644
index 0000000..424d0ea
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassProcessorFactory.java
@@ -0,0 +1,20 @@
+/*
+ * 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;
+
+public interface TestClassProcessorFactory {
+    TestClassProcessor create();
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassRunInfo.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassRunInfo.java
new file mode 100644
index 0000000..c553c4e
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestClassRunInfo.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.api.internal.tasks.testing;
+
+import java.io.Serializable;
+
+/**
+ * @author Tom Eyckmans
+ */
+public interface TestClassRunInfo extends Serializable {
+    String getTestClassName();
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestCompleteEvent.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestCompleteEvent.java
new file mode 100644
index 0000000..ea1b323
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestCompleteEvent.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.api.internal.tasks.testing;
+
+import org.gradle.api.tasks.testing.TestResult;
+
+import java.io.Serializable;
+
+public class TestCompleteEvent implements Serializable {
+    private final long endTime;
+    private final TestResult.ResultType resultType;
+
+    public TestCompleteEvent(long endTime) {
+        this(endTime, null);
+    }
+
+    public TestCompleteEvent(long endTime, TestResult.ResultType resultType) {
+        this.endTime = endTime;
+        this.resultType = resultType;
+    }
+
+    public long getEndTime() {
+        return endTime;
+    }
+
+    public TestResult.ResultType getResultType() {
+        return resultType;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestDescriptorInternal.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestDescriptorInternal.java
new file mode 100644
index 0000000..fc13eab
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestDescriptorInternal.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+import org.gradle.api.tasks.testing.TestDescriptor;
+
+public interface TestDescriptorInternal extends TestDescriptor {
+    Object getId();
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestFramework.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestFramework.java
new file mode 100644
index 0000000..f4d2a79
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestFramework.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.api.internal.tasks.testing;
+
+import org.gradle.api.Action;
+import org.gradle.api.internal.tasks.testing.detection.TestFrameworkDetector;
+import org.gradle.api.tasks.testing.TestFrameworkOptions;
+import org.gradle.process.internal.WorkerProcessBuilder;
+
+/**
+ * @author Tom Eyckmans
+ */
+public interface TestFramework {
+
+    /**
+     * Returns a detector which is used to determine which of the candidate class files correspond to test classes to be
+     * executed.
+     */
+    TestFrameworkDetector getDetector();
+
+    void report();
+
+    TestFrameworkOptions getOptions();
+
+    /**
+     * Returns a factory which is used to create a {@link org.gradle.api.internal.tasks.testing.TestClassProcessor} in
+     * each worker process. This factory is serialized across to the worker process, and then it's {@link
+     * org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory#create(org.gradle.api.internal.project.ServiceRegistry)}
+     * method is called to create the test processor.
+     */
+    WorkerTestClassProcessorFactory getProcessorFactory();
+
+    /**
+     * Returns an action which is used to perform some framework specific worker process configuration. This action is
+     * executed before starting each worker process.
+     */
+    Action<WorkerProcessBuilder> getWorkerConfigurationAction();
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestOutputEvent.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestOutputEvent.java
new file mode 100644
index 0000000..e810205
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestOutputEvent.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+import java.io.Serializable;
+
+public class TestOutputEvent implements Serializable {
+    public enum Destination {
+        StdOut, StdErr
+    }
+
+    private final Destination destination;
+    private final String message;
+
+    public TestOutputEvent(Destination destination, String message) {
+        this.destination = destination;
+        this.message = message;
+    }
+
+    public Destination getDestination() {
+        return destination;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestResultProcessor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestResultProcessor.java
new file mode 100644
index 0000000..72b5ced
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestResultProcessor.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+/**
+ * A processor for test results. Implementations are not required to be thread-safe.
+ */
+public interface TestResultProcessor {
+    /**
+     * Notifies this processor that a test has started execution.
+     */
+    void started(TestDescriptorInternal test, TestStartEvent event);
+
+    /**
+     * Notifies this processor that a test has completed execution.
+     */
+    void completed(Object testId, TestCompleteEvent event);
+
+    /**
+     * Notifies this processor that a test has produced some output.
+     */
+    void output(Object testId, TestOutputEvent event);
+
+    /**
+     * Notifies this processor that a failure has occurred in the given test.
+     */
+    void failure(Object testId, Throwable result);
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestStartEvent.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestStartEvent.java
new file mode 100644
index 0000000..f762275
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestStartEvent.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import java.io.Serializable;
+
+public class TestStartEvent implements Serializable {
+    private final long startTime;
+    private final Object parentId;
+
+    public TestStartEvent(long startTime) {
+        this(startTime, null);
+    }
+
+    public TestStartEvent(long startTime, Object parentId) {
+        this.startTime = startTime;
+        this.parentId = parentId;
+    }
+
+    public long getStartTime() {
+        return startTime;
+    }
+
+    public Object getParentId() {
+        return parentId;
+    }
+
+    /**
+     * Creates a copy of this event with a new parent id.
+     */
+    public TestStartEvent withParentId(Object parentId) {
+        return new TestStartEvent(startTime, parentId);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestSuiteExecutionException.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestSuiteExecutionException.java
new file mode 100644
index 0000000..a827534
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/TestSuiteExecutionException.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+import org.gradle.api.GradleException;
+
+/**
+ * Thrown when some internal exception occurs executing a test suite.
+ */
+public class TestSuiteExecutionException extends GradleException {
+    public TestSuiteExecutionException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/WorkerTestClassProcessorFactory.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/WorkerTestClassProcessorFactory.java
new file mode 100644
index 0000000..3981954
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/WorkerTestClassProcessorFactory.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+import org.gradle.api.internal.project.ServiceRegistry;
+
+public interface WorkerTestClassProcessorFactory {
+    TestClassProcessor create(ServiceRegistry serviceRegistry);
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/AbstractTestFrameworkDetector.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/AbstractTestFrameworkDetector.java
new file mode 100644
index 0000000..c146669
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/AbstractTestFrameworkDetector.java
@@ -0,0 +1,179 @@
+/*
+ * 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.detection;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.gradle.api.GradleException;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.tasks.testing.DefaultTestClassRunInfo;
+import org.gradle.api.internal.tasks.testing.TestClassProcessor;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Type;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.*;
+
+/**
+ * @author Tom Eyckmans
+ */
+public abstract class AbstractTestFrameworkDetector<T extends TestClassVisitor> implements TestFrameworkDetector {
+    protected static final String TEST_CASE = "junit/framework/TestCase";
+    protected static final String GROOVY_TEST_CASE = "groovy/util/GroovyTestCase";
+
+    private final File testClassesDirectory;
+    private final FileCollection testClasspath;
+    private List<File> testClassDirectories;
+    private ClassFileExtractionManager classFileExtractionManager;
+    private final Map<File, Boolean> superClasses;
+
+    protected TestClassProcessor testClassProcessor;
+
+    protected List<String> knownTestCaseClassNames;
+
+    protected AbstractTestFrameworkDetector(File testClassesDirectory, FileCollection testClasspath) {
+        this.testClassesDirectory = testClassesDirectory;
+        this.testClasspath = testClasspath;
+        this.superClasses = new HashMap<File, Boolean>();
+        this.knownTestCaseClassNames = new ArrayList<String>();
+        addKnownTestCaseClassNames(TEST_CASE, GROOVY_TEST_CASE);
+    }
+
+    protected abstract T createClassVisitor();
+
+    protected File getSuperTestClassFile(String superClassName) {
+        prepareClasspath();
+        if (StringUtils.isEmpty(superClassName)) {
+            throw new IllegalArgumentException("superClassName is empty!");
+        }
+
+        final Iterator<File> testClassDirectoriesIt = testClassDirectories.iterator();
+
+        File superTestClassFile = null;
+        while (superTestClassFile == null && testClassDirectoriesIt.hasNext()) {
+            final File testClassDirectory = testClassDirectoriesIt.next();
+            final File superTestClassFileCandidate = new File(testClassDirectory, superClassName + ".class");
+            if (superTestClassFileCandidate.exists()) {
+                superTestClassFile = superTestClassFileCandidate;
+            }
+        }
+
+        if (superTestClassFile != null) {
+            return superTestClassFile;
+        } else { // super test class file not in test class directories
+            return classFileExtractionManager.getLibraryClassFile(superClassName);
+        }
+    }
+
+    private void prepareClasspath() {
+        if (classFileExtractionManager != null) {
+            return;
+        }
+
+        classFileExtractionManager = new ClassFileExtractionManager();
+        testClassDirectories = new ArrayList<File>();
+
+        testClassDirectories.add(testClassesDirectory);
+        if (testClasspath != null) {
+            for (File file : testClasspath) {
+                if (file.isDirectory()) {
+                    testClassDirectories.add(file);
+                } else if (file.isFile() && file.getName().endsWith(".jar")) {
+                    classFileExtractionManager.addLibraryJar(file);
+                }
+            }
+        }
+    }
+
+    protected TestClassVisitor classVisitor(final File testClassFile) {
+        final TestClassVisitor classVisitor = createClassVisitor();
+
+        InputStream classStream = null;
+        try {
+            classStream = new BufferedInputStream(new FileInputStream(testClassFile));
+            final ClassReader classReader = new ClassReader(classStream);
+            classReader.accept(classVisitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES);
+        } catch (Throwable e) {
+            throw new GradleException("failed to read class file " + testClassFile.getAbsolutePath(), e);
+        } finally {
+            IOUtils.closeQuietly(classStream);
+        }
+
+        return classVisitor;
+    }
+
+    public boolean processTestClass(File testClassFile) {
+        return processTestClass(testClassFile, false);
+    }
+
+    protected abstract boolean processTestClass(File testClassFile, boolean superClass);
+
+    protected boolean processSuperClass(File testClassFile) {
+        boolean isTest = false;
+
+        Boolean isSuperTest = superClasses.get(testClassFile);
+
+        if (isSuperTest == null) {
+            isTest = processTestClass(testClassFile, true);
+
+            superClasses.put(testClassFile, isTest);
+        } else {
+            isTest = isSuperTest;
+        }
+
+        return isTest;
+    }
+
+    /**
+     * 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).
+     */
+    protected void publishTestClass(boolean isTest, TestClassVisitor classVisitor, boolean superClass) {
+        if (isTest && !classVisitor.isAbstract() && !superClass) {
+            String className = Type.getObjectType(classVisitor.getClassName()).getClassName();
+            testClassProcessor.processTestClass(new DefaultTestClassRunInfo(className));
+        }
+    }
+
+    public void startDetection(TestClassProcessor testClassProcessor) {
+        this.testClassProcessor = testClassProcessor;
+    }
+
+    public void addKnownTestCaseClassNames(String... knownTestCaseClassNames) {
+        if (knownTestCaseClassNames != null && knownTestCaseClassNames.length != 0) {
+            for (String knownTestCaseClassName : knownTestCaseClassNames) {
+                if (StringUtils.isNotEmpty(knownTestCaseClassName)) {
+                    this.knownTestCaseClassNames.add(knownTestCaseClassName.replaceAll("\\.", "/"));
+                }
+            }
+        }
+    }
+
+    protected boolean isKnownTestCaseClassName(String testCaseClassName) {
+        boolean isKnownTestCase = false;
+
+        if (StringUtils.isNotEmpty(testCaseClassName)) {
+            isKnownTestCase = knownTestCaseClassNames.contains(testCaseClassName);
+        }
+
+        return isKnownTestCase;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/ClassFileExtractionManager.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/ClassFileExtractionManager.java
new file mode 100644
index 0000000..804771c
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/ClassFileExtractionManager.java
@@ -0,0 +1,146 @@
+/*
+ * 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.tasks.testing.detection;
+
+import org.apache.commons.lang.text.StrBuilder;
+import org.gradle.api.GradleException;
+import org.gradle.api.artifacts.indexing.JarFilePackageListener;
+import org.gradle.api.artifacts.indexing.JarFilePackageLister;
+import org.gradle.util.JarUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * This class manages class file extraction from library jar files.
+ *
+ * @author Tom Eyckmans
+ */
+public class ClassFileExtractionManager {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ClassFileExtractionManager.class);
+    protected final Map<String, Set<File>> packageJarFilesMappings;
+    protected final Map<String, File> extractedJarClasses;
+    protected final Set<String> unextractableClasses;
+
+    public ClassFileExtractionManager() {
+        packageJarFilesMappings = new HashMap<String, Set<File>>();
+        extractedJarClasses = new HashMap<String, File>();
+        unextractableClasses = new TreeSet<String>();
+    }
+
+    /**
+     * Add all packages found in the jar file to the package <> jar(s) index.
+     *
+     * @param libraryJar Jar file to add to the index.
+     */
+    public void addLibraryJar(final File libraryJar) {
+        new JarFilePackageLister().listJarPackages(libraryJar, new JarFilePackageListener() {
+            public void receivePackage(String packageName) {
+                Set<File> jarFiles = packageJarFilesMappings.get(packageName);
+                if (jarFiles == null) {
+                    jarFiles = new TreeSet<File>();
+                }
+                jarFiles.add(libraryJar);
+
+                packageJarFilesMappings.put(packageName, jarFiles);
+            }
+        });
+    }
+
+    /**
+     * Retrieve the file that contains the extracted class file. <p/> This method will extract the class file if it is
+     * not extracted yet. Extracted class files are deleted on exit of the Gradle process. The same class is only
+     * extracted once.
+     *
+     * @param className Name of the class to extract.
+     * @return File that contains the extracted class file.
+     */
+    public File getLibraryClassFile(final String className) {
+        if (unextractableClasses.contains(className)) {
+            return null;
+        } else {
+            if (!extractedJarClasses.containsKey(className)) {
+                if (!extractClassFile(className)) {
+                    unextractableClasses.add(className);
+                }
+            }
+
+            return extractedJarClasses.get(className);
+        }
+    }
+
+    boolean extractClassFile(final String className) {
+        boolean classFileExtracted = false;
+
+        final File extractedClassFile = tempFile();
+        final String classFileName = new StrBuilder().append(className).append(".class").toString();
+        final String classNamePackage = classNamePackage(className);
+        final Set<File> packageJarFiles = packageJarFilesMappings.get(classNamePackage);
+
+        File classFileSourceJar = null;
+
+        if (packageJarFiles != null && !packageJarFiles.isEmpty()) {
+            final Iterator<File> packageJarFilesIt = packageJarFiles.iterator();
+
+            while (!classFileExtracted && packageJarFilesIt.hasNext()) {
+                final File jarFile = packageJarFilesIt.next();
+
+                try {
+                    classFileExtracted = JarUtil.extractZipEntry(jarFile, classFileName, extractedClassFile);
+
+                    if (classFileExtracted) {
+                        classFileSourceJar = jarFile;
+                    }
+                } catch (IOException e) {
+                    throw new GradleException("failed to extract class file from jar (" + jarFile + ")", e);
+                }
+            }
+
+            if (classFileExtracted) {
+                LOGGER.debug("extracted class {} from {}", className, classFileSourceJar.getName());
+
+                extractedJarClasses.put(className, extractedClassFile);
+            }
+        } // super class not on the classpath - unable to scan parent class
+
+        return classFileExtracted;
+    }
+
+    String classNamePackage(final String className) {
+        final int lastSlashIndex = className.lastIndexOf('/');
+
+        if (lastSlashIndex == -1) {
+            return null; // class in root package - should not happen
+        } else {
+            return className.substring(0, lastSlashIndex + 1);
+        }
+    }
+
+    File tempFile() {
+        try {
+            final File tempFile = File.createTempFile("jar_extract_", "_tmp");
+
+            tempFile.deleteOnExit();
+
+            return tempFile;
+        } catch (IOException e) {
+            throw new GradleException("failed to create temp file to extract class from jar into", e);
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestClassScanner.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestClassScanner.java
new file mode 100644
index 0000000..e480028
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestClassScanner.java
@@ -0,0 +1,84 @@
+/*
+ * 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.detection;
+
+import org.gradle.api.file.EmptyFileVisitor;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.FileVisitDetails;
+import org.gradle.api.internal.tasks.testing.DefaultTestClassRunInfo;
+import org.gradle.api.internal.tasks.testing.TestClassProcessor;
+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.
+ *
+ * @author Tom Eyckmans
+ */
+public class DefaultTestClassScanner implements Runnable {
+    private final FileTree candidateClassFiles;
+    private final TestFrameworkDetector testFrameworkDetector;
+    private final TestClassProcessor testClassProcessor;
+
+    public DefaultTestClassScanner(FileTree candidateClassFiles, TestFrameworkDetector testFrameworkDetector,
+                                   TestClassProcessor testClassProcessor) {
+        this.candidateClassFiles = candidateClassFiles;
+        this.testFrameworkDetector = testFrameworkDetector;
+        this.testClassProcessor = testClassProcessor;
+    }
+
+    public void run() {
+        if (testFrameworkDetector == null) {
+            filenameScan();
+        } else {
+            detectionScan();
+        }
+    }
+
+    private void detectionScan() {
+        testFrameworkDetector.startDetection(testClassProcessor);
+        candidateClassFiles.visit(new ClassFileVisitor() {
+            public void visitClassFile(FileVisitDetails fileDetails) {
+                testFrameworkDetector.processTestClass(fileDetails.getFile());
+            }
+        });
+    }
+
+    private void filenameScan() {
+        candidateClassFiles.visit(new ClassFileVisitor() {
+            public void visitClassFile(FileVisitDetails fileDetails) {
+                String className = fileDetails.getRelativePath().getPathString().replaceAll("\\.class", "").replace('/', '.');
+                TestClassRunInfo testClass = new DefaultTestClassRunInfo(className);
+                testClassProcessor.processTestClass(testClass);
+            }
+        });
+    }
+
+    private abstract class ClassFileVisitor extends EmptyFileVisitor {
+        public void visitFile(FileVisitDetails fileDetails) {
+            final File file = fileDetails.getFile();
+
+            if (file.getAbsolutePath().endsWith(".class")) {
+                visitClassFile(fileDetails);
+            }
+        }
+
+        public abstract void visitClassFile(FileVisitDetails fileDetails);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestExecuter.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestExecuter.java
new file mode 100644
index 0000000..bd5f726
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestExecuter.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.api.internal.tasks.testing.detection;
+
+import org.gradle.api.file.FileTree;
+import org.gradle.api.internal.tasks.testing.*;
+import org.gradle.api.internal.tasks.testing.processors.MaxNParallelTestClassProcessor;
+import org.gradle.api.internal.tasks.testing.processors.RestartEveryNTestClassProcessor;
+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.messaging.actor.ActorFactory;
+import org.gradle.process.internal.WorkerProcessFactory;
+import org.gradle.util.TrueTimeProvider;
+
+/**
+ * The default test class scanner factory.
+ *
+ * @author Tom Eyckmans
+ */
+public class DefaultTestExecuter implements TestExecuter {
+    private final WorkerProcessFactory workerFactory;
+    private final ActorFactory actorFactor;
+
+    public DefaultTestExecuter(WorkerProcessFactory workerFactory, ActorFactory actorFactor) {
+        this.workerFactory = workerFactory;
+        this.actorFactor = actorFactor;
+    }
+
+    public void execute(final Test testTask, TestResultProcessor testResultProcessor) {
+        final TestFramework testFramework = testTask.getTestFramework();
+        final WorkerTestClassProcessorFactory testInstanceFactory = testFramework.getProcessorFactory();
+        final TestClassProcessorFactory forkingProcessorFactory = new TestClassProcessorFactory() {
+            public TestClassProcessor create() {
+                return new ForkingTestClassProcessor(workerFactory, testInstanceFactory, testTask,
+                        testTask.getClasspath(), testFramework.getWorkerConfigurationAction());
+            }
+        };
+        TestClassProcessorFactory reforkingProcessorFactory = new TestClassProcessorFactory() {
+            public TestClassProcessor create() {
+                return new RestartEveryNTestClassProcessor(forkingProcessorFactory, testTask.getForkEvery());
+            }
+        };
+
+        TestClassProcessor processor = new MaxNParallelTestClassProcessor(testTask.getMaxParallelForks(),
+                reforkingProcessorFactory, actorFactor);
+
+        final FileTree testClassFiles = testTask.getCandidateClassFiles();
+
+        Runnable detector;
+        if (testTask.isScanForTestClasses()) {
+            TestFrameworkDetector testFrameworkDetector = testTask.getTestFramework().getDetector();
+            detector = new DefaultTestClassScanner(testClassFiles, testFrameworkDetector, processor);
+        } else {
+            detector = new DefaultTestClassScanner(testClassFiles, null, processor);
+        }
+        new TestMainAction(detector, processor, testResultProcessor, new TrueTimeProvider()).run();
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestClassVisitor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestClassVisitor.java
new file mode 100644
index 0000000..9a7678c
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestClassVisitor.java
@@ -0,0 +1,44 @@
+/*
+ * 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.tasks.testing.detection;
+
+import org.objectweb.asm.commons.EmptyVisitor;
+
+/**
+ * Base class for ASM test class scanners.
+ *
+ * @author Tom Eyckmans
+ */
+public abstract class TestClassVisitor extends EmptyVisitor {
+
+    protected final TestFrameworkDetector detector;
+
+    protected TestClassVisitor(TestFrameworkDetector detector) {
+        if (detector == null) {
+            throw new IllegalArgumentException("detector == null!");
+        }
+        this.detector = detector;
+    }
+
+    public abstract String getClassName();
+
+    public abstract boolean isTest();
+
+    public abstract boolean isAbstract();
+
+    public abstract String getSuperClassName();
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestExecuter.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestExecuter.java
new file mode 100644
index 0000000..012ea2f
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestExecuter.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.api.internal.tasks.testing.detection;
+
+import org.gradle.api.internal.tasks.testing.TestResultProcessor;
+import org.gradle.api.tasks.testing.Test;
+
+/**
+ * @author Tom Eyckmans
+ */
+public interface TestExecuter {
+    void execute(Test testTask, TestResultProcessor testResultProcessor);
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestFrameworkDetector.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestFrameworkDetector.java
new file mode 100644
index 0000000..64bfb51
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/detection/TestFrameworkDetector.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.api.internal.tasks.testing.detection;
+
+import org.gradle.api.internal.tasks.testing.TestClassProcessor;
+
+import java.io.File;
+
+/**
+ * @author Tom Eyckmans
+ */
+public interface TestFrameworkDetector {
+    void startDetection(TestClassProcessor testClassProcessor);
+
+    boolean processTestClass(File testClassFile);
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/AntJUnitReport.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/AntJUnitReport.groovy
new file mode 100644
index 0000000..d444421
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/AntJUnitReport.groovy
@@ -0,0 +1,34 @@
+/*
+ * 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.junit
+
+import org.apache.tools.ant.taskdefs.optional.junit.XMLResultAggregator
+/**
+ * @author Tom Eyckmans
+ */
+
+class AntJUnitReport {
+
+    void execute(File testResultsDir, File testReportDir, AntBuilder ant) {
+        ant.project.addTaskDefinition('junitreport2', XMLResultAggregator.class)
+        ant.junitreport2(todir: testResultsDir.absolutePath) {
+            fileset(dir: testResultsDir.absolutePath, includes: 'TEST-*.xml')
+            report(todir: testReportDir.absolutePath)
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/CaptureTestOutputTestResultProcessor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/CaptureTestOutputTestResultProcessor.java
new file mode 100644
index 0000000..6879a66
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/CaptureTestOutputTestResultProcessor.java
@@ -0,0 +1,70 @@
+/*
+ * 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.junit;
+
+import org.gradle.api.internal.tasks.testing.*;
+import org.gradle.api.logging.StandardOutputListener;
+import org.gradle.logging.StandardOutputRedirector;
+
+public class CaptureTestOutputTestResultProcessor implements TestResultProcessor {
+    private final TestResultProcessor processor;
+    private final StandardOutputRedirector outputRedirector;
+    private Object suite;
+
+    public CaptureTestOutputTestResultProcessor(TestResultProcessor processor, StandardOutputRedirector outputRedirector) {
+        this.processor = processor;
+        this.outputRedirector = outputRedirector;
+    }
+
+    public void started(final TestDescriptorInternal test, TestStartEvent event) {
+        processor.started(test, event);
+        if (suite != null) {
+            return;
+        }
+        suite = test.getId();
+        outputRedirector.redirectStandardOutputTo(new StandardOutputListener() {
+            public void onOutput(CharSequence output) {
+                processor.output(suite, new TestOutputEvent(TestOutputEvent.Destination.StdOut, output.toString()));
+            }
+        });
+        outputRedirector.redirectStandardErrorTo(new StandardOutputListener() {
+            public void onOutput(CharSequence output) {
+                processor.output(suite, new TestOutputEvent(TestOutputEvent.Destination.StdErr, output.toString()));
+            }
+        });
+        outputRedirector.start();
+    }
+
+    public void completed(Object testId, TestCompleteEvent event) {
+        if (testId.equals(suite)) {
+            try {
+                outputRedirector.stop();
+            } finally {
+                suite = null;
+            }
+        }
+        processor.completed(testId, event);
+    }
+
+    public void output(Object testId, TestOutputEvent event) {
+        processor.output(testId, event);
+    }
+
+    public void failure(Object testId, Throwable result) {
+        processor.failure(testId, result);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JULRedirector.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JULRedirector.java
new file mode 100644
index 0000000..969869b
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JULRedirector.java
@@ -0,0 +1,42 @@
+/*
+ * 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.junit;
+
+import org.gradle.logging.StandardOutputCapture;
+import org.gradle.logging.DefaultStandardOutputRedirector;
+
+import java.util.logging.ConsoleHandler;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+
+/**
+ * Some hackery to get JUL output redirected to test output
+ */
+public class JULRedirector extends DefaultStandardOutputRedirector {
+    private boolean reset;
+
+    @Override
+    public StandardOutputCapture start() {
+        super.start();
+        if (!reset) {
+            LogManager.getLogManager().reset();
+            Logger.getLogger("").addHandler(new ConsoleHandler());
+            reset = true;
+        }
+        return this;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnit4TestResultProcessorAdapter.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnit4TestResultProcessorAdapter.java
new file mode 100644
index 0000000..2f469fe
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnit4TestResultProcessorAdapter.java
@@ -0,0 +1,58 @@
+/*
+ * 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.junit;
+
+import junit.framework.JUnit4TestCaseFacade;
+import junit.framework.Test;
+import org.gradle.api.internal.tasks.testing.DefaultTestDescriptor;
+import org.gradle.api.internal.tasks.testing.TestDescriptorInternal;
+import org.gradle.api.internal.tasks.testing.TestResultProcessor;
+import org.gradle.util.IdGenerator;
+import org.gradle.util.TimeProvider;
+import org.junit.runner.Describable;
+import org.junit.runner.Description;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class JUnit4TestResultProcessorAdapter extends JUnitTestResultProcessorAdapter {
+    public JUnit4TestResultProcessorAdapter(TestResultProcessor resultProcessor, TimeProvider timeProvider, IdGenerator<?> idGenerator) {
+        super(resultProcessor, timeProvider, idGenerator);
+    }
+
+    @Override
+    protected TestDescriptorInternal convert(Object id, Test test) {
+        if (test instanceof JUnit4TestCaseFacade) {
+            JUnit4TestCaseFacade facade = (JUnit4TestCaseFacade) test;
+            Matcher matcher = Pattern.compile("(.*)\\((.*)\\)").matcher(facade.toString());
+            String className = facade.toString();
+            String methodName = null;
+            if (matcher.matches()) {
+                className = matcher.group(2);
+                methodName = matcher.group(1);
+            }
+            return new DefaultTestDescriptor(id, className, methodName);
+        }
+        // JUnit4TestCaseFacade is-a Describable in junit 4.7+
+        if (test instanceof Describable) {
+            Describable describable = (Describable) test;
+            Description description = describable.getDescription();
+            return new DefaultTestDescriptor(id, description.getClassName(), description.getMethodName());
+        }
+        return super.convert(id, test);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitDetector.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitDetector.java
new file mode 100644
index 0000000..2f2d4c1
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitDetector.java
@@ -0,0 +1,66 @@
+/*
+ * 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.tasks.testing.junit;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.tasks.testing.detection.TestClassVisitor;
+import org.gradle.api.internal.tasks.testing.detection.AbstractTestFrameworkDetector;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class JUnitDetector extends AbstractTestFrameworkDetector<JUnitTestClassDetecter> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(JUnitDetector.class);
+
+    JUnitDetector(File testClassesDirectory, FileCollection testClasspath) {
+        super(testClassesDirectory, testClasspath);
+    }
+
+    protected JUnitTestClassDetecter createClassVisitor() {
+        return new JUnitTestClassDetecter(this);
+    }
+
+    protected boolean processTestClass(final File testClassFile, boolean superClass) {
+        final TestClassVisitor classVisitor = classVisitor(testClassFile);
+
+        boolean isTest = classVisitor.isTest();
+
+        if (!isTest) { // scan parent class
+            final String superClassName = classVisitor.getSuperClassName();
+
+            if (isKnownTestCaseClassName(superClassName)) {
+                isTest = true;
+            } else {
+                final File superClassFile = getSuperTestClassFile(superClassName);
+
+                if (superClassFile != null) {
+                    isTest = processSuperClass(superClassFile);
+                } else {
+                    LOGGER.debug("test-class-scan : failed to scan parent class {}, could not find the class file",
+                            superClassName);
+                }
+            }
+        }
+
+        publishTestClass(isTest, classVisitor, superClass);
+
+        return isTest;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassDetecter.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassDetecter.java
new file mode 100644
index 0000000..9aabfc5
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassDetecter.java
@@ -0,0 +1,119 @@
+/*
+ * 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.junit;
+
+import org.gradle.api.internal.tasks.testing.detection.TestClassVisitor;
+import org.gradle.api.internal.tasks.testing.detection.TestFrameworkDetector;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.EmptyVisitor;
+
+/**
+ * @author Tom Eyckmans
+ */
+class JUnitTestClassDetecter extends TestClassVisitor {
+    private boolean isAbstract;
+    private String className;
+    private String superClassName;
+    private boolean test;
+
+    JUnitTestClassDetecter(final TestFrameworkDetector detector) {
+        super(detector);
+    }
+
+    /**
+     * Visits the header of the class.
+     *
+     * @param version the class version.
+     * @param access the class's access flags (see {@link Opcodes}). This parameter also indicates if the class is
+     * deprecated.
+     * @param name the internal name of the class (see {@link org.objectweb.asm.Type#getInternalName()
+     * getInternalName}).
+     * @param signature the signature of this class. May be <tt>null</tt> if the class is not a generic one, and does
+     * not extend or implement generic classes or interfaces.
+     * @param superName the internal of name of the super class (see {@link org.objectweb.asm.Type#getInternalName()
+     * getInternalName}). For interfaces, the super class is {@link Object}. May be <tt>null</tt>, but only for the
+     * {@link Object} class.
+     * @param interfaces the internal names of the class's interfaces (see {@link org.objectweb.asm.Type#getInternalName()
+     * getInternalName}). May be <tt>null</tt>.
+     */
+    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+        isAbstract = (access & Opcodes.ACC_ABSTRACT) != 0;
+
+        this.className = name;
+        this.superClassName = superName;
+    }
+
+    /**
+     * Visits an annotation of the class.
+     *
+     * @param desc the class descriptor of the annotation class.
+     * @param visible <tt>true</tt> if the annotation is visible at runtime.
+     * @return a visitor to visit the annotation values, or <tt>null</tt> if this visitor is not interested in visiting
+     *         this annotation.
+     */
+    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+        if ("Lorg/junit/runner/RunWith;".equals(desc)) {
+            test = true;
+        }
+
+        return new EmptyVisitor();
+    }
+
+    /**
+     * Visits a method of the class. This method <i>must</i> return a new {@link MethodVisitor} instance (or
+     * <tt>null</tt>) each time it is called, i.e., it should not return a previously returned visitor.
+     *
+     * @param access the method's access flags (see {@link Opcodes}). This parameter also indicates if the method is
+     * synthetic and/or deprecated.
+     * @param name the method's name.
+     * @param desc the method's descriptor (see {@link org.objectweb.asm.Type Type}).
+     * @param signature the method's signature. May be <tt>null</tt> if the method parameters, return type and
+     * exceptions do not use generic types.
+     * @param exceptions the internal names of the method's exception classes (see {@link
+     * org.objectweb.asm.Type#getInternalName() getInternalName}). May be <tt>null</tt>.
+     * @return an object to visit the byte code of the method, or <tt>null</tt> if this class visitor is not interested
+     *         in visiting the code of this method.
+     */
+    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+        if (!test) {
+            return new JUnitTestMethodDetecter(this);
+        } else {
+            return new EmptyVisitor();
+        }
+    }
+
+    public String getClassName() {
+        return className;
+    }
+
+    public boolean isAbstract() {
+        return isAbstract;
+    }
+
+    public boolean isTest() {
+        return test;
+    }
+
+    void setTest(boolean test) {
+        this.test = test;
+    }
+
+    public String getSuperClassName() {
+        return superClassName;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassExecuter.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassExecuter.java
new file mode 100644
index 0000000..e43557a
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassExecuter.java
@@ -0,0 +1,106 @@
+/*
+ * 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.junit;
+
+import junit.framework.JUnit4TestAdapter;
+import junit.framework.Test;
+import junit.framework.TestListener;
+import junit.framework.TestResult;
+import org.gradle.api.internal.tasks.testing.*;
+import org.gradle.util.IdGenerator;
+import org.gradle.util.TimeProvider;
+import org.junit.runner.Describable;
+import org.junit.runner.Description;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class JUnitTestClassExecuter {
+    private final ClassLoader applicationClassLoader;
+    private final TestListener listener;
+    private final TestResultProcessor resultProcessor;
+    private final IdGenerator<?> idGenerator;
+    private final TimeProvider timeProvider;
+
+    public JUnitTestClassExecuter(ClassLoader applicationClassLoader, TestListener listener, TestResultProcessor resultProcessor, IdGenerator<?> idGenerator, TimeProvider timeProvider) {
+        this.applicationClassLoader = applicationClassLoader;
+        this.listener = listener;
+        this.resultProcessor = resultProcessor;
+        this.idGenerator = idGenerator;
+        this.timeProvider = timeProvider;
+    }
+
+    public void execute(String testClassName) {
+        TestDescriptorInternal testInternal = new DefaultTestClassDescriptor(idGenerator.generateId(), testClassName);
+        resultProcessor.started(testInternal, new TestStartEvent(timeProvider.getCurrentTime()));
+
+        Test adapter = createTest(testClassName);
+        TestResult result = new TestResult();
+        result.addListener(listener);
+        adapter.run(result);
+
+        resultProcessor.completed(testInternal.getId(), new TestCompleteEvent(timeProvider.getCurrentTime()));
+    }
+
+    private Test createTest(String testClassName) {
+        Class<?> testClass;
+        try {
+            testClass = Class.forName(testClassName, true, applicationClassLoader);
+        } catch (Throwable e) {
+            return new BrokenTest(
+                    Description.createSuiteDescription(String.format("initializationError(%s)", testClassName)), e);
+        }
+
+        // Look for a static suite method
+        try {
+            Method suiteMethod = testClass.getMethod("suite", new Class[0]);
+            return (Test) suiteMethod.invoke(null);
+        } catch (NoSuchMethodException e) {
+            // Ignore
+        } catch (InvocationTargetException e) {
+            return new BrokenTest(Description.createTestDescription(testClass, "suite"), e.getCause());
+        } catch (IllegalAccessException e) {
+            return new BrokenTest(Description.createTestDescription(testClass, "suite"), e);
+        }
+
+        return new JUnit4TestAdapter(testClass);
+    }
+
+    private static class BrokenTest implements Test, Describable {
+        private final Throwable failure;
+        private final Description description;
+
+        public BrokenTest(Description description, Throwable failure) {
+            this.failure = failure;
+            this.description = description;
+        }
+
+        public Description getDescription() {
+            return description;
+        }
+
+        public int countTestCases() {
+            return 1;
+        }
+
+        public void run(TestResult result) {
+            result.startTest(this);
+            result.addError(this, failure);
+            result.endTest(this);
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessor.java
new file mode 100644
index 0000000..238a74e
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessor.java
@@ -0,0 +1,67 @@
+/*
+ * 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.junit;
+
+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.results.AttachParentTestResultProcessor;
+import org.gradle.listener.ListenerBroadcast;
+import org.gradle.logging.StandardOutputRedirector;
+import org.gradle.util.IdGenerator;
+import org.gradle.util.TimeProvider;
+import org.gradle.util.TrueTimeProvider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+public class JUnitTestClassProcessor implements TestClassProcessor {
+    private static final Logger LOGGER = LoggerFactory.getLogger(JUnitTestClassProcessor.class);
+    private final File testResultsDir;
+    private final IdGenerator<?> idGenerator;
+    private final StandardOutputRedirector outputRedirector;
+    private final TimeProvider timeProvider = new TrueTimeProvider();
+    private JUnitTestClassExecuter executer;
+
+    public JUnitTestClassProcessor(File testResultsDir, IdGenerator<?> idGenerator,
+                                   StandardOutputRedirector standardOutputRedirector) {
+        this.testResultsDir = testResultsDir;
+        this.idGenerator = idGenerator;
+        this.outputRedirector = standardOutputRedirector;
+    }
+
+    public void startProcessing(TestResultProcessor resultProcessor) {
+        ClassLoader applicationClassLoader = Thread.currentThread().getContextClassLoader();
+        ListenerBroadcast<TestResultProcessor> processors = new ListenerBroadcast<TestResultProcessor>(
+                TestResultProcessor.class);
+        processors.add(new JUnitXmlReportGenerator(testResultsDir));
+        processors.add(resultProcessor);
+        TestResultProcessor resultProcessorChain = new AttachParentTestResultProcessor(new CaptureTestOutputTestResultProcessor(processors.getSource(), outputRedirector));
+        JUnitTestResultProcessorAdapter listener = new JUnit4TestResultProcessorAdapter(resultProcessorChain,
+                timeProvider, idGenerator);
+        executer = new JUnitTestClassExecuter(applicationClassLoader, listener, resultProcessorChain, idGenerator, timeProvider);
+    }
+
+    public void processTestClass(TestClassRunInfo testClass) {
+        LOGGER.debug("Executing test {}", testClass.getTestClassName());
+        executer.execute(testClass.getTestClassName());
+    }
+
+    public void stop() {
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFramework.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFramework.java
new file mode 100644
index 0000000..36673cd
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFramework.java
@@ -0,0 +1,105 @@
+/*
+ * 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.junit;
+
+import org.gradle.api.Action;
+import org.gradle.api.internal.project.ServiceRegistry;
+import org.gradle.api.internal.tasks.testing.TestClassProcessor;
+import org.gradle.api.internal.tasks.testing.TestFramework;
+import org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory;
+import org.gradle.api.tasks.testing.Test;
+import org.gradle.api.tasks.testing.junit.JUnitOptions;
+import org.gradle.process.internal.WorkerProcessBuilder;
+import org.gradle.util.IdGenerator;
+
+import java.io.File;
+import java.io.Serializable;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class JUnitTestFramework implements TestFramework {
+    private AntJUnitReport antJUnitReport;
+    private JUnitOptions options;
+    private JUnitDetector detector;
+    private final Test testTask;
+
+    public JUnitTestFramework(Test testTask) {
+        this.testTask = testTask;
+        antJUnitReport = new AntJUnitReport();
+        options = new JUnitOptions();
+        detector = new JUnitDetector(testTask.getTestClassesDir(), testTask.getClasspath());
+    }
+
+    public WorkerTestClassProcessorFactory getProcessorFactory() {
+        final File testResultsDir = testTask.getTestResultsDir();
+        return new TestClassProcessorFactoryImpl(testResultsDir);
+    }
+
+    public Action<WorkerProcessBuilder> getWorkerConfigurationAction() {
+        return new Action<WorkerProcessBuilder>() {
+            public void execute(WorkerProcessBuilder workerProcessBuilder) {
+                workerProcessBuilder.sharedPackages("junit.framework");
+                workerProcessBuilder.sharedPackages("junit.extensions");
+                workerProcessBuilder.sharedPackages("org.junit");
+            }
+        };
+    }
+
+    public void report() {
+        if (!testTask.isTestReport()) {
+            return;
+        }
+        antJUnitReport.execute(testTask.getTestResultsDir(), testTask.getTestReportDir(),
+                testTask.getProject().getAnt());
+    }
+
+    public JUnitOptions getOptions() {
+        return options;
+    }
+
+    void setOptions(JUnitOptions options) {
+        this.options = options;
+    }
+
+    AntJUnitReport getAntJUnitReport() {
+        return antJUnitReport;
+    }
+
+    void setAntJUnitReport(AntJUnitReport antJUnitReport) {
+        this.antJUnitReport = antJUnitReport;
+    }
+
+    public JUnitDetector getDetector() {
+        return detector;
+    }
+
+    private static class TestClassProcessorFactoryImpl implements WorkerTestClassProcessorFactory, Serializable {
+        private final File testResultsDir;
+
+        public TestClassProcessorFactoryImpl(File testResultsDir) {
+            this.testResultsDir = testResultsDir;
+        }
+
+        public TestClassProcessor create(ServiceRegistry serviceRegistry) {
+            return new JUnitTestClassProcessor(
+                    testResultsDir,
+                    serviceRegistry.get(IdGenerator.class),
+                    new JULRedirector());
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestMethodDetecter.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestMethodDetecter.java
new file mode 100644
index 0000000..78d97fa
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestMethodDetecter.java
@@ -0,0 +1,46 @@
+/*
+ * 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.tasks.testing.junit;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.commons.EmptyVisitor;
+
+/**
+ * @author Tom Eyckmans
+ */
+class JUnitTestMethodDetecter extends EmptyVisitor {
+
+    private final JUnitTestClassDetecter testClassDetecter;
+
+    JUnitTestMethodDetecter(JUnitTestClassDetecter testClassDetecter) {
+        this.testClassDetecter = testClassDetecter;
+    }
+
+    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+        if ("Lorg/junit/Test;".equals(desc)) {
+            testClassDetecter.setTest(true);
+        }
+        return new EmptyVisitor();
+    }
+
+    public AnnotationVisitor visitAnnotationDefault() {
+        return new EmptyVisitor();
+    }
+
+    public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
+        return new EmptyVisitor();
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestResultProcessorAdapter.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestResultProcessorAdapter.java
new file mode 100644
index 0000000..3b1990f
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestResultProcessorAdapter.java
@@ -0,0 +1,107 @@
+/*
+ * 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.junit;
+
+import junit.extensions.TestSetup;
+import junit.framework.*;
+import org.gradle.api.internal.tasks.testing.*;
+import org.gradle.util.IdGenerator;
+import org.gradle.util.TimeProvider;
+
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+public class JUnitTestResultProcessorAdapter implements TestListener {
+    private final TestResultProcessor resultProcessor;
+    private final TimeProvider timeProvider;
+    private final IdGenerator<?> idGenerator;
+    private final Object lock = new Object();
+    private final Map<Object, TestDescriptorInternal> executing = new IdentityHashMap<Object, TestDescriptorInternal>();
+
+    public JUnitTestResultProcessorAdapter(TestResultProcessor resultProcessor, TimeProvider timeProvider,
+                                 IdGenerator<?> idGenerator) {
+        this.resultProcessor = resultProcessor;
+        this.timeProvider = timeProvider;
+        this.idGenerator = idGenerator;
+    }
+
+    public void startTest(Test test) {
+        TestDescriptorInternal descriptor = convert(idGenerator.generateId(), test);
+        doStartTest(test, descriptor);
+    }
+
+    private void doStartTest(Test test, TestDescriptorInternal descriptor) {
+        synchronized (lock) {
+            TestDescriptorInternal oldTest = executing.put(test, descriptor);
+            assert oldTest == null;
+        }
+        long startTime = timeProvider.getCurrentTime();
+        resultProcessor.started(descriptor, new TestStartEvent(startTime));
+    }
+
+    public void addError(Test test, Throwable throwable) {
+        TestDescriptorInternal testInternal;
+        synchronized (lock) {
+            testInternal = executing.get(test);
+        }
+        boolean needEndEvent = false;
+        if (testInternal == null) {
+            // this happens when @AfterClass fails, for example. Synthesise a start and end events
+            testInternal = convertForError(test);
+            needEndEvent = true;
+            doStartTest(test, testInternal);
+        }
+        resultProcessor.failure(testInternal.getId(), throwable);
+
+        if (needEndEvent) {
+            endTest(test);
+        }
+    }
+
+    private TestDescriptorInternal convertForError(Test test) {
+        if (test instanceof TestSetup) {
+            TestSetup testSetup = (TestSetup) test;
+            return new DefaultTestDescriptor(idGenerator.generateId(), testSetup.getClass().getName(), "classMethod");
+        }
+        assert test instanceof TestSuite : String.format("Should be TestSuite, is " + test.getClass());
+        TestSuite suite = (TestSuite) test;
+        return new DefaultTestMethodDescriptor(idGenerator.generateId(), suite.getName(), "classMethod");
+    }
+
+    public void addFailure(Test test, AssertionFailedError assertionFailedError) {
+        addError(test, assertionFailedError);
+    }
+
+    public void endTest(Test test) {
+        long endTime = timeProvider.getCurrentTime();
+        TestDescriptorInternal testInternal;
+        synchronized (lock) {
+            testInternal = executing.remove(test);
+            assert testInternal != null;
+        }
+        resultProcessor.completed(testInternal.getId(), new TestCompleteEvent(endTime));
+    }
+
+    protected TestDescriptorInternal convert(Object id, Test test) {
+        if (test instanceof TestCase) {
+            TestCase testCase = (TestCase) test;
+            return new DefaultTestDescriptor(id, testCase.getClass().getName(), testCase.getName());
+        }
+        return new DefaultTestDescriptor(id, test.getClass().getName(), test.toString());
+    }
+}
+
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitXmlReportGenerator.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitXmlReportGenerator.java
new file mode 100644
index 0000000..32501c1
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitXmlReportGenerator.java
@@ -0,0 +1,165 @@
+/*
+ * 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.junit;
+
+import org.apache.tools.ant.taskdefs.optional.junit.XMLConstants;
+import org.apache.tools.ant.util.DOMElementWriter;
+import org.apache.tools.ant.util.DateUtils;
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.tasks.testing.TestDescriptorInternal;
+import org.gradle.api.internal.tasks.testing.TestOutputEvent;
+import org.gradle.api.internal.tasks.testing.results.StateTrackingTestResultProcessor;
+import org.gradle.util.UncheckedException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import java.io.*;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.EnumMap;
+import java.util.Map;
+
+public class JUnitXmlReportGenerator extends StateTrackingTestResultProcessor {
+    private final File testResultsDir;
+    private final DocumentBuilder documentBuilder;
+    private final String hostName;
+    private Document testSuiteReport;
+    private TestState testSuite;
+    private Element rootElement;
+    private Map<TestOutputEvent.Destination, StringBuilder> outputs
+            = new EnumMap<TestOutputEvent.Destination, StringBuilder>(TestOutputEvent.Destination.class);
+
+    public JUnitXmlReportGenerator(File testResultsDir) {
+        this.testResultsDir = testResultsDir;
+        try {
+            documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+        } catch (Exception e) {
+            throw UncheckedException.asUncheckedException(e);
+        }
+        hostName = getHostname();
+    }
+
+    @Override
+    public void output(Object testId, TestOutputEvent event) {
+        outputs.get(event.getDestination()).append(event.getMessage());
+    }
+
+    @Override
+    protected void started(TestState state) {
+        TestDescriptorInternal test = state.test;
+        if (test.getName().equals(test.getClassName())) {
+            testSuiteReport = documentBuilder.newDocument();
+            rootElement = testSuiteReport.createElement(XMLConstants.TESTSUITE);
+            testSuiteReport.appendChild(rootElement);
+            // Add an empty properties element for compatibility
+            rootElement.appendChild(testSuiteReport.createElement(XMLConstants.PROPERTIES));
+            outputs.put(TestOutputEvent.Destination.StdOut, new StringBuilder());
+            outputs.put(TestOutputEvent.Destination.StdErr, new StringBuilder());
+            testSuite = state;
+        }
+    }
+
+    @Override
+    protected void completed(TestState state) {
+        String testClassName = state.test.getClassName();
+        Element element;
+        if (!state.equals(testSuite)) {
+            element = testSuiteReport.createElement(XMLConstants.TESTCASE);
+            element.setAttribute(XMLConstants.ATTR_NAME, state.test.getName());
+            element.setAttribute(XMLConstants.ATTR_CLASSNAME, testClassName);
+            rootElement.appendChild(element);
+        } else {
+            element = rootElement;
+            rootElement.setAttribute(XMLConstants.ATTR_NAME, testClassName);
+            rootElement.setAttribute(XMLConstants.ATTR_TESTS, String.valueOf(state.testCount));
+            rootElement.setAttribute(XMLConstants.ATTR_FAILURES, String.valueOf(state.failedCount));
+            rootElement.setAttribute(XMLConstants.ATTR_ERRORS, "0");
+            rootElement.setAttribute(XMLConstants.TIMESTAMP, DateUtils.format(state.getStartTime(),
+                    DateUtils.ISO8601_DATETIME_PATTERN));
+            rootElement.setAttribute(XMLConstants.HOSTNAME, hostName);
+            Element stdoutElement = testSuiteReport.createElement(XMLConstants.SYSTEM_OUT);
+            stdoutElement.appendChild(testSuiteReport.createCDATASection(outputs.get(
+                    TestOutputEvent.Destination.StdOut).toString()));
+            rootElement.appendChild(stdoutElement);
+            Element stderrElement = testSuiteReport.createElement(XMLConstants.SYSTEM_ERR);
+            stderrElement.appendChild(testSuiteReport.createCDATASection(outputs.get(
+                    TestOutputEvent.Destination.StdErr).toString()));
+            rootElement.appendChild(stderrElement);
+        }
+
+        element.setAttribute(XMLConstants.ATTR_TIME, String.valueOf(state.getExecutionTime() / 1000.0));
+        if (state.failure != null) {
+            Element failure = testSuiteReport.createElement(XMLConstants.FAILURE);
+            element.appendChild(failure);
+            failure.setAttribute(XMLConstants.ATTR_MESSAGE, failureMessage(state));
+            failure.setAttribute(XMLConstants.ATTR_TYPE, state.failure.getClass().getName());
+            failure.appendChild(testSuiteReport.createTextNode(stackTrace(state)));
+        }
+
+        if (state.equals(testSuite)) {
+            File reportFile = new File(testResultsDir, "TEST-" + testClassName + ".xml");
+            try {
+                OutputStream outstr = new BufferedOutputStream(new FileOutputStream(reportFile));
+                try {
+                    new DOMElementWriter(true).write(rootElement, outstr);
+                } finally {
+                    outstr.close();
+                }
+            } catch (IOException e) {
+                throw new GradleException(String.format("Could not write test report file '%s'.", reportFile), e);
+            }
+
+            testSuite = null;
+            outputs.clear();
+        }
+    }
+
+    private String stackTrace(TestState state) {
+        try {
+            StringWriter stringWriter = new StringWriter();
+            PrintWriter writer = new PrintWriter(stringWriter);
+            state.failure.printStackTrace(writer);
+            writer.close();
+            return stringWriter.toString();
+        } catch (Throwable t) {
+            StringWriter stringWriter = new StringWriter();
+            PrintWriter writer = new PrintWriter(stringWriter);
+            t.printStackTrace(writer);
+            writer.close();
+            return stringWriter.toString();
+        }
+    }
+
+    private String failureMessage(TestState state) {
+        try {
+            return state.failure.toString();
+        } catch (Throwable t) {
+            return String.format("Could not determine failure message for exception of type %s: %s",
+                    state.failure.getClass().getName(), t);
+        }
+    }
+
+    private String getHostname() {
+        try {
+            return InetAddress.getLocalHost().getHostName();
+        } catch (UnknownHostException e) {
+            return "localhost";
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/MaxNParallelTestClassProcessor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/MaxNParallelTestClassProcessor.java
new file mode 100644
index 0000000..6696edf
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/MaxNParallelTestClassProcessor.java
@@ -0,0 +1,80 @@
+/*
+ * 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.processors;
+
+import org.gradle.api.internal.tasks.testing.TestClassProcessor;
+import org.gradle.api.internal.tasks.testing.TestClassProcessorFactory;
+import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
+import org.gradle.api.internal.tasks.testing.TestResultProcessor;
+import org.gradle.messaging.actor.Actor;
+import org.gradle.messaging.actor.ActorFactory;
+import org.gradle.messaging.concurrent.CompositeStoppable;
+import org.gradle.messaging.dispatch.DispatchException;
+import org.gradle.util.UncheckedException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages a set of parallel TestClassProcessors. Uses a simple round-robin algorithm to assign test classes to
+ * processors.
+ */
+public class MaxNParallelTestClassProcessor implements TestClassProcessor {
+    private final int maxProcessors;
+    private final TestClassProcessorFactory factory;
+    private final ActorFactory actorFactory;
+    private TestResultProcessor resultProcessor;
+    private int pos;
+    private List<TestClassProcessor> processors = new ArrayList<TestClassProcessor>();
+    private List<Actor> actors = new ArrayList<Actor>();
+    private Actor resultProcessorActor;
+
+    public MaxNParallelTestClassProcessor(int maxProcessors, TestClassProcessorFactory factory, ActorFactory actorFactory) {
+        this.maxProcessors = maxProcessors;
+        this.factory = factory;
+        this.actorFactory = actorFactory;
+    }
+
+    public void startProcessing(TestResultProcessor resultProcessor) {
+        resultProcessorActor = actorFactory.createActor(resultProcessor);
+        this.resultProcessor = resultProcessorActor.getProxy(TestResultProcessor.class);
+    }
+
+    public void processTestClass(TestClassRunInfo testClass) {
+        TestClassProcessor processor;
+        if (processors.size() < maxProcessors) {
+            processor = factory.create();
+            Actor actor = actorFactory.createActor(processor);
+            processor = actor.getProxy(TestClassProcessor.class);
+            actors.add(actor);
+            processors.add(processor);
+            processor.startProcessing(resultProcessor);
+        } else {
+            processor = processors.get(pos);
+            pos = (pos + 1) % processors.size();
+        }
+        processor.processTestClass(testClass);
+    }
+
+    public void stop() {
+        try {
+            new CompositeStoppable(processors).add(actors).add(resultProcessorActor).stop();
+        } catch (DispatchException e) {
+            throw UncheckedException.asUncheckedException(e.getCause());
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/RestartEveryNTestClassProcessor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/RestartEveryNTestClassProcessor.java
new file mode 100644
index 0000000..879ff91
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/RestartEveryNTestClassProcessor.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.api.internal.tasks.testing.processors;
+
+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.TestClassProcessorFactory;
+
+public class RestartEveryNTestClassProcessor implements TestClassProcessor {
+    private final TestClassProcessorFactory factory;
+    private final long restartEvery;
+    private long testCount;
+    private TestClassProcessor processor;
+    private TestResultProcessor resultProcessor;
+
+    public RestartEveryNTestClassProcessor(TestClassProcessorFactory factory, long restartEvery) {
+        this.factory = factory;
+        this.restartEvery = restartEvery;
+    }
+
+    public void startProcessing(TestResultProcessor resultProcessor) {
+        this.resultProcessor = resultProcessor;
+    }
+
+    public void processTestClass(TestClassRunInfo testClass) {
+        if (processor == null) {
+            processor = factory.create();
+            processor.startProcessing(resultProcessor);
+        }
+        processor.processTestClass(testClass);
+        testCount++;
+        if (testCount == restartEvery) {
+            endBatch();
+        }
+    }
+
+    public void stop() {
+        if (processor != null) {
+            endBatch();
+        }
+    }
+
+    private void endBatch() {
+        try {
+            processor.stop();
+        } finally {
+            processor = null;
+            testCount = 0;
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainAction.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainAction.java
new file mode 100644
index 0000000..ecfdd3e
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainAction.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.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;
+
+public class TestMainAction implements Runnable {
+    private final TestClassProcessor processor;
+    private final TestResultProcessor resultProcessor;
+    private final TimeProvider timeProvider;
+    private final Runnable detector;
+
+    public TestMainAction(Runnable detector, TestClassProcessor processor, TestResultProcessor resultProcessor, TimeProvider timeProvider) {
+        this.detector = detector;
+        this.processor = processor;
+        this.resultProcessor = new AttachParentTestResultProcessor(resultProcessor);
+        this.timeProvider = timeProvider;
+    }
+
+    public void run() {
+        RootTestSuiteDescriptor suite = new RootTestSuiteDescriptor();
+        resultProcessor.started(suite, new TestStartEvent(timeProvider.getCurrentTime()));
+        try {
+            processor.startProcessing(resultProcessor);
+            try {
+                detector.run();
+            } finally {
+                processor.stop();
+            }
+        } finally {
+            resultProcessor.completed(suite.getId(), new TestCompleteEvent(timeProvider.getCurrentTime()));
+        }
+    }
+
+    private static class RootTestSuiteDescriptor extends DefaultTestSuiteDescriptor {
+        public RootTestSuiteDescriptor() {
+            super("root", "");
+        }
+
+        @Override
+        public String toString() {
+            return "tests";
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/AttachParentTestResultProcessor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/AttachParentTestResultProcessor.java
new file mode 100644
index 0000000..b789372
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/AttachParentTestResultProcessor.java
@@ -0,0 +1,54 @@
+/*
+ * 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.*;
+
+public class AttachParentTestResultProcessor implements TestResultProcessor {
+    private Object rootId;
+    private final TestResultProcessor processor;
+
+    public AttachParentTestResultProcessor(TestResultProcessor processor) {
+        this.processor = processor;
+    }
+
+    public void started(TestDescriptorInternal test, TestStartEvent event) {
+        if (rootId == null) {
+            assert test.isComposite();
+            rootId = test.getId();
+        }
+        else if (event.getParentId() == null) {
+            event = event.withParentId(rootId);
+        }
+        processor.started(test, event);
+    }
+
+    public void failure(Object testId, Throwable result) {
+        processor.failure(testId, result);
+    }
+
+    public void output(Object testId, TestOutputEvent event) {
+        processor.output(testId, event);
+    }
+
+    public void completed(Object testId, TestCompleteEvent event) {
+        if (testId.equals(rootId)) {
+            rootId = null;
+        }
+        processor.completed(testId, event);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/DefaultTestResult.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/DefaultTestResult.java
new file mode 100644
index 0000000..b4abc9d
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/DefaultTestResult.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.api.internal.tasks.testing.results;
+
+import org.gradle.api.tasks.testing.TestResult;
+
+import java.io.Serializable;
+
+public class DefaultTestResult implements TestResult, Serializable {
+    private final Throwable error;
+    private final ResultType result;
+    private final long startTime;
+    private final long endTime;
+    private final long testCount;
+    private final long successfulCount;
+    private final long failedCount;
+
+    public DefaultTestResult(ResultType result, Throwable error, long startTime, long endTime, long testCount, long successfulCount, long failedCount) {
+        this.error = error;
+        this.result = result;
+        this.startTime = startTime;
+        this.endTime = endTime;
+        this.testCount = testCount;
+        this.successfulCount = successfulCount;
+        this.failedCount = failedCount;
+    }
+
+    public ResultType getResultType() {
+        return result;
+    }
+
+    public Throwable getException() {
+        if (result != ResultType.FAILURE) {
+            throw new IllegalStateException("No exception to return");
+        }
+        return error;
+    }
+
+    public long getStartTime() {
+        return startTime;
+    }
+
+    public long getEndTime() {
+        return endTime;
+    }
+
+    public long getTestCount() {
+        return testCount;
+    }
+
+    public long getSuccessfulTestCount() {
+        return successfulCount;
+    }
+
+    public long getSkippedTestCount() {
+        return testCount - successfulCount - failedCount;
+    }
+
+    public long getFailedTestCount() {
+        return failedCount;
+    }
+
+    @Override
+    public String toString() {
+        return result.toString();
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/LoggingResultProcessor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/LoggingResultProcessor.java
new file mode 100644
index 0000000..dedb1ee
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/LoggingResultProcessor.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.api.internal.tasks.testing.results;
+
+import org.gradle.api.internal.tasks.testing.*;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+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/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/StateTrackingTestResultProcessor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/StateTrackingTestResultProcessor.java
new file mode 100644
index 0000000..744c3d4
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/StateTrackingTestResultProcessor.java
@@ -0,0 +1,135 @@
+/*
+ * 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.tasks.testing.TestResult;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class StateTrackingTestResultProcessor implements TestResultProcessor {
+    private final Map<Object, TestState> executing = new HashMap<Object, TestState>();
+
+    public void started(TestDescriptorInternal test, TestStartEvent event) {
+        TestDescriptorInternal parent = null;
+        if (event.getParentId() != null) {
+            parent = executing.get(event.getParentId()).test;
+        }
+        TestState state = new TestState(new DecoratingTestDescriptor(test, parent), event);
+        TestState oldState = executing.put(test.getId(), state);
+        if (oldState != null) {
+            throw new IllegalArgumentException(String.format("Received a start event for %s with duplicate id '%s'.",
+                    test, test.getId()));
+        }
+
+        started(state);
+    }
+
+    public void completed(Object testId, TestCompleteEvent event) {
+        TestState testState = executing.remove(testId);
+        if (testState == null) {
+            throw new IllegalArgumentException(String.format(
+                    "Received a completed event for test with unknown id '%s'.", testId));
+        }
+
+        testState.completed(event);
+        completed(testState);
+    }
+
+    public void failure(Object testId, Throwable result) {
+        TestState testState = executing.get(testId);
+        if (testState == null) {
+            throw new IllegalArgumentException(String.format("Received a failure event for test with unknown id '%s'.",
+                    testId));
+        }
+        testState.failure = result;
+    }
+
+    public void output(Object testId, TestOutputEvent event) {
+        // Don't care
+    }
+
+    protected void started(TestState state) {
+    }
+
+    protected void completed(TestState state) {
+    }
+
+    public class TestState {
+        public final TestDescriptorInternal test;
+        final TestStartEvent startEvent;
+        public boolean failedChild;
+        public Throwable failure;
+        public long testCount;
+        public long successfulCount;
+        public long failedCount;
+        public TestResult.ResultType resultType;
+        TestCompleteEvent completeEvent;
+
+        public TestState(TestDescriptorInternal test, TestStartEvent startEvent) {
+            this.test = test;
+            this.startEvent = startEvent;
+        }
+
+        public boolean isFailed() {
+            return failedChild || failure != null;
+        }
+
+        public long getStartTime() {
+            return startEvent.getStartTime();
+        }
+
+        public long getEndTime() {
+            return completeEvent.getEndTime();
+        }
+
+        public long getExecutionTime() {
+            return completeEvent.getEndTime() - startEvent.getStartTime();
+        }
+
+        public void completed(TestCompleteEvent event) {
+            this.completeEvent = event;
+            resultType = isFailed() ? TestResult.ResultType.FAILURE
+                    : event.getResultType() != null ? event.getResultType() : TestResult.ResultType.SUCCESS;
+
+            if (!test.isComposite()) {
+                testCount = 1;
+                switch (resultType) {
+                    case SUCCESS:
+                        successfulCount = 1;
+                        break;
+                    case FAILURE:
+                        failedCount = 1;
+                        break;
+                }
+            }
+
+            if (startEvent.getParentId() != null) {
+                TestState parentState = executing.get(startEvent.getParentId());
+                if (parentState != null) {
+                    if (isFailed()) {
+                        parentState.failedChild = true;
+                    }
+                    parentState.testCount += testCount;
+                    parentState.successfulCount += successfulCount;
+                    parentState.failedCount += failedCount;
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestListenerAdapter.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestListenerAdapter.java
new file mode 100644
index 0000000..9fe2959
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestListenerAdapter.java
@@ -0,0 +1,51 @@
+/*
+ * 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.TestDescriptorInternal;
+import org.gradle.api.tasks.testing.TestListener;
+import org.gradle.api.tasks.testing.TestResult;
+
+public class TestListenerAdapter extends StateTrackingTestResultProcessor {
+    private final TestListener listener;
+
+    public TestListenerAdapter(TestListener listener) {
+        this.listener = listener;
+    }
+
+    @Override
+    protected void started(TestState state) {
+        TestDescriptorInternal test = state.test;
+        if (test.isComposite()) {
+            listener.beforeSuite(test);
+        } else {
+            listener.beforeTest(test);
+        }
+    }
+
+    @Override
+    protected void completed(TestState state) {
+        TestResult result = new DefaultTestResult(state.resultType, state.failure, state.getStartTime(),
+                state.getEndTime(), state.testCount, state.successfulCount, state.failedCount);
+        TestDescriptorInternal test = state.test;
+        if (test.isComposite()) {
+            listener.afterSuite(test, result);
+        } else {
+            listener.afterTest(test, result);
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestLogger.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestLogger.java
new file mode 100644
index 0000000..765b6b3
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestLogger.java
@@ -0,0 +1,75 @@
+/*
+ * 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;
+
+public class TestLogger implements TestListener {
+    private final ProgressLoggerFactory factory;
+    private ProgressLogger logger;
+    private long totalTests;
+    private long failedTests;
+
+    public TestLogger(ProgressLoggerFactory factory) {
+        this.factory = factory;
+    }
+
+    public void beforeTest(TestDescriptor testDescriptor) {
+    }
+
+    public void afterTest(TestDescriptor testDescriptor, TestResult result) {
+        totalTests += result.getTestCount();
+        failedTests += result.getFailedTestCount();
+        logger.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) {
+            logger = factory.start();
+        }
+    }
+
+    public void afterSuite(TestDescriptor suite, TestResult result) {
+        if (suite.getParent() == null) {
+            logger.completed(failedTests > 0 ? summary() : "");
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestSummaryListener.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestSummaryListener.java
new file mode 100644
index 0000000..6795b4c
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/results/TestSummaryListener.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.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/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGDetector.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGDetector.java
new file mode 100644
index 0000000..a832a3a
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGDetector.java
@@ -0,0 +1,75 @@
+/*
+ * 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.tasks.testing.testng;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.tasks.testing.detection.AbstractTestFrameworkDetector;
+import org.gradle.api.internal.tasks.testing.detection.TestClassVisitor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+/**
+ * @author Tom Eyckmans
+ */
+class TestNGDetector extends AbstractTestFrameworkDetector<TestNGTestClassDetecter> {
+    private static final Logger LOGGER = LoggerFactory.getLogger(TestNGDetector.class);
+
+    TestNGDetector(File testClassesDirectory, FileCollection testClasspath) {
+        super(testClassesDirectory, testClasspath);
+    }
+
+    protected TestNGTestClassDetecter createClassVisitor() {
+        return new TestNGTestClassDetecter(this);
+    }
+
+    /**
+     * Uses a TestClassVisitor to detect whether the class in the testClassFile is a test class.
+     * <p/>
+     * If the class is not a test, this function will go up the inheritance tree to check if a
+     * parent class is a test class. First the package of the parent class is checked, if it is a java.lang or groovy.lang
+     * the class can't be a test class, otherwise the parent class is scanned.
+     * <p/>
+     * When a parent class is a test class all the extending classes are marked as test classes.
+     *
+     * @param testClassFile
+     * @param superClass
+     * @return
+     */
+    protected boolean processTestClass(final File testClassFile, boolean superClass) {
+        final TestClassVisitor classVisitor = classVisitor(testClassFile);
+
+        boolean isTest = classVisitor.isTest();
+
+        if (!isTest) {
+            final String superClassName = classVisitor.getSuperClassName();
+
+            final File superClassFile = getSuperTestClassFile(superClassName);
+
+            if (superClassFile != null) {
+                isTest = processSuperClass(superClassFile);
+            } else {
+                LOGGER.debug("test-class-scan : failed to scan parent class {}, could not find the class file",
+                        superClassName);
+            }
+        }
+
+        publishTestClass(isTest, classVisitor, superClass);
+
+        return isTest;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassDetecter.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassDetecter.java
new file mode 100644
index 0000000..209cf82
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassDetecter.java
@@ -0,0 +1,118 @@
+/*
+ * 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.testng;
+
+import org.gradle.api.internal.tasks.testing.detection.TestClassVisitor;
+import org.gradle.api.internal.tasks.testing.detection.TestFrameworkDetector;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.commons.EmptyVisitor;
+
+/**
+ * @author Tom Eyckmans
+ */
+class TestNGTestClassDetecter extends TestClassVisitor {
+    private boolean isAbstract;
+    private String className;
+    private String superClassName;
+    private boolean test;
+
+    TestNGTestClassDetecter(final TestFrameworkDetector detector) {
+        super(detector);
+    }
+
+    /**
+     * Visits the header of the class.
+     *
+     * @param version the class version.
+     * @param access the class's access flags (see {@link org.objectweb.asm.Opcodes}). This parameter also indicates if
+     * the class is deprecated.
+     * @param name the internal name of the class (see {@link org.objectweb.asm.Type#getInternalName()
+     * getInternalName}).
+     * @param signature the signature of this class. May be <tt>null</tt> if the class is not a generic one, and does
+     * not extend or implement generic classes or interfaces.
+     * @param superName the internal of name of the super class (see {@link org.objectweb.asm.Type#getInternalName()
+     * getInternalName}). For interfaces, the super class is {@link Object}. May be <tt>null</tt>, but only for the
+     * {@link Object} class.
+     * @param interfaces the internal names of the class's interfaces (see {@link org.objectweb.asm.Type#getInternalName()
+     * getInternalName}). May be <tt>null</tt>.
+     */
+    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+        isAbstract = (access & Opcodes.ACC_ABSTRACT) != 0;
+
+        this.className = name;
+        this.superClassName = superName;
+    }
+
+    /**
+     * Visits an annotation of the class.
+     *
+     * @param desc the class descriptor of the annotation class.
+     * @param visible <tt>true</tt> if the annotation is visible at runtime.
+     * @return a visitor to visit the annotation values, or <tt>null</tt> if this visitor is not interested in visiting
+     *         this annotation.
+     */
+    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+        if ("Lorg/testng/annotations/Test;".equals(desc)) {
+            test = true;
+        }
+        return new EmptyVisitor();
+    }
+
+    /**
+     * Visits a method of the class. This method <i>must</i> return a new {@link org.objectweb.asm.MethodVisitor}
+     * instance (or <tt>null</tt>) each time it is called, i.e., it should not return a previously returned visitor.
+     *
+     * @param access the method's access flags (see {@link org.objectweb.asm.Opcodes}). This parameter also indicates if
+     * the method is synthetic and/or deprecated.
+     * @param name the method's name.
+     * @param desc the method's descriptor (see {@link org.objectweb.asm.Type Type}).
+     * @param signature the method's signature. May be <tt>null</tt> if the method parameters, return type and
+     * exceptions do not use generic types.
+     * @param exceptions the internal names of the method's exception classes (see {@link
+     * org.objectweb.asm.Type#getInternalName() getInternalName}). May be <tt>null</tt>.
+     * @return an object to visit the byte code of the method, or <tt>null</tt> if this class visitor is not interested
+     *         in visiting the code of this method.
+     */
+    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
+        if (!isAbstract && !test) {
+            return new TestNGTestMethodDetecter(this);
+        } else {
+            return new EmptyVisitor();
+        }
+    }
+
+    public String getClassName() {
+        return className;
+    }
+
+    public boolean isAbstract() {
+        return isAbstract;
+    }
+
+    public boolean isTest() {
+        return test;
+    }
+
+    void setTest(boolean test) {
+        this.test = test;
+    }
+
+    public String getSuperClassName() {
+        return superClassName;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessor.java
new file mode 100644
index 0000000..14330bf
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessor.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.api.internal.tasks.testing.testng;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.internal.tasks.testing.TestClassProcessor;
+import org.gradle.api.internal.tasks.testing.TestResultProcessor;
+import org.gradle.api.internal.tasks.testing.junit.CaptureTestOutputTestResultProcessor;
+import org.gradle.api.tasks.testing.testng.TestNGOptions;
+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.testng.TestNG;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class TestNGTestClassProcessor implements TestClassProcessor {
+    private final List<Class<?>> testClasses = new ArrayList<Class<?>>();
+    private final File testReportDir;
+    private final TestNGOptions options;
+    private final List<File> suiteFiles;
+    private final IdGenerator<?> idGenerator;
+    private final StandardOutputRedirector outputRedirector;
+    private TestNGTestResultProcessorAdapter testResultProcessor;
+    private ClassLoader applicationClassLoader;
+
+    public TestNGTestClassProcessor(File testReportDir, TestNGOptions options, List<File> suiteFiles, IdGenerator<?> idGenerator, StandardOutputRedirector outputRedirector) {
+        this.testReportDir = testReportDir;
+        this.options = options;
+        this.suiteFiles = suiteFiles;
+        this.idGenerator = idGenerator;
+        this.outputRedirector = outputRedirector;
+    }
+
+    public void startProcessing(TestResultProcessor resultProcessor) {
+        testResultProcessor = new TestNGTestResultProcessorAdapter(new CaptureTestOutputTestResultProcessor(resultProcessor, outputRedirector), idGenerator);
+        applicationClassLoader = Thread.currentThread().getContextClassLoader();
+    }
+
+    public void processTestClass(TestClassRunInfo testClass) {
+        try {
+            testClasses.add(applicationClassLoader.loadClass(testClass.getTestClassName()));
+        } catch (Throwable e) {
+            throw new GradleException(String.format("Could not load test class '%s'.", testClass.getTestClassName()), e);
+        }
+    }
+
+    public void stop() {
+        TestNG testNg = new TestNG();
+        testNg.setOutputDirectory(testReportDir.getAbsolutePath());
+        testNg.setDefaultSuiteName(options.getSuiteName());
+        testNg.setDefaultTestName(options.getTestName());
+        testNg.setAnnotations(options.getAnnotations());
+        if (options.getJavadocAnnotations()) {
+            testNg.setSourcePath(GUtil.join(options.getTestResources(), File.pathSeparator));
+        }
+        testNg.setUseDefaultListeners(options.getUseDefaultListeners());
+        testNg.addListener(testResultProcessor);
+        testNg.setVerbose(0);
+        testNg.setGroups(GUtil.join(options.getIncludeGroups(), ","));
+        testNg.setExcludedGroups(GUtil.join(options.getExcludeGroups(), ","));
+        for (String listenerClass : options.getListeners()) {
+            try {
+                testNg.addListener(applicationClassLoader.loadClass(listenerClass).newInstance());
+            } catch (Throwable e) {
+                throw new GradleException(String.format("Could not add a test listener with class '%s'.", listenerClass), e);
+            }
+        }
+
+        if (!suiteFiles.isEmpty()) {
+            testNg.setTestSuites(GFileUtils.toPaths(suiteFiles));
+        } else {
+            Class[] classes = testClasses.toArray(new Class[testClasses.size()]);
+            testNg.setTestClasses(classes);
+        }
+
+        testNg.run();
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFramework.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFramework.java
new file mode 100644
index 0000000..b8dd7cb
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFramework.java
@@ -0,0 +1,98 @@
+/*
+ * 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.testng;
+
+import org.gradle.api.Action;
+import org.gradle.api.JavaVersion;
+import org.gradle.api.internal.project.ServiceRegistry;
+import org.gradle.api.internal.tasks.testing.TestClassProcessor;
+import org.gradle.api.internal.tasks.testing.TestFramework;
+import org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory;
+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;
+import java.util.List;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class TestNGTestFramework implements TestFramework {
+
+    private TestNGOptions options;
+    private TestNGDetector detector;
+    private final Test testTask;
+
+    public TestNGTestFramework(Test testTask) {
+        this.testTask = testTask;
+        options = new TestNGOptions(testTask.getProject().getProjectDir());
+        options.setAnnotationsOnSourceCompatibility(JavaVersion.toVersion(testTask.getProject().property(
+                "sourceCompatibility")));
+        detector = new TestNGDetector(testTask.getTestClassesDir(), testTask.getClasspath());
+    }
+
+    public WorkerTestClassProcessorFactory getProcessorFactory() {
+        options.setTestResources(testTask.getTestSrcDirs());
+        List<File> suiteFiles = options.getSuites(testTask.getTemporaryDir());
+        return new TestClassProcessorFactoryImpl(testTask.getTestReportDir(), options, suiteFiles);
+    }
+
+    public Action<WorkerProcessBuilder> getWorkerConfigurationAction() {
+        return new Action<WorkerProcessBuilder>() {
+            public void execute(WorkerProcessBuilder workerProcessBuilder) {
+                workerProcessBuilder.sharedPackages("org.testng");
+            }
+        };
+    }
+
+    public void report() {
+        // TODO currently reports are always generated because the antTestNGExecute task uses the
+        // default listeners and these generate reports by default.
+    }
+
+    public TestNGOptions getOptions() {
+        return options;
+    }
+
+    void setOptions(TestNGOptions options) {
+        this.options = options;
+    }
+
+    public TestNGDetector getDetector() {
+        return detector;
+    }
+
+    private static class TestClassProcessorFactoryImpl implements WorkerTestClassProcessorFactory, Serializable {
+        private final File testReportDir;
+        private final TestNGOptions options;
+        private final List<File> suiteFiles;
+
+        public TestClassProcessorFactoryImpl(File testReportDir, TestNGOptions options, List<File> suiteFiles) {
+            this.testReportDir = testReportDir;
+            this.options = options;
+            this.suiteFiles = suiteFiles;
+        }
+
+        public TestClassProcessor create(ServiceRegistry serviceRegistry) {
+            return new TestNGTestClassProcessor(testReportDir, options, suiteFiles, serviceRegistry.get(IdGenerator.class), new JULRedirector());
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestMethodDetecter.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestMethodDetecter.java
new file mode 100644
index 0000000..46b02a8
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestMethodDetecter.java
@@ -0,0 +1,56 @@
+/*
+ * 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.testng;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.commons.EmptyVisitor;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Tom Eyckmans
+ */
+class TestNGTestMethodDetecter extends EmptyVisitor {
+    private final TestNGTestClassDetecter testClassDetecter;
+    private final Set<String> testMethodAnnotations = new HashSet<String>();
+
+    public TestNGTestMethodDetecter(TestNGTestClassDetecter testClassDetecter) {
+        this.testClassDetecter = testClassDetecter;
+        testMethodAnnotations.add("Lorg/testng/annotations/Test;");
+        testMethodAnnotations.add("Lorg/testng/annotations/BeforeSuite;");
+        testMethodAnnotations.add("Lorg/testng/annotations/AfterSuite;");
+        testMethodAnnotations.add("Lorg/testng/annotations/BeforeTest;");
+        testMethodAnnotations.add("Lorg/testng/annotations/AfterTest;");
+        testMethodAnnotations.add("Lorg/testng/annotations/BeforeGroups;");
+        testMethodAnnotations.add("Lorg/testng/annotations/AfterGroups;");
+    }
+
+    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+        if (testMethodAnnotations.contains(desc)) {
+            testClassDetecter.setTest(true);
+        }
+        return new EmptyVisitor();
+    }
+
+    public AnnotationVisitor visitAnnotationDefault() {
+        return new EmptyVisitor();
+    }
+
+    public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
+        return new EmptyVisitor();
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestResultProcessorAdapter.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestResultProcessorAdapter.java
new file mode 100644
index 0000000..389d2b6
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestResultProcessorAdapter.java
@@ -0,0 +1,121 @@
+/*
+ * 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.testng;
+
+import org.gradle.api.internal.tasks.testing.*;
+import org.gradle.api.tasks.testing.TestResult;
+import org.gradle.util.IdGenerator;
+import org.testng.ITestContext;
+import org.testng.ITestListener;
+import org.testng.ITestNGMethod;
+import org.testng.ITestResult;
+import org.testng.internal.IConfigurationListener;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class TestNGTestResultProcessorAdapter implements ITestListener, IConfigurationListener {
+    private final TestResultProcessor resultProcessor;
+    private final IdGenerator<?> idGenerator;
+    private final Object lock = new Object();
+    private Map<String, Object> suites = new HashMap<String, Object>();
+    private Map<String, Object> tests = new HashMap<String, Object>();
+    private Map<ITestNGMethod, Object> testMethodToSuiteMapping = new HashMap<ITestNGMethod, Object>();
+
+    public TestNGTestResultProcessorAdapter(TestResultProcessor resultProcessor, IdGenerator<?> idGenerator) {
+        this.resultProcessor = resultProcessor;
+        this.idGenerator = idGenerator;
+    }
+
+    public void onStart(ITestContext iTestContext) {
+        TestDescriptorInternal testInternal;
+        synchronized (lock) {
+            testInternal = new DefaultTestSuiteDescriptor(idGenerator.generateId(), iTestContext.getName());
+            suites.put(testInternal.getName(), testInternal.getId());
+            for (ITestNGMethod method : iTestContext.getAllTestMethods()) {
+                testMethodToSuiteMapping.put(method, testInternal.getId());
+            }
+        }
+        resultProcessor.started(testInternal, new TestStartEvent(iTestContext.getStartDate().getTime()));
+    }
+
+    public void onFinish(ITestContext iTestContext) {
+        Object id;
+        synchronized (lock) {
+            id = suites.remove(iTestContext.getName());
+            for (ITestNGMethod method : iTestContext.getAllTestMethods()) {
+                testMethodToSuiteMapping.remove(method);
+            }
+        }
+        resultProcessor.completed(id, new TestCompleteEvent(iTestContext.getEndDate().getTime()));
+    }
+
+    public void onTestStart(ITestResult iTestResult) {
+        TestDescriptorInternal testInternal;
+        Object parentId;
+        synchronized (lock) {
+            testInternal = new DefaultTestMethodDescriptor(idGenerator.generateId(), iTestResult.getTestClass().getName(), iTestResult.getName());
+            Object oldTestId = tests.put(testInternal.getName(), testInternal.getId());
+            assert oldTestId == null;
+            parentId = testMethodToSuiteMapping.get(iTestResult.getMethod());
+            assert parentId != null;
+        }
+        resultProcessor.started(testInternal, new TestStartEvent(iTestResult.getStartMillis(), parentId));
+    }
+
+    public void onTestSuccess(ITestResult iTestResult) {
+        onTestFinished(iTestResult, TestResult.ResultType.SUCCESS);
+    }
+
+    public void onTestFailure(ITestResult iTestResult) {
+        onTestFinished(iTestResult, TestResult.ResultType.FAILURE);
+    }
+
+    public void onTestSkipped(ITestResult iTestResult) {
+        onTestFinished(iTestResult, TestResult.ResultType.SKIPPED);
+    }
+
+    public void onTestFailedButWithinSuccessPercentage(ITestResult iTestResult) {
+        onTestFinished(iTestResult, TestResult.ResultType.SUCCESS);
+    }
+
+    private void onTestFinished(ITestResult iTestResult, TestResult.ResultType resultType) {
+        Object testId;
+        synchronized (lock) {
+            testId = tests.remove(iTestResult.getName());
+        }
+        if (resultType == TestResult.ResultType.FAILURE) {
+            resultProcessor.failure(testId, iTestResult.getThrowable());
+        }
+        resultProcessor.completed(testId, new TestCompleteEvent(iTestResult.getEndMillis(), resultType));
+    }
+
+    public void onConfigurationSuccess(ITestResult testResult) {
+    }
+
+    public void onConfigurationSkip(ITestResult testResult) {
+    }
+
+    public void onConfigurationFailure(ITestResult testResult) {
+        // Synthesise a test for the broken configuration method
+        TestDescriptorInternal test = new DefaultTestMethodDescriptor(idGenerator.generateId(),
+                testResult.getMethod().getTestClass().getName(), testResult.getMethod().getMethodName());
+        resultProcessor.started(test, new TestStartEvent(testResult.getStartMillis()));
+        resultProcessor.failure(test.getId(), testResult.getThrowable());
+        resultProcessor.completed(test.getId(), new TestCompleteEvent(testResult.getEndMillis(), TestResult.ResultType.FAILURE));
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessor.java
new file mode 100644
index 0000000..13a52e8
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessor.java
@@ -0,0 +1,80 @@
+/*
+ * 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.worker;
+
+import org.gradle.api.Action;
+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.process.JavaForkOptions;
+import org.gradle.process.internal.WorkerProcess;
+import org.gradle.process.internal.WorkerProcessBuilder;
+import org.gradle.process.internal.WorkerProcessFactory;
+
+import java.io.File;
+
+public class ForkingTestClassProcessor implements TestClassProcessor {
+    private final WorkerProcessFactory workerFactory;
+    private final WorkerTestClassProcessorFactory processorFactory;
+    private final JavaForkOptions options;
+    private final Iterable<File> classPath;
+    private final Action<WorkerProcessBuilder> buildConfigAction;
+    private RemoteTestClassProcessor remoteProcessor;
+    private WorkerProcess workerProcess;
+    private TestResultProcessor resultProcessor;
+
+    public ForkingTestClassProcessor(WorkerProcessFactory workerFactory, WorkerTestClassProcessorFactory processorFactory, JavaForkOptions options, Iterable<File> classPath, Action<WorkerProcessBuilder> buildConfigAction) {
+        this.workerFactory = workerFactory;
+        this.processorFactory = processorFactory;
+        this.options = options;
+        this.classPath = classPath;
+        this.buildConfigAction = buildConfigAction;
+    }
+
+    public void startProcessing(TestResultProcessor resultProcessor) {
+        this.resultProcessor = resultProcessor;
+    }
+
+    public void processTestClass(TestClassRunInfo testClass) {
+        if (remoteProcessor == null) {
+            WorkerProcessBuilder builder = workerFactory.newProcess();
+            builder.applicationClasspath(classPath);
+            builder.setLoadApplicationInSystemClassLoader(true);
+            builder.worker(new TestWorker(processorFactory));
+            options.copyTo(builder.getJavaCommand());
+            buildConfigAction.execute(builder);
+            
+            workerProcess = builder.build();
+            workerProcess.start();
+
+            workerProcess.getConnection().addIncoming(TestResultProcessor.class, resultProcessor);
+            remoteProcessor = workerProcess.getConnection().addOutgoing(RemoteTestClassProcessor.class);
+
+            remoteProcessor.startProcessing();
+        }
+
+        remoteProcessor.processTestClass(testClass);
+    }
+
+    public void stop() {
+        if (remoteProcessor != null) {
+            remoteProcessor.stop();
+            workerProcess.waitForStop();
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/RemoteTestClassProcessor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/RemoteTestClassProcessor.java
new file mode 100644
index 0000000..b44d0ab
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/RemoteTestClassProcessor.java
@@ -0,0 +1,39 @@
+/*
+ * 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.worker;
+
+import org.gradle.api.internal.tasks.testing.TestClassRunInfo;
+
+/**
+ * @see org.gradle.api.internal.tasks.testing.TestClassProcessor
+ */
+public interface RemoteTestClassProcessor {
+    /**
+     * Does not block.
+     */
+    void startProcessing();
+
+    /**
+     * Does not block.
+     */
+    void processTestClass(TestClassRunInfo testClass);
+
+    /**
+     * Does not block.
+     */
+    void stop();
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorker.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorker.java
new file mode 100644
index 0000000..a7bd8a9
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorker.java
@@ -0,0 +1,96 @@
+/*
+ * 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.worker;
+
+import org.gradle.api.Action;
+import org.gradle.api.internal.project.DefaultServiceRegistry;
+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.listener.ContextClassLoaderProxy;
+import org.gradle.messaging.remote.ObjectConnection;
+import org.gradle.process.internal.WorkerProcessContext;
+import org.gradle.util.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Serializable;
+import java.util.concurrent.CountDownLatch;
+
+public class TestWorker implements Action<WorkerProcessContext>, RemoteTestClassProcessor, Serializable {
+    private static final Logger LOGGER = LoggerFactory.getLogger(TestWorker.class);
+    public static final String WORKER_ID_SYS_PROPERTY = "org.gradle.test.worker";
+    private final WorkerTestClassProcessorFactory factory;
+    private CountDownLatch completed;
+    private TestClassProcessor processor;
+    private TestResultProcessor resultProcessor;
+
+    public TestWorker(WorkerTestClassProcessorFactory factory) {
+        this.factory = factory;
+    }
+
+    public void execute(WorkerProcessContext workerProcessContext) {
+        LOGGER.info("{} executing tests.", workerProcessContext.getDisplayName());
+
+        completed = new CountDownLatch(1);
+
+        System.setProperty(WORKER_ID_SYS_PROPERTY, workerProcessContext.getWorkerId().toString());
+        
+        ObjectConnection serverConnection = workerProcessContext.getServerConnection();
+
+        IdGenerator<Object> idGenerator = new CompositeIdGenerator(workerProcessContext.getWorkerId(),
+                new LongIdGenerator());
+
+        DefaultServiceRegistry testServices = new DefaultServiceRegistry();
+        testServices.add(IdGenerator.class, idGenerator);
+        TestClassProcessor targetProcessor = factory.create(testServices);
+
+        targetProcessor = new WorkerTestClassProcessor(targetProcessor, idGenerator.generateId(),
+                workerProcessContext.getDisplayName(), new TrueTimeProvider());
+        ContextClassLoaderProxy<TestClassProcessor> proxy = new ContextClassLoaderProxy<TestClassProcessor>(
+                TestClassProcessor.class, targetProcessor, workerProcessContext.getApplicationClassLoader());
+        processor = proxy.getSource();
+
+        this.resultProcessor = serverConnection.addOutgoing(TestResultProcessor.class);
+
+        serverConnection.addIncoming(RemoteTestClassProcessor.class, this);
+
+        try {
+            completed.await();
+        } catch (InterruptedException e) {
+            throw new UncheckedException(e);
+        }
+        LOGGER.info("{} finished executing tests.", workerProcessContext.getDisplayName());
+    }
+
+    public void startProcessing() {
+        processor.startProcessing(resultProcessor);
+    }
+
+    public void processTestClass(TestClassRunInfo testClass) {
+        processor.processTestClass(testClass);
+    }
+
+    public void stop() {
+        try {
+            processor.stop();
+        } finally {
+            completed.countDown();
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/WorkerTestClassProcessor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/WorkerTestClassProcessor.java
new file mode 100644
index 0000000..b138b6d
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/internal/tasks/testing/worker/WorkerTestClassProcessor.java
@@ -0,0 +1,39 @@
+/*
+ * 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.worker;
+
+import org.gradle.api.internal.tasks.testing.*;
+import org.gradle.util.TimeProvider;
+
+public class WorkerTestClassProcessor extends SuiteTestClassProcessor {
+
+    public WorkerTestClassProcessor(TestClassProcessor processor, Object workerSuiteId, String workerDisplayName,
+                                    TimeProvider timeProvider) {
+        super(new WorkerTestSuiteDescriptor(workerSuiteId, workerDisplayName), processor, timeProvider);
+    }
+
+    private static class WorkerTestSuiteDescriptor extends DefaultTestSuiteDescriptor {
+        private WorkerTestSuiteDescriptor(Object id, String name) {
+            super(id, name);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("test process '%s'", getName());
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/Attributes.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/Attributes.java
new file mode 100644
index 0000000..05b30a3
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/Attributes.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.api.java.archives;
+
+import java.util.Map;
+
+/**
+ * Represent the attributes of a manifest section.
+ *
+ * @author Hans Dockter
+ */
+public interface Attributes extends Map<String, Object> {
+    
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/Manifest.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/Manifest.java
new file mode 100644
index 0000000..8b59148
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/Manifest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.java.archives;
+
+import groovy.lang.Closure;
+
+import java.io.Writer;
+import java.util.Map;
+
+public interface Manifest {
+    /**
+     * Returns the main attributes of the manifest.
+     */
+    Attributes getAttributes();
+
+    /**
+     * Returns the sections of the manifest (excluding the main section).
+     *
+     * @return A map with the sections, where the key represents the section name and value the section attributes.
+     */
+    Map<String, Attributes> getSections();
+
+    /**
+     * Adds content to the main attributes of the manifest.
+     *
+     * @param attributes The values to add to the main attributes. The values can be any object. For evaluating the value objects
+     * their {@link Object#toString()} method is used. This is done lazily either before writing or when {@link #getEffectiveManifest()}
+     * is called. 
+     *
+     * @return this
+     * @throws ManifestException If a key is invalid according to the manifest spec or if a key or value is null.
+     */
+    Manifest attributes(Map<String, ?> attributes) throws ManifestException;
+
+    /**
+     * 
+     * @param attributes The values to add to the section. The values can be any object. For evaluating the value objects
+     * their {@link Object#toString()} method is used. This is done lazily either before writing or when {@link #getEffectiveManifest()}
+     * is called.
+     * @param sectionName The name of the section
+     *
+     * @return this
+     * @throws ManifestException If a key is invalid according to the manifest spec or if a key or value is null.
+     */
+    Manifest attributes(Map<String, ?> attributes, String sectionName) throws ManifestException;
+
+    /**
+     * Returns a new manifest instance where all the attribute values are expanded (e.g. there toString method is called).
+     * The returned manifest also contains all the attributes of the to be merged manifests specified in {@link #from(Object...)}.
+     */
+    Manifest getEffectiveManifest();
+
+    /**
+     * Writes the manifest into a writer.
+     *
+     * @param writer The writer to write the manifest to
+     * @return this
+     */
+    Manifest writeTo(Writer writer);
+
+    /**
+     * Writes the manifest into a file. The path's are resolved as defined by {@link org.gradle.api.Project#files(Object...)}
+     *
+     * @param path The path of the file to write the manifest into.
+     * @return this
+     */
+    Manifest writeTo(Object path);
+
+    /**
+     * Specifies other manifests to be merged into this manifest. A merge path can either be another instance of
+     * {@link org.gradle.api.java.archives.Manifest} or a file path as interpreted by {@link org.gradle.api.Project#files(Object...)}.
+     *
+     * The merge is not happening instantaneously. It happens either before writing or when {@link #getEffectiveManifest()}
+     * is called.
+     *
+     * @param mergePath
+     * @return this
+     */
+    Manifest from(Object... mergePath);
+
+    /**
+     * Specifies other manifests to be merged into this manifest. A merge path is interpreted as described in
+     * {@link #from(Object...)}.
+     *
+     * The merge is not happening instantaneously. It happens either before writing or when {@link #getEffectiveManifest()}
+     * is called.
+     *
+     * The closure configures the underlying {@link org.gradle.api.java.archives.ManifestMergeSpec}.
+     *
+     * @param mergePath
+     * @param closure
+     * @return this
+     */
+    Manifest from(Object mergePath, Closure closure);
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/ManifestException.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/ManifestException.java
new file mode 100644
index 0000000..0cf3cb6
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/ManifestException.java
@@ -0,0 +1,34 @@
+/*
+ * 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.java.archives;
+
+import org.gradle.api.GradleException;
+
+/**
+ * Is thrown in the case an operation is applied against a {@link org.gradle.api.java.archives.Manifest} that violates
+ * the Manifest specification.
+ * 
+ * @author Hans Dockter
+ */
+public class ManifestException extends GradleException {
+    public ManifestException(String message) {
+        super(message);
+    }
+
+    public ManifestException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/ManifestMergeDetails.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/ManifestMergeDetails.java
new file mode 100644
index 0000000..c994700
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/ManifestMergeDetails.java
@@ -0,0 +1,56 @@
+/*
+ * 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.java.archives;
+
+public interface ManifestMergeDetails {
+    /**
+     * Returns the section this merge details belongs to.
+     */
+    String getSection();
+
+    /**
+     * Returns the key that is to be merged
+     */
+    String getKey();
+
+    /**
+     * Returns the value for the key of the manifest that is the target of the merge.
+     */
+    String getBaseValue();
+
+    /**
+     * Returns the value for the key of the manifest that is the source for the merge.
+     */
+    String getMergeValue();
+
+    /**
+     * Returns the value for the key of the manifest after the merge takes place. By default this is the value
+     * of the source for the merge.
+     */
+    String getValue();
+
+    /**
+     * Set's the value for the key of the manifest after the merge takes place.
+     *
+     * @param value
+     */
+    void setValue(String value);
+
+    /**
+     * Excludes this key from being in the manifest after the merge.
+     */
+    void exclude();
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/ManifestMergeSpec.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/ManifestMergeSpec.java
new file mode 100644
index 0000000..8165b07
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/ManifestMergeSpec.java
@@ -0,0 +1,54 @@
+/*
+ * 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.java.archives;
+
+import groovy.lang.Closure;
+import org.gradle.api.Action;
+
+public interface ManifestMergeSpec {
+    /**
+     * Adds a merge path to a manifest that should be merged into the base manifest. A merge path can be either another
+     * {@link org.gradle.api.java.archives.Manifest} or a path that is evaluated as for
+     * {@link org.gradle.api.Project#files(Object...)} . If multiple merge paths are specified, the manifest are merged
+     * in the order in which they are added.
+     * 
+     * @param mergePaths The paths of manifests to be merged
+     * @return this
+     */
+    ManifestMergeSpec from(Object... mergePaths);
+
+    /**
+     * Adds an action to be applied to each key-value tuple in a merge operation. If multiple merge paths are specified,
+     * the action is called for each key-value tuple of each merge operation. The given action is called with a
+     * {@link org.gradle.api.java.archives.ManifestMergeDetails} as its parameter. Actions are executed
+     * in the order added.
+     *
+     * @param mergeAction A merge action to be executed.
+     * @return this
+     */
+    ManifestMergeSpec eachEntry(Action<? super ManifestMergeDetails> mergeAction);
+
+    /**
+     * Adds an action to be applied to each key-value tuple in a merge operation. If multiple merge paths are specified,
+     * the action is called for each key-value tuple of each merge operation. The given closure is called with a
+     * {@link org.gradle.api.java.archives.ManifestMergeDetails} as its parameter. Actions are executed
+     * in the order added.
+     *
+     * @param mergeAction The action to execute.
+     * @return this
+     */
+    ManifestMergeSpec eachEntry(Closure mergeAction);
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultAttributes.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultAttributes.java
new file mode 100644
index 0000000..631a7aa
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultAttributes.java
@@ -0,0 +1,100 @@
+/*
+ * 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.java.archives.internal;
+
+import org.gradle.api.java.archives.Attributes;
+import org.gradle.api.java.archives.ManifestException;
+
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultAttributes implements Attributes {
+    protected Map<String, Object> attributes = new LinkedHashMap<String, Object>();
+
+    public int size() {
+        return attributes.size();
+    }
+
+    public boolean isEmpty() {
+        return attributes.isEmpty();
+    }
+
+    public boolean containsKey(Object key) {
+        return attributes.containsKey(key);
+    }
+
+    public boolean containsValue(Object value) {
+        return attributes.containsValue(value);
+    }
+
+    public Object get(Object key) {
+        return attributes.get(key);
+    }
+
+    public Object put(String key, Object value) {
+        if (key == null) {
+            throw new ManifestException("The key of a manifest attribute must not be null.");
+        }
+        if (value == null) {
+            throw new ManifestException("The value of a manifest attribute must not be null.");
+        }
+        try {
+            new java.util.jar.Attributes.Name(key);
+        } catch(IllegalArgumentException e) {
+            throw new ManifestException(String.format("The Key=%s violates the Manifest spec!", key));   
+        }
+        return attributes.put(key, value);
+    }
+
+    public Object remove(Object key) {
+        return attributes.remove(key);
+    }
+
+    public void putAll(Map<? extends String, ? extends Object> m) {
+        for (Entry<? extends String, ? extends Object> entry : m.entrySet()) {
+            put(entry.getKey(), entry.getValue());
+        }
+    }
+
+    public void clear() {
+        attributes.clear();
+    }
+
+    public Set<String> keySet() {
+        return attributes.keySet();
+    }
+
+    public Collection<Object> values() {
+        return attributes.values();
+    }
+
+    public Set<Entry<String, Object>> entrySet() {
+        return attributes.entrySet();
+    }
+
+    public boolean equals(Object o) {
+        return attributes.equals(o);
+    }
+
+    public int hashCode() {
+        return attributes.hashCode();
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifest.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifest.java
new file mode 100644
index 0000000..e12494c
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifest.java
@@ -0,0 +1,238 @@
+/*
+ * 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.api.java.archives.internal;
+
+import groovy.lang.Closure;
+import org.apache.tools.ant.taskdefs.Manifest;
+import org.apache.tools.ant.taskdefs.Manifest.Attribute;
+import org.apache.tools.ant.taskdefs.Manifest.Section;
+import org.apache.tools.ant.taskdefs.ManifestException;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.java.archives.Attributes;
+import org.gradle.api.java.archives.ManifestMergeSpec;
+import org.gradle.util.ConfigureUtil;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultManifest implements org.gradle.api.java.archives.Manifest {
+    private List<ManifestMergeSpec> manifestMergeSpecs = new ArrayList<ManifestMergeSpec>();
+
+    private DefaultAttributes attributes = new DefaultAttributes();
+
+    private Map<String, Attributes> sections = new LinkedHashMap<String, Attributes>();
+
+    private FileResolver fileResolver;
+
+    public DefaultManifest(FileResolver fileResolver) {
+        this.fileResolver = fileResolver;
+        init();
+    }
+
+    public DefaultManifest(Object manifestPath, FileResolver fileResolver) {
+        this.fileResolver = fileResolver;
+        read(manifestPath);
+    }
+
+    private void init() {
+        getAttributes().put("Manifest-Version", "1.0");
+    }
+
+    public DefaultManifest mainAttributes(Map<String, ?> attributes) {
+        return attributes(attributes);
+    }
+
+    public DefaultManifest attributes(Map<String, ?> attributes) {
+        getAttributes().putAll(attributes);
+        return this;
+    }
+
+    public DefaultManifest attributes(Map<String, ?> attributes, String sectionName) {
+        if (!sections.containsKey(sectionName)) {
+            sections.put(sectionName, new DefaultAttributes());
+        }
+        sections.get(sectionName).putAll(attributes);
+        return this;
+    }
+
+    public Attributes getAttributes() {
+        return attributes;
+    }
+
+    public Map<String, Attributes> getSections() {
+        return sections;
+    }
+
+    public DefaultManifest clear() {
+        attributes.clear();
+        sections.clear();
+        manifestMergeSpecs.clear();
+        init();
+        return this;
+    }
+
+    private Manifest generateAntManifest() {
+        Manifest antManifest = new Manifest();
+        addAttributesToAnt(antManifest);
+        addSectionAttributesToAnt(antManifest);
+        return antManifest;
+    }
+
+    private void addAttributesToAnt(Manifest antManifest) {
+        for (Map.Entry<String, Object> entry : attributes.entrySet()) {
+            try {
+                antManifest.addConfiguredAttribute(new Attribute(entry.getKey().toString(), entry.getValue().toString()));
+            } catch (ManifestException e) {
+                throw new org.gradle.api.java.archives.ManifestException(e.getMessage(), e);
+            }
+        }
+    }
+
+    private void addSectionAttributesToAnt(Manifest antManifest) {
+        for (Map.Entry<String, Attributes> entry : sections.entrySet()) {
+            Section section = new Section();
+            section.setName(entry.getKey());
+            try {
+                antManifest.addConfiguredSection(section);
+                for (Map.Entry<String, Object> attributeEntry : entry.getValue().entrySet()) {
+                    section.addConfiguredAttribute(new Attribute(attributeEntry.getKey().toString(), attributeEntry.getValue().toString()));
+                }
+            } catch (ManifestException e) {
+                throw new org.gradle.api.java.archives.ManifestException(e.getMessage(), e);
+            }
+        }
+    }
+
+    public DefaultManifest from(Object... mergePaths) {
+        from(mergePaths, null);
+        return this;
+    }
+
+    public DefaultManifest from(Object mergePaths, Closure closure) {
+        DefaultManifestMergeSpec mergeSpec = new DefaultManifestMergeSpec();
+        mergeSpec.from(mergePaths);
+        manifestMergeSpecs.add(mergeSpec);
+        ConfigureUtil.configure(closure, mergeSpec);
+        return this;
+    }
+
+    public DefaultManifest getEffectiveManifest() {
+        return getEffectiveManifestInternal(this);
+    }
+
+    protected DefaultManifest getEffectiveManifestInternal(DefaultManifest baseManifest) {
+        DefaultManifest resultManifest = baseManifest;
+        for (ManifestMergeSpec manifestMergeSpec : manifestMergeSpecs) {
+            resultManifest = ((DefaultManifestMergeSpec) manifestMergeSpec).merge(resultManifest, fileResolver);
+        }
+        return resultManifest;
+    }
+
+    public DefaultManifest writeTo(Writer writer) {
+        PrintWriter printWriter = new PrintWriter(writer);
+        try {
+            getEffectiveManifest().generateAntManifest().write(printWriter);
+            printWriter.flush();
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        return this;
+    }
+
+    public org.gradle.api.java.archives.Manifest writeTo(Object path) {
+        try {
+            File file = fileResolver.resolve(path);
+            if (file.getParentFile() != null) {
+                file.getParentFile().mkdirs();
+            }
+            return writeTo(new FileWriter(file));
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    public List<ManifestMergeSpec> getMergeSpecs() {
+        return manifestMergeSpecs;
+    }
+
+    public boolean isEqualsTo(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || !(o instanceof DefaultManifest)) {
+            return false;
+        }
+
+        DefaultManifest effectiveThis = getEffectiveManifest();
+        DefaultManifest effectiveThat = ((DefaultManifest) o).getEffectiveManifest();
+
+        if (!effectiveThis.attributes.equals(effectiveThat.attributes)) {
+            return false;
+        }
+        if (!effectiveThis.sections.equals(effectiveThat.sections)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    private void read(Object manifestPath) {
+        File manifestFile = fileResolver.resolve(manifestPath);
+        try {
+            Manifest antManifest = new Manifest(new FileReader(manifestFile));
+            addAntManifestToAttributes(antManifest);
+            addAntManifestToSections(antManifest);
+        } catch (ManifestException e) {
+            throw new org.gradle.api.java.archives.ManifestException(e.getMessage(), e);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e.getMessage(), e);
+        }
+    }
+
+    private void addAntManifestToAttributes(Manifest antManifest) {
+        Enumeration attributeKeys = antManifest.getMainSection().getAttributeKeys();
+        while (attributeKeys.hasMoreElements()) {
+            String key = (String) attributeKeys.nextElement();
+            String attributeKey = antManifest.getMainSection().getAttribute(key).getName();
+            attributes.put(attributeKey, antManifest.getMainSection().getAttributeValue(key));
+        }
+        attributes.put("Manifest-Version", antManifest.getManifestVersion());
+    }
+
+    private void addAntManifestToSections(Manifest antManifest) {
+        Enumeration sectionNames = antManifest.getSectionNames();
+        while (sectionNames.hasMoreElements()) {
+            String sectionName = (String) sectionNames.nextElement();
+            addAntManifestToSection(antManifest, sectionName);
+        }
+    }
+
+    private void addAntManifestToSection(Manifest antManifest, String sectionName) {
+        DefaultAttributes attributes = new DefaultAttributes();
+        sections.put(sectionName, attributes);
+        Enumeration attributeKeys = antManifest.getSection(sectionName).getAttributeKeys();
+        while (attributeKeys.hasMoreElements()) {
+            String key = (String) attributeKeys.nextElement();
+            String attributeKey = antManifest.getSection(sectionName).getAttribute(key).getName();
+            attributes.put(attributeKey, antManifest.getSection(sectionName).getAttributeValue(key));
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeDetails.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeDetails.java
new file mode 100644
index 0000000..4871cfe
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeDetails.java
@@ -0,0 +1,70 @@
+/*
+ * 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.java.archives.internal;
+
+import org.gradle.api.java.archives.ManifestMergeDetails;
+
+/**
+ * @author Hans Dockter
+ */
+public class DefaultManifestMergeDetails implements ManifestMergeDetails {
+    private String section;
+    private String key;
+    private String baseValue;
+    private String mergeValue;
+    private String value;
+    private boolean excluded;
+
+    public DefaultManifestMergeDetails(String section, String key, String baseValue, String mergeValue, String value) {
+        this.section = section;
+        this.key = key;
+        this.baseValue = baseValue;
+        this.mergeValue = mergeValue;
+        this.value = value;
+    }
+
+    public String getSection() {
+        return section;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public String getBaseValue() {
+        return baseValue;
+    }
+
+    public String getMergeValue() {
+        return mergeValue;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public boolean isExcluded() {
+        return excluded;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    public void exclude() {
+        excluded = true;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpec.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpec.java
new file mode 100644
index 0000000..a8ac5e1
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpec.java
@@ -0,0 +1,121 @@
+/*
+ * 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.java.archives.internal;
+
+import groovy.lang.Closure;
+import org.codehaus.groovy.runtime.DefaultGroovyMethods;
+import org.gradle.api.Action;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.java.archives.Attributes;
+import org.gradle.api.java.archives.Manifest;
+import org.gradle.api.java.archives.ManifestMergeDetails;
+import org.gradle.api.java.archives.ManifestMergeSpec;
+import org.gradle.util.GUtil;
+import org.gradle.util.WrapUtil;
+
+import java.util.*;
+
+public class DefaultManifestMergeSpec implements ManifestMergeSpec {
+    List<Object> mergePaths = new ArrayList<Object>();
+    private final List<Action<? super ManifestMergeDetails>> actions = new ArrayList<Action<? super ManifestMergeDetails>>();
+
+    public ManifestMergeSpec from(Object... mergePaths) {
+        GUtil.flatten(mergePaths, this.mergePaths);
+        return this;
+    }
+
+    public ManifestMergeSpec eachEntry(Action<? super ManifestMergeDetails> mergeAction) {
+        actions.add(mergeAction);
+        return this;
+    }
+
+    public ManifestMergeSpec eachEntry(Closure mergeAction) {
+        return eachEntry((Action<? super ManifestMergeDetails>) DefaultGroovyMethods.asType(mergeAction, Action.class));
+    }
+
+    public DefaultManifest merge(Manifest baseManifest, FileResolver fileResolver) {
+        DefaultManifest mergedManifest = new DefaultManifest(fileResolver);
+        mergedManifest.getAttributes().putAll(baseManifest.getAttributes());
+        mergedManifest.getSections().putAll(baseManifest.getSections());
+        for (Object mergePath : mergePaths) {
+            DefaultManifest manifestToMerge = createManifest(mergePath, fileResolver);
+            mergedManifest = mergeManifest(mergedManifest, manifestToMerge, fileResolver);
+        }
+        return mergedManifest;
+    }
+
+    private DefaultManifest mergeManifest(DefaultManifest baseManifest, DefaultManifest toMergeManifest, FileResolver fileResolver) {
+        DefaultManifest mergedManifest = new DefaultManifest(fileResolver);
+        mergeSection(null, mergedManifest, baseManifest.getAttributes(), toMergeManifest.getAttributes());
+        Set<String> allSections = GUtil.addSets(baseManifest.getSections().keySet(), toMergeManifest.getSections().keySet());
+        for (String section : allSections) {
+            mergeSection(section, mergedManifest,
+                    GUtil.elvis(baseManifest.getSections().get(section), new DefaultAttributes()),
+                    GUtil.elvis(toMergeManifest.getSections().get(section), new DefaultAttributes()));
+        }
+        return mergedManifest;
+    }
+
+    private void mergeSection(String section, DefaultManifest mergedManifest, Attributes baseAttributes, Attributes mergeAttributes) {
+        Map<String, Object> mergeOnlyAttributes = new LinkedHashMap<String, Object>(mergeAttributes);
+        Set<DefaultManifestMergeDetails> mergeDetailsSet = new LinkedHashSet<DefaultManifestMergeDetails>();
+
+        for (Map.Entry<String, Object> baseEntry : baseAttributes.entrySet()) {
+            Object mergeValue = mergeAttributes.get(baseEntry.getKey());
+            mergeDetailsSet.add(getMergeDetails(section, baseEntry.getKey(), baseEntry.getValue(), mergeValue));
+            mergeOnlyAttributes.remove(baseEntry.getKey());
+        }
+        for (Map.Entry<String, Object> mergeEntry : mergeOnlyAttributes.entrySet()) {
+            mergeDetailsSet.add(getMergeDetails(section, mergeEntry.getKey(), null, mergeEntry.getValue()));
+        }
+        
+        for (DefaultManifestMergeDetails mergeDetails : mergeDetailsSet) {
+            for (Action<? super ManifestMergeDetails> action : actions) {
+                action.execute(mergeDetails);
+            }
+            addMergeDetailToManifest(section, mergedManifest, mergeDetails);
+        }
+    }
+
+    private DefaultManifestMergeDetails getMergeDetails(String section, String key, Object baseValue, Object mergeValue) {
+        String value = null;
+        String baseValueString = baseValue != null ? baseValue.toString() : null;
+        String mergeValueString = mergeValue != null ? mergeValue.toString() : null;
+        value = mergeValueString == null ? baseValueString : mergeValueString; 
+        return new DefaultManifestMergeDetails(section, key, baseValueString, mergeValueString, value);
+    }
+
+    private void addMergeDetailToManifest(String section, DefaultManifest mergedManifest, DefaultManifestMergeDetails mergeDetails) {
+        if (!mergeDetails.isExcluded()) {
+            if (section == null) {
+                mergedManifest.attributes(WrapUtil.toMap(mergeDetails.getKey(), mergeDetails.getValue()));
+            } else {
+                mergedManifest.attributes(WrapUtil.toMap(mergeDetails.getKey(), mergeDetails.getValue()), section);
+            }
+        }
+    }
+
+    private DefaultManifest createManifest(Object mergePath, FileResolver fileResolver) {
+        if (mergePath instanceof DefaultManifest) {
+            return ((DefaultManifest) mergePath).getEffectiveManifest();
+        }
+        return new DefaultManifest(mergePath, fileResolver);
+    }
+
+    public List<Object> getMergePaths() {
+        return mergePaths;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/BasePlugin.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/BasePlugin.groovy
new file mode 100644
index 0000000..c820d83
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/BasePlugin.groovy
@@ -0,0 +1,171 @@
+/*
+ * 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.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.tasks.Delete
+import org.gradle.api.tasks.Upload
+import org.gradle.api.tasks.bundling.AbstractArchiveTask
+import org.gradle.api.tasks.bundling.Jar
+import org.gradle.api.artifacts.ConfigurationContainer
+import org.gradle.api.artifacts.Dependency
+import org.apache.commons.lang.StringUtils
+
+/**
+ * <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) {
+        project.convention.plugins.base = new BasePluginConvention(project)
+
+        configureBuildConfigurationRule(project)
+        configureUploadRules(project)
+        configureArchiveDefaults(project, project.convention.plugins.base)
+        configureConfigurations(project)
+
+        addClean(project)
+        addCleanRule(project)
+        addAssemble(project);
+    }
+
+    private Task addAssemble(Project project) {
+        Task assembleTask = project.tasks.add(ASSEMBLE_TASK_NAME);
+        assembleTask.description = "Builds all Jar, War, Zip, and Tar archives.";
+        assembleTask.group = BUILD_GROUP
+        assembleTask.dependsOn { project.tasks.withType(AbstractArchiveTask.class).all }
+    }
+
+    private void configureArchiveDefaults(Project project, BasePluginConvention pluginConvention) {
+        project.tasks.withType(AbstractArchiveTask).allTasks {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
+                    }
+                    Task task = project.tasks.findByName(StringUtils.uncapitalize(taskName.substring(prefix.length())))
+                    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>: Builds the artifacts belonging to 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.getBuildArtifacts()).setDescription(String.format("Builds the artifacts belonging to %s.", configuration))
+                        }
+                    }
+                },
+                toString: { "Rule: " + description }
+        ] as Rule
+
+        project.configurations.allObjects {
+            if (!project.tasks.rules.contains(rule)) {
+                project.tasks.addRule(rule)
+            }
+        }
+    }
+
+    private void configureUploadRules(final Project project) {
+        String description = "Pattern: upload<ConfigurationName>: Uploads the project artifacts of a configuration to a public Gradle repository."
+        Rule rule = [
+                getDescription: {
+                    description
+                },
+                apply: {String taskName ->
+                    Set<Configuration> configurations = project.configurations.all
+                    for (Configuration configuration: configurations) {
+                        if (taskName.equals(configuration.uploadTaskName)) {
+                            createUploadTask(configuration.uploadTaskName, configuration, project)
+                        }
+                    }
+                },
+                toString: { "Rule: " + description }
+        ] as Rule
+        project.configurations.allObjects {
+            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 the default artifacts.");
+
+        configurations.add(Dependency.DEFAULT_CONFIGURATION).extendsFrom(archivesConfiguration).
+                setDescription("Configuration the default artifacts and its dependencies.");
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/BasePluginConvention.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/BasePluginConvention.groovy
new file mode 100644
index 0000000..43c0037
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/BasePluginConvention.groovy
@@ -0,0 +1,42 @@
+/*
+ * 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.plugins
+
+import org.gradle.api.Project
+import org.gradle.api.internal.project.ProjectInternal
+
+public class BasePluginConvention {
+    ProjectInternal project
+
+    String distsDirName
+    String libsDirName
+    String archivesBaseName
+
+    BasePluginConvention(Project project) {
+        this.project = project
+        archivesBaseName = project.name
+        distsDirName = 'distributions'
+        libsDirName = 'libs'
+    }
+
+    File getDistsDir() {
+        project.fileResolver.withBaseDir(project.buildDir).resolve(distsDirName)
+    }
+
+    File getLibsDir() {
+        project.fileResolver.withBaseDir(project.buildDir).resolve(libsDirName)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/GroovyBasePlugin.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/GroovyBasePlugin.java
new file mode 100644
index 0000000..01083da
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/GroovyBasePlugin.java
@@ -0,0 +1,131 @@
+/*
+ * 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.plugins;
+
+import org.gradle.api.Action;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.file.FileTreeElement;
+import org.gradle.api.internal.DynamicObjectAware;
+import org.gradle.api.internal.IConventionAware;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.gradle.api.internal.tasks.DefaultGroovySourceSet;
+import org.gradle.api.internal.tasks.DefaultSourceSet;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.ConventionValue;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.compile.GroovyCompile;
+import org.gradle.api.tasks.javadoc.Groovydoc;
+
+import java.io.File;
+
+/**
+ * <p>A {@link org.gradle.api.Plugin} which extends the {@link org.gradle.api.plugins.JavaBasePlugin} to provide support for compiling and documenting Groovy
+ * source files.</p>
+ *
+ * @author Hans Dockter
+ */
+public class GroovyBasePlugin implements Plugin<Project> {
+    public static final String GROOVY_CONFIGURATION_NAME = "groovy";
+
+    public void apply(Project project) {
+        JavaBasePlugin javaBasePlugin = project.getPlugins().apply(JavaBasePlugin.class);
+
+        project.getConfigurations().add(GROOVY_CONFIGURATION_NAME).setVisible(false).setTransitive(false).
+                setDescription("The groovy libraries to be used for this Groovy project.");
+
+        configureCompileDefaults(project);
+        configureSourceSetDefaults(project, javaBasePlugin);
+
+        configureGroovydoc(project);
+    }
+
+    private void configureCompileDefaults(final Project project) {
+        project.getTasks().withType(GroovyCompile.class).allTasks(new Action<GroovyCompile>() {
+            public void execute(GroovyCompile compile) {
+                compile.getConventionMapping().map("groovyClasspath", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return project.getConfigurations().getByName(GROOVY_CONFIGURATION_NAME).copy().setTransitive(true);
+                    }
+                });
+            }
+        });
+    }
+
+    private void configureSourceSetDefaults(final Project project, final JavaBasePlugin javaBasePlugin) {
+        final ProjectInternal projectInternal = (ProjectInternal) project;
+        project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().allObjects(new Action<SourceSet>() {
+            public void execute(SourceSet sourceSet) {
+                final DefaultGroovySourceSet groovySourceSet = new DefaultGroovySourceSet(((DefaultSourceSet) sourceSet).getDisplayName(), projectInternal.getFileResolver());
+                ((DynamicObjectAware) sourceSet).getConvention().getPlugins().put("groovy", groovySourceSet);
+
+                groovySourceSet.getGroovy().srcDir(String.format("src/%s/groovy", sourceSet.getName()));
+                sourceSet.getResources().getFilter().exclude(new Spec<FileTreeElement>() {
+                    public boolean isSatisfiedBy(FileTreeElement element) {
+                        return groovySourceSet.getGroovy().contains(element.getFile());
+                    }
+                });
+                sourceSet.getAllJava().add(groovySourceSet.getGroovy().matching(sourceSet.getJava().getFilter()));
+                sourceSet.getAllSource().add(groovySourceSet.getGroovy());
+
+                String compileTaskName = sourceSet.getCompileTaskName("groovy");
+                GroovyCompile compile = project.getTasks().add(compileTaskName, GroovyCompile.class);
+                javaBasePlugin.configureForSourceSet(sourceSet, compile);
+                compile.dependsOn(sourceSet.getCompileJavaTaskName());
+                compile.setDescription(String.format("Compiles the %s Groovy source.", sourceSet.getName()));
+                compile.conventionMapping("defaultSource", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return groovySourceSet.getGroovy();
+                    }
+                });
+
+                project.getTasks().getByName(sourceSet.getClassesTaskName()).dependsOn(compileTaskName);
+            }
+        });
+    }
+
+    private void configureGroovydoc(final Project project) {
+        project.getTasks().withType(Groovydoc.class).allTasks(new Action<Groovydoc>() {
+            public void execute(Groovydoc groovydoc) {
+                groovydoc.getConventionMapping().map("groovyClasspath", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return project.getConfigurations().getByName(GROOVY_CONFIGURATION_NAME).copy().setTransitive(true);
+                    }
+                });
+                groovydoc.getConventionMapping().map("destinationDir", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return new File(java(convention).getDocsDir(), "groovydoc");
+                    }
+                });
+                groovydoc.getConventionMapping().map("docTitle", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return convention.getPlugin(ReportingBasePluginConvention.class).getApiDocTitle();
+                    }
+                });
+                groovydoc.getConventionMapping().map("windowTitle", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return convention.getPlugin(ReportingBasePluginConvention.class).getApiDocTitle();
+                    }
+                });
+            }
+        });
+    }
+
+    private JavaPluginConvention java(Convention convention) {
+        return convention.getPlugin(JavaPluginConvention.class);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/GroovyPlugin.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/GroovyPlugin.java
new file mode 100644
index 0000000..f680776
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/GroovyPlugin.java
@@ -0,0 +1,62 @@
+/*
+ * 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.plugins;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.internal.DynamicObjectAware;
+import org.gradle.api.tasks.GroovySourceSet;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.javadoc.Groovydoc;
+
+import static org.gradle.api.plugins.JavaPlugin.COMPILE_CONFIGURATION_NAME;
+
+/**
+ * <p>A {@link Plugin} which extends the {@link JavaPlugin} to provide support for compiling and documenting Groovy
+ * source files.</p>
+ *
+ * @author Hans Dockter
+ */
+public class GroovyPlugin implements Plugin<Project> {
+    public static final String GROOVYDOC_TASK_NAME = "groovydoc";
+
+    public void apply(Project project) {
+        project.getPlugins().apply(GroovyBasePlugin.class);
+        project.getPlugins().apply(JavaPlugin.class);
+
+        project.getConfigurations().getByName(COMPILE_CONFIGURATION_NAME).extendsFrom(
+                project.getConfigurations().getByName(GroovyBasePlugin.GROOVY_CONFIGURATION_NAME)
+        );
+        configureGroovydoc(project);
+    }
+
+    private void configureGroovydoc(final Project project) {
+        Groovydoc groovyDoc = project.getTasks().add(GROOVYDOC_TASK_NAME, Groovydoc.class);
+        groovyDoc.setDescription("Generates the groovydoc for the source code.");
+        groovyDoc.setGroup(JavaBasePlugin.DOCUMENTATION_GROUP);
+        groovyDoc.setSource(mainGroovy(project.getConvention()).getGroovy());
+    }
+
+    private SourceSet main(Convention convention) {
+        return convention.getPlugin(JavaPluginConvention.class).getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME);
+    }
+
+    private GroovySourceSet mainGroovy(Convention convention) {
+        return ((DynamicObjectAware) main(convention)).getConvention().getPlugin(GroovySourceSet.class);
+    }
+
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/JavaBasePlugin.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/JavaBasePlugin.java
new file mode 100644
index 0000000..7ecf983
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/JavaBasePlugin.java
@@ -0,0 +1,295 @@
+/*
+ * 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.*;
+import org.gradle.api.internal.ConventionMapping;
+import org.gradle.api.internal.IConventionAware;
+import org.gradle.api.tasks.ConventionValue;
+import org.gradle.api.tasks.Copy;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.compile.AbstractCompile;
+import org.gradle.api.tasks.compile.Compile;
+import org.gradle.api.tasks.javadoc.Javadoc;
+import org.gradle.api.tasks.testing.Test;
+import org.gradle.api.tasks.testing.TestDescriptor;
+import org.gradle.api.tasks.testing.TestListener;
+import org.gradle.api.tasks.testing.TestResult;
+import org.gradle.util.WrapUtil;
+
+import java.io.File;
+
+/**
+ * <p>A {@link org.gradle.api.Plugin} which compiles and tests Java source, and assembles it into a JAR file.</p>
+ *
+ * @author Hans Dockter
+ */
+public class JavaBasePlugin implements Plugin<Project> {
+    public static final String CHECK_TASK_NAME = "check";
+    public static final String BUILD_TASK_NAME = "build";
+    public static final String BUILD_DEPENDENTS_TASK_NAME = "buildDependents";
+    public static final String BUILD_NEEDED_TASK_NAME = "buildNeeded";
+    public static final String VERIFICATION_GROUP = "verification";
+    public static final String DOCUMENTATION_GROUP = "documentation";
+
+    public void apply(Project project) {
+        project.getPlugins().apply(BasePlugin.class);
+        project.getPlugins().apply(ReportingBasePlugin.class);
+
+        JavaPluginConvention javaConvention = new JavaPluginConvention(project);
+        project.getConvention().getPlugins().put("java", javaConvention);
+
+        configureCompileDefaults(project, javaConvention);
+        configureSourceSetDefaults(javaConvention);
+
+        configureJavaDoc(project);
+        configureTest(project);
+        configureCheck(project);
+        configureBuild(project);
+        configureBuildNeeded(project);
+        configureBuildDependents(project);
+    }
+
+    private void configureSourceSetDefaults(final JavaPluginConvention pluginConvention) {
+        pluginConvention.getSourceSets().allObjects(new Action<SourceSet>() {
+            public void execute(final SourceSet sourceSet) {
+                final Project project = pluginConvention.getProject();
+                ConventionMapping conventionMapping = ((IConventionAware) sourceSet).getConventionMapping();
+
+                conventionMapping.map("classesDir", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        String classesDirName = String.format("classes/%s", sourceSet.getName());
+                        return new File(project.getBuildDir(), classesDirName);
+                    }
+                });
+                sourceSet.getJava().srcDir(String.format("src/%s/java", sourceSet.getName()));
+                sourceSet.getResources().srcDir(String.format("src/%s/resources", sourceSet.getName()));
+
+                Copy processResources = project.getTasks().add(sourceSet.getProcessResourcesTaskName(), ProcessResources.class);
+                processResources.setDescription(String.format("Processes the %s.", sourceSet.getResources()));
+                conventionMapping = processResources.getConventionMapping();
+                conventionMapping.map("defaultSource", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return sourceSet.getResources();
+                    }
+                });
+                conventionMapping.map("destinationDir", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return sourceSet.getClassesDir();
+                    }
+                });
+
+                String compileTaskName = sourceSet.getCompileJavaTaskName();
+                Compile compileJava = project.getTasks().add(compileTaskName, Compile.class);
+                configureForSourceSet(sourceSet, compileJava);
+
+                Task classes = project.getTasks().add(sourceSet.getClassesTaskName());
+                classes.dependsOn(sourceSet.getProcessResourcesTaskName(), compileTaskName);
+                classes.setDescription(String.format("Assembles the %s classes.", sourceSet.getName()));
+                classes.setGroup(BasePlugin.BUILD_GROUP);
+
+                sourceSet.compiledBy(sourceSet.getClassesTaskName());
+            }
+        });
+    }
+
+    public void configureForSourceSet(final SourceSet sourceSet, AbstractCompile compile) {
+        ConventionMapping conventionMapping;
+        compile.setDescription(String.format("Compiles the %s.", sourceSet.getJava()));
+        conventionMapping = compile.getConventionMapping();
+        conventionMapping.map("classpath", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return sourceSet.getCompileClasspath();
+            }
+        });
+        conventionMapping.map("defaultSource", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return sourceSet.getJava();
+            }
+        });
+        conventionMapping.map("destinationDir", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return sourceSet.getClassesDir();
+            }
+        });
+    }
+
+    private void configureCompileDefaults(final Project project, final JavaPluginConvention javaConvention) {
+        project.getTasks().withType(AbstractCompile.class).allTasks(new Action<AbstractCompile>() {
+            public void execute(final AbstractCompile compile) {
+                ConventionMapping conventionMapping = compile.getConventionMapping();
+                conventionMapping.map("sourceCompatibility", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return javaConvention.getSourceCompatibility().toString();
+                    }
+                });
+                conventionMapping.map("targetCompatibility", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return javaConvention.getTargetCompatibility().toString();
+                    }
+                });
+            }
+        });
+        project.getTasks().withType(Compile.class).allTasks(new Action<Compile>() {
+            public void execute(final Compile compile) {
+                ConventionMapping conventionMapping = compile.getConventionMapping();
+                conventionMapping.map("dependencyCacheDir", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return javaConvention.getDependencyCacheDir();
+                    }
+                });
+            }
+        });
+    }
+
+    private void configureJavaDoc(final Project project) {
+        project.getTasks().withType(Javadoc.class).allTasks(new Action<Javadoc>() {
+            public void execute(Javadoc javadoc) {
+                javadoc.getConventionMapping().map("destinationDir", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return new File(convention.getPlugin(JavaPluginConvention.class).getDocsDir(), "javadoc");
+                    }
+                });
+                javadoc.getConventionMapping().map("title", new ConventionValue() {
+                    public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                        return convention.getPlugin(ReportingBasePluginConvention.class).getApiDocTitle();
+                    }
+                });
+            }
+        });
+    }
+
+    private void configureCheck(final Project project) {
+        Task checkTask = project.getTasks().add(CHECK_TASK_NAME);
+        checkTask.setDescription("Runs all checks.");
+        checkTask.setGroup(VERIFICATION_GROUP);
+    }
+
+    private void configureBuild(Project project) {
+        DefaultTask buildTask = project.getTasks().add(BUILD_TASK_NAME, DefaultTask.class);
+        buildTask.setDescription("Assembles and tests this project.");
+        buildTask.setGroup(BasePlugin.BUILD_GROUP);
+        buildTask.dependsOn(BasePlugin.ASSEMBLE_TASK_NAME);
+        buildTask.dependsOn(CHECK_TASK_NAME);
+    }
+
+    private void configureBuildNeeded(Project project) {
+        DefaultTask buildTask = project.getTasks().add(BUILD_NEEDED_TASK_NAME, DefaultTask.class);
+        buildTask.setDescription("Assembles and tests this project and all projects it depends on.");
+        buildTask.setGroup(BasePlugin.BUILD_GROUP);
+        buildTask.dependsOn(BUILD_TASK_NAME);
+    }
+
+    private void configureBuildDependents(Project project) {
+        DefaultTask buildTask = project.getTasks().add(BUILD_DEPENDENTS_TASK_NAME, DefaultTask.class);
+        buildTask.setDescription("Assembles and tests this project and all projects that depend on it.");
+        buildTask.setGroup(BasePlugin.BUILD_GROUP);
+        buildTask.dependsOn(BUILD_TASK_NAME);
+    }
+
+    private void configureTest(final Project project) {
+        project.getTasks().withType(Test.class).allTasks(new Action<Test>() {
+            public void execute(Test test) {
+                configureTestDefaults(test, project);
+            }
+        });
+        project.afterEvaluate(new Action<Project>() {
+            @Override
+            public void execute(Project project) {
+                project.getTasks().withType(Test.class).allTasks(new Action<Test>() {
+                    public void execute(Test test) {
+                        overwriteIncludesIfSinglePropertyIsSet(test);
+                        overwriteDebugIfDebugPropertyIsSet(test);
+                    }
+                });
+            }
+        });
+    }
+
+    private void overwriteDebugIfDebugPropertyIsSet(Test test) {
+        String debugProp = getTaskPrefixedProperty(test, "debug");
+        if (debugProp != null) {
+            test.doFirst(new Action<Task>() {
+                @Override
+                public void execute(Task task) {
+                    task.getLogger().info("Running tests for remote debugging.");
+                }
+            });
+            test.setDebug(true);
+        }
+    }
+
+    private void overwriteIncludesIfSinglePropertyIsSet(final Test test) {
+        String singleTest = getTaskPrefixedProperty(test, "single");
+        if (singleTest == null) {
+            return;
+        }
+        test.doFirst(new Action<Task>() {
+            @Override
+            public void execute(Task task) {
+                test.getLogger().info("Running single tests with pattern: {}", test.getIncludes());
+            }
+        });
+        test.setIncludes(WrapUtil.toSet(String.format("**/%s*.class", singleTest)));
+        failIfNoTestIsExecuted(test, singleTest);
+    }
+
+    private void failIfNoTestIsExecuted(Test test, final String pattern) {
+        test.addTestListener(new TestListener() {
+            public void beforeSuite(TestDescriptor suite) {
+                // do nothing
+            }
+
+            public void afterSuite(TestDescriptor suite, TestResult result) {
+                if (suite.getParent() == null && result.getTestCount() == 0) {
+                    throw new GradleException("Could not find matching test for pattern: " + pattern);
+                }
+            }
+
+            public void beforeTest(TestDescriptor testDescriptor) {
+                // do nothing
+            }
+
+            public void afterTest(TestDescriptor testDescriptor, TestResult result) {
+                // do nothing
+            }
+        });
+    }
+
+    private String getTaskPrefixedProperty(Task task, String propertyName) {
+        String suffix = '.' + propertyName;
+        String value = System.getProperty(task.getPath() + suffix);
+        if (value == null) {
+            return System.getProperty(task.getName() + suffix);
+        }
+        return value;
+    }
+
+    private void configureTestDefaults(Test test, Project project) {
+        test.getConventionMapping().map("testResultsDir", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return convention.getPlugin(JavaPluginConvention.class).getTestResultsDir();
+            }
+        });
+        test.getConventionMapping().map("testReportDir", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return convention.getPlugin(JavaPluginConvention.class).getTestReportDir();
+            }
+        });
+        test.workingDir(project.getProjectDir());
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/JavaPlugin.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/JavaPlugin.java
new file mode 100644
index 0000000..c701836
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/JavaPlugin.java
@@ -0,0 +1,209 @@
+/*
+ * 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.file.FileCollection;
+import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact;
+import org.gradle.api.internal.plugins.EmbeddableJavaProject;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.bundling.Jar;
+import org.gradle.api.tasks.javadoc.Javadoc;
+import org.gradle.api.tasks.testing.Test;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.Callable;
+
+/**
+ * <p>A {@link Plugin} which compiles and tests Java source, and assembles it into a JAR file.</p>
+ *
+ * @author Hans Dockter
+ */
+public class JavaPlugin implements Plugin<Project> {
+    public static final String PROCESS_RESOURCES_TASK_NAME = "processResources";
+    public static final String CLASSES_TASK_NAME = "classes";
+    public static final String COMPILE_JAVA_TASK_NAME = "compileJava";
+    public static final String PROCESS_TEST_RESOURCES_TASK_NAME = "processTestResources";
+    public static final String TEST_CLASSES_TASK_NAME = "testClasses";
+    public static final String COMPILE_TEST_JAVA_TASK_NAME = "compileTestJava";
+    public static final String TEST_TASK_NAME = "test";
+    public static final String JAR_TASK_NAME = "jar";
+    public static final String JAVADOC_TASK_NAME = "javadoc";
+
+    public static final String COMPILE_CONFIGURATION_NAME = "compile";
+    public static final String RUNTIME_CONFIGURATION_NAME = "runtime";
+    public static final String TEST_RUNTIME_CONFIGURATION_NAME = "testRuntime";
+    public static final String TEST_COMPILE_CONFIGURATION_NAME = "testCompile";
+
+    public void apply(Project project) {
+        project.getPlugins().apply(JavaBasePlugin.class);
+
+        JavaPluginConvention javaConvention = (JavaPluginConvention) project.getConvention().getPlugins().get("java");
+        project.getConvention().getPlugins().put("embeddedJavaProject", new EmbeddableJavaProjectImpl(javaConvention));
+
+        configureConfigurations(project);
+
+        configureSourceSets(javaConvention);
+
+        configureJavaDoc(javaConvention);
+        configureTest(project, javaConvention);
+        configureArchives(project, javaConvention);
+        configureBuild(project);
+    }
+
+    private void configureSourceSets(final JavaPluginConvention pluginConvention) {
+        final Project project = pluginConvention.getProject();
+
+        pluginConvention.getSourceSets().allObjects(new Action<SourceSet>() {
+            public void execute(SourceSet sourceSet) {
+                sourceSet.setCompileClasspath(project.getConfigurations().getByName(COMPILE_CONFIGURATION_NAME));
+                sourceSet.setRuntimeClasspath(sourceSet.getClasses().plus(project.getConfigurations().getByName(
+                        RUNTIME_CONFIGURATION_NAME)));
+            }
+        });
+        SourceSet main = pluginConvention.getSourceSets().add(SourceSet.MAIN_SOURCE_SET_NAME);
+
+        SourceSet test = pluginConvention.getSourceSets().add(SourceSet.TEST_SOURCE_SET_NAME);
+        test.setCompileClasspath(project.files(main.getClasses(), project.getConfigurations().getByName(
+                TEST_COMPILE_CONFIGURATION_NAME)));
+        test.setRuntimeClasspath(project.files(test.getClasses(), main.getClasses(),
+                project.getConfigurations().getByName(TEST_RUNTIME_CONFIGURATION_NAME)));
+    }
+
+    private void configureJavaDoc(final JavaPluginConvention pluginConvention) {
+        Project project = pluginConvention.getProject();
+
+        SourceSet mainSourceSet = pluginConvention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME);
+        Javadoc javadoc = project.getTasks().add(JAVADOC_TASK_NAME, Javadoc.class);
+        javadoc.setDescription("Generates the javadoc for the source code.");
+        javadoc.setGroup(JavaBasePlugin.DOCUMENTATION_GROUP);
+        javadoc.setClasspath(mainSourceSet.getClasses().plus(mainSourceSet.getCompileClasspath()));
+        javadoc.setSource(mainSourceSet.getAllJava());
+        addDependsOnTaskInOtherProjects(javadoc, true, JAVADOC_TASK_NAME, COMPILE_CONFIGURATION_NAME);
+    }
+
+    private void configureArchives(final Project project, final JavaPluginConvention pluginConvention) {
+        project.getTasks().getByName(JavaBasePlugin.CHECK_TASK_NAME).dependsOn(TEST_TASK_NAME);
+        Jar jar = project.getTasks().add(JAR_TASK_NAME, Jar.class);
+        jar.getManifest().from(pluginConvention.getManifest());
+        jar.setDescription("Generates a jar archive with all the compiled classes.");
+        jar.setGroup(BasePlugin.BUILD_GROUP);
+        jar.from(pluginConvention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getClasses());
+        jar.getMetaInf().from(new Callable() {
+            public Object call() throws Exception {
+                return pluginConvention.getMetaInf();
+            }
+        });
+
+        project.getConfigurations().getByName(Dependency.ARCHIVES_CONFIGURATION).addArtifact(new ArchivePublishArtifact(
+                jar));
+    }
+
+    private void configureBuild(Project project) {
+        addDependsOnTaskInOtherProjects(project.getTasks().getByName(JavaBasePlugin.BUILD_NEEDED_TASK_NAME), true,
+                JavaBasePlugin.BUILD_TASK_NAME, TEST_RUNTIME_CONFIGURATION_NAME);
+        addDependsOnTaskInOtherProjects(project.getTasks().getByName(JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME), false,
+                JavaBasePlugin.BUILD_TASK_NAME, TEST_RUNTIME_CONFIGURATION_NAME);
+    }
+
+    private void configureTest(final Project project, final JavaPluginConvention pluginConvention) {
+        Test test = project.getTasks().add(TEST_TASK_NAME, Test.class);
+        test.setDescription("Runs the unit tests.");
+        test.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
+        test.getConventionMapping().map("testClassesDir", new Callable<Object>() {
+            public Object call() throws Exception {
+                return pluginConvention.getSourceSets().getByName(SourceSet.TEST_SOURCE_SET_NAME).getClassesDir();
+            }
+        });
+        test.getConventionMapping().map("classpath", new Callable<Object>() {
+            public Object call() throws Exception {
+                return pluginConvention.getSourceSets().getByName(SourceSet.TEST_SOURCE_SET_NAME).getRuntimeClasspath();
+            }
+        });
+        test.getConventionMapping().map("testSrcDirs", new Callable<Object>() {
+            public Object call() throws Exception {
+                return new ArrayList<File>(pluginConvention.getSourceSets().getByName(SourceSet.TEST_SOURCE_SET_NAME)
+                        .getJava().getSrcDirs());
+            }
+        });
+    }
+
+    void configureConfigurations(final Project project) {
+        ConfigurationContainer configurations = project.getConfigurations();
+        Configuration compileConfiguration = configurations.add(COMPILE_CONFIGURATION_NAME).setVisible(false).
+                setDescription("Classpath for compiling the sources.");
+        Configuration runtimeConfiguration = configurations.add(RUNTIME_CONFIGURATION_NAME).setVisible(false)
+                .extendsFrom(compileConfiguration).
+                        setDescription("Classpath for running the compiled sources.");
+
+        Configuration compileTestsConfiguration = configurations.add(TEST_COMPILE_CONFIGURATION_NAME).setVisible(false)
+                .extendsFrom(compileConfiguration).setDescription("Classpath for compiling the test sources.");
+
+        configurations.add(TEST_RUNTIME_CONFIGURATION_NAME).setVisible(false).extendsFrom(runtimeConfiguration,
+                compileTestsConfiguration).
+                setDescription("Classpath for running the test sources.");
+
+        configurations.getByName(Dependency.DEFAULT_CONFIGURATION).extendsFrom(runtimeConfiguration);
+    }
+
+    /**
+     * Adds a dependency on tasks with the specified name in other projects.  The other projects are determined from
+     * project lib dependencies using the specified configuration name. These may be projects this project depends on or
+     * projects that depend on this project based on the useDependOn argument.
+     *
+     * @param task Task to add dependencies to
+     * @param useDependedOn if true, add tasks from projects this project depends on, otherwise use projects that depend
+     * on this one.
+     * @param otherProjectTaskName name of task in other projects
+     * @param configurationName name of configuration to use to find the other projects
+     */
+    private void addDependsOnTaskInOtherProjects(final Task task, boolean useDependedOn, String otherProjectTaskName,
+                                                 String configurationName) {
+        Project project = task.getProject();
+        final Configuration configuration = project.getConfigurations().getByName(configurationName);
+        task.dependsOn(configuration.getTaskDependencyFromProjectDependency(useDependedOn, otherProjectTaskName));
+    }
+
+    private static class EmbeddableJavaProjectImpl implements EmbeddableJavaProject {
+        private final JavaPluginConvention convention;
+
+        public EmbeddableJavaProjectImpl(JavaPluginConvention convention) {
+            this.convention = convention;
+        }
+
+        public Collection<String> getRebuildTasks() {
+            return Arrays.asList(BasePlugin.CLEAN_TASK_NAME, JavaBasePlugin.BUILD_TASK_NAME);
+        }
+
+        public Collection<String> getBuildTasks() {
+            return Arrays.asList(JavaBasePlugin.BUILD_TASK_NAME);
+        }
+
+        public FileCollection getRuntimeClasspath() {
+            return convention.getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath();
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/JavaPluginConvention.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/JavaPluginConvention.groovy
new file mode 100644
index 0000000..54f5297
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/JavaPluginConvention.groovy
@@ -0,0 +1,159 @@
+/*
+ * 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.plugins
+
+import org.gradle.api.JavaVersion
+import org.gradle.api.Project
+import org.gradle.api.internal.ClassGenerator
+import org.gradle.api.internal.project.ProjectInternal
+import org.gradle.api.internal.tasks.DefaultSourceSetContainer
+import org.gradle.api.java.archives.Manifest
+import org.gradle.api.java.archives.internal.DefaultManifest
+import org.gradle.api.tasks.SourceSetContainer
+import org.gradle.util.ConfigureUtil
+
+/**
+ * Is mixed in into the project when applying the {@org.gradle.api.plugins.JavaBasePlugin} or the
+ * {@org.gradle.api.plugins.JavaPlugin}.
+ *
+ * @author Hans Dockter
+ */
+class JavaPluginConvention {
+    ProjectInternal project
+
+    String dependencyCacheDirName
+
+    /**
+     * The name of the docs directory. Can be a name or a path relative to the build dir.
+     */
+    String docsDirName
+
+    /**
+     * The name of the test results directory. Can be a name or a path relative to the build dir.
+     */
+    String testResultsDirName
+
+    /**
+     * The name of the test reports directory. Can be a name or a path relative to the build dir.
+     */
+    String testReportDirName
+
+    /**
+     * The source sets container.
+     */
+    final SourceSetContainer sourceSets
+
+    private JavaVersion srcCompat
+    private JavaVersion targetCompat
+
+    @Deprecated
+    List metaInf
+
+    @Deprecated
+    DefaultManifest manifest
+
+    JavaPluginConvention(Project project) {
+        this.project = project
+        def classGenerator = project.serviceRegistryFactory.get(ClassGenerator)
+        sourceSets = classGenerator.newInstance(DefaultSourceSetContainer.class, project.fileResolver, project.tasks, classGenerator)
+        dependencyCacheDirName = 'dependency-cache'
+        docsDirName = 'docs'
+        testResultsDirName = 'test-results'
+        testReportDirName = 'tests'
+        manifest = manifest();
+        metaInf = []
+    }
+
+    def sourceSets(Closure closure) {
+        sourceSets.configure(closure)
+    }
+
+    File getDependencyCacheDir() {
+        project.fileResolver.withBaseDir(project.buildDir).resolve(dependencyCacheDirName)
+    }
+
+    /**
+     * Returns a file pointing to the root directory supposed to be used for all docs.
+     */
+    File getDocsDir() {
+        project.fileResolver.withBaseDir(project.buildDir).resolve(docsDirName)
+    }
+
+    /**
+     * Returns a file pointing to the root directory of the test results.
+     */
+    File getTestResultsDir() {
+        project.fileResolver.withBaseDir(project.buildDir).resolve(testResultsDirName)
+    }
+
+    /**
+     * Returns a file pointing to the root directory to be used for reports.
+     */
+    File getTestReportDir() {
+        project.fileResolver.withBaseDir(reportsDir).resolve(testReportDirName)
+    }
+
+    private File getReportsDir() {
+        project.convention.plugins.reportingBase.reportsDir
+    }
+
+    /**
+     * Returns the source compatibility used for compiling Java sources.
+     */
+    JavaVersion getSourceCompatibility() {
+            srcCompat ?: JavaVersion.VERSION_1_5
+    }
+
+    /**
+     * Sets the source compatibility used for compiling Java sources.
+     *
+     * @value The value for the source compatibilty as defined by   {@link JavaVersion#toVersion(Object)}
+     */
+    void setSourceCompatibility(def value) {
+        srcCompat = JavaVersion.toVersion(value)
+    }
+
+    /**
+     * Returns the target compatibility used for compiling Java sources.
+     */
+    JavaVersion getTargetCompatibility() {
+            targetCompat ?: sourceCompatibility
+    }
+
+    /**
+     * Sets the target compatibility used for compiling Java sources.
+     *
+     * @value The value for the target compatibilty as defined by {@link JavaVersion#toVersion(Object)}
+     */
+    void setTargetCompatibility(def value) {
+        targetCompat = JavaVersion.toVersion(value)
+    }
+
+    /**
+     * Returns a new instance of an {@link Manifest}.
+     */
+    public Manifest manifest() {
+        return manifest(null);
+    }
+
+    /**
+     * Returns a new instance of an {@link Manifest}. The closure configures
+     * the new manifest instance before it is returned.
+     */
+    public Manifest manifest(Closure closure) {
+        return ConfigureUtil.configure(closure, new DefaultManifest(((ProjectInternal) getProject()).fileResolver));
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/ProcessResources.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/ProcessResources.java
new file mode 100644
index 0000000..0faf8ec
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/ProcessResources.java
@@ -0,0 +1,30 @@
+/*
+ * 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.internal.tasks.compile.SimpleStaleClassCleaner;
+import org.gradle.api.internal.tasks.compile.StaleClassCleaner;
+import org.gradle.api.tasks.Copy;
+
+public class ProcessResources extends Copy {
+    @Override
+    protected void copy() {
+        StaleClassCleaner cleaner = new SimpleStaleClassCleaner(getOutputs());
+        cleaner.setDestinationDir(getDestinationDir());
+        cleaner.execute();
+        super.copy();
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/ProjectReportsPlugin.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/ProjectReportsPlugin.java
new file mode 100644
index 0000000..97af509
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/ProjectReportsPlugin.java
@@ -0,0 +1,88 @@
+/*
+ * 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.plugins;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.internal.IConventionAware;
+import org.gradle.api.tasks.ConventionValue;
+import org.gradle.api.tasks.diagnostics.DependencyReportTask;
+import org.gradle.api.tasks.diagnostics.PropertyReportTask;
+import org.gradle.api.tasks.diagnostics.TaskReportTask;
+
+import java.io.File;
+
+/**
+ * <p>A {@link Plugin} which adds some project visualization report tasks to a project.</p>
+ */
+public class ProjectReportsPlugin implements Plugin<Project> {
+    public static final String TASK_REPORT = "taskReport";
+    public static final String PROPERTY_REPORT = "propertyReport";
+    public static final String DEPENDENCY_REPORT = "dependencyReport";
+    public static final String PROJECT_REPORT = "projectReport";
+
+    public void apply(Project project) {
+        project.getPlugins().apply(ReportingBasePlugin.class);
+        project.getConvention().getPlugins().put("projectReports", new ProjectReportsPluginConvention(project));
+
+        TaskReportTask taskReportTask = project.getTasks().add(TASK_REPORT, TaskReportTask.class);
+        taskReportTask.setDescription("Generates a report about your tasks.");
+        taskReportTask.conventionMapping("outputFile", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return new File(convention.getPlugin(ProjectReportsPluginConvention.class).getProjectReportDir(), "tasks.txt");
+            }
+        });
+        taskReportTask.conventionMapping("projects", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return convention.getPlugin(ProjectReportsPluginConvention.class).getProjects();
+            }
+        });
+
+        PropertyReportTask propertyReportTask = project.getTasks().add(PROPERTY_REPORT, PropertyReportTask.class);
+        propertyReportTask.setDescription("Generates a report about your properties.");
+        propertyReportTask.conventionMapping("outputFile", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return new File(convention.getPlugin(ProjectReportsPluginConvention.class).getProjectReportDir(), "properties.txt");
+            }
+        });
+        propertyReportTask.conventionMapping("projects", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return convention.getPlugin(ProjectReportsPluginConvention.class).getProjects();
+            }
+        });
+
+        DependencyReportTask dependencyReportTask = project.getTasks().add(DEPENDENCY_REPORT,
+                DependencyReportTask.class);
+        dependencyReportTask.setDescription("Generates a report about your library dependencies.");
+        dependencyReportTask.conventionMapping("outputFile", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return new File(convention.getPlugin(ProjectReportsPluginConvention.class).getProjectReportDir(), "dependencies.txt");
+            }
+        });
+        dependencyReportTask.conventionMapping("projects", new ConventionValue() {
+            public Object getValue(Convention convention, IConventionAware conventionAwareObject) {
+                return convention.getPlugin(ProjectReportsPluginConvention.class).getProjects();
+            }
+        });
+
+
+        Task projectReportTask = project.getTasks().add(PROJECT_REPORT);
+        projectReportTask.dependsOn(TASK_REPORT, PROPERTY_REPORT, DEPENDENCY_REPORT);
+        projectReportTask.setDescription("Generates a report about your project.");
+        projectReportTask.setGroup("reporting");
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/ProjectReportsPluginConvention.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/ProjectReportsPluginConvention.groovy
new file mode 100644
index 0000000..42aeb87
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/ProjectReportsPluginConvention.groovy
@@ -0,0 +1,36 @@
+/*
+ * 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.plugins
+
+import org.gradle.api.Project
+import org.gradle.util.WrapUtil
+
+public class ProjectReportsPluginConvention {
+    String projectReportDirName = 'project'
+    private final Project project
+
+    def ProjectReportsPluginConvention(Project project) {
+        this.project = project;
+    }
+    
+    File getProjectReportDir() {
+        new File(project.convention.getPlugin(ReportingBasePluginConvention).reportsDir, projectReportDirName)
+    }
+
+    Set<Project> getProjects() {
+        WrapUtil.toSet(project)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/ReportingBasePlugin.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/ReportingBasePlugin.java
new file mode 100644
index 0000000..0a66df0
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/ReportingBasePlugin.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.api.plugins;
+
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.internal.project.ProjectInternal;
+
+/**
+ * <p>A {@link Plugin} which provides the basic skeleton for reporting.</p>
+ *
+ * <p>This plugin adds the following convention objects to the project:</p>
+ *
+ * <ul>
+ *
+ * <li>{@link ReportingBasePluginConvention}</li>
+ *
+ * </ul>
+ */
+public class ReportingBasePlugin implements Plugin<Project> {
+    public void apply(Project project) {
+        Convention convention = project.getConvention();
+        convention.getPlugins().put("reportingBase", new ReportingBasePluginConvention((ProjectInternal) project));
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/ReportingBasePluginConvention.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/ReportingBasePluginConvention.java
new file mode 100644
index 0000000..1ea83ba
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/ReportingBasePluginConvention.java
@@ -0,0 +1,75 @@
+/*
+ * 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.plugins;
+
+import org.gradle.api.Project;
+import org.gradle.api.internal.project.ProjectInternal;
+
+import java.io.File;
+
+/**
+ * <p>A {@code BasePluginConvention} defines the convention properties and methods used by the {@link
+ * ReportingBasePlugin}</p>
+ */
+public class ReportingBasePluginConvention {
+    private String reportsDirName = "reports";
+    private ProjectInternal project;
+
+    public ReportingBasePluginConvention(ProjectInternal project) {
+        this.project = project;
+    }
+
+    /**
+     * Returns the name of the reports directory, relative to the project's build directory.
+     *
+     * @return The reports directory name. Never returns null.
+     */
+    public String getReportsDirName() {
+        return reportsDirName;
+    }
+
+    /**
+     * Sets the name of the reports directory, relative to the project's build directory.
+     *
+     * @param reportsDirName The reports directory name. Should not be null.
+     */
+    public void setReportsDirName(String reportsDirName) {
+        this.reportsDirName = reportsDirName;
+    }
+
+    /**
+     * Returns the directory containing all reports for this project
+     *
+     * @return The reports directory. Never returns null.
+     */
+    public File getReportsDir() {
+        return project.getFileResolver().withBaseDir(project.getBuildDir()).resolve(reportsDirName);
+    }
+
+    /**
+     * Returns the title for API documentation for the project.
+     *
+     * @return The title. Never returns null.
+     */
+    public String getApiDocTitle() {
+        Object version = project.getVersion();
+        if (Project.DEFAULT_VERSION.equals(version)) {
+            return String.format("%s API", project.getName());
+        } else {
+            return String.format("%s %s API", project.getName(), version);
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/WarPlugin.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/WarPlugin.java
new file mode 100644
index 0000000..df7ee46
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/WarPlugin.java
@@ -0,0 +1,112 @@
+/*
+ * 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.plugins;
+
+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.PublishArtifact;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.artifacts.publish.ArchivePublishArtifact;
+import org.gradle.api.tasks.SourceSet;
+import org.gradle.api.tasks.bundling.Jar;
+import org.gradle.api.tasks.bundling.War;
+
+import java.util.concurrent.Callable;
+
+/**
+ * <p>A {@link Plugin} which extends the {@link JavaPlugin} to add tasks which assemble a web application into a WAR
+ * file.</p>
+ *
+ * @author Hans Dockter
+ */
+public class WarPlugin implements Plugin<Project> {
+    public static final String PROVIDED_COMPILE_CONFIGURATION_NAME = "providedCompile";
+    public static final String PROVIDED_RUNTIME_CONFIGURATION_NAME = "providedRuntime";
+    public static final String WAR_TASK_NAME = "war";
+    public static final String WEB_APP_GROUP = "web application";
+
+    public void apply(final Project project) {
+        project.getPlugins().apply(JavaPlugin.class);
+        final WarPluginConvention pluginConvention = new WarPluginConvention(project);
+        project.getConvention().getPlugins().put("war", pluginConvention);
+
+        project.getTasks().withType(War.class).allTasks(new Action<War>() {
+            public void execute(War task) {
+                task.from(new Callable() {
+                    public Object call() throws Exception {
+                        return pluginConvention.getWebAppDir();
+                    }
+                });
+                task.dependsOn(new Callable() {
+                    public Object call() throws Exception {
+                        return project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().getByName(
+                                SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath();
+                    }
+                });
+                task.classpath(new Object[] {new Callable() {
+                    public Object call() throws Exception {
+                        FileCollection runtimeClasspath = project.getConvention().getPlugin(JavaPluginConvention.class)
+                                .getSourceSets().getByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath();
+                        Configuration providedRuntime = project.getConfigurations().getByName(
+                                PROVIDED_RUNTIME_CONFIGURATION_NAME);
+                        return runtimeClasspath.minus(providedRuntime);
+                    }
+                }});
+            }
+        });
+        
+        War war = project.getTasks().add(WAR_TASK_NAME, War.class);
+        war.setDescription("Generates a war archive with all the compiled classes, the web-app content and the libraries.");
+        war.setGroup(BasePlugin.BUILD_GROUP);
+        Configuration archivesConfiguration = project.getConfigurations().getByName(Dependency.ARCHIVES_CONFIGURATION);
+        disableJarTaskAndRemoveFromArchivesConfiguration(project, archivesConfiguration);
+        archivesConfiguration.addArtifact(new ArchivePublishArtifact(war));
+        configureConfigurations(project.getConfigurations());
+    }
+
+    private void disableJarTaskAndRemoveFromArchivesConfiguration(Project project, Configuration archivesConfiguration) {
+        Jar jarTask = (Jar) project.getTasks().getByName(JavaPlugin.JAR_TASK_NAME);
+        jarTask.setEnabled(false);
+        removeJarTaskFromArchivesConfiguration(archivesConfiguration, jarTask);
+    }
+
+    private void removeJarTaskFromArchivesConfiguration(Configuration archivesConfiguration, Jar jar) {
+        // todo: There should be a richer connection between an ArchiveTask and a PublishArtifact
+        for (PublishArtifact publishArtifact : archivesConfiguration.getAllArtifacts()) {
+            if (publishArtifact instanceof ArchivePublishArtifact) {
+                ArchivePublishArtifact archivePublishArtifact = (ArchivePublishArtifact) publishArtifact;
+                if (archivePublishArtifact.getArchiveTask() == jar) {
+                    archivesConfiguration.removeArtifact(publishArtifact);
+                }
+            }
+        }
+    }
+
+    public void configureConfigurations(ConfigurationContainer configurationContainer) {
+        Configuration provideCompileConfiguration = configurationContainer.add(PROVIDED_COMPILE_CONFIGURATION_NAME).setVisible(false).
+                setDescription("Additional compile classpath for libraries that should not be part of the WAR archive.");
+        Configuration provideRuntimeConfiguration = configurationContainer.add(PROVIDED_RUNTIME_CONFIGURATION_NAME).setVisible(false).
+                extendsFrom(provideCompileConfiguration).
+                setDescription("Additional runtime classpath for libraries that should not be part of the WAR archive.");
+        configurationContainer.getByName(JavaPlugin.COMPILE_CONFIGURATION_NAME).extendsFrom(provideCompileConfiguration);
+        configurationContainer.getByName(JavaPlugin.RUNTIME_CONFIGURATION_NAME).extendsFrom(provideRuntimeConfiguration);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/WarPluginConvention.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/WarPluginConvention.groovy
new file mode 100644
index 0000000..2a4f998
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/plugins/WarPluginConvention.groovy
@@ -0,0 +1,32 @@
+/*
+ * 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.plugins
+
+import org.gradle.api.Project
+
+public class WarPluginConvention {
+    String webAppDirName
+    final Project project
+
+    def WarPluginConvention(Project project) {
+        this.project = project
+        webAppDirName = 'src/main/webapp'
+    }
+
+    File getWebAppDir() {
+        project.file(webAppDirName)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/GroovySourceSet.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/GroovySourceSet.java
new file mode 100644
index 0000000..c93c9ed
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/GroovySourceSet.java
@@ -0,0 +1,50 @@
+/*
+ * 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.tasks;
+
+import groovy.lang.Closure;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.SourceDirectorySet;
+
+/**
+ * A {@code GroovySourceSetConvention} defines the properties and methods added to a {@link SourceSet} by the {@link
+ * org.gradle.api.plugins.GroovyPlugin}.
+ */
+public interface GroovySourceSet {
+    /**
+     * Returns the source to be compiled by the Groovy compiler for this source set. Any Java source present in this set
+     * will be passed to the Groovy compiler for joint compilation.
+     *
+     * @return The Groovy/Java source. Never returns null.
+     */
+    SourceDirectorySet getGroovy();
+
+    /**
+     * Configures the Groovy source for this set. The given closure is used to configure the {@code SourceDirectorySet}
+     * which contains the Groovy source.
+     *
+     * @param configureClosure The closure to use to configure the Groovy source.
+     * @return this
+     */
+    GroovySourceSet groovy(Closure configureClosure);
+
+    /**
+     * All Groovy source for this source set.
+     *
+     * @return the Groovy source. Never returns null.
+     */
+    FileTree getAllGroovy();
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/SourceSet.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/SourceSet.java
new file mode 100644
index 0000000..b2c7683
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/SourceSet.java
@@ -0,0 +1,189 @@
+/*
+ * 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 groovy.lang.Closure;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.SourceDirectorySet;
+
+import java.io.File;
+
+/**
+ * <p>A {@code SourceSet} represents a logical group of Java source and resources.</p>
+ */
+public interface SourceSet {
+    /**
+     * The name of the main source set.
+     */
+    String MAIN_SOURCE_SET_NAME = "main";
+
+    /**
+     * The name of the test source set.
+     */
+    String TEST_SOURCE_SET_NAME = "test";
+
+    /**
+     * Returns the name of this source set.
+     *
+     * @return The name. Never returns null.
+     */
+    String getName();
+
+    /**
+     * Returns the classpath used to compile this source.
+     *
+     * @return The classpath. Never returns null.
+     */
+    FileCollection getCompileClasspath();
+
+    /**
+     * Sets the classpath used to compile this source.
+     *
+     * @param classpath The classpath. Should not be null.
+     */
+    void setCompileClasspath(FileCollection classpath);
+
+    /**
+     * Returns the classpath used to execute this source.
+     *
+     * @return The classpath. Never returns null.
+     */
+    FileCollection getRuntimeClasspath();
+
+    /**
+     * Sets the classpath used to execute this source.
+     *
+     * @param classpath The classpath. Should not be null.
+     */
+    void setRuntimeClasspath(FileCollection classpath);
+
+    /**
+     * Returns the directory to assemble the compiled classes into.
+     *
+     * @return The classes dir. Never returns null.
+     */
+    File getClassesDir();
+
+    /**
+     * Sets the directory to assemble the compiled classes into.
+     *
+     * @param classesDir the classes dir. Should not be null.
+     */
+    void setClassesDir(File classesDir);
+
+    /**
+     * Returns the compiled classes directory for this source set.
+     *
+     * @return The classes dir, as a {@link FileCollection}.
+     */
+    FileCollection getClasses();
+
+    /**
+     * Registers a set of tasks which are responsible for compiling this source set into the classes directory. The
+     * paths are evaluated as for {@link org.gradle.api.Task#dependsOn(Object...)}.
+     *
+     * @param taskPaths The tasks which compile this source set.
+     * @return this
+     */
+    SourceSet compiledBy(Object... taskPaths);
+
+    /**
+     * Returns the non-Java resources which are to be copied into the class output directory.
+     *
+     * @return the resources. Never returns null.
+     */
+    SourceDirectorySet getResources();
+
+    /**
+     * Configures the non-Java resources for this set. The given closure is used to configure the {@code
+     * SourceDirectorySet} which contains the resources.
+     *
+     * @param configureClosure The closure to use to configure the resources.
+     * @return this
+     */
+    SourceSet resources(Closure configureClosure);
+
+    /**
+     * Returns the Java source which is to be compiled by the Java compiler into the class output directory.
+     *
+     * @return the Java source. Never returns null.
+     */
+    SourceDirectorySet getJava();
+
+    /**
+     * Configures the Java source for this set. The given closure is used to configure the {@code SourceDirectorySet}
+     * which contains the Java source.
+     *
+     * @param configureClosure The closure to use to configure the Java source.
+     * @return this
+     */
+    SourceSet java(Closure configureClosure);
+
+    /**
+     * All Java source for this source set. This includes, for example, source which is directly compiled, and source
+     * which is indirectly compiled through joint compilation.
+     *
+     * @return the Java source. Never returns null.
+     */
+    FileTree getAllJava();
+
+    /**
+     * All source for this source set.
+     *
+     * @return the source. Never returns null.
+     */
+    FileTree getAllSource();
+
+    /**
+     * Returns the name of the classes task for this source set.
+     *
+     * @return The task name. Never returns null.
+     */
+    String getClassesTaskName();
+
+    /**
+     * Returns the name of the resource process task for this source set.
+     *
+     * @return The task name. Never returns null.
+     */
+    String getProcessResourcesTaskName();
+
+    /**
+     * Returns the name of the compile Java task for this source set.
+     *
+     * @return The task name. Never returns null.
+     */
+    String getCompileJavaTaskName();
+
+    /**
+     * Returns the name of a compile task for this source set.
+     *
+     * @param language The language to be compiled.
+     * @return The task name. Never returns null.
+     */
+    String getCompileTaskName(String language);
+
+    /**
+     * Returns the name of a task for this source set.
+     *
+     * @param verb The action, may be null.
+     * @param target The target, may be null
+     *
+     * @return The task name, generally of the form ${verb}${name}${noun}
+     */
+    String getTaskName(String verb, String target);
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/SourceSetContainer.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/SourceSetContainer.java
new file mode 100644
index 0000000..678af03
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/SourceSetContainer.java
@@ -0,0 +1,46 @@
+/*
+ * 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.tasks;
+
+import groovy.lang.Closure;
+import org.gradle.api.NamedDomainObjectContainer;
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.NamedDomainObjectCollection;
+
+/**
+ * A {@code SourceSetContainer} manages a set of {@link SourceSet} objects.
+ */
+public interface SourceSetContainer extends NamedDomainObjectContainer<SourceSet>, NamedDomainObjectCollection<SourceSet> {
+    /**
+     * Adds a source set with the given name.
+     *
+     * @param name The name of the new source set.
+     * @return The newly added source set.
+     * @throws org.gradle.api.InvalidUserDataException when a source set with the given name already exists in this container.
+     */
+    SourceSet add(String name) throws InvalidUserDataException;
+
+    /**
+     * Adds a source set with the given name. The given configuration closure is executed against the source set
+     * before it is returned from this method.
+     *
+     * @param name The name of the new source set.
+     * @param configureClosure The closure to use to configure the source set.
+     * @return The newly added source set.
+     * @throws InvalidUserDataException when a source set with the given name already exists in this container.
+     */
+    SourceSet add(String name, Closure configureClosure) throws InvalidUserDataException;
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/bundling/Jar.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/bundling/Jar.groovy
new file mode 100644
index 0000000..684e3fc
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/bundling/Jar.groovy
@@ -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.api.tasks.bundling
+
+import org.gradle.api.file.CopySpec
+import org.gradle.api.internal.file.MapFileTree
+import org.gradle.api.java.archives.internal.DefaultManifest
+import org.gradle.util.ConfigureUtil
+import org.gradle.api.internal.file.copy.CopySpecImpl
+
+/**
+* @author Hans Dockter
+*/
+
+public class Jar extends Zip {
+    public static final String DEFAULT_EXTENSION = 'jar'
+
+    private DefaultManifest manifest
+    private final CopySpecImpl metaInf
+
+    Jar() {
+        extension = DEFAULT_EXTENSION
+        manifest = new DefaultManifest(project.fileResolver)
+        // Add these as separate specs, so they are not affected by the changes to the main spec
+        metaInf = copyAction.rootSpec.addFirst().into('META-INF')
+        metaInf.addChild().from {
+            MapFileTree manifestSource = new MapFileTree(temporaryDir)
+            manifestSource.add('MANIFEST.MF') {OutputStream outstr ->
+                DefaultManifest manifest = getManifest() ?: new DefaultManifest(null)
+                manifest.writeTo(new OutputStreamWriter(outstr))
+            }
+            manifestSource
+        }
+    }
+
+    public DefaultManifest getManifest() {
+        return manifest;
+    }
+
+    public void setManifest(DefaultManifest manifest) {
+        this.manifest = manifest;
+    }
+
+    public Jar manifest(Closure configureClosure) {
+        if (getManifest() == null) {
+            manifest = new DefaultManifest(project.fileResolver)
+        }
+        ConfigureUtil.configure(configureClosure, getManifest());
+        return this;
+    }
+
+    public CopySpec getMetaInf() {
+        return metaInf.addChild()
+    }
+
+    public CopySpec metaInf(Closure configureClosure) {
+        return ConfigureUtil.configure(configureClosure, getMetaInf())
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/bundling/War.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/bundling/War.groovy
new file mode 100644
index 0000000..a485acf
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/bundling/War.groovy
@@ -0,0 +1,94 @@
+/*
+ * 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.bundling
+
+import org.gradle.api.file.CopySpec
+import org.gradle.api.file.FileCollection
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.Optional
+import org.gradle.util.ConfigureUtil
+import org.gradle.api.internal.file.copy.CopySpecImpl
+
+/**
+ * @author Hans Dockter
+ */
+class War extends Jar {
+    public static final String WAR_EXTENSION = 'war'
+
+    private File webXml
+
+    private FileCollection classpath
+    private final CopySpecImpl webInf
+
+    War() {
+        extension = WAR_EXTENSION
+        // Add these as separate specs, so they are not affected by the changes to the main spec
+        webInf = copyAction.rootSpec.addChild().into('WEB-INF')
+        webInf.into('classes') {
+            from {
+                def classpath = getClasspath()
+                classpath ? classpath.filter {File file -> file.isDirectory()} : []
+            }
+        }
+        webInf.into('lib') {
+            from {
+                def classpath = getClasspath()
+                classpath ? classpath.filter {File file -> file.isFile()} : []
+            }
+        }
+        webInf.into('') {
+            from {
+                getWebXml()
+            }
+            rename {
+                'web.xml'
+            }
+        }
+    }
+
+    CopySpec getWebInf() {
+        return webInf.addChild()
+    }
+
+    CopySpec webInf(Closure configureClosure) {
+        return ConfigureUtil.configure(configureClosure, getWebInf())
+    }
+
+    @InputFiles @Optional
+    FileCollection getClasspath() {
+        return classpath
+    }
+
+    void setClasspath(Object classpath) {
+        this.classpath = project.files(classpath)
+    }
+
+    void classpath(Object... classpath) {
+        FileCollection oldClasspath = getClasspath()
+        this.classpath = project.files(oldClasspath ?: [], classpath)
+    }
+
+    @InputFile @Optional
+    public File getWebXml() {
+        return webXml;
+    }
+
+    public void setWebXml(File webXml) {
+        this.webXml = webXml;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/AbstractCompile.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/AbstractCompile.java
new file mode 100644
index 0000000..e13a03b
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/AbstractCompile.java
@@ -0,0 +1,67 @@
+/*
+ * 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.compile;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.tasks.*;
+
+import java.io.File;
+
+public abstract class AbstractCompile extends SourceTask {
+    private File destinationDir;
+    private String sourceCompatibility;
+    private String targetCompatibility;
+    private FileCollection classpath;
+
+    @TaskAction
+    protected abstract void compile();
+    
+    @InputFiles
+    public FileCollection getClasspath() {
+        return classpath;
+    }
+
+    public void setClasspath(FileCollection configuration) {
+        this.classpath = configuration;
+    }
+
+    @OutputDirectory
+    public File getDestinationDir() {
+        return destinationDir;
+    }
+
+    public void setDestinationDir(File destinationDir) {
+        this.destinationDir = destinationDir;
+    }
+
+    @Input
+    public String getSourceCompatibility() {
+        return sourceCompatibility;
+    }
+
+    public void setSourceCompatibility(String sourceCompatibility) {
+        this.sourceCompatibility = sourceCompatibility;
+    }
+
+    @Input
+    public String getTargetCompatibility() {
+        return targetCompatibility;
+    }
+
+    public void setTargetCompatibility(String targetCompatibility) {
+        this.targetCompatibility = targetCompatibility;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/AbstractOptions.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/AbstractOptions.groovy
new file mode 100644
index 0000000..42d8eae
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/AbstractOptions.groovy
@@ -0,0 +1,79 @@
+/*
+ * 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.api.tasks.compile
+
+import java.lang.reflect.Field
+import java.lang.reflect.Modifier
+
+/**
+ * @author Hans Dockter
+ */
+class AbstractOptions {
+
+    void define(Map args) {
+        args.each {String key, Object value ->
+            this."$key" = value
+        }
+    }
+
+    Map optionMap() {
+        getClass().declaredFields.findAll {Field field -> isOptionField(field)}.inject([:]) {Map optionMap, Field field ->
+            addValueToMapIfNotNull(optionMap, field)
+        }
+    }
+
+    // todo: change modifier to private when GROOVY-2565 is fixed.
+    protected Map addValueToMapIfNotNull(Map map, Field field) {
+        def value = this."${field.name}"
+        if (value != null) {map.put(antProperty(field.name), antValue(field.name, value))}
+        map
+    }
+
+    // todo: change modifier to private when GROOVY-2565 is fixed.
+    protected boolean isOptionField(Field field) {
+        ((field.getModifiers() & Modifier.STATIC) == 0) &&
+                (!field.getName().equals("metaClass")) &&
+                (!excludedFieldsFromOptionMap().contains(field.name))
+    }
+
+    private def antProperty(String fieldName) {
+        String antProperty = null
+        if (fieldName2AntMap().keySet().contains(fieldName)) {
+            antProperty = fieldName2AntMap()[fieldName]
+        }
+        antProperty ?: fieldName
+    }
+
+    private def antValue(String fieldName, def value) {
+        if (fieldValue2AntMap().keySet().contains(fieldName)) {
+            return fieldValue2AntMap()[fieldName]()
+        }
+        value
+    }
+
+    List excludedFieldsFromOptionMap() {
+        []
+    }
+
+    Map fieldName2AntMap() {
+        [:]
+    }
+
+    Map fieldValue2AntMap() {
+        [:]
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/AntDepend.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/AntDepend.java
new file mode 100644
index 0000000..9185555
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/AntDepend.java
@@ -0,0 +1,37 @@
+/*
+ * 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.tasks.compile;
+
+import org.apache.tools.ant.taskdefs.optional.depend.Depend;
+import org.apache.tools.ant.types.Path;
+import org.apache.tools.ant.BuildException;
+
+public class AntDepend extends Depend {
+    private Path src;
+
+    public Path createSrc() {
+        if (src == null) {
+            src = new Path(getProject());
+        }
+        return src.createPath();
+    }
+
+    @Override
+    public void execute() throws BuildException {
+        setSrcdir(src);
+        super.execute();
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/Compile.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/Compile.java
new file mode 100644
index 0000000..6e1d7c7
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/Compile.java
@@ -0,0 +1,77 @@
+/*
+ * 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.api.tasks.compile;
+
+import org.gradle.api.internal.project.AntBuilderFactory;
+import org.gradle.api.internal.tasks.compile.AntJavaCompiler;
+import org.gradle.api.internal.tasks.compile.IncrementalJavaCompiler;
+import org.gradle.api.internal.tasks.compile.JavaCompiler;
+import org.gradle.api.tasks.Nested;
+import org.gradle.api.tasks.OutputDirectory;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.api.tasks.WorkResult;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class Compile extends AbstractCompile {
+
+    private JavaCompiler javaCompiler;
+
+    private File dependencyCacheDir;
+
+    public Compile() {
+        AntBuilderFactory antBuilderFactory = getServices().get(AntBuilderFactory.class);
+        javaCompiler = new IncrementalJavaCompiler(new AntJavaCompiler(antBuilderFactory), antBuilderFactory, getOutputs());
+    }
+
+    @TaskAction
+    protected void compile() {
+        javaCompiler.setSource(getSource());
+        javaCompiler.setDestinationDir(getDestinationDir());
+        javaCompiler.setClasspath(getClasspath());
+        javaCompiler.setDependencyCacheDir(getDependencyCacheDir());
+        javaCompiler.setSourceCompatibility(getSourceCompatibility());
+        javaCompiler.setTargetCompatibility(getTargetCompatibility());
+        WorkResult result = javaCompiler.execute();
+        setDidWork(result.getDidWork());
+    }
+
+    @OutputDirectory
+    public File getDependencyCacheDir() {
+        return dependencyCacheDir;
+    }
+
+    public void setDependencyCacheDir(File dependencyCacheDir) {
+        this.dependencyCacheDir = dependencyCacheDir;
+    }
+
+    @Nested
+    public CompileOptions getOptions() {
+        return javaCompiler.getCompileOptions();
+    }
+
+    public JavaCompiler getJavaCompiler() {
+        return javaCompiler;
+    }
+
+    public void setJavaCompiler(JavaCompiler javaCompiler) {
+        this.javaCompiler = javaCompiler;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/CompileOptions.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/CompileOptions.groovy
new file mode 100644
index 0000000..f594470
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/CompileOptions.groovy
@@ -0,0 +1,104 @@
+/*
+ * 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.api.tasks.compile
+
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Nested
+import org.gradle.api.tasks.Optional
+
+/**
+ * @author Hans Dockter
+ */
+class CompileOptions extends AbstractOptions {
+    @Input
+    boolean failOnError = true
+    boolean verbose = false
+    boolean listFiles = false
+    boolean deprecation = false
+    boolean warnings = true
+    @Input @Optional
+    String encoding = null
+    @Input
+    boolean optimize
+    @Input
+    boolean debug = true
+    @Nested
+    DebugOptions debugOptions = new DebugOptions()
+    boolean fork = false
+    @Nested
+    ForkOptions forkOptions = new ForkOptions()
+    boolean useDepend = false
+    DependOptions dependOptions = new DependOptions()
+    @Input @Optional
+    String compiler = null
+    @Input
+    boolean includeJavaRuntime = false
+    @Input @Optional
+    String bootClasspath = null
+    @Input @Optional
+    String extensionDirs = null
+    @Input
+    List compilerArgs = []
+
+    CompileOptions fork(Map forkArgs) {
+        fork = true
+        forkOptions.define(forkArgs)
+        this
+    }
+
+    CompileOptions debug(Map debugArgs) {
+        debug = true
+        debugOptions.define(debugArgs)
+        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.
+     */
+    CompileOptions depend(Map dependArgs) {
+        useDepend = true
+        dependOptions.define(dependArgs)
+        this
+    }
+
+    List excludedFieldsFromOptionMap() {
+        ['debugOptions', 'forkOptions', 'compilerArgs', 'dependOptions', 'useDepend']
+    }
+
+    Map fieldName2AntMap() {
+        [
+                warnings: 'nowarn',
+                bootClasspath: 'bootclasspath',
+                extensionDirs: 'extdirs',
+                failOnError: 'failonerror',
+                listFiles: 'listfiles',
+        ]
+    }
+
+    Map fieldValue2AntMap() {
+        [
+                warnings: {!warnings}
+        ]
+    }
+
+    Map optionMap() {
+        super.optionMap() + forkOptions.optionMap() + debugOptions.optionMap()
+    }
+}
+
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/DebugOptions.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/DebugOptions.groovy
new file mode 100644
index 0000000..739e1fd
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/DebugOptions.groovy
@@ -0,0 +1,32 @@
+/*
+ * 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.api.tasks.compile
+
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Optional
+
+/**
+ * @author Hans Dockter
+ */
+class DebugOptions extends AbstractOptions {
+    @Input @Optional
+    String debugLevel = null
+
+    Map fieldName2AntMap() {
+        [debugLevel: 'debuglevel']
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/DependOptions.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/DependOptions.groovy
new file mode 100644
index 0000000..75e9105
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/DependOptions.groovy
@@ -0,0 +1,51 @@
+/*
+ * 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.compile
+
+
+/**
+ * <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.</p>
+ * See the <a href="http://ant.apache.org/manual/OptionalTasks/depend.html" target="_blank">Ant Reference</a>
+ * for more information.</p>
+ * <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>
+ * There is an additional "useCache" boolean option to enable/disable caching of dependency information.  It is true
+ * by default.</p>
+ * @author Steve Appling
+ */
+public class DependOptions extends AbstractOptions {
+    boolean useCache = true
+    boolean closure = false
+    boolean dump = false
+    String classpath = ""
+    boolean warnOnRmiStubs = true
+
+    List excludedFieldsFromOptionMap() {
+        ['srcDir', 'destDir', 'cache', 'useCache']
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/ForkOptions.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/ForkOptions.groovy
new file mode 100644
index 0000000..c816b7d
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/ForkOptions.groovy
@@ -0,0 +1,38 @@
+/*
+ * 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.api.tasks.compile
+
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Optional
+
+/**
+ * @author Hans Dockter
+ */
+class ForkOptions extends AbstractOptions {
+    @Input @Optional
+    String executable = null
+    String memoryInitialSize = null
+    String memoryMaximumSize = null
+    String tempDir = null
+
+    List jvmArgs
+
+    Map fieldName2AntMap() {
+        [tempDir: 'tempdir']
+    }
+    
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompile.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompile.java
new file mode 100644
index 0000000..1359674
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompile.java
@@ -0,0 +1,99 @@
+/*
+ * 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.tasks.compile;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.project.IsolatedAntBuilder;
+import org.gradle.api.internal.tasks.compile.AntGroovyCompiler;
+import org.gradle.api.internal.tasks.compile.GroovyJavaJointCompiler;
+import org.gradle.api.internal.tasks.compile.IncrementalGroovyCompiler;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.Nested;
+import org.gradle.api.tasks.WorkResult;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * @author Hans Dockter
+ */
+public class GroovyCompile extends AbstractCompile {
+    private GroovyJavaJointCompiler compiler;
+
+    private FileCollection groovyClasspath;
+
+    public GroovyCompile() {
+        IsolatedAntBuilder antBuilder = getServices().get(IsolatedAntBuilder.class);
+        ClassPathRegistry classPathRegistry = getServices().get(ClassPathRegistry.class);
+        compiler = new IncrementalGroovyCompiler(new AntGroovyCompiler(antBuilder, classPathRegistry), getOutputs());
+    }
+
+    protected void compile() {
+        // todo We need to understand why it is not good enough to put groovy and ant in the task classpath but also Junit. As we don't understand we put the whole testCompile in it right now. It doesn't hurt, but understanding is better :)
+        List<File> taskClasspath = new ArrayList<File>(getGroovyClasspath().getFiles());
+        throwExceptionIfTaskClasspathIsEmpty(taskClasspath);
+        compiler.setSource(getSource());
+        compiler.setDestinationDir(getDestinationDir());
+        compiler.setClasspath(getClasspath());
+        compiler.setSourceCompatibility(getSourceCompatibility());
+        compiler.setTargetCompatibility(getTargetCompatibility());
+        compiler.setGroovyClasspath(taskClasspath);
+        WorkResult result = compiler.execute();
+        setDidWork(result.getDidWork());
+    }
+
+    private void throwExceptionIfTaskClasspathIsEmpty(Collection<File> taskClasspath) {
+        if (taskClasspath.size() == 0) {
+            throw new InvalidUserDataException("You must assign a Groovy library to the groovy configuration!");
+        }
+    }
+
+    /**
+     * Gets the options for the groovyc compilation. To set specific options for the nested javac compilation,
+     * use {@link #getOptions()}.
+     */
+    @Nested
+    public GroovyCompileOptions getGroovyOptions() {
+        return compiler.getGroovyCompileOptions();
+    }
+
+    @Nested
+    public CompileOptions getOptions() {
+        return compiler.getCompileOptions();
+    }
+
+    @InputFiles
+    public FileCollection getGroovyClasspath() {
+        return groovyClasspath;
+    }
+
+    public void setGroovyClasspath(FileCollection groovyClasspath) {
+        this.groovyClasspath = groovyClasspath;
+    }
+
+    public GroovyJavaJointCompiler getCompiler() {
+        return compiler;
+    }
+
+    public void setCompiler(GroovyJavaJointCompiler compiler) {
+        this.compiler = compiler;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompileOptions.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompileOptions.groovy
new file mode 100644
index 0000000..7c78296
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyCompileOptions.groovy
@@ -0,0 +1,56 @@
+/*
+ * 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.tasks.compile
+
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Optional;
+
+/**
+ * @author Hans Dockter
+ */
+public class GroovyCompileOptions extends AbstractOptions {
+    boolean failOnError = true
+    boolean verbose = false
+    boolean listFiles = false
+    @Input @Optional
+    String encoding = null
+    boolean fork = true
+    GroovyForkOptions forkOptions = new GroovyForkOptions()
+    @Input
+    boolean includeJavaRuntime = false
+    boolean stacktrace
+
+    GroovyCompileOptions fork(Map forkArgs) {
+        fork = true
+        forkOptions.define(forkArgs)
+        this
+    }
+
+    List excludedFieldsFromOptionMap() {
+        ['forkOptions']
+    }
+
+    Map fieldName2AntMap() {
+        [
+                failOnError: 'failonerror',
+                listFiles: 'listfiles',
+        ]
+    }
+
+    Map optionMap() {
+        super.optionMap() + forkOptions.optionMap()
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyForkOptions.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyForkOptions.groovy
new file mode 100644
index 0000000..2055d5e
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/GroovyForkOptions.groovy
@@ -0,0 +1,24 @@
+/*
+ * 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.tasks.compile;
+
+/**
+ * @author Hans Dockter
+ */
+class GroovyForkOptions extends AbstractOptions {
+    String memoryInitialSize = null
+    String memoryMaximumSize = null
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/package-info.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/package-info.java
new file mode 100644
index 0000000..8713f48
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/compile/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * The compilation {@link org.gradle.api.Task} implementations.
+ */
+package org.gradle.api.tasks.compile;
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/javadoc/AntGroovydoc.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/javadoc/AntGroovydoc.groovy
new file mode 100644
index 0000000..09bd496
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/javadoc/AntGroovydoc.groovy
@@ -0,0 +1,75 @@
+/*
+ * 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.javadoc
+
+import org.gradle.api.Project
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.ClassPathRegistry
+import org.gradle.api.internal.project.IsolatedAntBuilder
+
+/**
+ * @author Hans Dockter
+ */
+class AntGroovydoc {
+    private final IsolatedAntBuilder ant
+    private final ClassPathRegistry classPathRegistry
+
+    def AntGroovydoc(IsolatedAntBuilder ant, ClassPathRegistry classPathRegistry) {
+        this.ant = ant;
+        this.classPathRegistry = classPathRegistry;
+    }
+
+    void execute(FileCollection source, File destDir, boolean use, String windowTitle,
+                 String docTitle, String header, String footer, String overview, boolean includePrivate, Set links,
+                 List groovyClasspath, Project project) {
+
+        File tmpDir = new File(project.buildDir, "tmp/groovydoc")
+        project.delete tmpDir
+        project.copy {
+            from source
+            into tmpDir
+        }
+
+        Map args = [:]
+        args.sourcepath = tmpDir.toString()
+        args.destdir = destDir
+        args.use = use
+        args['private'] = includePrivate
+        addToMapIfNotNull(args, 'windowtitle', windowTitle)
+        addToMapIfNotNull(args, 'doctitle', docTitle)
+        addToMapIfNotNull(args, 'header', header)
+        addToMapIfNotNull(args, 'footer', footer)
+        addToMapIfNotNull(args, 'overview', overview)
+
+        ant.withGroovy(groovyClasspath).execute {
+            taskdef(name: 'groovydoc', classname: 'org.codehaus.groovy.ant.Groovydoc')
+            groovydoc(args) {
+                links.each {gradleLink ->
+                    link(packages: gradleLink.packages.join(','), href: gradleLink.url)
+                }
+            }
+        }
+    }
+
+    void addToMapIfNotNull(Map map, String key, Object value) {
+        if (value != null) {
+            map.put(key, value)
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/javadoc/AntJavadoc.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/javadoc/AntJavadoc.groovy
new file mode 100644
index 0000000..0f48808
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/javadoc/AntJavadoc.groovy
@@ -0,0 +1,50 @@
+/*
+ * 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.api.tasks.javadoc
+
+/**
+ * @author Hans Dockter
+ */
+class AntJavadoc {
+    void execute(List<File> sourceDirs, File destDir, Set<File> classpathFiles, String windowTitle, String maxMemory,
+                 List<String> includes, List<String> excludes, boolean verbose, AntBuilder ant) {
+        Map otherArgs = [:]
+        if (maxMemory) {otherArgs.maxmemory = maxMemory}
+        if (windowTitle) {
+            otherArgs.windowtitle = windowTitle
+            otherArgs.doctitle = "<p>$windowTitle</p>"
+        }
+        ant.javadoc([destdir: destDir, failonerror: true, verbose: verbose] + otherArgs) {
+            sourceDirs.each {
+                fileset(dir: it) {
+                    includes.each {
+                        include(name: it)
+                    }
+                    excludes.each {
+                        exclude(name: it)
+                    }
+                    // This looks wrong. However, javadoc fails when package.html files are included explicitly. Javadoc
+                    // will include them in the documentation even if they are not included. So, exclude them.
+                    exclude(name: '**/package.html')
+                }
+            }
+            classpathFiles.each {
+                classpath(location: it)
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Groovydoc.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Groovydoc.java
new file mode 100644
index 0000000..661062b
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Groovydoc.java
@@ -0,0 +1,328 @@
+/*
+ * 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.tasks.javadoc;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.ClassPathRegistry;
+import org.gradle.api.internal.project.IsolatedAntBuilder;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.tasks.*;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * This task generates html api doc for Groovy classes. It uses Groovy's Groovydoc tool for this. Please note that the
+ * Groovydoc tool has some severe limitations at the moment (for example no doc for properties comments). The version of
+ * the Groovydoc that is used, is the one from the Groovy defined in the build script. Please note also, that the
+ * Groovydoc tool prints to System.out for many of its statements and does circumvents our logging currently.
+ *
+ * @author Hans Dockter
+ */
+public class Groovydoc extends SourceTask {
+    private FileCollection groovyClasspath;
+
+    private File destinationDir;
+
+    private AntGroovydoc antGroovydoc;
+
+    private boolean use;
+
+    private String windowTitle;
+
+    private String docTitle;
+
+    private String header;
+
+    private String footer;
+
+    private String overview;
+
+    private Set<Link> links = new HashSet<Link>();
+
+    boolean includePrivate;
+
+    public Groovydoc() {
+        getLogging().captureStandardOutput(LogLevel.INFO);
+        IsolatedAntBuilder antBuilder = getServices().get(IsolatedAntBuilder.class);
+        ClassPathRegistry classPathRegistry = getServices().get(ClassPathRegistry.class);
+        antGroovydoc = new AntGroovydoc(antBuilder, classPathRegistry);
+    }
+
+    @TaskAction
+    protected void generate() {
+        List<File> taskClasspath = new ArrayList<File>(getGroovyClasspath().getFiles());
+        throwExceptionIfTaskClasspathIsEmpty(taskClasspath);
+        antGroovydoc.execute(getSource(), getDestinationDir(), isUse(), getWindowTitle(), getDocTitle(), getHeader(),
+                getFooter(), getOverview(), isIncludePrivate(), getLinks(), taskClasspath, getProject());
+    }
+
+    private void throwExceptionIfTaskClasspathIsEmpty(List taskClasspath) {
+        if (taskClasspath.size() == 0) {
+            throw new InvalidUserDataException("You must assign a Groovy library to the groovy configuration!");
+        }
+    }
+
+    /**
+     * <p>Returns the directory to generate the documentation into.</p>
+     *
+     * @return The directory.
+     */
+    @OutputDirectory
+    public File getDestinationDir() {
+        return destinationDir;
+    }
+
+    /**
+     * <p>Sets the directory to generate the documentation into.</p>
+     */
+    public void setDestinationDir(File destinationDir) {
+        this.destinationDir = destinationDir;
+    }
+
+    /**
+     * <p>Returns the classpath to use to locate classes referenced by the documented source.</p>
+     *
+     * @return The classpath.
+     */
+    @InputFiles
+    public FileCollection getGroovyClasspath() {
+        return groovyClasspath;
+    }
+
+    /**
+     * <p>Sets the classpath to use to locate classes referenced by the documented source.</p>
+     */
+    public void setGroovyClasspath(FileCollection groovyClasspath) {
+        this.groovyClasspath = groovyClasspath;
+    }
+
+    public AntGroovydoc getAntGroovydoc() {
+        return antGroovydoc;
+    }
+
+    public void setAntGroovydoc(AntGroovydoc antGroovydoc) {
+        this.antGroovydoc = antGroovydoc;
+    }
+
+    /**
+     * Returns whether to create class and package usage pages.
+     */
+    public boolean isUse() {
+        return use;
+    }
+
+    /**
+     * Set's whether to create class and package usage pages. Defaults to false.
+     */
+    public void setUse(boolean use) {
+        this.use = use;
+    }
+
+    /**
+     * Returns the browser window title for the documentation.
+     */
+    public String getWindowTitle() {
+        return windowTitle;
+    }
+
+    /**
+     * Set's the browser window title for the documentation.
+     *
+     * @param windowTitle A text for the windows title
+     */
+    public void setWindowTitle(String windowTitle) {
+        this.windowTitle = windowTitle;
+    }
+
+    /**
+     * Returns the title for the package index(first) page. Returns null if not set.
+     */
+    public String getDocTitle() {
+        return docTitle;
+    }
+
+    /**
+     * Set's title for the package index(first) page (optional).
+     *
+     * @param docTitle the docTitle as html-code
+     */
+    public void setDocTitle(String docTitle) {
+        this.docTitle = docTitle;
+    }
+
+    /**
+     * Returns the html header for each page. Returns null if not set.
+     */
+    public String getHeader() {
+        return header;
+    }
+
+    /**
+     * Set's header text for each page (optional).
+     *
+     * @param header the header as html-code
+     */
+    public void setHeader(String header) {
+        this.header = header;
+    }
+
+    /**
+     * Returns the html footer for each page. Returns null if not set.
+     */
+    public String getFooter() {
+        return footer;
+    }
+
+    /**
+     * Set's footer text for each page (optional).
+     *
+     * @param footer the footer as html-code
+     */
+    public void setFooter(String footer) {
+        this.footer = footer;
+    }
+
+    /**
+     * Returns a html file to be used for overview documentation. Returns null if such a file is not set.
+     */
+    public String getOverview() {
+        return overview;
+    }
+
+    /**
+     * Set's a html file to be used for overview documentation (optional).
+     */
+    public void setOverview(String overview) {
+        this.overview = overview;
+    }
+
+    /**
+     * Returns whether to include all classes and members (i.e. including private ones).
+     */
+    public boolean isIncludePrivate() {
+        return includePrivate;
+    }
+
+    /**
+     * Set's whether to include all classes and members (i.e. including private ones) if set to true. Defaults to
+     * false.
+     */
+    public void setIncludePrivate(boolean includePrivate) {
+        this.includePrivate = includePrivate;
+    }
+
+    /**
+     * Returns links to groovydoc/javadoc output at the given URL
+     */
+    public Set<Link> getLinks() {
+        return Collections.unmodifiableSet(links);
+    }
+
+    /**
+     * Sets links to groovydoc/javadoc output at the given URL
+     *
+     * @param links The links to set
+     * @see #link(String, String[])
+     */
+    public void setLinks(Set<Link> links) {
+        this.links = links;
+    }
+
+    /**
+     * Add links to groovydoc/javadoc output at the given URL
+     *
+     * @param url Base URL of external site
+     * @param packages list of package prefixes
+     */
+    public void link(String url, String... packages) {
+        links.add(new Link(url, packages));
+    }
+
+    /**
+     * A Link class represent a link between groovydoc/javadoc output and url.
+     */
+    public static class Link {
+        private List<String> packages = new ArrayList<String>();
+        private String url;
+
+        /**
+         * @param url Base URL of external site
+         * @param packages list of package prefixes
+         */
+        public Link(String url, String... packages) {
+            throwExceptionIfNull(url, "Url must not be null");
+            if (packages.length == 0) {
+                throw new InvalidUserDataException("You must specify at least one package!");
+            }
+            for (String aPackage : packages) {
+                throwExceptionIfNull(aPackage, "A package must not be null");
+            }
+            this.packages = Arrays.asList(packages);
+            this.url = url;
+        }
+
+        private void throwExceptionIfNull(String value, String message) {
+            if (value == null) {
+                throw new InvalidUserDataException(message);
+            }
+        }
+
+        /**
+         * Returns a list of package prefixes to be linked with an external site.
+         */
+        public List<String> getPackages() {
+            return Collections.unmodifiableList(packages);
+        }
+
+        /**
+         * Returns the base url for the external site.
+         */
+        public String getUrl() {
+            return url;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+
+            Link link = (Link) o;
+
+            if (packages != null ? !packages.equals(link.packages) : link.packages != null) {
+                return false;
+            }
+            if (url != null ? !url.equals(link.url) : link.url != null) {
+                return false;
+            }
+
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = packages != null ? packages.hashCode() : 0;
+            result = 31 * result + (url != null ? url.hashCode() : 0);
+            return result;
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Javadoc.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Javadoc.java
new file mode 100644
index 0000000..a7188ee
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/javadoc/Javadoc.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.api.tasks.javadoc;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.file.SimpleFileCollection;
+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.external.javadoc.JavadocExecHandleBuilder;
+import org.gradle.external.javadoc.MinimalJavadocOptions;
+import org.gradle.external.javadoc.StandardJavadocDocletOptions;
+import org.gradle.process.internal.ExecAction;
+import org.gradle.process.internal.ExecException;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * <p>Generates Javadoc from Java source.</p>
+ *
+ * @author Hans Dockter
+ */
+public class Javadoc extends SourceTask {
+    private JavadocExecHandleBuilder javadocExecHandleBuilder = new JavadocExecHandleBuilder();
+
+    private File destinationDir;
+
+    private boolean failOnError = true;
+
+    private String title;
+
+    private String maxMemory;
+
+    private MinimalJavadocOptions options = new StandardJavadocDocletOptions();
+
+    private FileCollection classpath = new SimpleFileCollection();
+
+    private String executable;
+
+    @TaskAction
+    protected void generate() {
+        final File destinationDir = getDestinationDir();
+
+        if (options.getDestinationDirectory() == null) {
+            options.destinationDirectory(destinationDir);
+        }
+
+        options.classpath(new ArrayList<File>(getClasspath().getFiles()));
+
+        if (!GUtil.isTrue(options.getWindowTitle()) && GUtil.isTrue(getTitle())) {
+            options.windowTitle(getTitle());
+        }
+        if (options instanceof StandardJavadocDocletOptions) {
+            StandardJavadocDocletOptions docletOptions = (StandardJavadocDocletOptions) options;
+            if (!GUtil.isTrue(docletOptions.getDocTitle()) && GUtil.isTrue(getTitle())) {
+                docletOptions.setDocTitle(getTitle());
+            }
+        }
+
+        if (maxMemory != null) {
+            final List<String> jFlags = options.getJFlags();
+            final Iterator<String> jFlagsIt = jFlags.iterator();
+            boolean containsXmx = false;
+            while (!containsXmx && jFlagsIt.hasNext()) {
+                final String jFlag = jFlagsIt.next();
+                if (jFlag.startsWith("-Xmx")) {
+                    containsXmx = true;
+                }
+            }
+            if (!containsXmx) {
+                options.jFlags("-Xmx" + maxMemory);
+            }
+        }
+
+        List<String> sourceNames = new ArrayList<String>();
+        for (File sourceFile : getSource()) {
+            sourceNames.add(sourceFile.getAbsolutePath());
+        }
+        options.setSourceNames(sourceNames);
+
+        executeExternalJavadoc();
+    }
+
+    private void executeExternalJavadoc() {
+        javadocExecHandleBuilder.setExecutable(executable);
+        javadocExecHandleBuilder.execDirectory(getProject().getRootDir()).options(options).optionsFile(getOptionsFile());
+
+        ExecAction execAction = javadocExecHandleBuilder.getExecHandle();
+        if (!failOnError) {
+            execAction.setIgnoreExitValue(true);
+        }
+
+        try {
+            execAction.execute();
+        } catch (ExecException e) {
+            throw new GradleException("Javadoc generation failed.", e);
+        }
+    }
+
+    void setJavadocExecHandleBuilder(JavadocExecHandleBuilder javadocExecHandleBuilder) {
+        if (javadocExecHandleBuilder == null) {
+            throw new IllegalArgumentException("javadocExecHandleBuilder == null!");
+        }
+        this.javadocExecHandleBuilder = javadocExecHandleBuilder;
+    }
+
+    /**
+     * <p>Returns the directory to generate the documentation into.</p>
+     *
+     * @return The directory.
+     */
+    @OutputDirectory
+    public File getDestinationDir() {
+        return destinationDir;
+    }
+
+    /**
+     * <p>Sets the directory to generate the documentation into.</p>
+     */
+    public void setDestinationDir(File destinationDir) {
+        this.destinationDir = destinationDir;
+    }
+
+    /**
+     * Returns the amount of memory allocated to this task.
+     */
+    public String getMaxMemory() {
+        return maxMemory;
+    }
+
+    /**
+     * Sets the amount of memory allocated to this task.
+     *
+     * @param maxMemory The amount of memory
+     */
+    public void setMaxMemory(String maxMemory) {
+        this.maxMemory = maxMemory;
+    }
+
+    /**
+     * <p>Returns the title for the generated documentation.</p>
+     *
+     * @return The title, possibly null.
+     */
+    public String getTitle() {
+        return title;
+    }
+
+    /**
+     * <p>Sets the title for the generated documentation.</p>
+     */
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    /**
+     * Returns whether javadoc generation is accompanied by verbose output.
+     *
+     * @see #setVerbose(boolean)
+     */
+    public boolean isVerbose() {
+        return options.isVerbose();
+    }
+
+    /**
+     * Sets whether javadoc generation is accompanied by verbose output or not. The verbose output is done via println
+     * (by the underlying ant task). Thus it is not catched by our logging.
+     *
+     * @param verbose Whether the output should be verbose.
+     */
+    public void setVerbose(boolean verbose) {
+        if (verbose) {
+            options.verbose();
+        }
+    }
+
+    @InputFiles
+    public FileCollection getClasspath() {
+        return classpath;
+    }
+
+    public void setClasspath(FileCollection configuration) {
+        this.classpath = configuration;
+    }
+
+    public MinimalJavadocOptions getOptions() {
+        return options;
+    }
+
+    public void setOptions(MinimalJavadocOptions options) {
+        this.options = options;
+    }
+
+    public boolean isFailOnError() {
+        return failOnError;
+    }
+
+    public void setFailOnError(boolean failOnError) {
+        this.failOnError = failOnError;
+    }
+
+    public File getOptionsFile() {
+        return new File(getTemporaryDir(), "javadoc.options");
+    }
+
+    public String getExecutable() {
+        return executable;
+    }
+
+    public void setExecutable(String executable) {
+        this.executable = executable;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/javadoc/package-info.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/javadoc/package-info.java
new file mode 100644
index 0000000..1610469
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/javadoc/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 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.
+ */
+
+/**
+ * The documentation generation {@link org.gradle.api.Task} implementations.
+ */
+package org.gradle.api.tasks.javadoc;
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/Test.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/Test.java
new file mode 100644
index 0000000..af433c2
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/Test.java
@@ -0,0 +1,769 @@
+/*
+ * 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.testing;
+
+import groovy.lang.Closure;
+import org.gradle.api.GradleException;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.FileTreeElement;
+import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.tasks.testing.TestFramework;
+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.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.testng.TestNGTestFramework;
+import org.gradle.api.specs.Spec;
+import org.gradle.api.tasks.*;
+import org.gradle.api.tasks.util.PatternFilterable;
+import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.listener.ListenerBroadcast;
+import org.gradle.listener.ListenerManager;
+import org.gradle.logging.ProgressLoggerFactory;
+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.WorkerProcessFactory;
+import org.gradle.util.ConfigureUtil;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A task for executing JUnit (3.8.x or 4.x) or TestNG tests.
+ *
+ * @author Hans Dockter
+ */
+public class Test extends ConventionTask implements JavaForkOptions, PatternFilterable, VerificationTask {
+    private TestExecuter testExecuter;
+    private final DefaultJavaForkOptions options;
+    private List<File> testSrcDirs = new ArrayList<File>();
+    private File testClassesDir;
+    private File testResultsDir;
+    private File testReportDir;
+    private PatternFilterable patternSet = new PatternSet();
+    private boolean ignoreFailures;
+    private FileCollection classpath;
+    private TestFramework testFramework;
+    private boolean testReport = true;
+    private boolean scanForTestClasses = true;
+    private long forkEvery;
+    private int maxParallelForks = 1;
+    private ListenerBroadcast<TestListener> testListenerBroadcaster;
+
+    public Test() {
+        testListenerBroadcaster = getServices().get(ListenerManager.class).createAnonymousBroadcaster(
+                TestListener.class);
+        this.testExecuter = new DefaultTestExecuter(getServices().get(WorkerProcessFactory.class), getServices().get(
+                ActorFactory.class));
+        options = new DefaultJavaForkOptions(getServices().get(FileResolver.class));
+        options.setEnableAssertions(true);
+    }
+
+    void setTestExecuter(TestExecuter testExecuter) {
+        this.testExecuter = testExecuter;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public File getWorkingDir() {
+        return options.getWorkingDir();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setWorkingDir(Object dir) {
+        options.setWorkingDir(dir);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Test workingDir(Object dir) {
+        options.workingDir(dir);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getExecutable() {
+        return options.getExecutable();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Test executable(Object executable) {
+        options.executable(executable);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setExecutable(Object executable) {
+        options.setExecutable(executable);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Map<String, Object> getSystemProperties() {
+        return options.getSystemProperties();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setSystemProperties(Map<String, ?> properties) {
+        options.setSystemProperties(properties);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Test systemProperties(Map<String, ?> properties) {
+        options.systemProperties(properties);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Test systemProperty(String name, Object value) {
+        options.systemProperty(name, value);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public FileCollection getBootstrapClasspath() {
+        return options.getBootstrapClasspath();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setBootstrapClasspath(FileCollection classpath) {
+        options.setBootstrapClasspath(classpath);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Test bootstrapClasspath(Object... classpath) {
+        options.bootstrapClasspath(classpath);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getMaxHeapSize() {
+        return options.getMaxHeapSize();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setMaxHeapSize(String heapSize) {
+        options.setMaxHeapSize(heapSize);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List<String> getJvmArgs() {
+        return options.getJvmArgs();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setJvmArgs(Iterable<?> arguments) {
+        options.setJvmArgs(arguments);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Test jvmArgs(Iterable<?> arguments) {
+        options.jvmArgs(arguments);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Test jvmArgs(Object... arguments) {
+        options.jvmArgs(arguments);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean getEnableAssertions() {
+        return options.getEnableAssertions();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setEnableAssertions(boolean enabled) {
+        options.setEnableAssertions(enabled);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean getDebug() {
+        return options.getDebug();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setDebug(boolean enabled) {
+        options.setDebug(enabled);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List<String> getAllJvmArgs() {
+        return options.getAllJvmArgs();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setAllJvmArgs(Iterable<?> arguments) {
+        options.setAllJvmArgs(arguments);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Map<String, Object> getEnvironment() {
+        return options.getEnvironment();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Test environment(Map<String, ?> environmentVariables) {
+        options.environment(environmentVariables);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Test environment(String name, Object value) {
+        options.environment(name, value);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setEnvironment(Map<String, ?> environmentVariables) {
+        options.setEnvironment(environmentVariables);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Test copyTo(ProcessForkOptions target) {
+        options.copyTo(target);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Test copyTo(JavaForkOptions target) {
+        options.copyTo(target);
+        return this;
+    }
+
+    @TaskAction
+    public void executeTests() {
+        TestSummaryListener listener = new TestSummaryListener(LoggerFactory.getLogger(Test.class));
+        addTestListener(listener);
+        addTestListener(new TestLogger(getServices().get(ProgressLoggerFactory.class)));
+
+        TestResultProcessor resultProcessor = new TestListenerAdapter(getTestListenerBroadcaster().getSource());
+        testExecuter.execute(this, resultProcessor);
+
+        testFramework.report();
+
+        if (!isIgnoreFailures() && listener.hadFailures()) {
+            throw new GradleException("There were failing tests. See the report at " + getTestReportDir() + ".");
+        }
+    }
+
+    /**
+     * @return The {@link org.gradle.api.tasks.testing.TestListener} broadcaster.  This broadcaster will send messages
+     *         to all listeners that have been registered with the ListenerManager.
+     */
+    ListenerBroadcast<TestListener> getTestListenerBroadcaster() {
+        return testListenerBroadcaster;
+    }
+
+    /**
+     * Registers a test listener with this task.  This listener will NOT be notified of tests executed by other tasks.
+     * To get that behavior, use {@link org.gradle.api.invocation.Gradle#addListener(Object)}.
+     *
+     * @param listener The listener to add.
+     */
+    public void addTestListener(TestListener listener) {
+        testListenerBroadcaster.add(listener);
+    }
+
+    /**
+     * Unregisters a test listener with this task.  This method will only remove listeners that were added by calling
+     * {@link #addTestListener(org.gradle.api.tasks.testing.TestListener)} on this task.  If the listener was registered
+     * with Gradle using {@link org.gradle.api.invocation.Gradle#addListener(Object)} this method will not do anything.
+     * Instead, use {@link org.gradle.api.invocation.Gradle#removeListener(Object)}.
+     *
+     * @param listener The listener to remove.
+     */
+    public void removeTestListener(TestListener listener) {
+        testListenerBroadcaster.remove(listener);
+    }
+
+    /**
+     * <p>Adds a closure to be notified before a test suite is executed. A {@link org.gradle.api.tasks.testing.TestDescriptor}
+     * instance is passed to the closure as a parameter.</p>
+     *
+     * <p>This method is also called before any test suites are executed. The provided descriptor will have a null
+     * parent suite.</p>
+     *
+     * @param closure The closure to call.
+     */
+    public void beforeSuite(Closure closure) {
+        testListenerBroadcaster.add("beforeSuite", closure);
+    }
+
+    /**
+     * <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>
+     *
+     * <p>This method is also called after all test suites are executed. The provided descriptor will have a null parent
+     * suite.</p>
+     *
+     * @param closure The closure to call.
+     */
+    public void afterSuite(Closure closure) {
+        testListenerBroadcaster.add("afterSuite", closure);
+    }
+
+    /**
+     * Adds a closure to be notified before a test is executed. A {@link org.gradle.api.tasks.testing.TestDescriptor}
+     * instance is passed to the closure as a parameter.
+     *
+     * @param closure The closure to call.
+     */
+    public void beforeTest(Closure closure) {
+        testListenerBroadcaster.add("beforeTest", closure);
+    }
+
+    /**
+     * 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.
+     *
+     * @param closure The closure to call.
+     */
+    public void afterTest(Closure closure) {
+        testListenerBroadcaster.add("afterTest", closure);
+    }
+
+    /**
+     * Adds include patterns for the files in the test classes directory (e.g. '**&#2F;*Test.class')).
+     *
+     * @see #setIncludes(Iterable)
+     */
+    public Test include(String... includes) {
+        patternSet.include(includes);
+        return this;
+    }
+
+    /**
+     * Adds include patterns for the files in the test classes directory (e.g. '**&#2F;*Test.class')).
+     *
+     * @see #setIncludes(Iterable)
+     */
+    public Test include(Iterable<String> includes) {
+        patternSet.include(includes);
+        return this;
+    }
+
+    public Test include(Spec<FileTreeElement> includeSpec) {
+        patternSet.include(includeSpec);
+        return this;
+    }
+
+    public Test include(Closure includeSpec) {
+        patternSet.include(includeSpec);
+        return this;
+    }
+
+    /**
+     * Adds exclude patterns for the files in the test classes directory (e.g. '**&#2F;*Test.class')).
+     *
+     * @see #setExcludes(Iterable)
+     */
+    public Test exclude(String... excludes) {
+        patternSet.exclude(excludes);
+        return this;
+    }
+
+    /**
+     * Adds exclude patterns for the files in the test classes directory (e.g. '**&#2F;*Test.class')).
+     *
+     * @see #setExcludes(Iterable)
+     */
+    public Test exclude(Iterable<String> excludes) {
+        patternSet.exclude(excludes);
+        return this;
+    }
+
+    public Test exclude(Spec<FileTreeElement> excludeSpec) {
+        patternSet.exclude(excludeSpec);
+        return this;
+    }
+
+    public Test exclude(Closure excludeSpec) {
+        patternSet.exclude(excludeSpec);
+        return this;
+    }
+
+    /**
+     * Returns the root folder for the compiled test sources.
+     *
+     * @return All test class directories to be used.
+     */
+    public File getTestClassesDir() {
+        return testClassesDir;
+    }
+
+    /**
+     * Sets the root folder for the compiled test sources.
+     *
+     * @param testClassesDir The root folder
+     */
+    public void setTestClassesDir(File testClassesDir) {
+        this.testClassesDir = testClassesDir;
+    }
+
+    /**
+     * Returns the root folder for the test results.
+     *
+     * @return the test result directory, containing the internal test results, mostly in xml form.
+     */
+    @OutputDirectory
+    public File getTestResultsDir() {
+        return testResultsDir;
+    }
+
+    /**
+     * Sets the root folder for the test results.
+     *
+     * @param testResultsDir The root folder
+     */
+    public void setTestResultsDir(File testResultsDir) {
+        this.testResultsDir = testResultsDir;
+    }
+
+    /**
+     * Returns the root folder for the test reports.
+     *
+     * @return the test report directory, containing the test report mostly in HTML form.
+     */
+    @OutputDirectory
+    public File getTestReportDir() {
+        return testReportDir;
+    }
+
+    /**
+     * Sets the root folder for the test reports.
+     *
+     * @param testReportDir The root folder
+     */
+    public void setTestReportDir(File testReportDir) {
+        this.testReportDir = testReportDir;
+    }
+
+    /**
+     * Returns the include patterns for test execution.
+     *
+     * @see #include(String...)
+     */
+    public Set<String> getIncludes() {
+        return patternSet.getIncludes();
+    }
+
+    /**
+     * Sets the include patterns for test execution.
+     *
+     * @param includes The patterns list
+     * @see #include(String...)
+     */
+    public Test setIncludes(Iterable<String> includes) {
+        patternSet.setIncludes(includes);
+        return this;
+    }
+
+    /**
+     * Returns the exclude patterns for test execution.
+     *
+     * @see #exclude(String...)
+     */
+    public Set<String> getExcludes() {
+        return patternSet.getExcludes();
+    }
+
+    /**
+     * Sets the exclude patterns for test execution.
+     *
+     * @param excludes The patterns list
+     * @see #exclude(String...)
+     */
+    public Test setExcludes(Iterable<String> excludes) {
+        patternSet.setExcludes(excludes);
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isIgnoreFailures() {
+        return ignoreFailures;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Test setIgnoreFailures(boolean ignoreFailures) {
+        this.ignoreFailures = ignoreFailures;
+        return this;
+    }
+
+    public TestFramework getTestFramework() {
+        return testFramework(null);
+    }
+
+    public TestFramework testFramework(Closure testFrameworkConfigure) {
+        if (testFramework == null) {
+            return useJUnit(testFrameworkConfigure);
+        }
+
+        return testFramework;
+    }
+
+    /**
+     * <p>Returns the test options options.</p>
+     *
+     * <p>Be sure to call the appropriate {@link #useJUnit} or {@link #useTestNG} method before using this method.</p>
+     *
+     * @return The testframework options.
+     */
+    @Nested
+    public TestFrameworkOptions getOptions() {
+        return options(null);
+    }
+
+    public TestFrameworkOptions options(Closure testFrameworkConfigure) {
+        TestFrameworkOptions options = getTestFramework().getOptions();
+        ConfigureUtil.configure(testFrameworkConfigure, testFramework.getOptions());
+        return options;
+    }
+
+    TestFramework useTestFramework(TestFramework testFramework) {
+        return useTestFramework(testFramework, null);
+    }
+
+    private TestFramework useTestFramework(TestFramework testFramework, Closure testFrameworkConfigure) {
+        if (testFramework == null) {
+            throw new IllegalArgumentException("testFramework is null!");
+        }
+
+        this.testFramework = testFramework;
+
+        if (testFrameworkConfigure != null) {
+            ConfigureUtil.configure(testFrameworkConfigure, this.testFramework.getOptions());
+        }
+
+        return this.testFramework;
+    }
+
+    /**
+     * Specifies that JUnit should be used to execute the tests.
+     */
+    public TestFramework useJUnit() {
+        return useJUnit(null);
+    }
+
+    /**
+     * Specifies that JUnit should be used to execute the tests.
+     *
+     * @param testFrameworkConfigure A closure used to configure the JUint options. This closure is passed an instance
+     * of type {@link org.gradle.api.tasks.testing.junit.JUnitOptions}.
+     */
+    public TestFramework useJUnit(Closure testFrameworkConfigure) {
+        return useTestFramework(new JUnitTestFramework(this), testFrameworkConfigure);
+    }
+
+    /**
+     * Specifies that TestNG should be used to execute the tests.
+     */
+    public TestFramework useTestNG() {
+        return useTestNG(null);
+    }
+
+    /**
+     * Specifies that TestNG should be used to execute the tests.
+     * @param testFrameworkConfigure A closure used to configure the JUint options. This closure is passed an instance
+     * of type {@link org.gradle.api.tasks.testing.junit.JUnitOptions}.
+     */
+    public TestFramework useTestNG(Closure testFrameworkConfigure) {
+        return useTestFramework(new TestNGTestFramework(this), testFrameworkConfigure);
+    }
+
+    @InputFiles
+    public FileCollection getClasspath() {
+        return classpath;
+    }
+
+    public void setClasspath(FileCollection classpath) {
+        this.classpath = classpath;
+    }
+
+    public boolean isTestReport() {
+        return testReport;
+    }
+
+    public void setTestReport(boolean testReport) {
+        this.testReport = testReport;
+    }
+
+    public void enableTestReport() {
+        this.testReport = true;
+    }
+
+    public void disableTestReport() {
+        this.testReport = false;
+    }
+
+    @InputFiles
+    public List<File> getTestSrcDirs() {
+        return testSrcDirs;
+    }
+
+    public void setTestSrcDirs(List<File> testSrcDir) {
+        this.testSrcDirs = testSrcDir;
+    }
+
+    public boolean isScanForTestClasses() {
+        return scanForTestClasses;
+    }
+
+    public void setScanForTestClasses(boolean scanForTestClasses) {
+        this.scanForTestClasses = scanForTestClasses;
+    }
+
+    /**
+     * Returns the maximum number of test classes to execute in a forked test process. The forked test process will be
+     * restarted when this limit is reached. The default value is 0 (no maximum).
+     *
+     * @return The maximum number of test classes. Returns 0 when there is no maximum.
+     */
+    public long getForkEvery() {
+        return forkEvery;
+    }
+
+    /**
+     * Sets the maximum number of test classes to execute in a forked test process. Use null or 0 to use no maximum.
+     *
+     * @param forkEvery The maximum number of test classes. Use null or 0 to specify no maximum.
+     */
+    public void setForkEvery(Long forkEvery) {
+        if (forkEvery != null && forkEvery < 0) {
+            throw new IllegalArgumentException("Cannot set forkEvery to a value less than 0.");
+        }
+        this.forkEvery = forkEvery == null ? 0 : forkEvery;
+    }
+
+    /**
+     * Returns the maximum number of forked test processes to execute in parallel. The default value is 1 (no parallel
+     * test execution).
+     *
+     * @return The maximum number of forked test processes.
+     */
+    public int getMaxParallelForks() {
+        return maxParallelForks;
+    }
+
+    /**
+     * Sets the maximum number of forked test processes to execute in parallel. Set to 1 to disable parallel test
+     * execution.
+     *
+     * @param maxParallelForks The maximum number of forked test processes.
+     */
+    public void setMaxParallelForks(int maxParallelForks) {
+        if (maxParallelForks < 1) {
+            throw new IllegalArgumentException("Cannot set maxParallelForks to a value less than 1.");
+        }
+        this.maxParallelForks = maxParallelForks;
+    }
+
+    /**
+     * Returns the classes files to scan for test classes.
+     * @return The candidate class files.
+     */
+    @InputFiles
+    @Input // Also marked as input to force tests to run when the set of candidate class files changes 
+    public FileTree getCandidateClassFiles() {
+        PatternSet patterns = new PatternSet();
+        patterns.copyFrom(patternSet);
+        if (!isScanForTestClasses()) {
+            if (patterns.getIncludes().isEmpty()) {
+                patterns.include("**/*Tests.class", "**/*Test.class");
+            }
+            if (patterns.getExcludes().isEmpty()) {
+                patterns.exclude("**/Abstract*.class");
+            }
+        }
+        return getProject().fileTree(getTestClassesDir()).matching(patterns);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/TestDescriptor.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/TestDescriptor.java
new file mode 100644
index 0000000..e523534
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/TestDescriptor.java
@@ -0,0 +1,49 @@
+/*
+ * 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.testing;
+
+/**
+ * Describes a test. A test may be a single atomic test, such as the execution of a test method, or it may be a
+ * composite test, made up of zero or more tests.
+ */
+public interface TestDescriptor {
+    /**
+     * @return The name of the test.  Not guaranteed to be unique.
+     */
+    String getName();
+
+    /**
+     * Returns the test class name for this test, if any.
+     *
+     * @return The class name. May return null.
+     */
+    String getClassName();
+
+    /**
+     * Is this test a composite test?
+     *
+     * @return true if this test is a composite test.
+     */
+    boolean isComposite();
+
+    /**
+     * Returns the parent of this test, if any.
+     *
+     * @return The parent of this test. Null if this test has no parent.
+     */
+    TestDescriptor getParent();
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/TestFrameworkOptions.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/TestFrameworkOptions.java
new file mode 100644
index 0000000..93da843
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/TestFrameworkOptions.java
@@ -0,0 +1,23 @@
+/*
+ * 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.testing;
+
+/**
+ * The base class for any test framework specific options.
+ */
+public class TestFrameworkOptions {
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/TestListener.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/TestListener.java
new file mode 100644
index 0000000..385172c
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/TestListener.java
@@ -0,0 +1,54 @@
+/*
+ * 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.testing;
+
+// todo: consider multithreading/multiprocess issues
+// Teamcity has the concept of a "wave" of messages
+// where each thread/process uses a unique wave id
+
+/**
+ * Interface for listening to test execution.  The intent is to be
+ * framework agnostic.  Currently this interface can support feedback
+ * from JUnit and TestNG tests.
+ */
+public interface TestListener {
+    /**
+     * Called before a test suite is started.
+     * @param suite The suite whose tests are about to be executed.
+     */
+    void beforeSuite(TestDescriptor suite);
+
+    /**
+     * Called after a test suite is finished.
+     * @param suite The suite whose tests have finished being executed.
+     * @param result The aggregate result for the suite.
+     */
+    void afterSuite(TestDescriptor suite, TestResult result);
+
+    /**
+     * Called before a test is started.
+     * @param testDescriptor The test which is about to be executed.
+     */
+    void beforeTest(TestDescriptor testDescriptor);
+
+    /**
+     * Called after a test is finished.
+     * @param testDescriptor The test which has finished executing.
+     * @param result The test result.
+     */
+    void afterTest(TestDescriptor testDescriptor, TestResult result);
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/TestResult.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/TestResult.java
new file mode 100644
index 0000000..740e55d
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/TestResult.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.api.tasks.testing;
+
+/**
+ * Describes a test result.
+ */
+public interface TestResult {
+    public enum ResultType { SUCCESS, FAILURE, SKIPPED }
+
+    /**
+     * @return The type of result.  Generally one wants it to be SUCCESS!
+     */
+    ResultType getResultType();
+
+    /**
+     * If the test failed with an exception, this will be the exception.  Some
+     * test frameworks do not fail without an exception (JUnit), so in those cases
+     * this method will never return null.  If the resultType is not FAILURE an IllegalStateException is thrown.
+     * @return The exception, if any, logged for this test.  If none, a null is returned.
+     * @throws IllegalStateException If the result type is anything other than FAILURE.
+     */
+    Throwable getException(); // throws exception if type !=  FAILURE
+
+    /**
+     * Returns the time when this test started execution.
+     *
+     * @return The start time, in milliseconds since the epoch.
+     */
+    long getStartTime();
+
+    /**
+     * Returns the time when this test completed execution.
+     *
+     * @return The end t ime, in milliseconds since the epoch.
+     */
+    long getEndTime();
+
+    /**
+     * Returns the total number of atomic tests executed for this test. This will return 1 if this test is itself an
+     * atomic test.
+     *
+     * @return The number of tests, possibly 0
+     */
+    long getTestCount();
+
+    /**
+     * Returns the number of successful atomic tests executed for this test.
+     *
+     * @return The number of tests, possibly 0
+     */
+    long getSuccessfulTestCount();
+
+    /**
+     * Returns the number of failed atomic tests executed for this test.
+     *
+     * @return The number of tests, possibly 0
+     */
+    long getFailedTestCount();
+
+    /**
+     * Returns the number of skipped atomic tests executed for this test.
+     *
+     * @return The number of tests, possibly 0
+     */
+    long getSkippedTestCount();
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/junit/JUnitOptions.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/junit/JUnitOptions.java
new file mode 100644
index 0000000..05db5ad
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/junit/JUnitOptions.java
@@ -0,0 +1,25 @@
+/*
+ * 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.testing.junit;
+
+import org.gradle.api.tasks.testing.TestFrameworkOptions;
+
+/**
+ * The JUnit specific test options.
+ */
+public class JUnitOptions extends TestFrameworkOptions {
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/junit/package-info.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/junit/package-info.java
new file mode 100644
index 0000000..9dad92a
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/junit/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * JUnit specific testing classes.
+ */
+package org.gradle.api.tasks.testing.junit;
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/package-info.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/package-info.java
new file mode 100644
index 0000000..002994f
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 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.
+ */
+
+/**
+ * The unit testing {@link org.gradle.api.Task} implementations.
+ */
+package org.gradle.api.tasks.testing;
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/TestNGOptions.groovy b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/TestNGOptions.groovy
new file mode 100644
index 0000000..fbfc740
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/TestNGOptions.groovy
@@ -0,0 +1,212 @@
+/*
+ * 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.testing.testng
+
+import groovy.xml.MarkupBuilder
+import org.gradle.api.JavaVersion
+import org.gradle.api.tasks.testing.TestFrameworkOptions
+
+/**
+ * @author Tom Eyckmans
+ */
+public class TestNGOptions extends TestFrameworkOptions implements Serializable {
+
+    public static final String JDK_ANNOTATIONS = 'JDK'
+    public static final String JAVADOC_ANNOTATIONS = 'Javadoc'
+
+    /**
+     * When true, Javadoc annotations are used for these tests. When false, JDK annotations are used. If you use
+     * Javadoc annotations, you will also need to specify "sourcedir".
+     *
+     * Defaults to JDK annotations if you're using the JDK 5 jar and to Javadoc annotations if you're using the JDK 1.4
+     * jar.
+     */
+    boolean javadocAnnotations
+
+    def String getAnnotations() {
+        return javadocAnnotations ? JAVADOC_ANNOTATIONS : JDK_ANNOTATIONS;
+    }
+
+    /**
+     * List of all directories containing Test sources. Should be set if annotations is 'Javadoc'.
+     */
+    List testResources
+
+    /**
+     * The set of groups to run.
+     */
+    Set<String> includeGroups = new HashSet<String>()
+
+    /**
+     * The set of groups to exclude.
+     */
+    Set<String> excludeGroups = new HashSet<String>()
+
+    /**
+     * The set of fully qualified classes that are TestNG listeners (for example org.testng.ITestListener or
+     * org.testng.IReporter)
+     */
+    Set<String> listeners = new LinkedHashSet<String>()
+
+    /**
+     * The parallel mode to use for running the tests - either methods or tests.
+     *
+     * Not required.
+     *
+     * If not present, parallel mode will not be selected 
+     */
+    String parallel = null
+
+    /**
+     * The number of threads to use for this run. Ignored unless the parallel mode is also specified
+     */
+    int threadCount = 1
+
+    /**
+     * Whether the default listeners and reporters should be used.
+     *
+     * Defaults to true.
+     */
+    boolean useDefaultListeners = true
+
+    /**
+     * Sets the default name of the test suite, if one is not specified in a suite xml file or in the source code.
+     */
+    String suiteName = 'Gradle suite'
+
+    /**
+     * Sets the default name of the test, if one is not specified in a suite xml file or in the source code.
+     */
+    String testName = 'Gradle test'
+
+    /**
+     * The suiteXmlFiles to use for running TestNG.
+     *
+     * Note: The suiteXmlFiles can be used in conjunction with the suiteXmlBuilder.
+     */
+    List<File> suiteXmlFiles = []
+
+    transient StringWriter suiteXmlWriter = null
+    transient MarkupBuilder suiteXmlBuilder = null
+    private File projectDir
+
+    public TestNGOptions(File projectDir) {
+        this.projectDir = projectDir
+    }
+
+    void setAnnotationsOnSourceCompatibility(JavaVersion sourceCompatibilityProp) {
+        if (sourceCompatibilityProp >= JavaVersion.VERSION_1_5) {
+            jdkAnnotations()
+        } else {
+            javadocAnnotations()
+        }
+    }
+
+    MarkupBuilder suiteXmlBuilder() {
+        suiteXmlWriter = new StringWriter()
+        suiteXmlBuilder = new MarkupBuilder(suiteXmlWriter)
+        return suiteXmlBuilder
+    }
+
+    /**
+     * Add suite files by Strings. Each suiteFile String should be a path relative to the project root.
+     */
+    void suites(String ... suiteFiles) {
+        suiteFiles.each {it ->
+            suiteXmlFiles.add(new File(projectDir, it))
+        }
+    }
+
+    /**
+     * Add suite files by File objects.
+     */
+    void suites(File ... suiteFiles) {
+        suiteXmlFiles.addAll(Arrays.asList(suiteFiles))
+    }
+
+    List<File> getSuites(File testSuitesDir) {
+        List<File> suites = []
+
+        suites.addAll(suiteXmlFiles)
+
+        if (suiteXmlBuilder != null) {
+            File buildSuiteXml = new File(testSuitesDir.absolutePath, "build-suite.xml");
+
+            if (buildSuiteXml.exists()) {
+                if (!buildSuiteXml.delete()) {
+                    throw new RuntimeException("failed to remove already existing build-suite.xml file");
+                }
+            }
+
+            buildSuiteXml.append('<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">');
+            buildSuiteXml.append(suiteXmlWriter.toString());
+
+            suites.add(buildSuiteXml);
+        }
+
+        return suites
+    }
+
+    TestNGOptions jdkAnnotations() {
+        javadocAnnotations = false
+        return this
+    }
+
+    TestNGOptions javadocAnnotations() {
+        javadocAnnotations = true
+        return this
+    }
+
+    public TestNGOptions includeGroups(String ... includeGroups) {
+        this.includeGroups.addAll(Arrays.asList(includeGroups))
+        return this
+    }
+
+    public TestNGOptions excludeGroups(String ... excludeGroups) {
+        this.excludeGroups.addAll(Arrays.asList(excludeGroups))
+        return this;
+    }
+
+    TestNGOptions useDefaultListeners() {
+        useDefaultListeners = true;
+
+        return this;
+    }
+
+    TestNGOptions useDefaultListeners(boolean useDefaultListeners) {
+        this.useDefaultListeners = useDefaultListeners;
+
+        return this;
+    }
+
+    public def propertyMissing(String name) {
+        if (suiteXmlBuilder != null) {
+            return suiteXmlBuilder.getMetaClass()."${name}"
+        } else {
+            return super.propertyMissing(name)
+        }
+    }
+
+    public def methodMissing(String name, args) {
+        if (suiteXmlBuilder != null) {
+            return suiteXmlBuilder.getMetaClass().invokeMethod(suiteXmlBuilder, name, args);
+        } else {
+            return super.methodMissing(name, args)
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/package-info.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/package-info.java
new file mode 100644
index 0000000..97d61bb
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/api/tasks/testing/testng/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * TestNG specific testing classes.
+ */
+package org.gradle.api.tasks.testing.testng;
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/CoreJavadocOptions.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/CoreJavadocOptions.java
new file mode 100644
index 0000000..33bb692
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/CoreJavadocOptions.java
@@ -0,0 +1,569 @@
+/*
+ * 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.external.javadoc;
+
+import org.gradle.external.javadoc.optionfile.JavadocOptionFile;
+import org.gradle.external.javadoc.optionfile.JavadocOptionFileOption;
+import org.gradle.external.javadoc.optionfile.OptionLessJavadocOptionFileOption;
+import org.gradle.process.ExecSpec;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 
+ *
+ *
+ * @author Tom Eyckmans
+ */
+public abstract class CoreJavadocOptions implements MinimalJavadocOptions
+{
+    private final JavadocOptionFile optionFile;
+
+    public CoreJavadocOptions() {
+        this(new JavadocOptionFile());
+    }
+
+    protected CoreJavadocOptions(JavadocOptionFile optionFile) {
+        this.optionFile = optionFile;
+
+        overview = addStringOption("overview");
+        memberLevel = addEnumOption("memberLevel");
+        doclet = addStringOption("doclet");
+        docletpath = addPathOption("docletpath");
+        source = addStringOption("source");
+        classpath = addPathOption("classpath");
+        bootClasspath = addPathOption("bootclasspath");
+        extDirs = addPathOption("extdirs");
+        outputLevel = addEnumOption("outputLevel", JavadocOutputLevel.QUIET);
+        breakIterator = addBooleanOption("breakiterator");
+        locale = addStringOption("locale");
+        encoding = addStringOption("encoding");
+
+        sourceNames = optionFile.getSourceNames();
+    }
+
+    /**
+     * -overview  path\filename
+     * Specifies that javadoc should retrieve the text for the overview documentation from
+     * the "source" file specified by path/filename and place it on the Overview page (overview-summary.html).
+     * The path/filename is relative to the -sourcepath.
+     *
+     * While you can use any name you want for filename and place it anywhere you want for path,
+     * a typical thing to do is to name it overview.html and place it in the source tree at the directory that contains the topmost package directories.
+     * In this location, no path is needed when documenting packages, since -sourcepath will point to this file.
+     * For example, if the source tree for the java.lang package is C:\src\classes\java\lang\,
+     * then you could place the overview file at C:\src\classes\overview.html. See Real World Example.
+     *
+     * For information about the file specified by path/filename, see overview comment file.
+     *
+     * Note that the overview page is created only if you pass into javadoc two or more package names.
+     * For further explanation, see HTML Frames.)
+     *
+     * The title on the overview page is set by -doctitle.
+     */
+    private final JavadocOptionFileOption<String> overview;
+
+    public String getOverview() {
+        return overview.getValue();
+    }
+
+    public void setOverview(String overview) {
+        this.overview.setValue(overview);
+    }
+
+    /**
+     * Fluent setter for the overview option.
+     * @param overview The new overview.
+     * @return The <code>MinimalJavadocOptions</code> object.
+     */
+    public MinimalJavadocOptions overview(String overview) {
+        setOverview(overview);
+        return this;
+    }
+
+    /**
+     * Switch to set the members that should be included in the Javadoc. (-public, -protected, -package, -private)
+     */
+    private final JavadocOptionFileOption<JavadocMemberLevel> memberLevel;
+
+    public JavadocMemberLevel getMemberLevel() {
+        return memberLevel.getValue();
+    }
+
+    public void setMemberLevel(JavadocMemberLevel memberLevel) {
+        this.memberLevel.setValue(memberLevel);
+    }
+
+    public MinimalJavadocOptions showFromPublic() {
+        setMemberLevel(JavadocMemberLevel.PUBLIC);
+        return this;
+    }
+
+    public MinimalJavadocOptions showFromProtected() {
+        setMemberLevel(JavadocMemberLevel.PROTECTED);
+        return this;
+    }
+
+    public MinimalJavadocOptions showFromPackage() {
+        setMemberLevel(JavadocMemberLevel.PACKAGE);
+        return this;
+    }
+
+    public void contributeCommandLineOptions(ExecSpec execHandleBuilder) {
+        execHandleBuilder
+            .args(GUtil.prefix("-J", jFlags)) // J flags can not be set in the option file
+            .args(GUtil.prefix("@", GFileUtils.toPaths(optionFiles))); // add additional option files
+    }
+
+    public MinimalJavadocOptions showFromPrivate() {
+        setMemberLevel(JavadocMemberLevel.PRIVATE);
+        return this;
+    }
+
+    public MinimalJavadocOptions showAll() {
+        return showFromPrivate();
+    }
+
+    /**
+     * -doclet  class
+     * Specifies the class file that starts the doclet used in generating the documentation. Use the fully-qualified name.
+     * This doclet defines the content and formats the output. If the -doclet option is not used,
+     * javadoc uses the standard doclet for generating the default HTML format.
+     * This class must contain the start(Root) method.
+     * The path to this starting class is defined by the -docletpath option.
+     *
+     * For example, to call the MIF doclet, use:
+     *
+     *     -doclet com.sun.tools.doclets.mif.MIFDoclet
+     *
+     * For full, working examples of running a particular doclet, see Running the MIF Doclet.
+     */
+    private final JavadocOptionFileOption<String> doclet;
+
+    public String getDoclet() {
+        return doclet.getValue();
+    }
+
+    public void setDoclet(String doclet) {
+        this.doclet.setValue(doclet);
+    }
+
+    public MinimalJavadocOptions doclet(String doclet) {
+        setDoclet(doclet);
+        return this;
+    }
+
+    /**
+     * -docletpath  classpathlist 
+     * Specifies the path to the doclet starting class file (specified with the -doclet option) and any jar files it depends on.
+     * If the starting class file is in a jar file, then this specifies the path to that jar file, as shown in the example below.
+     * You can specify an absolute path or a path relative to the current directory. If classpathlist contains multiple paths or jar files,
+     * they should be separated with a colon (:) on Solaris and a semi-colon (;) on Windows.
+     * This option is not necessary if the doclet starting class is already in the search path.
+     *
+     * Example of path to jar file that contains the starting doclet class file. Notice the jar filename is included.
+     *
+     *    -docletpath C:/user/mifdoclet/lib/mifdoclet.jar
+     *
+     * Example of path to starting doclet class file. Notice the class filename is omitted.
+     *
+     *    -docletpath C:/user/mifdoclet/classes/com/sun/tools/doclets/mif/
+     *
+     * For full, working examples of running a particular doclet, see Running the MIF Doclet.
+     */
+    private final JavadocOptionFileOption<List<File>> docletpath;
+
+    public List<File> getDocletpath() {
+        return docletpath.getValue();
+    }
+
+    public void setDocletpath(List<File> docletpath) {
+        this.docletpath.setValue(docletpath);
+    }
+
+    public MinimalJavadocOptions docletpath(File ... docletpath) {
+        this.docletpath.getValue().addAll(Arrays.asList(docletpath));
+        return this;
+    }
+
+    /**
+     * -source release
+     * Specifies the version of source code accepted. The following values for release are allowed:
+     * 1.5 	javadoc accepts code containing generics and other language features introduced in JDK 1.5.
+     * The compiler defaults to the 1.5 behavior if the -source flag is not used.
+     * 1.4 	javadoc accepts code containing assertions, which were introduced in JDK 1.4.
+     * 1.3 	javadoc does not support assertions, generics, or other language features introduced after JDK 1.3.
+     *
+     * Use the value of release corresponding to that used when compiling the code with javac.
+     */
+    private final JavadocOptionFileOption<String> source;// TODO bind with the sourceCompatibility property
+
+    public String getSource() {
+        return source.getValue();
+    }
+
+    public void setSource(String source) {
+        this.source.setValue(source);
+    }
+
+    public MinimalJavadocOptions source(String source) {
+        setSource(source);
+        return this;
+    }
+
+    /**
+     * -classpath  classpathlist
+     * Specifies the paths where javadoc will look for referenced classes (.class files)
+     * -- these are the documented classes plus any classes referenced by those classes.
+     * The classpathlist can contain multiple paths by separating them with a semicolon (;).
+     * The Javadoc tool will search in all subdirectories of the specified paths.
+     * Follow the instructions in class path documentation for specifying classpathlist.
+     *
+     * If -sourcepath is omitted, the Javadoc tool uses -classpath to find the source files as well as
+     * class files (for backward compatibility). Therefore, if you want to search for source and class files in separate paths,
+     * use both -sourcepath and -classpath.
+     *
+     * For example, if you want to document com.mypackage, whose source files reside in the directory C:/user/src/com/mypackage,
+     * and if this package relies on a library in C:/user/lib, you would specify:
+     *
+     *   C:> javadoc -classpath /user/lib -sourcepath /user/src com.mypackage
+     *
+     * As with other tools, if you do not specify -classpath, the Javadoc tool uses the CLASSPATH environment variable,
+     * if it is set. If both are not set, the Javadoc tool searches for classes from the current directory.
+     *
+     * For an in-depth description of how the Javadoc tool uses -classpath to find user classes as it relates to extension classes and
+     * bootstrap classes, see How Classes Are Found.
+     */
+    private final JavadocOptionFileOption<List<File>> classpath;// TODO link to runtime configuration ?
+
+    public List<File> getClasspath() {
+        return classpath.getValue();
+    }
+
+    public void setClasspath(List<File> classpath) {
+        this.classpath.setValue(classpath);
+    }
+
+    public MinimalJavadocOptions classpath(List<File> classpath) {
+        this.classpath.getValue().addAll(classpath);
+        return this;
+    }
+
+    public MinimalJavadocOptions classpath(File ... classpath) {
+        this.classpath.getValue().addAll(Arrays.asList(classpath));
+        return this;
+    }
+
+    /**
+     * -bootclasspath  classpathlist
+     * Specifies the paths where the boot classes reside. These are nominally the Java platform classes.
+     * The bootclasspath is part of the search path the Javadoc tool will use to look up source and class files.
+     * See How Classes Are Found. for more details. Separate directories in classpathlist with semicolons (;).
+     */
+    private final JavadocOptionFileOption<List<File>> bootClasspath;
+
+    public List<File> getBootClasspath() {
+        return bootClasspath.getValue();
+    }
+
+    public void setBootClasspath(List<File> bootClasspath) {
+        this.bootClasspath.setValue(bootClasspath);
+    }
+
+    public MinimalJavadocOptions bootClasspath(File ... bootClasspath) {
+        this.bootClasspath.getValue().addAll(Arrays.asList(bootClasspath));
+        return this;
+    }
+
+    /**
+     * -extdirs  dirlist
+     * Specifies the directories where extension classes reside.
+     * These are any classes that use the Java Extension mechanism.
+     * The extdirs is part of the search path the Javadoc tool will use to look up source and class files.
+     * See -classpath (above) for more details. Separate directories in dirlist with semicolons (;).
+     */
+    private final JavadocOptionFileOption<List<File>> extDirs;
+
+    public List<File> getExtDirs() {
+        return extDirs.getValue();
+    }
+
+    public void setExtDirs(List<File> extDirs) {
+        this.extDirs.setValue(extDirs);
+    }
+
+    public MinimalJavadocOptions extDirs(File ... extDirs) {
+        this.extDirs.getValue().addAll(Arrays.asList(extDirs));
+        return this;
+    }
+
+    /**
+     * Control the Javadoc output level (-verbose or -quiet
+     */
+    private final JavadocOptionFileOption<JavadocOutputLevel> outputLevel;
+
+    public JavadocOutputLevel getOutputLevel() {
+        return outputLevel.getValue();
+    }
+
+    public void setOutputLevel(JavadocOutputLevel outputLevel) {
+        this.outputLevel.setValue(outputLevel);
+    }
+
+    public MinimalJavadocOptions verbose() {
+        setOutputLevel(JavadocOutputLevel.VERBOSE);
+        return this;
+    }
+
+    public boolean isVerbose() {
+        return outputLevel.getValue() == JavadocOutputLevel.VERBOSE;
+    }
+
+    public MinimalJavadocOptions quiet() {
+        setOutputLevel(JavadocOutputLevel.QUIET);
+        return this;
+    }
+
+    /**
+     * -breakiterator
+     * Uses the internationalized sentence boundary of java.text.BreakIterator to determine the end of the first sentence
+     * for English (all other locales already use BreakIterator), rather than an English language, locale-specific algorithm.
+     * By first sentence, we mean the first sentence in the main description of a package, class or member.
+     * This sentence is copied to the package, class or member summary, and to the alphabetic index.
+     *
+     * From JDK 1.2 forward, the BreakIterator class is already used to determine the end of sentence for all languages but English.
+     * Therefore, the -breakiterator option has no effect except for English from 1.2 forward. English has its own default algorithm:
+     *
+     *     * English default sentence-break algorithm - Stops at a period followed by a space or a HTML block tag, such as <P>.
+     *     * Breakiterator sentence-break algorithm - In general, stops at a period,
+     *       question mark or exclamation mark followed by a space if the next word starts with a capital letter.
+     *       This is meant to handle most abbreviations (such as "The serial no. is valid", but won't handle "Mr. Smith").
+     *       Doesn't stop at HTML tags or sentences that begin with numbers or symbols.
+     *       Stops at the last period in "../filename", even if embedded in an HTML tag.
+     *
+     *     NOTE: We have removed from 1.5.0 the breakiterator warning messages that were in 1.4.x and
+     *           have left the default sentence-break algorithm unchanged. That is, the -breakiterator option is not the default in 1.5.0,
+     *           nor do we expect it to become the default. This is a reversal from our former intention that
+     *           the default would change in the "next major release" (1.5.0).
+     *           This means if you have not modified your source code to eliminate the breakiterator warnings in 1.4.x,
+     *           then you don't have to do anything, and the warnings go away starting with 1.5.0.
+     *           The reason for this reversal is because any benefit to having breakiterator become the default
+     *           would be outweighed by the incompatible source change it would require.
+     *           We regret any extra work and confusion this has caused.
+     */
+    private final JavadocOptionFileOption<Boolean> breakIterator;
+
+    public boolean isBreakIterator() {
+        return breakIterator.getValue();
+    }
+
+    public void setBreakIterator(boolean breakIterator) {
+        this.breakIterator.setValue(breakIterator);
+    }
+
+    public MinimalJavadocOptions breakIterator(boolean breakIterator) {
+        setBreakIterator(breakIterator);
+        return this;
+    }
+
+    public MinimalJavadocOptions breakIterator() {
+        setBreakIterator(true);
+        return this;
+    }
+
+    /**
+     * -locale  language_country_variant
+     *     Important - The -locale option must be placed ahead (to the left) of any options provided by the standard doclet or
+     *                 any other doclet. Otherwise, the navigation bars will appear in English.
+     *                 This is the only command-line option that is order-dependent.
+     *
+     * Specifies the locale that javadoc uses when generating documentation.
+     * The argument is the name of the locale, as described in java.util.Locale documentation, such as
+     * en_US (English, United States) or en_US_WIN (Windows variant).
+     *
+     * Specifying a locale causes javadoc to choose the resource files of that locale for messages
+     * (strings in the navigation bar, headings for lists and tables, help file contents,
+     * comments in stylesheet.css, and so forth).
+     * It also specifies the sorting order for lists sorted alphabetically,
+     * and the sentence separator to determine the end of the first sentence.
+     * It does not determine the locale of the doc comment text specified in the source files of the documented classes.
+     */
+    private final JavadocOptionFileOption<String> locale;
+
+    public String getLocale() {
+        return locale.getValue();
+    }
+
+    public void setLocale(String locale) {
+        this.locale.setValue(locale);
+    }
+
+    public MinimalJavadocOptions locale(String locale) {
+        setLocale(locale);
+        return this;
+    }
+
+    /**
+     * -encoding  name
+     * Specifies the encoding name of the source files, such as EUCJIS/SJIS. If this option is not specified, the platform default converter is used.
+     *
+     * Also see -docencoding and -charset.
+     */
+    private final JavadocOptionFileOption<String> encoding;
+
+    public String getEncoding() {
+        return encoding.getValue();
+    }
+
+    public void setEncoding(String encoding) {
+        this.encoding.setValue(encoding);
+    }
+
+    public MinimalJavadocOptions encoding(String encoding) {
+        setEncoding(encoding);
+        return this;
+    }
+
+    private final OptionLessJavadocOptionFileOption<List<String>> sourceNames;
+
+    public List<String> getSourceNames() {
+        return sourceNames.getValue();
+    }
+
+    public void setSourceNames(List<String> sourceNames) {
+        this.sourceNames.setValue(sourceNames);
+    }
+
+    public MinimalJavadocOptions sourceNames(String ... sourceNames) {
+        this.sourceNames.getValue().addAll(Arrays.asList(sourceNames));
+        return this;
+    }
+
+    /**
+     * -Jflag
+     * Passes flag directly to the runtime system java that runs javadoc.
+     * Notice there must be no space between the J and the flag. For example,
+     * if you need to ensure that the system sets aside 32 megabytes of memory in which to process the generated documentation,
+     * then you would call the -Xmx option of java as follows (-Xms is optional, as it only sets the size of initial memory,
+     * which is useful if you know the minimum amount of memory required):
+     *
+     *    C:> javadoc -J-Xmx32m -J-Xms32m com.mypackage
+     *
+     * To tell what version of javadoc you are using, call the "-version" option of java:
+     *
+     *    C:> javadoc -J-version
+     *    java version "1.2"
+     *    Classic VM (build JDK-1.2-V, green threads, sunwjit)
+     *
+     * (The version number of the standard doclet appears in its output stream.)
+     */
+    private List<String> jFlags = new ArrayList<String>();
+
+    public List<String> getJFlags() {
+        return jFlags;
+    }
+
+    public void setJFlags(List<String> jFlags) {
+        this.jFlags = jFlags;
+    }
+
+    public MinimalJavadocOptions jFlags(String ... jFlags) {
+        this.jFlags.addAll(Arrays.asList(jFlags));
+        return this;
+    }
+
+    private List<File> optionFiles = new ArrayList<File>();
+
+    public List<File> getOptionFiles() {
+        return optionFiles;
+    }
+
+    public void setOptionFiles(List<File> optionFiles) {
+        this.optionFiles = optionFiles;
+    }
+
+    public MinimalJavadocOptions optionFiles(File ... argumentFiles) {
+        this.optionFiles.addAll(Arrays.asList(argumentFiles));
+        return this;
+    }
+
+    public final void write(File outputFile) throws IOException {
+        optionFile.write(outputFile);
+    }
+
+    public <T> JavadocOptionFileOption<T> addOption(JavadocOptionFileOption<T> option) {
+        return optionFile.addOption(option);
+    }
+
+    public JavadocOptionFileOption<String> addStringOption(String option) {
+        return optionFile.addStringOption(option);
+    }
+
+    public JavadocOptionFileOption<String> addStringOption(String option, String value) {
+        return optionFile.addStringOption(option, value);
+    }
+
+    public <T> JavadocOptionFileOption<T> addEnumOption(String option) {
+        return optionFile.addEnumOption(option);
+    }
+
+    public <T> JavadocOptionFileOption<T> addEnumOption(String option, T value) {
+        return optionFile.addEnumOption(option, value);
+    }
+
+    public JavadocOptionFileOption<List<File>> addPathOption(String option) {
+        return optionFile.addPathOption(option);
+    }
+
+    public JavadocOptionFileOption<List<File>> addPathOption(String option, String joinBy) {
+        return optionFile.addPathOption(option, joinBy);
+    }
+
+    public JavadocOptionFileOption<List<String>> addStringsOption(String option) {
+        return optionFile.addStringsOption(option);
+    }
+
+    public JavadocOptionFileOption<List<String>> addStringsOption(String option, String joinBy) {
+        return optionFile.addStringsOption(option, joinBy);
+    }
+
+   public JavadocOptionFileOption<List<String>> addMultilineStringsOption(String option) {
+       return optionFile.addMultilineStringsOption(option);
+   }
+
+    public JavadocOptionFileOption<Boolean> addBooleanOption(String option) {
+        return optionFile.addBooleanOption(option);
+    }
+
+    public JavadocOptionFileOption<Boolean> addBooleanOption(String option, boolean value) {
+        return optionFile.addBooleanOption(option, value);
+    }
+
+    public JavadocOptionFileOption<File> addFileOption(String option) {
+        return optionFile.addFileOption(option);
+    }
+
+    public JavadocOptionFileOption<File> addFileOption(String option, File value) {
+        return optionFile.addFileOption(option, value);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/JavadocExecHandleBuilder.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/JavadocExecHandleBuilder.java
new file mode 100644
index 0000000..fbbf23b
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/JavadocExecHandleBuilder.java
@@ -0,0 +1,90 @@
+/*
+ * 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.external.javadoc;
+
+import org.gradle.api.GradleException;
+import org.gradle.process.internal.DefaultExecAction;
+import org.gradle.process.internal.ExecAction;
+import org.gradle.util.GUtil;
+import org.gradle.util.Jvm;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class JavadocExecHandleBuilder {
+    private File execDirectory;
+    private MinimalJavadocOptions options;
+    private File optionsFile;
+    private String executable;
+
+    public JavadocExecHandleBuilder execDirectory(File directory) {
+        if (directory == null) {
+            throw new IllegalArgumentException("execDirectory == null!");
+        }
+        if (!directory.exists()) {
+            throw new IllegalArgumentException("execDirectory doesn't exists!");
+        }
+        if (directory.isFile()) {
+            throw new IllegalArgumentException("execDirectory is a file");
+        }
+
+        this.execDirectory = directory;
+        return this;
+    }
+
+    public JavadocExecHandleBuilder options(MinimalJavadocOptions options) {
+        if (options == null) {
+            throw new IllegalArgumentException("options == null!");
+        }
+
+        this.options = options;
+        return this;
+    }
+
+    public JavadocExecHandleBuilder optionsFile(File optionsFile) {
+        this.optionsFile = optionsFile;
+        return this;
+    }
+
+    public ExecAction getExecHandle() {
+        try {
+            options.write(optionsFile);
+        } catch (IOException e) {
+            throw new GradleException("Faild to store javadoc options.", e);
+        }
+
+        ExecAction execAction = new DefaultExecAction();
+        execAction.workingDir(execDirectory);
+        execAction.executable(GUtil.elvis(executable, Jvm.current().getJavadocExecutable()));
+        execAction.args("@" + optionsFile.getAbsolutePath());
+
+        options.contributeCommandLineOptions(execAction);
+
+        return execAction;
+    }
+
+    public String getExecutable() {
+        return executable;
+    }
+
+    public void setExecutable(String executable) {
+        this.executable = executable;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/JavadocMemberLevel.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/JavadocMemberLevel.java
new file mode 100644
index 0000000..337f955
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/JavadocMemberLevel.java
@@ -0,0 +1,41 @@
+/*
+ * 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.external.javadoc;
+
+/**
+ * This enum maps to the -public, -protected, -package and -private options of the javadoc executable. 
+ *
+ * @author Tom Eyckmans
+ */
+public enum JavadocMemberLevel {
+    /**
+     * Shows only public classes and members.
+     */
+    PUBLIC,
+    /**
+     * Shows only protected and public classes and members. This is the default.
+     */
+    PROTECTED,
+    /**
+     * Shows only package, protected, and public classes and members.
+     */
+    PACKAGE,
+    /**
+     * Shows all classes and members. 
+     */
+    PRIVATE
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOfflineLink.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOfflineLink.java
new file mode 100644
index 0000000..be86bb1
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOfflineLink.java
@@ -0,0 +1,46 @@
+/*
+ * 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.external.javadoc;
+
+
+/**
+ * This class is used to hold the information that can be provided to the javadoc executable via the -linkoffline
+ * option.
+ *
+ * @author Tom Eyckmans
+ */
+public class JavadocOfflineLink {
+    private final String extDocUrl;
+    private final String packagelistLoc;
+
+    public JavadocOfflineLink(String extDocUrl, String packagelistLoc) {
+        this.extDocUrl = extDocUrl;
+        this.packagelistLoc = packagelistLoc;
+    }
+
+    public String getExtDocUrl() {
+        return extDocUrl;
+    }
+
+    public String getPackagelistLoc() {
+        return packagelistLoc;
+    }
+
+    public String toString() {
+        return extDocUrl + "' '" + packagelistLoc;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOutputLevel.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOutputLevel.java
new file mode 100644
index 0000000..d7dcf43
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/JavadocOutputLevel.java
@@ -0,0 +1,40 @@
+/*
+ * 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.external.javadoc;
+
+/**
+ * This enum maps to the -verbose and -quiet options of the javadoc executable.
+ *
+ * @author Tom Eyckmans
+ */
+public enum JavadocOutputLevel {
+    /**
+     * -verbose
+     * 
+     * Provides more detailed messages while javadoc is running. Without the verbose option,
+     * messages appear for loading the source files, generating the documentation (one message per source file), and sorting.
+     * The verbose option causes the printing of additional messages specifying the number of milliseconds to parse each java source file.
+     */
+    VERBOSE,
+    /**
+     * -quiet
+     *
+     * Shuts off non-error and non-warning messages, leaving only the warnings and errors appear,
+     * making them easier to view. Also suppresses the version string. 
+     */
+    QUIET
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/MinimalJavadocOptions.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/MinimalJavadocOptions.java
new file mode 100644
index 0000000..18b98fa
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/MinimalJavadocOptions.java
@@ -0,0 +1,156 @@
+/*
+ * 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.external.javadoc;
+
+import org.gradle.process.ExecSpec;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * @author Tom Eyckmans
+ */
+public interface MinimalJavadocOptions {
+    String getOverview();
+
+    void setOverview(String overview);
+
+    MinimalJavadocOptions overview(String overview);
+
+    JavadocMemberLevel getMemberLevel();
+
+    void setMemberLevel(JavadocMemberLevel memberLevel);
+
+    MinimalJavadocOptions showFromPublic();
+
+    MinimalJavadocOptions showFromProtected();
+
+    MinimalJavadocOptions showFromPackage();
+
+    MinimalJavadocOptions showFromPrivate();
+
+    MinimalJavadocOptions showAll();
+
+    String getDoclet();
+
+    void setDoclet(String docletClass);
+
+    MinimalJavadocOptions doclet(String docletClass);
+
+    List<File> getDocletpath();
+
+    void setDocletpath(List<File> docletpath);
+
+    MinimalJavadocOptions docletpath(File ... docletpath);
+
+    String getSource();
+
+    void setSource(String source);
+
+    MinimalJavadocOptions source(String source);
+
+    List<File> getClasspath();
+
+    void setClasspath(List<File> classpath);
+
+    MinimalJavadocOptions classpath(List<File> classpath);
+
+    MinimalJavadocOptions classpath(File ... classpath);
+
+    List<File> getBootClasspath();
+
+    void setBootClasspath(List<File> bootClasspath);
+
+    MinimalJavadocOptions bootClasspath(File ... bootClasspath);
+
+    List<File> getExtDirs();
+
+    void setExtDirs(List<File> extDirs);
+
+    MinimalJavadocOptions extDirs(File ... extDirs);
+
+    JavadocOutputLevel getOutputLevel();
+
+    void setOutputLevel(JavadocOutputLevel outputLevel);
+
+    MinimalJavadocOptions verbose();
+
+    boolean isVerbose();
+
+    MinimalJavadocOptions quiet();
+
+    boolean isBreakIterator();
+
+    void setBreakIterator(boolean breakIterator);
+
+    MinimalJavadocOptions breakIterator(boolean breakIterator);
+
+    MinimalJavadocOptions breakIterator();
+
+    String getLocale();
+
+    void setLocale(String locale);
+
+    MinimalJavadocOptions locale(String locale);
+
+    String getEncoding();
+
+    void setEncoding(String encoding);
+
+    MinimalJavadocOptions encoding(String encoding);
+
+    List<String> getJFlags();
+
+    void setJFlags(List<String> jFlags);
+
+    MinimalJavadocOptions jFlags(String ... jFlags);
+
+    List<File> getOptionFiles();
+
+    void setOptionFiles(List<File> optionFiles);
+
+    MinimalJavadocOptions optionFiles(File ... argumentFiles);
+
+    File getDestinationDirectory();
+
+    void setDestinationDirectory(File directory);
+
+    MinimalJavadocOptions destinationDirectory(File directory);
+
+    String getWindowTitle();
+
+    void setWindowTitle(String windowTitle);
+
+    StandardJavadocDocletOptions windowTitle(String windowTitle);
+
+    String getHeader();
+
+    void setHeader(String header);
+
+    StandardJavadocDocletOptions header(String header);
+
+    void write(File outputFile) throws IOException;
+
+    List<String> getSourceNames();
+
+    void setSourceNames(List<String> sourceNames);
+
+    MinimalJavadocOptions sourceNames(String ... sourceNames);
+
+    void contributeCommandLineOptions(ExecSpec execHandleBuilder);
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptions.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptions.java
new file mode 100644
index 0000000..212cbf4
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptions.java
@@ -0,0 +1,969 @@
+/*
+ * 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.external.javadoc;
+
+import org.gradle.external.javadoc.optionfile.JavadocOptionFileOption;
+import org.gradle.external.javadoc.optionfile.GroupsJavadocOptionFileOption;
+import org.gradle.external.javadoc.optionfile.LinksOfflineJavadocOptionFileOption;
+import org.gradle.external.javadoc.optionfile.JavadocOptionFile;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class StandardJavadocDocletOptions extends CoreJavadocOptions implements MinimalJavadocOptions {
+
+    public StandardJavadocDocletOptions() {
+        this(new JavadocOptionFile());
+    }
+
+    public StandardJavadocDocletOptions(JavadocOptionFile javadocOptionFile) {
+        super(javadocOptionFile);
+
+        destinationDirectory = addFileOption("d");
+        use = addBooleanOption("use");
+        version = addBooleanOption("version");
+        author = addBooleanOption("author");
+        splitIndex = addBooleanOption("splitindex");
+        header = addStringOption("header");
+        windowTitle = addStringOption("windowtitle");
+        docTitle = addStringOption("doctitle");
+        footer = addStringOption("footer");
+        bottom = addStringOption("bottom");
+        links = addMultilineStringsOption("link");
+        linksOffline = addOption(new LinksOfflineJavadocOptionFileOption("linkoffline"));
+        linkSource = addBooleanOption("linksource");
+        groups = addOption(new GroupsJavadocOptionFileOption("group"));
+        noDeprecated = addBooleanOption("nodeprecated");
+        noDeprecatedList = addBooleanOption("nodeprecatedlist");
+        noSince = addBooleanOption("nosince");
+        noTree = addBooleanOption("notree");
+        noIndex = addBooleanOption("noindex");
+        noHelp = addBooleanOption("nohelp");
+        noNavBar = addBooleanOption("nonavbar");
+        helpFile = addFileOption("helpfile");
+        stylesheetFile = addFileOption("stylesheetfile");
+        serialWarn = addBooleanOption("serialwarn");
+        charSet = addStringOption("charset");
+        docEncoding = addStringOption("docencoding");
+        keyWords = addBooleanOption("keywords");
+        tags = addStringsOption("tags");
+        tagletPath = addPathOption("tagletpath");
+        docFilesSubDirs = addBooleanOption("docfilessubdirs");
+        excludeDocFilesSubDir = addStringsOption("excludedocfilessubdir", ":");
+        noQualifiers = addStringsOption("noqualifier", ":");
+        noTimestamp = addBooleanOption("notimestamp");
+        noComment = addBooleanOption("nocomment");
+    }
+
+    /**
+     * -d  directory
+     * Specifies the destination directory where javadoc saves the generated HTML files. (The "d" means "destination.")
+     * Omitting this option causes the files to be saved to the current directory.
+     * The value directory can be absolute, or relative to the current working directory.
+     * As of 1.4, the destination directory is automatically created when javadoc is run.
+     * For example, the following generates the documentation for the package com.mypackage and
+     * saves the results in the C:/user/doc/ directory:
+     * <p/>
+     * C:> javadoc -d /user/doc com.mypackage
+     */
+    private final JavadocOptionFileOption<File> destinationDirectory;
+
+    public File getDestinationDirectory() {
+        return destinationDirectory.getValue();
+    }
+
+    public void setDestinationDirectory(File directory) {
+        this.destinationDirectory.setValue(directory);
+    }
+
+    public StandardJavadocDocletOptions destinationDirectory(File destinationDirectory) {
+        setDestinationDirectory(destinationDirectory);
+        return this;
+    }
+
+    /**
+     * -use
+     * Includes one "Use" page for each documented class and package. The page describes what packages, classes, methods,
+     * constructors and fields use any API of the given class or package. Given class C,
+     * things that use class C would include subclasses of C, fields declared as C, methods that return C,
+     * and methods and constructors with parameters of type C.
+     * For example, let's look at what might appear on the "Use" page for String.
+     * The getName() method in the java.awt.Font class returns type String. Therefore, getName() uses String,
+     * and you will find that method on the "Use" page for String.
+     * <p/>
+     * Note that this documents only uses of the API, not the implementation.
+     * If a method uses String in its implementation but does not take a string as an argument or return a string,
+     * that is not considered a "use" of String.
+     * <p/>
+     * You can access the generated "Use" page by first going to the class or package,
+     * then clicking on the "Use" link in the navigation bar.
+     */
+    private final JavadocOptionFileOption<Boolean> use;
+
+    public boolean isUse() {
+        return use.getValue();
+    }
+
+    public void setUse(boolean use) {
+        this.use.setValue(use);
+    }
+
+    public StandardJavadocDocletOptions use(boolean use) {
+        setUse(use);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions use() {
+        return use(true);
+    }
+
+    /**
+     * -version
+     * Includes the @version text in the generated docs. This text is omitted by default.
+     * To tell what version of the Javadoc tool you are using, use the -J-version option.
+     */
+    private final JavadocOptionFileOption<Boolean> version;
+
+    public boolean isVersion() {
+        return version.getValue();
+    }
+
+    public void setVersion(boolean version) {
+        this.version.setValue(version);
+    }
+
+    public StandardJavadocDocletOptions version(boolean version) {
+        setVersion(version);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions version() {
+        return version(true);
+    }
+
+    /**
+     * -author
+     * Includes the @author text in the generated docs.
+     */
+    private final JavadocOptionFileOption<Boolean> author;
+
+    public boolean isAuthor() {
+        return author.getValue();
+    }
+
+    public void setAuthor(boolean author) {
+        this.author.setValue(author);
+    }
+
+    public StandardJavadocDocletOptions author(boolean author) {
+        setAuthor(author);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions author() {
+        return author(true);
+    }
+
+    /**
+     * -splitindex
+     * Splits the index file into multiple files, alphabetically, one file per letter,
+     * plus a file for any index entries that start with non-alphabetical characters.
+     */
+    private final JavadocOptionFileOption<Boolean> splitIndex;
+
+    public boolean isSplitIndex() {
+        return splitIndex.getValue();
+    }
+
+    public void setSplitIndex(boolean splitIndex) {
+        this.splitIndex.setValue(splitIndex);
+    }
+
+    public StandardJavadocDocletOptions splitIndex(boolean splitIndex) {
+        setSplitIndex(splitIndex);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions splitIndex() {
+        return splitIndex(true);
+    }
+
+    /**
+     * -windowtitle  title
+     * Specifies the title to be placed in the HTML <title> tag.
+     * This appears in the window title and in any browser bookmarks (favorite places) that someone creates for this page.
+     * This title should not contain any HTML tags, as the browser will not properly interpret them.
+     * Any internal quotation marks within title may have to be escaped. If -windowtitle is omitted,
+     * the Javadoc tool uses the value of -doctitle for this option.
+     * C:> javadoc -windowtitle "Java 2 Platform" com.mypackage
+     */
+    private final JavadocOptionFileOption<String> windowTitle;
+
+    public String getWindowTitle() {
+        return windowTitle.getValue();
+    }
+
+    public void setWindowTitle(String windowTitle) {
+        this.windowTitle.setValue(windowTitle);
+    }
+
+    public StandardJavadocDocletOptions windowTitle(String windowTitle) {
+        setWindowTitle(windowTitle);
+        return this;
+    }
+
+    /**
+     * -header  header
+     * Specifies the header text to be placed at the top of each output file. The header will be placed to the right of
+     * the upper navigation bar. header may contain HTML tags and white space, though if it does, it must be enclosed
+     * in quotes. Any internal quotation marks within header may have to be escaped.
+     * C:> javadoc -header "<b>Java 2 Platform </b><br>v1.4" com.mypackage
+     */
+    private final JavadocOptionFileOption<String> header;
+
+    public String getHeader() {
+        return header.getValue();
+    }
+
+    public void setHeader(String header) {
+        this.header.setValue(header);
+    }
+
+    public StandardJavadocDocletOptions header(String header) {
+        setHeader(header);
+        return this;
+    }
+
+
+    /**
+     * -doctitle  title
+     * Specifies the title to be placed near the top of the overview summary file. The title will be placed as a centered,
+     * level-one heading directly beneath the upper navigation bar. The title may contain html tags and white space,
+     * though if it does, it must be enclosed in quotes. Any internal quotation marks within title may have to be escaped.
+     * C:> javadoc -doctitle "Java<sup><font size=\"-2\">TM</font></sup>" com.mypackage
+     */
+    private final JavadocOptionFileOption<String> docTitle;
+
+    public String getDocTitle() {
+        return docTitle.getValue();
+    }
+
+    public void setDocTitle(String docTitle) {
+        this.docTitle.setValue(docTitle);
+    }
+
+    public StandardJavadocDocletOptions docTitle(String docTitle) {
+        setDocTitle(docTitle);
+        return this;
+    }
+
+    /**
+     * -footer  footer
+     * Specifies the footer text to be placed at the bottom of each output file.
+     * The footer will be placed to the right of the lower navigation bar. footer may contain html tags and white space,
+     * though if it does, it must be enclosed in quotes. Any internal quotation marks within footer may have to be escaped.
+     */
+    private final JavadocOptionFileOption<String> footer;
+
+    public String getFooter() {
+        return footer.getValue();
+    }
+
+    public void setFooter(String footer) {
+        this.footer.setValue(footer);
+    }
+
+    public StandardJavadocDocletOptions footer(String footer) {
+        setFooter(footer);
+        return this;
+    }
+
+    /**
+     * -bottom  text
+     * Specifies the text to be placed at the bottom of each output file.
+     * The text will be placed at the bottom of the page, below the lower navigation bar.
+     * The text may contain HTML tags and white space, though if it does, it must be enclosed in quotes.
+     * Any internal quotation marks within text may have to be escaped.
+     */
+    private final JavadocOptionFileOption<String> bottom;
+
+    public String getBottom() {
+        return bottom.getValue();
+    }
+
+    public void setBottom(String bottom) {
+        this.bottom.setValue(bottom);
+    }
+
+    public StandardJavadocDocletOptions bottom(String bottom) {
+        setBottom(bottom);
+        return this;
+    }
+
+    /**
+     * -link  extdocURL
+     * Creates links to existing javadoc-generated documentation of external referenced classes. It takes one argument:
+     * <p/>
+     * extdocURL is the absolute or relative URL of the directory containing the external javadoc-generated documentation
+     * you want to link to. Examples are shown below.
+     * The package-list file must be found in this directory (otherwise, use -linkoffline).
+     * The Javadoc tool reads the package names from the package-list file and then links to those packages at that URL.
+     * When the Javadoc tool is run, the extdocURL value is copied literally into the <A HREF> links that are created.
+     * Therefore, extdocURL must be the URL to the directory, not to a file.
+     * You can use an absolute link for extdocURL to enable your docs to link to a document on any website,
+     * or can use a relative link to link only to a relative location. If relative,
+     * the value you pass in should be the relative path from the destination directory (specified with -d) to the directory containing the packages being linked to.
+     * <p/>
+     * When specifying an absolute link you normally use an http: link. However,
+     * if you want to link to a file system that has no web server, you can use a file: link -- however,
+     * do this only if everyone wanting to access the generated documentation shares the same file system.
+     */
+    private final JavadocOptionFileOption<List<String>> links;
+
+    public List<String> getLinks() {
+        return links.getValue();
+    }
+
+    public void setLinks(List<String> links) {
+        this.links.setValue(links);
+    }
+
+    public StandardJavadocDocletOptions links(String... links) {
+        this.links.getValue().addAll(Arrays.asList(links));
+        return this;
+    }
+
+    public StandardJavadocDocletOptions linksFile(File linksFile) {
+        return (StandardJavadocDocletOptions) optionFiles(linksFile);
+    }
+
+    /**
+     * -linkoffline  extdocURL  packagelistLoc
+     * This option is a variation of -link; they both create links to javadoc-generated documentation
+     * for external referenced classes. Use the -linkoffline option when linking to a document on the web
+     * when the Javadoc tool itself is "offline" -- that is, it cannot access the document through a web connection.
+     * More specifically, use -linkoffline if the external document's package-list file is not accessible or
+     * does not exist at the extdocURL location but does exist at a different location,
+     * which can be specified by packageListLoc (typically local). Thus, if extdocURL is accessible only on the World Wide Web,
+     * -linkoffline removes the constraint that the Javadoc tool have a web connection when generating the documentation.
+     * <p/>
+     * Another use is as a "hack" to update docs: After you have run javadoc on a full set of packages,
+     * then you can run javadoc again on onlya smaller set of changed packages,
+     * so that the updated files can be inserted back into the original set. Examples are given below.
+     * <p/>
+     * The -linkoffline option takes two arguments -- the first for the string to be embedded in the <a href> links,
+     * the second telling it where to find package-list:
+     * <p/>
+     * extdocURL is the absolute or relative URL of the directory containing the external javadoc-generated documentation you want to link to.
+     * If relative, the value should be the relative path from the destination directory (specified with -d) to the root of the packages being linked to.
+     * For more details, see extdocURL in the -link option.
+     * packagelistLoc is the path or URL to the directory containing the package-list file for the external documentation.
+     * This can be a URL (http: or file:) or file path, and can be absolute or relative. If relative,
+     * make it relative to the current directory from where javadoc was run. Do not include the package-list filename.
+     */
+    private final JavadocOptionFileOption<List<JavadocOfflineLink>> linksOffline;
+
+    public List<JavadocOfflineLink> getLinksOffline() {
+        return linksOffline.getValue();
+    }
+
+    public void setLinksOffline(List<JavadocOfflineLink> linksOffline) {
+        this.linksOffline.setValue(linksOffline);
+    }
+
+    public StandardJavadocDocletOptions linksOffline(String extDocUrl, String packageListLoc) {
+        this.linksOffline.getValue().add(new JavadocOfflineLink(extDocUrl, packageListLoc));
+        return this;
+    }
+
+    public StandardJavadocDocletOptions linksOfflineFile(File linksOfflineFile) {
+        return (StandardJavadocDocletOptions) optionFiles(linksOfflineFile);
+    }
+
+    /**
+     * -linksource
+     * Creates an HTML version of each source file (with line numbers) and adds links to them from the standard HTML documentation. Links are created for classes, interfaces, constructors, methods and fields whose declarations are in a source file. Otherwise, links are not created, such as for default constructors and generated classes.
+     * This option exposes all private implementation details in the included source files, including private classes, private fields, and the bodies of private methods, regardless of the -public, -package, -protected and -private options. Unless you also use the -private option, not all private classes or interfaces will necessarily be accessible via links.
+     * <p/>
+     * Each link appears on the name of the identifier in its declaration. For example, the link to the source code of the Button class would be on the word "Button":
+     * <p/>
+     * public class Button
+     * extends Component
+     * implements Accessible
+     * and the link to the source code of the getLabel() method in the Button class would be on the word "getLabel":
+     * public String getLabel()
+     */
+    private final JavadocOptionFileOption<Boolean> linkSource;
+
+    public boolean isLinkSource() {
+        return linkSource.getValue();
+    }
+
+    public void setLinkSource(boolean linkSource) {
+        this.linkSource.setValue(linkSource);
+    }
+
+    public StandardJavadocDocletOptions linkSource(boolean linkSource) {
+        setLinkSource(linkSource);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions linkSource() {
+        return linkSource(true);
+    }
+
+    /**
+     * -group  groupheading  packagepattern:packagepattern:...
+     * Separates packages on the overview page into whatever groups you specify, one group per table.
+     * You specify each group with a different -group option.
+     * The groups appear on the page in the order specified on the command line; packages are alphabetized within a group.
+     * For a given -group option, the packages matching the list of packagepattern expressions appear in a table
+     * with the heading groupheading.
+     * groupheading can be any text, and can include white space. This text is placed in the table heading for the group.
+     * packagepattern can be any package name, or can be the start of any package name followed by an asterisk (*).
+     * The asterisk is a wildcard meaning "match any characters". This is the only wildcard allowed.
+     * Multiple patterns can be included in a group by separating them with colons (:).
+     * NOTE: If using an asterisk in a pattern or pattern list, the pattern list must be inside quotes,
+     * such as "java.lang*:java.util"
+     * If you do not supply any -group option, all packages are placed in one group with the heading "Packages".
+     * If the all groups do not include all documented packages,
+     * any leftover packages appear in a separate group with the heading "Other Packages".
+     * <p/>
+     * For example, the following option separates the four documented packages into core,
+     * extension and other packages. Notice the trailing "dot" does not appear in "java.lang*" -- including the dot,
+     * such as "java.lang.*" would omit the java.lang package.
+     * <p/>
+     * C:> javadoc -group "Core Packages" "java.lang*:java.util"
+     * -group "Extension Packages" "javax.*"
+     * java.lang java.lang.reflect java.util javax.servlet java.new
+     * This results in the groupings:
+     * Core Packages
+     * java.lang
+     * java.lang.reflect
+     * java.util
+     * Extension Packages
+     * javax.servlet
+     * Other Packages
+     * java.new
+     */
+    private final JavadocOptionFileOption<Map<String, List<String>>> groups;
+
+    public Map<String, List<String>> getGroups() {
+        return groups.getValue();
+    }
+
+    public void setGroups(Map<String, List<String>> groups) {
+        this.groups.setValue(groups);
+    }
+
+    public StandardJavadocDocletOptions group(Map<String, List<String>> groups) {
+        setGroups(groups);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions group(String groupName, List<String> packagePatterns) {
+        this.groups.getValue().put(groupName, packagePatterns);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions group(String groupName, String... packagePatterns) {
+        return group(groupName, Arrays.asList(packagePatterns));
+    }
+
+    public StandardJavadocDocletOptions groupsFile(File groupsFile) {
+        return (StandardJavadocDocletOptions) optionFiles(groupsFile);
+    }
+
+    /**
+     * -nodeprecated
+     * Prevents the generation of any deprecated API at all in the documentation.
+     * This does what -nodeprecatedlist does, plus it does not generate any deprecated API throughout the rest of the documentation.
+     * This is useful when writing code and you don't want to be distracted by the deprecated code.
+     */
+    private final JavadocOptionFileOption<Boolean> noDeprecated;
+
+    public boolean isNoDeprecated() {
+        return noDeprecated.getValue();
+    }
+
+    public void setNoDeprecated(boolean noDeprecated) {
+        this.noDeprecated.setValue(noDeprecated);
+    }
+
+    public StandardJavadocDocletOptions noDeprecated(boolean nodeprecated) {
+        setNoDeprecated(nodeprecated);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions noDeprecated() {
+        return noDeprecated(true);
+    }
+
+    /**
+     * -nodeprecatedlist
+     * Prevents the generation of the file containing the list of deprecated APIs (deprecated-list.html) and
+     * the link in the navigation bar to that page.
+     * (However, javadoc continues to generate the deprecated API throughout the rest of the document.)
+     * This is useful if your source code contains no deprecated API, and you want to make the navigation bar cleaner.
+     */
+    private final JavadocOptionFileOption<Boolean> noDeprecatedList;
+
+    public boolean isNoDeprecatedList() {
+        return noDeprecatedList.getValue();
+    }
+
+    public void setNoDeprecatedList(boolean noDeprecatedList) {
+        this.noDeprecatedList.setValue(noDeprecatedList);
+    }
+
+    public StandardJavadocDocletOptions noDeprecatedList(boolean noDeprecatedList) {
+        setNoDeprecatedList(noDeprecatedList);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions noDeprecatedList() {
+        return noDeprecatedList(true);
+    }
+
+    /**
+     * -nosince
+     * Omits from the generated docs the "Since" sections associated with the @since tags.
+     */
+    private final JavadocOptionFileOption<Boolean> noSince;
+
+    public boolean isNoSince() {
+        return noSince.getValue();
+    }
+
+    public void setNoSince(boolean noSince) {
+        this.noSince.setValue(noSince);
+    }
+
+    public StandardJavadocDocletOptions noSince(boolean noSince) {
+        setNoSince(noSince);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions noSince() {
+        return noSince(true);
+    }
+
+    /**
+     * -notree
+     * Omits the class/interface hierarchy pages from the generated docs.
+     * These are the pages you reach using the "Tree" button in the navigation bar.
+     * The hierarchy is produced by default.
+     */
+    private final JavadocOptionFileOption<Boolean> noTree;
+
+    public boolean isNoTree() {
+        return noTree.getValue();
+    }
+
+    public void setNoTree(boolean noTree) {
+        this.noTree.setValue(noTree);
+    }
+
+    public StandardJavadocDocletOptions noTree(boolean noTree) {
+        setNoTree(noTree);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions noTree() {
+        return noTree(true);
+    }
+
+    /**
+     * -noindex
+     * Omits the index from the generated docs. The index is produced by default.
+     */
+    private final JavadocOptionFileOption<Boolean> noIndex;
+
+    public boolean isNoIndex() {
+        return noIndex.getValue();
+    }
+
+    public void setNoIndex(boolean noIndex) {
+        this.noIndex.setValue(noIndex);
+    }
+
+    public StandardJavadocDocletOptions noIndex(boolean noIndex) {
+        setNoIndex(noIndex);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions noIndex() {
+        return noIndex(true);
+    }
+
+    /**
+     * -nohelp
+     * Omits the HELP link in the navigation bars at the top and bottom of each page of output.
+     */
+    private final JavadocOptionFileOption<Boolean> noHelp;
+
+    public boolean isNoHelp() {
+        return noHelp.getValue();
+    }
+
+    public void setNoHelp(boolean noHelp) {
+        this.noHelp.setValue(noHelp);
+    }
+
+    public StandardJavadocDocletOptions noHelp(boolean noHelp) {
+        setNoHelp(noHelp);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions noHelp() {
+        return noHelp(true);
+    }
+
+    /**
+     * -nonavbar
+     * Prevents the generation of the navigation bar, header and footer,
+     * otherwise found at the top and bottom of the generated pages. Has no affect on the "bottom" option.
+     * The -nonavbar option is useful when you are interested only in the content and have no need for navigation,
+     * such as converting the files to PostScript or PDF for print only.
+     */
+    private final JavadocOptionFileOption<Boolean> noNavBar;
+
+    public boolean isNoNavBar() {
+        return noNavBar.getValue();
+    }
+
+    public void setNoNavBar(boolean noNavBar) {
+        this.noNavBar.setValue(noNavBar);
+    }
+
+    public StandardJavadocDocletOptions noNavBar(boolean noNavBar) {
+        setNoNavBar(noNavBar);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions noNavBar() {
+        return noNavBar(true);
+    }
+
+    /**
+     * -helpfile  path/filename
+     * Specifies the path of an alternate help file path\filename that the HELP link in the top and bottom navigation bars link to. Without this option, the Javadoc tool automatically creates a help file help-doc.html that is hard-coded in the Javadoc tool. This option enables you to override this default. The filename can be any name and is not restricted to help-doc.html -- the Javadoc tool will adjust the links in the navigation bar accordingly. For example:
+     * <p/>
+     * C:> javadoc -helpfile C:/user/myhelp.html java.awt
+     */
+    private final JavadocOptionFileOption<File> helpFile;
+
+    public File getHelpFile() {
+        return helpFile.getValue();
+    }
+
+    public void setHelpFile(File helpFile) {
+        this.helpFile.setValue(helpFile);
+    }
+
+    public StandardJavadocDocletOptions helpFile(File helpFile) {
+        setHelpFile(helpFile);
+        return this;
+    }
+
+    /**
+     * -stylesheetfile  path\filename
+     * Specifies the path of an alternate HTML stylesheet file. Without this option, the Javadoc tool automatically creates a stylesheet file stylesheet.css that is hard-coded in the Javadoc tool. This option enables you to override this default. The filename can be any name and is not restricted to stylesheet.css. For example:
+     * <p/>
+     * C:> javadoc -stylesheetfile C:/user/mystylesheet.css com.mypackage
+     */
+    private final JavadocOptionFileOption<File> stylesheetFile;
+
+    public File getStylesheetFile() {
+        return stylesheetFile.getValue();
+    }
+
+    public void setStylesheetFile(File stylesheetFile) {
+        this.stylesheetFile.setValue(stylesheetFile);
+    }
+
+    public StandardJavadocDocletOptions stylesheetFile(File stylesheetFile) {
+        setStylesheetFile(stylesheetFile);
+        return this;
+    }
+
+    /**
+     * -serialwarn
+     * Generates compile-time warnings for missing @serial tags.
+     * By default, Javadoc 1.2.2 (and later versions) generates no serial warnings.
+     * (This is a reversal from earlier versions.) Use this option to display the serial warnings,
+     * which helps to properly document default serializable fields and writeExternal methods.
+     */
+    private final JavadocOptionFileOption<Boolean> serialWarn;
+
+    public boolean isSerialWarn() {
+        return serialWarn.getValue();
+    }
+
+    public void setSerialWarn(boolean serialWarn) {
+        this.serialWarn.setValue(serialWarn);
+    }
+
+    public StandardJavadocDocletOptions serialWarn(boolean serialWarn) {
+        setSerialWarn(serialWarn);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions serialWarn() {
+        return serialWarn(true);
+    }
+
+    /**
+     * -charset  name
+     * Specifies the HTML character set for this document. The name should be a preferred MIME name as given in the IANA Registry. For example:
+     * <p/>
+     * C:> javadoc -charset "iso-8859-1" mypackage
+     * <p/>
+     * would insert the following line in the head of every generated page:
+     * <p/>
+     * <META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+     * <p/>
+     * This META tag is described in the HTML standard. (4197265 and 4137321)
+     * <p/>
+     * Also see -encoding and -docencoding.
+     */
+    private final JavadocOptionFileOption<String> charSet;
+
+    public String getCharSet() {
+        return charSet.getValue();
+    }
+
+    public void setCharSet(String charSet) {
+        this.charSet.setValue(charSet);
+    }
+
+    public StandardJavadocDocletOptions charSet(String charSet) {
+        setCharSet(charSet);
+        return this;
+    }
+
+    /**
+     * -docencoding  name
+     * Specifies the encoding of the generated HTML files. The name should be a preferred MIME name as given in the IANA Registry. If you omit this option but use -encoding, then the encoding of the generated HTML files is determined by -encoding. Example:
+     * <p/>
+     * % javadoc -docencoding "ISO-8859-1" mypackage
+     * <p/>
+     * Also see -encoding and -charset.
+     */
+    private final JavadocOptionFileOption<String> docEncoding;
+
+    public String getDocEncoding() {
+        return docEncoding.getValue();
+    }
+
+    public void setDocEncoding(String docEncoding) {
+        this.docEncoding.setValue(docEncoding);
+    }
+
+    public StandardJavadocDocletOptions docEncoding(String docEncoding) {
+        setDocEncoding(docEncoding);
+        return this;
+    }
+
+    /**
+     * -keywords
+     */
+    private final JavadocOptionFileOption<Boolean> keyWords;
+
+    public boolean isKeyWords() {
+        return keyWords.getValue();
+    }
+
+    public void setKeyWords(boolean keyWords) {
+        this.keyWords.setValue(keyWords);
+    }
+
+    public StandardJavadocDocletOptions keyWords(boolean keyWords) {
+        setKeyWords(keyWords);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions keyWords() {
+        return keyWords(true);
+    }
+
+    /**
+     * -tag  tagname:Xaoptcmf:"taghead"
+     * -taglet  class
+     */
+    private final JavadocOptionFileOption<List<String>> tags;
+
+    public List<String> getTags() {
+        return tags.getValue();
+    }
+
+    public void setTags(List<String> tags) {
+        this.tags.setValue(tags);
+    }
+
+    public StandardJavadocDocletOptions tags(List<String> tags) {
+        this.tags.getValue().addAll(tags);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions tags(String... tags) {
+        return tags(Arrays.asList(tags));
+    }
+
+    public StandardJavadocDocletOptions taglets(String... taglets) {
+        return tags(Arrays.asList(taglets));
+    }
+
+    public StandardJavadocDocletOptions tagsFile(File tagsFile) {
+        return (StandardJavadocDocletOptions) optionFiles(tagsFile);
+    }
+
+    /**
+     * -tagletpath  tagletpathlist
+     */
+    private final JavadocOptionFileOption<List<File>> tagletPath;
+
+    public List<File> getTagletPath() {
+        return tagletPath.getValue();
+    }
+
+    public void setTagletPath(List<File> tagletPath) {
+        this.tagletPath.setValue(tagletPath);
+    }
+
+    public StandardJavadocDocletOptions tagletPath(List<File> tagletPath) {
+        this.tagletPath.getValue().addAll(tagletPath);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions tagletPath(File... tagletPath) {
+        return tagletPath(Arrays.asList(tagletPath));
+    }
+
+    /**
+     * -docfilessubdirs
+     */
+    private final JavadocOptionFileOption<Boolean> docFilesSubDirs;
+
+    public boolean isDocFilesSubDirs() {
+        return docFilesSubDirs.getValue();
+    }
+
+    public void setDocFilesSubDirs(boolean docFilesSubDirs) {
+        this.docFilesSubDirs.setValue(docFilesSubDirs);
+    }
+
+    public StandardJavadocDocletOptions docFilesSubDirs(boolean docFilesSubDirs) {
+        setDocFilesSubDirs(docFilesSubDirs);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions docFilesSubDirs() {
+        return docFilesSubDirs(true);
+    }
+
+    /**
+     * -excludedocfilessubdir  name1:name2...
+     */
+    private final JavadocOptionFileOption<List<String>> excludeDocFilesSubDir;
+
+    public List<String> getExcludeDocFilesSubDir() {
+        return excludeDocFilesSubDir.getValue();
+    }
+
+    public void setExcludeDocFilesSubDir(List<String> excludeDocFilesSubDir) {
+        this.excludeDocFilesSubDir.setValue(excludeDocFilesSubDir);
+    }
+
+    public StandardJavadocDocletOptions excludeDocFilesSubDir(List<String> excludeDocFilesSubDir) {
+        this.excludeDocFilesSubDir.getValue().addAll(excludeDocFilesSubDir);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions excludeDocFilesSubDir(String... excludeDocFilesSubDir) {
+        return excludeDocFilesSubDir(Arrays.asList(excludeDocFilesSubDir));
+    }
+
+    /**
+     * -noqualifier  all  |  packagename1:packagename2:...
+     */
+    private final JavadocOptionFileOption<List<String>> noQualifiers;
+
+    public List<String> getNoQualifiers() {
+        return noQualifiers.getValue();
+    }
+
+    public void setNoQualifiers(List<String> noQualifiers) {
+        this.noQualifiers.setValue(noQualifiers);
+    }
+
+    public StandardJavadocDocletOptions noQualifier(List<String> noQualifiers) {
+        this.noQualifiers.getValue().addAll(noQualifiers);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions noQualifiers(String... noQualifiers) {
+        return noQualifier(Arrays.asList(noQualifiers));
+    }
+
+    /**
+     * -notimestamp
+     */
+    public final JavadocOptionFileOption<Boolean> noTimestamp;
+
+    public boolean isNoTimestamp() {
+        return noTimestamp.getValue();
+    }
+
+    public void setNoTimestamp(boolean noTimestamp) {
+        this.noTimestamp.setValue(noTimestamp);
+    }
+
+    public StandardJavadocDocletOptions noTimestamp(boolean noTimestamp) {
+        setNoTimestamp(noTimestamp);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions noTimestamp() {
+        return noTimestamp(true);
+    }
+
+    /**
+     * -nocomment
+     */
+    private final JavadocOptionFileOption<Boolean> noComment;
+
+    public boolean isNoComment() {
+        return noComment.getValue();
+    }
+
+    public void setNoComment(boolean noComment) {
+        this.noComment.setValue(noComment);
+    }
+
+    public StandardJavadocDocletOptions noComment(boolean noComment) {
+        setNoComment(noComment);
+        return this;
+    }
+
+    public StandardJavadocDocletOptions noComment() {
+        return noComment(true);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/AbstractJavadocOptionFileOption.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/AbstractJavadocOptionFileOption.java
new file mode 100644
index 0000000..cae36c3
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/AbstractJavadocOptionFileOption.java
@@ -0,0 +1,50 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+/**
+ * @author Tom Eyckmans
+ */
+public abstract class AbstractJavadocOptionFileOption<T> implements JavadocOptionFileOption<T> {
+    protected final String option;
+    protected T value;
+
+    protected AbstractJavadocOptionFileOption(String option) {
+        this(option, null);
+    }
+
+    protected AbstractJavadocOptionFileOption(String option, T value) {
+        if (option == null) {
+            throw new IllegalArgumentException("option == null!");
+        }
+
+        this.option = option;
+        this.value = value;
+    }
+
+    public final String getOption() {
+        return option;
+    }
+
+    public T getValue() {
+        return value;
+    }
+
+    public void setValue(T value) {
+        this.value = value;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/AbstractListJavadocOptionFileOption.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/AbstractListJavadocOptionFileOption.java
new file mode 100644
index 0000000..f761bdd
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/AbstractListJavadocOptionFileOption.java
@@ -0,0 +1,58 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+import java.util.List;
+import java.io.IOException;
+
+/**
+ * @author Tom Eyckmans
+ */
+public abstract class AbstractListJavadocOptionFileOption<T extends List> extends AbstractJavadocOptionFileOption<T> {
+    protected String joinBy;
+
+    protected AbstractListJavadocOptionFileOption(String option, String joinBy) {
+        super(option);
+        this.joinBy = joinBy;
+    }
+
+    protected AbstractListJavadocOptionFileOption(String option, T value, String joinBy) {
+        super(option, value);
+        this.joinBy = joinBy;
+    }
+
+    public T getValue() {
+        return value;
+    }
+
+    public void setValue(T value) {
+        if ( value == null ) {
+            this.value.clear();
+        }
+        else {
+            this.value = value;
+        }
+    }
+
+    public void write(JavadocOptionFileWriterContext writerContext) throws IOException {
+        if ( value != null && !value.isEmpty() ) {
+            writeCollectionValue(writerContext);
+        }
+    }
+
+    protected abstract void writeCollectionValue(JavadocOptionFileWriterContext writerContext) throws IOException;
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/BooleanJavadocOptionFileOption.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/BooleanJavadocOptionFileOption.java
new file mode 100644
index 0000000..e7dc670
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/BooleanJavadocOptionFileOption.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.external.javadoc.optionfile;
+
+import java.io.IOException;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class BooleanJavadocOptionFileOption extends AbstractJavadocOptionFileOption<Boolean> {
+    protected BooleanJavadocOptionFileOption(String option) {
+        super(option);
+    }
+
+    protected BooleanJavadocOptionFileOption(String option, Boolean value) {
+        super(option, value);
+    }
+
+    public void write(JavadocOptionFileWriterContext writerContext) throws IOException {
+        if ( value != null && value) {
+            writerContext.writeOption(option);
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/EnumJavadocOptionFileOption.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/EnumJavadocOptionFileOption.java
new file mode 100644
index 0000000..6c96146
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/EnumJavadocOptionFileOption.java
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+/*
+ * 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.external.javadoc.optionfile;
+
+import java.io.IOException;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class EnumJavadocOptionFileOption<T> extends AbstractJavadocOptionFileOption<T> {
+    public EnumJavadocOptionFileOption(String option) {
+        super(option);
+    }
+
+    public EnumJavadocOptionFileOption(String option, T value) {
+        super(option, value);
+    }
+
+    public void write(JavadocOptionFileWriterContext writerContext) throws IOException {
+        if ( value != null ) {
+            writerContext.writeOption(value.toString().toLowerCase());
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/FileJavadocOptionFileOption.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/FileJavadocOptionFileOption.java
new file mode 100644
index 0000000..a0a6c51
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/FileJavadocOptionFileOption.java
@@ -0,0 +1,39 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class FileJavadocOptionFileOption extends AbstractJavadocOptionFileOption<File> {
+    protected FileJavadocOptionFileOption(String option) {
+        super(option);
+    }
+
+    protected FileJavadocOptionFileOption(String option, File value) {
+        super(option, value);
+    }
+
+    public void write(JavadocOptionFileWriterContext writerContext) throws IOException {
+        if ( value != null ) {
+            writerContext.writeValueOption(option, value.getAbsolutePath());
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/GroupsJavadocOptionFileOption.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/GroupsJavadocOptionFileOption.java
new file mode 100644
index 0000000..9d0acea
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/GroupsJavadocOptionFileOption.java
@@ -0,0 +1,58 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+import org.gradle.util.GUtil;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class GroupsJavadocOptionFileOption extends AbstractJavadocOptionFileOption<Map<String, List<String>>> {
+    public GroupsJavadocOptionFileOption(String option) {
+        super(option, new LinkedHashMap<String, List<String>>());
+    }
+
+    public void write(JavadocOptionFileWriterContext writerContext) throws IOException {
+        if ( value != null && !value.isEmpty() ) {
+            for ( final String group : value.keySet() ) {
+                final List<String> groupPackages = value.get(group);
+
+                writerContext
+                    .writeOptionHeader(option)
+                    .write(
+                        new StringBuffer()
+                            .append("\"")
+                            .append(group)
+                            .append("\"")
+                            .toString())
+                    .write(" ")
+                    .write(
+                        new StringBuffer()
+                            .append("\"")
+                            .append(GUtil.join(groupPackages, ":"))
+                            .append("\"")
+                            .toString())
+                    .newLine();
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/JavadocOptionFile.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/JavadocOptionFile.java
new file mode 100644
index 0000000..4fa20fb
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/JavadocOptionFile.java
@@ -0,0 +1,115 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class JavadocOptionFile {
+    private final Map<String, JavadocOptionFileOption> options;
+
+    private final OptionLessJavadocOptionFileOption<List<String>> sourceNames;
+
+    public JavadocOptionFile() {
+        options = new HashMap<String, JavadocOptionFileOption>();
+        sourceNames = new OptionLessStringsJavadocOptionFileOption();
+    }
+
+    public OptionLessJavadocOptionFileOption<List<String>> getSourceNames() {
+        return sourceNames;
+    }
+
+    Map<String, JavadocOptionFileOption> getOptions() {
+        return Collections.unmodifiableMap(options);
+    }
+
+    public <T> JavadocOptionFileOption<T> addOption(JavadocOptionFileOption<T> option) {
+        if (option == null) {
+            throw new IllegalArgumentException("option == null!");
+        }
+
+        options.put(option.getOption(), option);
+
+        return option;
+    }
+
+    public JavadocOptionFileOption<String> addStringOption(String option) {
+        return addStringOption(option, null);
+    }
+
+    public JavadocOptionFileOption<String> addStringOption(String option, String value) {
+        return addOption(new StringJavadocOptionFileOption(option, value));
+    }
+
+    public <T> JavadocOptionFileOption<T> addEnumOption(String option) {
+        return addEnumOption(option, null);
+    }
+
+    public <T> JavadocOptionFileOption<T> addEnumOption(String option, T value) {
+        return addOption(new EnumJavadocOptionFileOption<T>(option, value));
+    }
+
+    public JavadocOptionFileOption<List<File>> addPathOption(String option) {
+        return addPathOption(option, System.getProperty("path.separator"));
+    }
+
+    public JavadocOptionFileOption<List<File>> addPathOption(String option, String joinBy) {
+        return addOption(new PathJavadocOptionFileOption(option, joinBy));
+    }
+
+    public JavadocOptionFileOption<List<String>> addStringsOption(String option) {
+        return addStringsOption(option, System.getProperty("path.separator"));
+    }
+
+    public JavadocOptionFileOption<List<String>> addStringsOption(String option, String joinBy) {
+        return addOption(new StringsJavadocOptionFileOption(option, new ArrayList<String>(), joinBy));
+    }
+
+    public JavadocOptionFileOption<List<String>> addMultilineStringsOption(String option) {
+        return addOption(new MultilineStringsJavadocOptionFileOption(option, new ArrayList<String>()));
+    }
+
+    public JavadocOptionFileOption<Boolean> addBooleanOption(String option) {
+        return addBooleanOption(option, false);
+    }
+
+    public JavadocOptionFileOption<Boolean> addBooleanOption(String option, boolean value) {
+        return addOption(new BooleanJavadocOptionFileOption(option, value));
+    }
+
+    public JavadocOptionFileOption<File> addFileOption(String option) {
+        return addFileOption(option, null);
+    }
+
+    public JavadocOptionFileOption<File> addFileOption(String option, File value) {
+        return addOption(new FileJavadocOptionFileOption(option, value));
+    }
+
+    public void write(File optionFile) throws IOException {
+        if (optionFile == null) {
+            throw new IllegalArgumentException("optionFile == null!");
+        }
+
+        final JavadocOptionFileWriter optionFileWriter = new JavadocOptionFileWriter(this);
+
+        optionFileWriter.write(optionFile);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/JavadocOptionFileOption.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/JavadocOptionFileOption.java
new file mode 100644
index 0000000..c1697f3
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/JavadocOptionFileOption.java
@@ -0,0 +1,28 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+/**
+ * @author Tom Eyckmans
+ */
+public interface JavadocOptionFileOption<T> extends OptionLessJavadocOptionFileOption<T> {
+
+    String getOption();
+
+
+
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/JavadocOptionFileWriter.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/JavadocOptionFileWriter.java
new file mode 100644
index 0000000..0cb82fb
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/JavadocOptionFileWriter.java
@@ -0,0 +1,56 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+import org.apache.commons.io.IOUtils;
+
+import java.io.IOException;
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.File;
+import java.util.Map;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class JavadocOptionFileWriter {
+    private final JavadocOptionFile optionFile;
+
+    public JavadocOptionFileWriter(JavadocOptionFile optionFile) {
+        if (optionFile == null) {
+            throw new IllegalArgumentException("optionFile == null!");
+        }
+        this.optionFile = optionFile;
+    }
+
+    void write(File outputFile) throws IOException {
+        BufferedWriter writer = null;
+        try {
+            final Map<String, JavadocOptionFileOption> options = optionFile.getOptions();
+            writer = new BufferedWriter(new FileWriter(outputFile));
+            JavadocOptionFileWriterContext writerContext = new JavadocOptionFileWriterContext(writer);
+
+            for (final String option : options.keySet()) {
+                options.get(option).write(writerContext);
+            }
+
+            optionFile.getSourceNames().write(writerContext);
+        } finally {
+            IOUtils.closeQuietly(writer);
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/JavadocOptionFileWriterContext.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/JavadocOptionFileWriterContext.java
new file mode 100644
index 0000000..0e2d5d1
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/JavadocOptionFileWriterContext.java
@@ -0,0 +1,112 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.File;
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class JavadocOptionFileWriterContext {
+    private final BufferedWriter writer;
+
+    public JavadocOptionFileWriterContext(BufferedWriter writer) {
+        this.writer = writer;
+    }
+
+    public JavadocOptionFileWriterContext write(String string) throws IOException {
+        writer.write(string);
+        return this;
+    }
+
+    public JavadocOptionFileWriterContext newLine() throws IOException {
+        writer.newLine();
+        return this;
+    }
+
+    public JavadocOptionFileWriterContext writeOptionHeader(String option) throws IOException {
+        write("-");
+        write(option);
+        write(" ");
+        return this;
+    }
+
+    public JavadocOptionFileWriterContext writeOption(String option) throws IOException {
+        writeOptionHeader(option);
+        newLine();
+        return this;
+    }
+
+    public JavadocOptionFileWriterContext writeValueOption(String option, String value) throws IOException {
+        writeOptionHeader(option);
+        writeValue(value);
+        newLine();
+        return this;
+    }
+
+    public JavadocOptionFileWriterContext writeValue(String value) throws IOException {
+        write("\'");
+        write(value.replaceAll("\\\\", "\\\\\\\\"));
+        write("\'");
+        return this;
+    }
+
+    public JavadocOptionFileWriterContext writeValueOption(String option, Collection<String> values) throws IOException {
+        for ( final String value : values ) {
+            writeValueOption(option, value);
+        }
+        return this;
+    }
+
+    public JavadocOptionFileWriterContext writeValuesOption(String option, Collection<String> values, String joinValuesBy) throws IOException {
+        StringBuilder builder = new StringBuilder();
+        Iterator<String> valuesIt = values.iterator();
+        while (valuesIt.hasNext()) {
+            builder.append(valuesIt.next());
+            if (valuesIt.hasNext()) {
+                builder.append(joinValuesBy);
+            }
+        }
+        writeValueOption(option, builder.toString());
+        return this;
+    }
+
+    public JavadocOptionFileWriterContext writeMultilineValuesOption(String option, Collection<String> values) throws IOException {
+        for(String value : values) {
+            writeValueOption(option, value);
+        }
+        return this;
+    }
+
+    public JavadocOptionFileWriterContext writePathOption(String option, Collection<File> files, String joinValuesBy) throws IOException {
+        StringBuilder builder = new StringBuilder();
+        Iterator<File> filesIt = files.iterator();
+        while ( filesIt.hasNext() ) {
+            builder.append(filesIt.next().getAbsolutePath());
+            if (filesIt.hasNext()) {
+                builder.append(joinValuesBy);
+            }
+        }
+        writeValueOption(option, builder.toString());
+        return this;
+    }
+
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/LinksOfflineJavadocOptionFileOption.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/LinksOfflineJavadocOptionFileOption.java
new file mode 100644
index 0000000..2f27f44
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/LinksOfflineJavadocOptionFileOption.java
@@ -0,0 +1,40 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+import org.gradle.external.javadoc.JavadocOfflineLink;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.io.IOException;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class LinksOfflineJavadocOptionFileOption extends AbstractJavadocOptionFileOption<List<JavadocOfflineLink>> {
+    public LinksOfflineJavadocOptionFileOption(String option) {
+        super(option, new ArrayList<JavadocOfflineLink>());
+    }
+
+    public void write(JavadocOptionFileWriterContext writerContext) throws IOException {
+        if ( value != null && !value.isEmpty() ) {
+            for ( final JavadocOfflineLink offlineLink : value ) {
+                writerContext.writeValueOption(option, offlineLink.toString());
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/MultilineStringsJavadocOptionFileOption.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/MultilineStringsJavadocOptionFileOption.java
new file mode 100644
index 0000000..e732844
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/MultilineStringsJavadocOptionFileOption.java
@@ -0,0 +1,44 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.io.IOException;
+
+/**
+ * @author Melanie Pfautz
+ */
+public class MultilineStringsJavadocOptionFileOption extends AbstractListJavadocOptionFileOption<List<String>> {
+
+   // We should never attempt to join strings so if you see this, there's a problem
+    private static final String JOIN_BY = "Not Used!";
+
+    protected MultilineStringsJavadocOptionFileOption(String option) {
+        super(option, new ArrayList<String>(), JOIN_BY);
+    }
+
+    protected MultilineStringsJavadocOptionFileOption(String option, List<String> value) {
+        super(option, value, JOIN_BY);
+    }
+
+    public void writeCollectionValue(JavadocOptionFileWriterContext writerContext) throws IOException {
+        if ( value != null && !value.isEmpty() ) {
+            writerContext.writeMultilineValuesOption(option, value);
+       }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/OptionLessJavadocOptionFileOption.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/OptionLessJavadocOptionFileOption.java
new file mode 100644
index 0000000..fe4007e
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/OptionLessJavadocOptionFileOption.java
@@ -0,0 +1,30 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+import java.io.IOException;
+
+/**
+ * @author Tom Eyckmans
+ */
+public interface OptionLessJavadocOptionFileOption<T> {
+    T getValue();
+
+    void setValue(T value);
+
+    void write(JavadocOptionFileWriterContext writerContext) throws IOException;
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/OptionLessStringsJavadocOptionFileOption.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/OptionLessStringsJavadocOptionFileOption.java
new file mode 100644
index 0000000..27c372d
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/OptionLessStringsJavadocOptionFileOption.java
@@ -0,0 +1,58 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.io.IOException;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class OptionLessStringsJavadocOptionFileOption implements OptionLessJavadocOptionFileOption<List<String>> {
+    private List<String> value;
+
+    public OptionLessStringsJavadocOptionFileOption() {
+        value = new ArrayList<String>();
+    }
+
+    public OptionLessStringsJavadocOptionFileOption(List<String> value) {
+        this.value = value;
+    }
+
+    public List<String> getValue() {
+        return value;
+    }
+
+    public void setValue(List<String> value) {
+        if ( value == null ) {
+            this.value.clear();
+        }
+        else {
+            this.value = value;
+        }
+    }
+
+    public void write(JavadocOptionFileWriterContext writerContext) throws IOException {
+        if ( value != null && !value.isEmpty() ) {
+            for ( String singleValue : value ) {
+                writerContext.writeValue(singleValue);
+                writerContext.newLine();
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/PathJavadocOptionFileOption.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/PathJavadocOptionFileOption.java
new file mode 100644
index 0000000..0df2a0f
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/PathJavadocOptionFileOption.java
@@ -0,0 +1,40 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class PathJavadocOptionFileOption extends AbstractListJavadocOptionFileOption<List<File>> {
+
+    public PathJavadocOptionFileOption(String option, String joinBy) {
+        super(option, new ArrayList<File>(), joinBy);
+    }
+
+    public PathJavadocOptionFileOption(String option, List<File> value, String joinBy) {
+        super(option, value, joinBy);
+    }
+
+    public void writeCollectionValue(JavadocOptionFileWriterContext writerContext) throws IOException {
+        writerContext.writePathOption(option, value, joinBy);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/StringJavadocOptionFileOption.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/StringJavadocOptionFileOption.java
new file mode 100644
index 0000000..1393df4
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/StringJavadocOptionFileOption.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.external.javadoc.optionfile;
+
+import java.io.IOException;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class StringJavadocOptionFileOption extends AbstractJavadocOptionFileOption<String> {
+    public StringJavadocOptionFileOption(String option) {
+        super(option);
+    }
+
+    public StringJavadocOptionFileOption(String option, String value) {
+        super(option, value);
+    }
+
+    public void write(JavadocOptionFileWriterContext writerContext) throws IOException {
+        if (value != null) {
+            writerContext.writeValueOption(option, value);
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/StringsJavadocOptionFileOption.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/StringsJavadocOptionFileOption.java
new file mode 100644
index 0000000..63d25a2
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/StringsJavadocOptionFileOption.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.external.javadoc.optionfile;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.io.IOException;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class StringsJavadocOptionFileOption extends AbstractListJavadocOptionFileOption<List<String>> {
+    protected StringsJavadocOptionFileOption(String option, String joinBy) {
+        super(option, new ArrayList<String>(), joinBy);
+    }
+
+    protected StringsJavadocOptionFileOption(String option, List<String> value, String joinBy) {
+        super(option, value, joinBy);
+    }
+
+    public void writeCollectionValue(JavadocOptionFileWriterContext writerContext) throws IOException {
+        writerContext.writeValuesOption(option, value, joinBy);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/TagsJavadocOptionFileOption.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/TagsJavadocOptionFileOption.java
new file mode 100644
index 0000000..6a1e708
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/optionfile/TagsJavadocOptionFileOption.java
@@ -0,0 +1,56 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.io.IOException;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class TagsJavadocOptionFileOption extends AbstractJavadocOptionFileOption<List<String>> {
+    protected TagsJavadocOptionFileOption(String option) {
+        super(option, new ArrayList<String>());
+    }
+
+    public List<String> getValue() {
+        return value;
+    }
+
+    public void setValue(List<String> value) {
+        if ( value == null ) {
+            this.value.clear();
+        }
+        else {
+            this.value = value;
+        }
+    }
+
+    public void write(JavadocOptionFileWriterContext writerContext) throws IOException {
+        if ( value != null && !value.isEmpty() ) {
+            for ( final String tag : value ) {
+                if ( tag.contains(":") || tag.contains("\"") ) {
+                    writerContext.writeValueOption("tag", tag);
+                }
+                else {
+                    writerContext.writeValueOption("taglet", tag);
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/package-info.java b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/package-info.java
new file mode 100644
index 0000000..34ba6c2
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/groovy/org/gradle/external/javadoc/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 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.
+ */
+
+/**
+ * Classes to run Javadoc.
+ *
+ * 
+ */
+package org.gradle.external.javadoc;
diff --git a/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/base.properties b/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/base.properties
new file mode 100644
index 0000000..6123954
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/base.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+implementation-class=org.gradle.api.plugins.BasePlugin
diff --git a/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/groovy-base.properties b/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/groovy-base.properties
new file mode 100644
index 0000000..0a6c50b
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/groovy-base.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+implementation-class=org.gradle.api.plugins.GroovyBasePlugin
diff --git a/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/groovy.properties b/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/groovy.properties
new file mode 100644
index 0000000..96ee710
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/groovy.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+implementation-class=org.gradle.api.plugins.GroovyPlugin
diff --git a/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/java-base.properties b/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/java-base.properties
new file mode 100644
index 0000000..004c545
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/java-base.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+implementation-class=org.gradle.api.plugins.JavaBasePlugin
diff --git a/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/java.properties b/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/java.properties
new file mode 100644
index 0000000..053a8d8
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/java.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+implementation-class=org.gradle.api.plugins.JavaPlugin
diff --git a/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/project-report.properties b/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/project-report.properties
new file mode 100644
index 0000000..19dd923
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/project-report.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+implementation-class=org.gradle.api.plugins.ProjectReportsPlugin
diff --git a/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/project-reports.properties b/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/project-reports.properties
new file mode 100644
index 0000000..d01711c
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/project-reports.properties
@@ -0,0 +1,17 @@
+#
+# 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.
+#
+implementation-class=org.gradle.api.plugins.ProjectReportsPlugin
+deprecated-by=project-report
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/war.properties b/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/war.properties
new file mode 100644
index 0000000..2084bba
--- /dev/null
+++ b/subprojects/gradle-plugins/src/main/resources/META-INF/gradle-plugins/war.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+implementation-class=org.gradle.api.plugins.WarPlugin
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultGroovySourceSetTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultGroovySourceSetTest.groovy
new file mode 100644
index 0000000..87dacc1
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultGroovySourceSetTest.groovy
@@ -0,0 +1,49 @@
+/*
+ * 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.tasks
+
+import org.gradle.api.internal.file.DefaultSourceDirectorySet
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.file.UnionFileTree
+import org.junit.Test
+import static org.gradle.util.Matchers.*
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+class DefaultGroovySourceSetTest {
+    private final DefaultGroovySourceSet sourceSet = new DefaultGroovySourceSet("<set-display-name>", [resolve: {it as File}] as FileResolver)
+    
+    @Test
+    public void defaultValues() {
+        assertThat(sourceSet.groovy, instanceOf(DefaultSourceDirectorySet))
+        assertThat(sourceSet.groovy, isEmpty())
+        assertThat(sourceSet.groovy.displayName, equalTo('<set-display-name> Groovy source'))
+
+        assertThat(sourceSet.groovySourcePatterns.includes, equalTo(['**/*.groovy'] as Set))
+        assertThat(sourceSet.groovySourcePatterns.excludes, isEmpty())
+
+        assertThat(sourceSet.allGroovy, instanceOf(UnionFileTree))
+        assertThat(sourceSet.allGroovy, isEmpty())
+        assertThat(sourceSet.allGroovy.displayName, equalTo('<set-display-name> Groovy source'))
+        assertThat(sourceSet.allGroovy.sourceTrees, not(isEmpty()))
+    }
+
+    @Test
+    public void canConfigureGroovySource() {
+        sourceSet.groovy { srcDir 'src/groovy' }
+        assertThat(sourceSet.groovy.srcDirs, equalTo([new File('src/groovy')] as Set))
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainerTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainerTest.java
new file mode 100644
index 0000000..caf1279
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultSourceSetContainerTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.tasks;
+
+import org.gradle.api.internal.AsmBackedClassGenerator;
+import org.gradle.api.internal.IConventionAware;
+import org.gradle.api.tasks.SourceSet;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+public class DefaultSourceSetContainerTest {
+    private final DefaultSourceSetContainer container = new DefaultSourceSetContainer(null, null, new AsmBackedClassGenerator());
+
+    @Test
+    public void createsASourceSet() {
+        SourceSet set = container.create("main");
+        assertThat(set, instanceOf(DefaultSourceSet.class));
+        assertThat(set, instanceOf(IConventionAware.class));
+        assertThat(set.getName(), equalTo("main"));
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultSourceSetTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultSourceSetTest.groovy
new file mode 100644
index 0000000..498d06b
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/DefaultSourceSetTest.groovy
@@ -0,0 +1,137 @@
+/*
+ * 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
+
+import org.gradle.api.internal.file.DefaultSourceDirectorySet
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.file.UnionFileTree
+import org.gradle.api.tasks.SourceSet
+import org.junit.Test
+import static org.gradle.util.Matchers.*
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.gradle.api.Task
+
+class DefaultSourceSetTest {
+    private final FileResolver fileResolver = [resolve: {it as File}] as FileResolver
+    private final TaskResolver taskResolver = [resolveTask: {name -> [getName: {name}] as Task}] as TaskResolver
+
+    @Test
+    public void hasUsefulDisplayName() {
+        SourceSet sourceSet = new DefaultSourceSet('int-test', fileResolver, taskResolver)
+        assertThat(sourceSet.toString(), equalTo('source set int test'));
+    }
+
+    @Test public void defaultValues() {
+        SourceSet sourceSet = new DefaultSourceSet('set-name', fileResolver, taskResolver)
+
+        assertThat(sourceSet.classesDir, nullValue())
+        assertThat(sourceSet.classes.files, isEmpty())
+        assertThat(sourceSet.classes.displayName, equalTo('set name classes'))
+        assertThat(sourceSet.classes.toString(), equalTo('set name classes'))
+        assertThat(sourceSet.classes.buildDependencies.getDependencies(null), isEmpty())
+
+        assertThat(sourceSet.compileClasspath, nullValue())
+
+        assertThat(sourceSet.runtimeClasspath, nullValue())
+
+        assertThat(sourceSet.resources, instanceOf(DefaultSourceDirectorySet))
+        assertThat(sourceSet.resources, isEmpty())
+        assertThat(sourceSet.resources.displayName, equalTo('set name resources'))
+        assertThat(sourceSet.resources.toString(), equalTo('set name resources'))
+
+        assertThat(sourceSet.resources.filter.includes, isEmpty())
+        assertThat(sourceSet.resources.filter.excludes, isEmpty())
+
+        assertThat(sourceSet.java, instanceOf(DefaultSourceDirectorySet))
+        assertThat(sourceSet.java, isEmpty())
+        assertThat(sourceSet.java.displayName, equalTo('set name Java source'))
+        assertThat(sourceSet.java.toString(), equalTo('set name Java source'))
+
+        assertThat(sourceSet.java.filter.includes, equalTo(['**/*.java'] as Set))
+        assertThat(sourceSet.java.filter.excludes, isEmpty())
+        
+
+        assertThat(sourceSet.allJava, instanceOf(UnionFileTree))
+        assertThat(sourceSet.allJava, isEmpty())
+        assertThat(sourceSet.allJava.displayName, equalTo('set name Java source'))
+        assertThat(sourceSet.allJava.toString(), equalTo('set name Java source'))
+        assertThat(sourceSet.allJava.sourceTrees, not(isEmpty()))
+
+        assertThat(sourceSet.allSource, instanceOf(UnionFileTree))
+        assertThat(sourceSet.allSource, isEmpty())
+        assertThat(sourceSet.allSource.displayName, equalTo('set name source'))
+        assertThat(sourceSet.allSource.toString(), equalTo('set name source'))
+        assertThat(sourceSet.allSource.sourceTrees, not(isEmpty()))
+    }
+
+    @Test public void constructsTaskNamesUsingSourceSetName() {
+        SourceSet sourceSet = new DefaultSourceSet('set-name', fileResolver, taskResolver)
+
+        assertThat(sourceSet.classesTaskName, equalTo('setNameClasses'))
+        assertThat(sourceSet.getCompileTaskName('java'), equalTo('compileSetNameJava'))
+        assertThat(sourceSet.compileJavaTaskName, equalTo('compileSetNameJava'))
+        assertThat(sourceSet.processResourcesTaskName, equalTo('processSetNameResources'))
+        assertThat(sourceSet.getTaskName('build', null), equalTo('buildSetName'))
+        assertThat(sourceSet.getTaskName(null, 'jar'), equalTo('setNameJar'))
+        assertThat(sourceSet.getTaskName('build', 'jar'), equalTo('buildSetNameJar'))
+    }
+
+    @Test public void mainSourceSetUsesSpecialCaseTaskNames() {
+        SourceSet sourceSet = new DefaultSourceSet('main', fileResolver, taskResolver)
+
+        assertThat(sourceSet.classesTaskName, equalTo('classes'))
+        assertThat(sourceSet.getCompileTaskName('java'), equalTo('compileJava'))
+        assertThat(sourceSet.compileJavaTaskName, equalTo('compileJava'))
+        assertThat(sourceSet.processResourcesTaskName, equalTo('processResources'))
+        assertThat(sourceSet.getTaskName('build', null), equalTo('buildMain'))
+        assertThat(sourceSet.getTaskName(null, 'jar'), equalTo('jar'))
+        assertThat(sourceSet.getTaskName('build', 'jar'), equalTo('buildJar'))
+    }
+
+    @Test public void canConfigureResources() {
+        SourceSet sourceSet = new DefaultSourceSet('main', fileResolver, taskResolver)
+        sourceSet.resources { srcDir 'src/resources' }
+        assertThat(sourceSet.resources.srcDirs, equalTo([new File('src/resources')] as Set))
+    }
+    
+    @Test public void canConfigureJavaSource() {
+        SourceSet sourceSet = new DefaultSourceSet('main', fileResolver, taskResolver)
+        sourceSet.java { srcDir 'src/java' }
+        assertThat(sourceSet.java.srcDirs, equalTo([new File('src/java')] as Set))
+    }
+
+    @Test
+    public void classesCollectionTracksChangesToClassesDir() {
+        SourceSet sourceSet = new DefaultSourceSet('set-name', fileResolver, taskResolver)
+        assertThat(sourceSet.classes.files, isEmpty())
+
+        sourceSet.classesDir = new File('classes')
+        assertThat(sourceSet.classes.files, equalTo([new File('classes')] as Set))
+        sourceSet.classesDir = new File('other-classes')
+        assertThat(sourceSet.classes.files, equalTo([new File('other-classes')] as Set))
+    }
+
+    @Test
+    public void classesCollectionDependenciesTrackChangesToCompileTasks() {
+        SourceSet sourceSet = new DefaultSourceSet('set-name', fileResolver, taskResolver)
+        assertThat(sourceSet.classes.buildDependencies.getDependencies(null), isEmpty())
+
+        sourceSet.classesDir = new File('classes')
+        sourceSet.compiledBy('a', 'b')
+        assertThat(sourceSet.classes.buildDependencies.getDependencies(null)*.name as Set, equalTo(['a', 'b'] as Set))
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaSourceCompilerTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaSourceCompilerTest.groovy
new file mode 100644
index 0000000..5de4093
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/IncrementalJavaSourceCompilerTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * 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.compile
+
+import spock.lang.Specification
+import org.gradle.api.tasks.WorkResult
+import org.gradle.api.file.FileCollection
+
+class IncrementalJavaSourceCompilerTest extends Specification {
+    private final JavaSourceCompiler target = Mock()
+    private final StaleClassCleaner cleaner = Mock()
+    private final IncrementalJavaSourceCompiler compiler = new IncrementalJavaSourceCompiler(target) {
+        protected StaleClassCleaner createCleaner() {
+            return cleaner
+        }
+    }
+    
+    def cleansStaleClassesAndThenInvokesCompiler() {
+        WorkResult result = Mock()
+        File destDir = new File('dest')
+        FileCollection source = Mock()
+        compiler.destinationDir = destDir
+        compiler.source = source
+
+        when:
+        def r = compiler.execute()
+
+        then:
+        r == result
+        1 * cleaner.setDestinationDir(destDir)
+        1 * cleaner.setSource(source)
+        1 * cleaner.execute()
+        1 * target.execute() >> result
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/SimpleStaleClassCleanerTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/SimpleStaleClassCleanerTest.groovy
new file mode 100644
index 0000000..9c12df6
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/compile/SimpleStaleClassCleanerTest.groovy
@@ -0,0 +1,57 @@
+/*
+ * 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.compile
+
+import spock.lang.Specification
+import org.junit.Rule
+import org.gradle.util.TemporaryFolder
+import org.gradle.api.internal.TaskOutputsInternal
+import org.gradle.api.file.FileCollection
+
+class SimpleStaleClassCleanerTest extends Specification {
+    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder()
+    private final TaskOutputsInternal outputs = Mock()
+    private final SimpleStaleClassCleaner cleaner = new SimpleStaleClassCleaner(outputs)
+    
+    def deletesAllPreviousOutputFiles() {
+        def file1 = tmpDir.file('file1').createFile()
+        def file2 = tmpDir.file('file2').createFile()
+        cleaner.destinationDir = tmpDir.dir
+
+        when:
+        cleaner.execute()
+
+        then:
+        !file1.exists()
+        !file2.exists()
+        1 * outputs.previousFiles >> { [iterator: { [file1, file2].iterator() }] as FileCollection }
+    }
+
+    def doesNotDeleteFilesWhichAreNotUnderTheDestinationDir() {
+        def destDir = tmpDir.file('dir')
+        def file1 = destDir.file('file1').createFile()
+        def file2 = tmpDir.file('file2').createFile()
+        cleaner.destinationDir = destDir
+
+        when:
+        cleaner.execute()
+
+        then:
+        !file1.exists()
+        file2.exists()
+        1 * outputs.previousFiles >> { [iterator: { [file1, file2].iterator() }] as FileCollection }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/AbstractTestFrameworkTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/AbstractTestFrameworkTest.java
new file mode 100644
index 0000000..acb6d0b
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/AbstractTestFrameworkTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+import org.gradle.api.AntBuilder;
+import org.gradle.api.Project;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.tasks.testing.Test;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author Tom Eyckmans
+ */
+ at RunWith(JMock.class)
+public abstract class AbstractTestFrameworkTest {
+
+    protected JUnit4GroovyMockery context = new JUnit4GroovyMockery();
+
+    protected Project projectMock;
+    protected Test testMock;
+
+    protected final File projectDir = new File("projectDir");
+    protected final File testClassesDir = new File("testClassesDir");
+    protected final List<File> testSrcDirs = Arrays.asList(new File("testSrcDirs"));
+    protected final File testResultsDir = new File("testResultsDir");
+    protected final File testReportDir = new File("testReportDir");
+    protected final File temporaryDir = new File("tempDir");
+    protected AntBuilder antBuilderMock;
+    protected FileCollection classpathMock;
+    protected FileTree classpathAsFileTreeMock;
+
+    protected void setUp() throws Exception {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+
+        projectMock = context.mock(Project.class);
+        testMock = context.mock(Test.class);
+        antBuilderMock = context.mock(AntBuilder.class);
+        classpathMock = context.mock(FileCollection.class);
+        classpathAsFileTreeMock = context.mock(FileTree.class);
+
+        context.checking(new Expectations(){{
+            allowing(testMock).getProject();
+            will(returnValue(projectMock));
+        }});
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/DefaultTestClassDescriptorTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/DefaultTestClassDescriptorTest.groovy
new file mode 100644
index 0000000..8066fdd
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/DefaultTestClassDescriptorTest.groovy
@@ -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.api.internal.tasks.testing
+
+import spock.lang.Specification
+
+class DefaultTestClassDescriptorTest extends Specification {
+    def hasUsefulToString() {
+        DefaultTestClassDescriptor descriptor = new DefaultTestClassDescriptor('id', '<class-name>')
+
+        expect:
+        descriptor.toString() == 'test class <class-name>'
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/DefaultTestSuiteDescriptorTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/DefaultTestSuiteDescriptorTest.groovy
new file mode 100644
index 0000000..e78f85d
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/DefaultTestSuiteDescriptorTest.groovy
@@ -0,0 +1,30 @@
+/*
+ * 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
+
+import spock.lang.Specification
+
+class DefaultTestSuiteDescriptorTest extends Specification {
+    def hasUsefulToString() {
+        DefaultTestSuiteDescriptor descriptor = new DefaultTestSuiteDescriptor('id', '<suite-name>')
+
+        expect:
+        descriptor.toString() == 'test \'<suite-name>\''
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/SuiteTestClassProcessorTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/SuiteTestClassProcessorTest.groovy
new file mode 100644
index 0000000..48ecaad
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/SuiteTestClassProcessorTest.groovy
@@ -0,0 +1,115 @@
+/*
+ * 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
+
+import spock.lang.Specification
+import org.gradle.util.TimeProvider
+import org.gradle.api.internal.tasks.testing.results.AttachParentTestResultProcessor
+
+class SuiteTestClassProcessorTest extends Specification {
+    private final TestResultProcessor resultProcessor = Mock()
+    private final TestClassProcessor targetProcessor = Mock()
+    private final TestDescriptorInternal suiteDescriptor = Mock()
+    private final TestClassRunInfo testClass = Mock()
+    private final TimeProvider timeProvider = Mock()
+    private final SuiteTestClassProcessor processor = new SuiteTestClassProcessor(suiteDescriptor, targetProcessor, timeProvider)
+
+    def setup() {
+        _ * suiteDescriptor.getId() >> 'id'
+        _ * suiteDescriptor.toString() >> '<suite>'
+        _ * suiteDescriptor.isComposite() >> true
+        _ * testClass.getTestClassName() >> '<class-name>'
+    }
+
+    def firesSuiteStartEventOnStartProcessing() {
+        when:
+        processor.startProcessing(resultProcessor)
+
+        then:
+        1 * resultProcessor.started(suiteDescriptor, !null)
+        1 * targetProcessor.startProcessing(!null) >> { args ->
+            def processor = args[0]
+            processor instanceof AttachParentTestResultProcessor && args[0].processor == resultProcessor
+        }
+    }
+
+    def firesSuiteCompleteEventOnEndProcessing() {
+        processor.startProcessing(resultProcessor)
+
+        when:
+        processor.stop()
+
+        then:
+        1 * resultProcessor.completed('id', !null)
+        1 * targetProcessor.stop()
+    }
+
+    def firesAFailureEventOnStartProcessingFailure() {
+        RuntimeException failure = new RuntimeException()
+        processor.startProcessing(resultProcessor)
+
+        when:
+        processor.startProcessing(resultProcessor)
+
+        then:
+        1 * resultProcessor.started(suiteDescriptor, !null)
+        1 * targetProcessor.startProcessing(!null) >> { throw failure }
+        1 * resultProcessor.failure('id', !null) >> { args ->
+            def e = args[1]
+            assert e instanceof TestSuiteExecutionException
+            assert e.message == 'Could not start <suite>.'
+            assert e.cause == failure
+        }
+    }
+
+    def firesAFailureEventOnTestClassProcessingFailure() {
+        RuntimeException failure = new RuntimeException()
+        processor.startProcessing(resultProcessor)
+
+        when:
+        processor.processTestClass(testClass)
+
+        then:
+        1 * targetProcessor.processTestClass(testClass) >> { throw failure }
+        1 * resultProcessor.failure('id', !null) >> { args ->
+            def e = args[1]
+            assert e instanceof TestSuiteExecutionException
+            assert e.message == 'Could not execute test class \'<class-name>\'.'
+            assert e.cause == failure
+        }
+    }
+
+    def firesAFailureEventOnCompleteProcessingFailure() {
+        RuntimeException failure = new RuntimeException()
+        processor.startProcessing(resultProcessor)
+
+        when:
+        processor.stop()
+
+        then:
+        1 * targetProcessor.stop() >> { throw failure }
+        1 * resultProcessor.failure('id', !null) >> { args ->
+            def e = args[1]
+            assert e instanceof TestSuiteExecutionException
+            assert e.message == 'Could not complete execution for <suite>.'
+            assert e.cause == failure
+        }
+        1 * resultProcessor.completed('id', !null)
+    }
+}
+
+
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/TestStartEventTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/TestStartEventTest.groovy
new file mode 100644
index 0000000..9277c7d
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/TestStartEventTest.groovy
@@ -0,0 +1,33 @@
+/*
+ * 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
+
+import spock.lang.Specification
+
+class TestStartEventTest extends Specification {
+    def canCreateCopyWithNonNullParentId() {
+        TestStartEvent event = new TestStartEvent(200L)
+
+        expect:
+        def newEvent = event.withParentId('parent')
+        newEvent != event
+        newEvent.startTime == 200L
+        newEvent.parentId == 'parent'
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestClassScannerTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestClassScannerTest.groovy
new file mode 100644
index 0000000..f806313
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/detection/DefaultTestClassScannerTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * 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.detection
+
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.runner.RunWith
+import org.junit.Test
+
+import org.gradle.api.internal.tasks.testing.TestClassProcessor
+import org.jmock.Sequence
+import org.gradle.api.file.FileTree
+import static org.hamcrest.Matchers.*
+import org.gradle.api.file.FileVisitDetails
+
+ at RunWith(JMock.class)
+public class DefaultTestClassScannerTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final TestFrameworkDetector detector = context.mock(TestFrameworkDetector.class)
+    private final TestClassProcessor processor = context.mock(TestClassProcessor.class)
+    private final FileTree files = context.mock(FileTree.class)
+
+    @Test
+    public void passesEachClassFileToTestClassDetector() {
+        DefaultTestClassScanner scanner = new DefaultTestClassScanner(files, detector, processor)
+
+        context.checking {
+            Sequence sequence = context.sequence('seq')
+            one(files).visit(withParam(notNullValue()))
+            will { visitor ->
+                visitor.visitFile({new File('class1.class')} as FileVisitDetails)
+                visitor.visitFile({new File('class2.class')} as FileVisitDetails)
+            }
+            one(detector).startDetection(processor)
+            inSequence(sequence)
+            one(detector).processTestClass(new File('class1.class'))
+            one(detector).processTestClass(new File('class2.class'))
+            inSequence(sequence)
+        }
+        
+        scanner.run()
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/CaptureTestOutputTestResultProcessorTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/CaptureTestOutputTestResultProcessorTest.groovy
new file mode 100644
index 0000000..00b8a86
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/CaptureTestOutputTestResultProcessorTest.groovy
@@ -0,0 +1,64 @@
+/*
+ * 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.junit
+
+import org.gradle.logging.StandardOutputRedirector
+import spock.lang.Specification
+import org.gradle.api.internal.tasks.testing.*
+
+class CaptureTestOutputTestResultProcessorTest extends Specification {
+    private final TestResultProcessor target = Mock()
+    private final StandardOutputRedirector redirector = Mock()
+    private final CaptureTestOutputTestResultProcessor processor = new CaptureTestOutputTestResultProcessor(target, redirector)
+
+    def capturesStdOutputAndStdErrorWhileTestIsExecuting() {
+        TestDescriptorInternal test = Mock()
+        TestStartEvent startEvent = Mock()
+        TestCompleteEvent completeEvent = Mock()
+        String testId = 'id'
+        _ * test.getId() >> testId
+        def stdoutListener
+        def stderrListener
+
+        when:
+        processor.started(test, startEvent)
+
+        then:
+        1 * target.started(test, startEvent)
+        1 * redirector.redirectStandardOutputTo(!null) >> { args -> stdoutListener = args[0] }
+        1 * redirector.redirectStandardErrorTo(!null) >> { args -> stderrListener = args[0] }
+        1 * redirector.start()
+
+        when:
+        stdoutListener.onOutput('this is stdout')
+        stderrListener.onOutput('this is stderr')
+
+        then:
+        1 * target.output(testId, { TestOutputEvent event ->
+            event.destination == TestOutputEvent.Destination.StdOut && event.message == 'this is stdout'
+        })
+        1 * target.output(testId, { TestOutputEvent event ->
+            event.destination == TestOutputEvent.Destination.StdErr && event.message == 'this is stderr' })
+
+        when:
+        processor.completed(testId, completeEvent)
+
+        then:
+        1 * redirector.stop()
+        1 * target.completed(testId, completeEvent)
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy
new file mode 100644
index 0000000..eb0bdca
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestClassProcessorTest.groovy
@@ -0,0 +1,608 @@
+/*
+ * 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.junit
+
+import junit.framework.TestCase
+import org.gradle.api.internal.tasks.testing.TestCompleteEvent
+import org.gradle.api.internal.tasks.testing.TestDescriptorInternal
+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.util.TemporaryFolder
+import org.jmock.integration.junit4.JMock
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.Description
+import org.junit.runner.RunWith
+import org.junit.runner.Runner
+import org.junit.runner.notification.Failure
+import org.junit.runner.notification.RunNotifier
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.gradle.logging.StandardOutputRedirector
+import org.junit.BeforeClass
+import junit.extensions.TestSetup
+
+ at RunWith(JMock.class)
+class JUnitTestClassProcessorTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder();
+    private final TestResultProcessor resultProcessor = context.mock(TestResultProcessor.class);
+    private final JUnitTestClassProcessor processor = new JUnitTestClassProcessor(tmpDir.dir, new LongIdGenerator(), {} as StandardOutputRedirector);
+
+    @Test
+    public void executesATestClass() {
+        context.checking {
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite, TestStartEvent event ->
+                assertThat(suite.id, equalTo(1L))
+                assertThat(suite.name, equalTo(ATestClass.class.name))
+                assertThat(suite.className, equalTo(ATestClass.class.name))
+                assertThat(event.parentId, nullValue())
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test, TestStartEvent event ->
+                assertThat(test.id, equalTo(2L))
+                assertThat(test.name, equalTo('ok'))
+                assertThat(test.className, equalTo(ATestClass.class.name))
+                assertThat(event.parentId, equalTo(1L))
+            }
+            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+        }
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(ATestClass.class));
+        processor.stop();
+    }
+
+    @Test
+    public void executesAJUnit3TestClass() {
+        context.checking {
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite, TestStartEvent event ->
+                assertThat(suite.id, equalTo(1L))
+                assertThat(suite.name, equalTo(AJunit3TestClass.class.name))
+                assertThat(suite.className, equalTo(AJunit3TestClass.class.name))
+                assertThat(event.parentId, nullValue())
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test, TestStartEvent event ->
+                assertThat(test.id, equalTo(2L))
+                assertThat(test.name, equalTo('testOk'))
+                assertThat(test.className, equalTo(AJunit3TestClass.class.name))
+                assertThat(event.parentId, equalTo(1L))
+            }
+            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+        }
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(AJunit3TestClass.class));
+        processor.stop();
+    }
+
+    @Test
+    public void executesMultipleTestClasses() {
+        context.checking {
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite, TestStartEvent event ->
+                assertThat(suite.id, equalTo(1L))
+                assertThat(suite.name, equalTo(ATestClass.class.name))
+                assertThat(suite.className, equalTo(ATestClass.class.name))
+                assertThat(event.parentId, nullValue())
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test, TestStartEvent event ->
+                assertThat(test.id, equalTo(2L))
+                assertThat(test.name, equalTo('ok'))
+                assertThat(test.className, equalTo(ATestClass.class.name))
+                assertThat(event.parentId, equalTo(1L))
+            }
+            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite, TestStartEvent event ->
+                assertThat(suite.id, equalTo(3L))
+                assertThat(suite.name, equalTo(AJunit3TestClass.class.name))
+                assertThat(suite.className, equalTo(AJunit3TestClass.class.name))
+                assertThat(event.parentId, nullValue())
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test, TestStartEvent event ->
+                assertThat(test.id, equalTo(4L))
+                assertThat(test.name, equalTo('testOk'))
+                assertThat(test.className, equalTo(AJunit3TestClass.class.name))
+                assertThat(event.parentId, equalTo(3L))
+            }
+            one(resultProcessor).completed(withParam(equalTo(4L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+            one(resultProcessor).completed(withParam(equalTo(3L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+        }
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(ATestClass.class));
+        processor.processTestClass(testClass(AJunit3TestClass.class));
+        processor.stop();
+    }
+
+    @Test
+    public void executesATestClassWithRunWithAnnotation() {
+        context.checking {
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite ->
+                assertThat(suite.id, equalTo(1L))
+                assertThat(suite.name, equalTo(ATestClassWithRunner.class.name))
+                assertThat(suite.className, equalTo(ATestClassWithRunner.class.name))
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test ->
+                assertThat(test.id, equalTo(2L))
+                assertThat(test.name, equalTo('broken'))
+                assertThat(test.className, equalTo(ATestClassWithRunner.class.name))
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test ->
+                assertThat(test.id, equalTo(3L))
+                assertThat(test.name, equalTo('ok'))
+                assertThat(test.className, equalTo(ATestClassWithRunner.class.name))
+            }
+            one(resultProcessor).completed(withParam(equalTo(3L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+            one(resultProcessor).failure(2L, CustomRunner.failure)
+            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+        }
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(ATestClassWithRunner.class));
+        processor.stop();
+    }
+
+    @Test
+    public void executesATestClassWithASuiteMethod() {
+        context.checking {
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite ->
+                assertThat(suite.name, equalTo(ATestClassWithSuiteMethod.class.name))
+                assertThat(suite.className, equalTo(ATestClassWithSuiteMethod.class.name))
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test ->
+                assertThat(test.id, equalTo(2L))
+                assertThat(test.name, equalTo('testOk'))
+                assertThat(test.className, equalTo(AJunit3TestClass.class.name))
+            }
+            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test ->
+                assertThat(test.id, equalTo(3L))
+                assertThat(test.name, equalTo('testOk'))
+                assertThat(test.className, equalTo(AJunit3TestClass.class.name))
+            }
+            one(resultProcessor).completed(withParam(equalTo(3L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+        }
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(ATestClassWithSuiteMethod.class));
+        processor.stop();
+    }
+
+    @Test
+    public void executesATestClassWithBrokenSuiteMethod() {
+        context.checking {
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite ->
+                assertThat(suite.name, equalTo(ATestClassWithBrokenSuiteMethod.class.name))
+                assertThat(suite.className, equalTo(ATestClassWithBrokenSuiteMethod.class.name))
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test ->
+                assertThat(test.id, equalTo(2L))
+                assertThat(test.name, equalTo('suite'))
+                assertThat(test.className, equalTo(ATestClassWithBrokenSuiteMethod.class.name))
+            }
+            one(resultProcessor).failure(2L, ATestClassWithBrokenSuiteMethod.failure)
+            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+        }
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(ATestClassWithBrokenSuiteMethod.class));
+        processor.stop();
+    }
+
+    @Test
+    public void executesATestClassWithBrokenSetUp() {
+        context.checking {
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite ->
+                assertThat(suite.name, equalTo(ATestSetUpWithBrokenSetUp.class.name))
+                assertThat(suite.className, equalTo(ATestSetUpWithBrokenSetUp.class.name))
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test ->
+                assertThat(test.id, equalTo(2L))
+                assertThat(test.name, equalTo('classMethod'))
+                assertThat(test.className, equalTo(ATestSetUpWithBrokenSetUp.class.name))
+            }
+            one(resultProcessor).failure(2L, ATestSetUpWithBrokenSetUp.failure)
+            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+        }
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(ATestSetUpWithBrokenSetUp.class));
+        processor.stop();
+    }
+
+    @Test
+    public void executesATestClassWithBrokenConstructor() {
+        context.checking {
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite ->
+                assertThat(suite.name, equalTo(ATestClassWithBrokenConstructor.class.name))
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test ->
+                assertThat(test.id, equalTo(2L))
+                assertThat(test.name, equalTo('test'))
+                assertThat(test.className, equalTo(ATestClassWithBrokenConstructor.class.name))
+            }
+            one(resultProcessor).failure(2L, ATestClassWithBrokenConstructor.failure)
+            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+            one(resultProcessor).completed(withParam(notNullValue()), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+        }
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(ATestClassWithBrokenConstructor.class));
+        processor.stop();
+    }
+
+    @Test
+    public void executesATestClassWithBrokenSetup() {
+        context.checking {
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite ->
+                assertThat(suite.name, equalTo(ATestClassWithBrokenBeforeMethod.class.name))
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test ->
+                assertThat(test.name, equalTo('test'))
+                assertThat(test.className, equalTo(ATestClassWithBrokenBeforeMethod.class.name))
+            }
+            one(resultProcessor).failure(2L, ATestClassWithBrokenBeforeMethod.failure)
+            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+        }
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(ATestClassWithBrokenBeforeMethod.class));
+        processor.stop();
+    }
+
+    @Test
+    public void executesATestClassWithBrokenClassSetup() {
+        context.checking {
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite ->
+                assertThat(suite.name, equalTo(ATestClassWithBrokenBeforeClassMethod.class.name))
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test ->
+                assertThat(test.name, equalTo('classMethod'))
+                assertThat(test.className, equalTo(ATestClassWithBrokenBeforeClassMethod.class.name))
+            }
+            one(resultProcessor).failure(2L, ATestClassWithBrokenBeforeClassMethod.failure)
+            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+        }
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(ATestClassWithBrokenBeforeClassMethod.class));
+        processor.stop();
+    }
+
+    @Test
+    public void executesATestClassWithRunnerThatCannotBeConstructed() {
+        context.checking {
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite ->
+                assertThat(suite.name, equalTo(ATestClassWithUnconstructableRunner.class.name))
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test ->
+                assertThat(test.name, equalTo('initializationError'))
+                assertThat(test.className, equalTo(ATestClassWithUnconstructableRunner.class.name))
+            }
+            one(resultProcessor).failure(2L, CustomRunnerWithBrokenConstructor.failure)
+            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+        }
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(ATestClassWithUnconstructableRunner.class));
+        processor.stop();
+    }
+
+    @Test
+    public void executesATestClassWhichCannotBeLoaded() {
+        String testClassName = 'org.gradle.api.internal.tasks.testing.junit.ATestClassWhichCannotBeLoaded'
+
+        context.checking {
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite ->
+                assertThat(suite.name, equalTo(testClassName))
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test ->
+                assertThat(test.name, equalTo('initializationError'))
+                assertThat(test.className, equalTo(testClassName))
+            }
+            one(resultProcessor).failure(withParam(equalTo(2L)), withParam(instanceOf(NoClassDefFoundError)))
+            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+        }
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(testClassName));
+        processor.stop();
+    }
+
+    private TestClassRunInfo testClass(Class<?> type) {
+        return testClass(type.name)
+    }
+
+    private TestClassRunInfo testClass(String testClassName) {
+        TestClassRunInfo runInfo = context.mock(TestClassRunInfo.class, testClassName)
+        context.checking {
+            allowing(runInfo).getTestClassName()
+            will(returnValue(testClassName))
+        }
+        return runInfo;
+    }
+}
+
+public static class ATestClass {
+    @Test
+    public void ok() {
+    }
+
+    @Test @Ignore
+    public void ignored() {
+    }
+}
+
+public static class ATestClassWithBrokenConstructor {
+    static RuntimeException failure = new RuntimeException()
+
+    def ATestClassWithBrokenConstructor() {
+        throw failure.fillInStackTrace()
+    }
+
+    @Test
+    public void test() {
+    }
+}
+
+public static class ATestClassWithBrokenBeforeMethod {
+    static RuntimeException failure = new RuntimeException()
+
+    @Before
+    public void setup() {
+        throw failure.fillInStackTrace()
+    }
+
+    @Test
+    public void test() {
+    }
+}
+
+public static class ATestClassWithBrokenBeforeClassMethod {
+    static RuntimeException failure = new RuntimeException()
+
+    @BeforeClass
+    public static void setup() {
+        throw failure.fillInStackTrace()
+    }
+
+    @Test
+    public void test() {
+    }
+}
+
+public static class AJunit3TestClass extends TestCase {
+    public void testOk() {
+    }
+}
+
+public static class ATestClassWithSuiteMethod {
+    public static junit.framework.Test suite() {
+        return new junit.framework.TestSuite(AJunit3TestClass.class, AJunit3TestClass.class);
+    }
+}
+
+public static class ATestClassWithBrokenSuiteMethod {
+    static RuntimeException failure = new RuntimeException('broken')
+
+    public static junit.framework.Test suite() {
+        throw failure
+    }
+}
+
+public static class ATestSetUpWithBrokenSetUp extends TestSetup {
+    static RuntimeException failure = new RuntimeException('broken')
+
+
+    def ATestSetUpWithBrokenSetUp() {
+        super(null)
+    }
+
+    protected void setUp() {
+        throw failure
+    }
+
+    public static junit.framework.Test suite() {
+        return new ATestSetUpWithBrokenSetUp()
+    }
+}
+
+ at RunWith(CustomRunner.class)
+public static class ATestClassWithRunner {}
+
+public static class CustomRunner extends Runner {
+    static RuntimeException failure = new RuntimeException('broken')
+    Class<?> type
+
+    def CustomRunner(Class<?> type) {
+        this.type = type
+    }
+
+    @Override
+    public Description getDescription() {
+        Description description = Description.createSuiteDescription(type)
+        description.addChild(Description.createTestDescription(type, 'ok1'))
+        description.addChild(Description.createTestDescription(type, 'ok2'))
+        return description
+    }
+
+    @Override
+    public void run(RunNotifier runNotifier) {
+        // Run tests in 'parallel'
+        Description test1 = Description.createTestDescription(type, 'broken')
+        Description test2 = Description.createTestDescription(type, 'ok')
+        runNotifier.fireTestStarted(test1)
+        runNotifier.fireTestStarted(test2)
+        runNotifier.fireTestFailure(new Failure(test1, failure.fillInStackTrace()))
+        runNotifier.fireTestFinished(test2)
+        runNotifier.fireTestFinished(test1)
+    }
+}
+
+ at RunWith(CustomRunnerWithBrokenConstructor.class)
+public static class ATestClassWithUnconstructableRunner {}
+
+public static class CustomRunnerWithBrokenConstructor extends Runner {
+    static RuntimeException failure = new RuntimeException()
+
+    def CustomRunnerWithBrokenConstructor(Class<?> type) {
+        throw failure.fillInStackTrace()
+    }
+
+    Description getDescription() {
+        throw new UnsupportedOperationException();
+    }
+
+    void run(RunNotifier notifier) {
+        throw new UnsupportedOperationException();
+    }
+}
+
+public static class ATestClassWhichCannotBeLoaded {
+    static {
+        throw new NoClassDefFoundError()
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFrameworkTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFrameworkTest.java
new file mode 100644
index 0000000..24d25b9
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/junit/JUnitTestFrameworkTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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.junit;
+
+import org.gradle.api.internal.project.ServiceRegistry;
+import org.gradle.api.internal.tasks.testing.AbstractTestFrameworkTest;
+import org.gradle.api.internal.tasks.testing.TestClassProcessor;
+import org.gradle.api.tasks.testing.junit.JUnitOptions;
+import org.gradle.util.IdGenerator;
+import org.jmock.Expectations;
+import org.junit.Before;
+
+import static junit.framework.Assert.assertNotNull;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class JUnitTestFrameworkTest extends AbstractTestFrameworkTest {
+    private JUnitTestFramework jUnitTestFramework;
+    private AntJUnitReport antJUnitReportMock;
+    private JUnitOptions jUnitOptionsMock;
+    private IdGenerator<?> idGenerator;
+    private ServiceRegistry serviceRegistry;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        antJUnitReportMock = context.mock(AntJUnitReport.class);
+        jUnitOptionsMock = context.mock(JUnitOptions.class);
+        idGenerator = context.mock(IdGenerator.class);
+        serviceRegistry = context.mock(ServiceRegistry.class);
+
+        context.checking(new Expectations(){{
+            allowing(testMock).getTestClassesDir();will(returnValue(testClassesDir));
+            allowing(testMock).getClasspath();will(returnValue(classpathMock));
+        }});
+    }
+
+    @org.junit.Test
+    public void testInitialize() {
+        jUnitTestFramework = new JUnitTestFramework(testMock);
+        setMocks();
+
+        assertNotNull(jUnitTestFramework.getOptions());
+        assertNotNull(jUnitTestFramework.getAntJUnitReport());
+    }
+
+    @org.junit.Test
+    public void testCreatesTestProcessor() {
+        jUnitTestFramework = new JUnitTestFramework(testMock);
+        setMocks();
+
+        context.checking(new Expectations() {{
+            one(testMock).getTestResultsDir(); will(returnValue(testResultsDir));
+            one(serviceRegistry).get(IdGenerator.class); will(returnValue(idGenerator));
+        }});
+
+        TestClassProcessor testClassProcessor = jUnitTestFramework.getProcessorFactory().create(serviceRegistry);
+        assertThat(testClassProcessor, instanceOf(JUnitTestClassProcessor.class));
+    }
+
+    @org.junit.Test
+    public void testReport() {
+        jUnitTestFramework = new JUnitTestFramework(testMock);
+        setMocks();
+
+        context.checking(new Expectations() {{
+            one(testMock).getTestResultsDir(); will(returnValue(testResultsDir));
+            one(testMock).getTestReportDir(); will(returnValue(testReportDir));
+            one(projectMock).getAnt(); will(returnValue(antBuilderMock));
+            one(testMock).isTestReport(); will(returnValue(true));
+            one(antJUnitReportMock).execute(
+                    testResultsDir, testReportDir,
+                    antBuilderMock
+            );
+        }});
+
+        jUnitTestFramework.report();
+    }
+
+    @org.junit.Test
+    public void testReportWithDisabledReport() {
+        jUnitTestFramework = new JUnitTestFramework(testMock);
+        setMocks();
+
+        context.checking(new Expectations() {{
+            one(testMock).isTestReport(); will(returnValue(false));
+        }});
+
+        jUnitTestFramework.report();
+    }
+
+    private void setMocks() {
+        jUnitTestFramework.setAntJUnitReport(antJUnitReportMock);
+        jUnitTestFramework.setOptions(jUnitOptionsMock);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/MaxNParallelTestClassProcessorTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/MaxNParallelTestClassProcessorTest.groovy
new file mode 100644
index 0000000..4a93907
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/MaxNParallelTestClassProcessorTest.groovy
@@ -0,0 +1,170 @@
+/*
+ * 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.processors
+
+import spock.lang.Specification
+import org.gradle.api.internal.tasks.testing.TestClassProcessorFactory
+import org.gradle.api.internal.tasks.testing.TestResultProcessor
+import org.gradle.api.internal.tasks.testing.TestClassRunInfo
+import org.gradle.api.internal.tasks.testing.TestClassProcessor
+import org.gradle.messaging.actor.ActorFactory
+import org.gradle.messaging.actor.Actor
+
+class MaxNParallelTestClassProcessorTest extends Specification {
+    private final TestClassProcessorFactory factory = Mock()
+    private final TestResultProcessor resultProcessor = Mock()
+    private final TestResultProcessor asyncResultProcessor = Mock()
+    private final Actor resultProcessorActor = Mock()
+    private final ActorFactory actorFactory = Mock()
+    private final MaxNParallelTestClassProcessor processor = new MaxNParallelTestClassProcessor(2, factory, actorFactory)
+
+    def createsThreadSafeWrapperForResultProcessorOnStart() {
+        when:
+        processor.startProcessing(resultProcessor)
+
+        then:
+        1 * actorFactory.createActor(resultProcessor) >> resultProcessorActor
+        1 * resultProcessorActor.getProxy(TestResultProcessor) >> asyncResultProcessor
+    }
+
+    def doesNothingWhenNoTestsProcessed() {
+        startProcessor()
+        
+        when:
+        processor.stop()
+
+        then:
+        0 * factory.create()
+        1 * resultProcessorActor.stop()
+    }
+
+    def startProcessor() {
+        1 * actorFactory.createActor(resultProcessor) >> resultProcessorActor
+        1 * resultProcessorActor.getProxy(TestResultProcessor) >> asyncResultProcessor
+        processor.startProcessing(resultProcessor)
+    }
+
+    def startsProcessorsOnDemandAndStopsAtEnd() {
+        TestClassRunInfo test = Mock()
+        TestClassProcessor processor1 = Mock()
+        TestClassProcessor asyncProcessor1 = Mock()
+        Actor actor1 = Mock()
+
+        startProcessor()
+
+        when:
+        processor.processTestClass(test)
+
+        then:
+        1 * factory.create() >> processor1
+        1 * actorFactory.createActor(processor1) >> actor1
+        1 * actor1.getProxy(TestClassProcessor) >> asyncProcessor1
+        1 * asyncProcessor1.startProcessing(asyncResultProcessor)
+        1 * asyncProcessor1.processTestClass(test)
+
+        when:
+        processor.stop()
+
+        then:
+        1 * asyncProcessor1.stop()
+        1 * actor1.stop()
+        1 * resultProcessorActor.stop()
+    }
+
+    def startsMultipleProcessorsOnDemandAndStopsAtEnd() {
+        TestClassRunInfo test = Mock()
+        TestClassProcessor processor1 = Mock()
+        TestClassProcessor processor2 = Mock()
+        TestClassProcessor asyncProcessor1 = Mock()
+        TestClassProcessor asyncProcessor2 = Mock()
+        Actor actor1 = Mock()
+        Actor actor2 = Mock()
+
+        startProcessor()
+
+        when:
+        processor.processTestClass(test)
+
+        then:
+        1 * factory.create() >> processor1
+        1 * actorFactory.createActor(processor1) >> actor1
+        1 * actor1.getProxy(TestClassProcessor) >> asyncProcessor1
+        1 * asyncProcessor1.startProcessing(asyncResultProcessor)
+        1 * asyncProcessor1.processTestClass(test)
+
+        when:
+        processor.processTestClass(test)
+
+        then:
+        1 * factory.create() >> processor2
+        1 * actorFactory.createActor(processor2) >> actor2
+        1 * actor2.getProxy(TestClassProcessor) >> asyncProcessor2
+        1 * asyncProcessor2.startProcessing(asyncResultProcessor)
+        1 * asyncProcessor2.processTestClass(test)
+
+        when:
+        processor.stop()
+
+        then:
+        1 * asyncProcessor1.stop()
+        1 * asyncProcessor2.stop()
+    }
+
+    def roundRobinsTestClassesToProcessors() {
+        TestClassRunInfo test = Mock()
+        TestClassProcessor processor1 = Mock()
+        TestClassProcessor processor2 = Mock()
+        TestClassProcessor asyncProcessor1 = Mock()
+        TestClassProcessor asyncProcessor2 = Mock()
+        Actor actor1 = Mock()
+        Actor actor2 = Mock()
+
+        startProcessor()
+
+        when:
+        processor.processTestClass(test)
+
+        then:
+        1 * factory.create() >> processor1
+        1 * actorFactory.createActor(processor1) >> actor1
+        1 * actor1.getProxy(TestClassProcessor) >> asyncProcessor1
+        1 * asyncProcessor1.startProcessing(asyncResultProcessor)
+        1 * asyncProcessor1.processTestClass(test)
+
+        when:
+        processor.processTestClass(test)
+
+        then:
+        1 * factory.create() >> processor2
+        1 * actorFactory.createActor(processor2) >> actor2
+        1 * actor2.getProxy(TestClassProcessor) >> asyncProcessor2
+        1 * asyncProcessor2.startProcessing(asyncResultProcessor)
+        1 * asyncProcessor2.processTestClass(test)
+
+        when:
+        processor.processTestClass(test)
+
+        then:
+        1 * asyncProcessor1.processTestClass(test)
+
+        when:
+        processor.processTestClass(test)
+
+        then:
+        1 * asyncProcessor2.processTestClass(test)
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/RestartEveryNTestClassProcessorTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/RestartEveryNTestClassProcessorTest.java
new file mode 100644
index 0000000..188645e
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/RestartEveryNTestClassProcessorTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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.processors;
+
+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.TestClassProcessorFactory;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+ at RunWith(JMock.class)
+public class RestartEveryNTestClassProcessorTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final TestClassProcessorFactory factory = context.mock(TestClassProcessorFactory.class);
+    private final TestClassProcessor delegate = context.mock(TestClassProcessor.class);
+    private final TestClassRunInfo test1 = context.mock(TestClassRunInfo.class, "test1");
+    private final TestClassRunInfo test2 = context.mock(TestClassRunInfo.class, "test2");
+    private final TestClassRunInfo test3 = context.mock(TestClassRunInfo.class, "test3");
+    private final TestResultProcessor resultProcessor = context.mock(TestResultProcessor.class);
+    private RestartEveryNTestClassProcessor processor = new RestartEveryNTestClassProcessor(factory, 2);
+
+    @Test
+    public void onFirstTestCreatesDelegateProcessor() {
+        context.checking(new Expectations() {{
+            one(factory).create();
+            will(returnValue(delegate));
+
+            one(delegate).startProcessing(resultProcessor);
+            one(delegate).processTestClass(test1);
+        }});
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(test1);
+    }
+
+    @Test
+    public void onNthTestEndsProcessingOnDelegateProcessor() {
+        context.checking(new Expectations() {{
+            one(factory).create();
+            will(returnValue(delegate));
+
+            one(delegate).startProcessing(resultProcessor);
+            one(delegate).processTestClass(test1);
+            one(delegate).processTestClass(test2);
+            one(delegate).stop();
+        }});
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(test1);
+        processor.processTestClass(test2);
+    }
+
+    @Test
+    public void onNPlus1TestCreatesNewDelegateProcessor() {
+        context.checking(new Expectations() {{
+            one(factory).create();
+            will(returnValue(delegate));
+
+            one(delegate).startProcessing(resultProcessor);
+            one(delegate).processTestClass(test1);
+            one(delegate).processTestClass(test2);
+            one(delegate).stop();
+
+            TestClassProcessor delegate2 = context.mock(TestClassProcessor.class, "delegate2");
+
+            one(factory).create();
+            will(returnValue(delegate2));
+
+            one(delegate2).startProcessing(resultProcessor);
+            one(delegate2).processTestClass(test3);
+        }});
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(test1);
+        processor.processTestClass(test2);
+        processor.processTestClass(test3);
+    }
+
+    @Test
+    public void onEndOfProcessingEndsProcessingOnDelegateProcessor() {
+        context.checking(new Expectations() {{
+            one(factory).create();
+            will(returnValue(delegate));
+
+            one(delegate).startProcessing(resultProcessor);
+            one(delegate).processTestClass(test1);
+            one(delegate).stop();
+        }});
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(test1);
+        processor.stop();
+    }
+
+    @Test
+    public void onEndOfProcessingDoesNothingWhenNoTestsReceived() {
+        processor.stop();
+    }
+
+    @Test
+    public void onEndOfProcessingDoesNothingWhenOnNthTest() {
+        context.checking(new Expectations() {{
+            one(factory).create();
+            will(returnValue(delegate));
+
+            one(delegate).startProcessing(resultProcessor);
+            one(delegate).processTestClass(test1);
+            one(delegate).processTestClass(test2);
+            one(delegate).stop();
+        }});
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(test1);
+        processor.processTestClass(test2);
+        processor.stop();
+    }
+
+    @Test
+    public void usesSingleBatchWhenNEqualsZero() {
+        processor = new RestartEveryNTestClassProcessor(factory, 0);
+
+        context.checking(new Expectations() {{
+            one(factory).create();
+            will(returnValue(delegate));
+
+            one(delegate).startProcessing(resultProcessor);
+            one(delegate).processTestClass(test1);
+            one(delegate).processTestClass(test2);
+            one(delegate).stop();
+        }});
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(test1);
+        processor.processTestClass(test2);
+        processor.stop();
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainActionTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainActionTest.groovy
new file mode 100644
index 0000000..9c26a31
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/processors/TestMainActionTest.groovy
@@ -0,0 +1,134 @@
+/*
+ * 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.processors
+
+import org.gradle.api.internal.tasks.testing.TestClassProcessor
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.TimeProvider
+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.*
+import org.gradle.api.internal.tasks.testing.TestResultProcessor
+import org.gradle.api.internal.tasks.testing.TestDescriptorInternal
+import org.gradle.api.internal.tasks.testing.TestStartEvent
+import org.gradle.api.internal.tasks.testing.TestCompleteEvent
+
+ at RunWith(JMock.class)
+class TestMainActionTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final TestClassProcessor processor = context.mock(TestClassProcessor.class)
+    private final TestResultProcessor resultProcessor = context.mock(TestResultProcessor.class)
+    private final Runnable detector = context.mock(Runnable.class)
+    private final TimeProvider timeProvider = context.mock(TimeProvider.class)
+    private final TestMainAction action = new TestMainAction(detector, processor, resultProcessor, timeProvider)
+
+    @Test
+    public void firesStartAndEndEventsAroundDetectorExecution() {
+        context.checking {
+            one(timeProvider).getCurrentTime()
+            will(returnValue(100L))
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite, TestStartEvent event ->
+                assertThat(suite.id, equalTo('root'))
+                assertThat(event.startTime, equalTo(100L))
+            }
+            one(processor).startProcessing(withParam(notNullValue()))
+            one(detector).run()
+            one(processor).stop()
+            one(timeProvider).getCurrentTime()
+            will(returnValue(200L))
+            one(resultProcessor).completed(withParam(equalTo('root')), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.endTime, equalTo(200L))
+                assertThat(event.resultType, nullValue())
+            }
+        }
+
+        action.run();
+    }
+
+    @Test
+    public void firesEndEventsWhenDetectorFails() {
+        RuntimeException failure = new RuntimeException()
+
+        context.checking {
+            ignoring(timeProvider).getCurrentTime()
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            one(processor).startProcessing(withParam(notNullValue()))
+            one(detector).run()
+            will(throwException(failure))
+            one(processor).stop()
+            one(resultProcessor).completed(withParam(notNullValue()), withParam(notNullValue()))
+        }
+
+        try {
+            action.run()
+            fail()
+        } catch (RuntimeException e) {
+            assertThat(e, sameInstance(failure))
+        }
+    }
+
+    @Test
+    public void firesEndEventsWhenStartProcessingFails() {
+        RuntimeException failure = new RuntimeException()
+
+        context.checking {
+            ignoring(timeProvider).getCurrentTime()
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            one(processor).startProcessing(withParam(notNullValue()))
+            will(throwException(failure))
+            one(resultProcessor).completed(withParam(notNullValue()), withParam(notNullValue()))
+        }
+
+        try {
+            action.run()
+            fail()
+        } catch (RuntimeException e) {
+            assertThat(e, sameInstance(failure))
+        }
+    }
+
+    @Test
+    public void firesEndEventsWhenEndProcessingFails() {
+        RuntimeException failure = new RuntimeException()
+
+        context.checking {
+            ignoring(timeProvider).getCurrentTime()
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            one(processor).startProcessing(withParam(notNullValue()))
+            one(detector).run()
+            one(processor).stop()
+            will(throwException(failure))
+            one(resultProcessor).completed(withParam(notNullValue()), withParam(notNullValue()))
+        }
+
+        try {
+            action.run()
+            fail()
+        } catch (RuntimeException e) {
+            assertThat(e, sameInstance(failure))
+        }
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/AttachParentTestResultProcessorTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/AttachParentTestResultProcessorTest.groovy
new file mode 100644
index 0000000..3c7fb9a
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/AttachParentTestResultProcessorTest.groovy
@@ -0,0 +1,80 @@
+/*
+ * 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.TestCompleteEvent
+import org.gradle.api.internal.tasks.testing.TestDescriptorInternal
+import org.gradle.api.internal.tasks.testing.TestResultProcessor
+import org.gradle.api.internal.tasks.testing.TestStartEvent
+import spock.lang.Specification
+
+class AttachParentTestResultProcessorTest extends Specification {
+    private final TestResultProcessor target = Mock()
+    private final AttachParentTestResultProcessor processor = new AttachParentTestResultProcessor(target)
+
+    def attachesTestToCurrentlyExecutingRootSuite() {
+        TestDescriptorInternal suite = suite('suite')
+        TestDescriptorInternal test = test('test')
+        TestStartEvent suiteStartEvent = new TestStartEvent(100L)
+
+        when:
+        processor.started(suite, suiteStartEvent)
+        processor.started(test, new TestStartEvent(200L))
+
+        then:
+        1 * target.started(suite, suiteStartEvent)
+        1 * target.started(test, { it.parentId == 'suite' })
+    }
+
+    def canHaveMoreThanOneRootSuite() {
+        TestDescriptorInternal root = suite('root')
+        TestDescriptorInternal other = suite('suite1')
+        TestDescriptorInternal test = test('test')
+
+        processor.started(root, new TestStartEvent(100L))
+        processor.completed('root', new TestCompleteEvent(200L))
+        processor.started(other, new TestStartEvent(200L))
+
+        when:
+        processor.started(test, new TestStartEvent(200L))
+
+        then:
+        1 * target.started(test, { it.parentId == 'suite1' })
+    }
+
+    def doesNothingToTestWhichHasAParentId() {
+        TestDescriptorInternal suite = suite('suite')
+        TestDescriptorInternal test = test('test')
+        TestStartEvent testStartEvent = new TestStartEvent(200L, 'parent')
+        processor.started(suite, new TestStartEvent(100L))
+
+        when:
+        processor.started(test, testStartEvent)
+
+        then:
+        1 * target.started(test, testStartEvent)
+    }
+
+    TestDescriptorInternal test(String id) {
+        [isComposite: {false}, getId: {id}, toString: {id}] as TestDescriptorInternal
+    }
+
+    TestDescriptorInternal suite(String id) {
+        [isComposite: {true}, getId: {id}, toString: {id}] as TestDescriptorInternal
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestListenerAdapterTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestListenerAdapterTest.groovy
new file mode 100644
index 0000000..463b2b3
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestListenerAdapterTest.groovy
@@ -0,0 +1,365 @@
+/*
+ * 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.TestListener
+import org.gradle.api.tasks.testing.TestResult
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.junit.Assert.*
+import static org.hamcrest.Matchers.*
+import org.gradle.api.tasks.testing.TestResult.ResultType
+import org.gradle.api.internal.tasks.testing.TestDescriptorInternal
+import org.gradle.api.internal.tasks.testing.TestStartEvent
+import org.gradle.api.internal.tasks.testing.TestCompleteEvent
+import org.gradle.api.tasks.testing.TestDescriptor
+
+ at RunWith(JMock.class)
+class TestListenerAdapterTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final TestListener listener = context.mock(TestListener.class)
+    private final TestListenerAdapter adapter = new TestListenerAdapter(listener)
+
+    @Test
+    public void createsAResultForATest() {
+        TestDescriptorInternal test = test('id')
+
+        context.checking {
+            one(listener).beforeTest(withParam(notNullValue()))
+            will { TestDescriptor t ->
+                assertThat(t.descriptor, equalTo(test))
+                assertThat(t.parent, nullValue())
+            }
+            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptor t, TestResult result ->
+                assertThat(t.descriptor, equalTo(test))
+                assertThat(result.resultType, equalTo(ResultType.SUCCESS))
+                assertThat(result.startTime, equalTo(100L))
+                assertThat(result.endTime, equalTo(200L))
+                assertThat(result.testCount, equalTo(1L))
+                assertThat(result.successfulTestCount, equalTo(1L))
+                assertThat(result.failedTestCount, equalTo(0L))
+            }
+        }
+
+        adapter.started(test, new TestStartEvent(100L))
+        adapter.completed('id', new TestCompleteEvent(200L))
+    }
+
+    @Test
+    public void createsAResultForATestWithFailure() {
+        RuntimeException failure = new RuntimeException()
+
+        TestDescriptorInternal test = test('id')
+
+        context.checking {
+            one(listener).beforeTest(withParam(notNullValue()))
+            will { TestDescriptor t ->
+                assertThat(t.descriptor, equalTo(test))
+                assertThat(t.parent, nullValue())
+            }
+            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptor t, TestResult result ->
+                assertThat(t.descriptor, equalTo(test))
+                assertThat(result.resultType, equalTo(ResultType.FAILURE))
+                assertThat(result.exception, sameInstance(failure))
+                assertThat(result.startTime, equalTo(100L))
+                assertThat(result.endTime, equalTo(200L))
+                assertThat(result.testCount, equalTo(1L))
+                assertThat(result.successfulTestCount, equalTo(0L))
+                assertThat(result.failedTestCount, equalTo(1L))
+            }
+        }
+
+        adapter.started(test, new TestStartEvent(100L))
+        adapter.failure('id', failure)
+        adapter.completed('id', new TestCompleteEvent(200L))
+    }
+
+    @Test
+    public void createsAResultForASkippedTest() {
+        TestDescriptorInternal test = test('id')
+
+        context.checking {
+            one(listener).beforeTest(withParam(notNullValue()))
+            will { TestDescriptor t ->
+                assertThat(t.descriptor, equalTo(test))
+                assertThat(t.parent, nullValue())
+            }
+            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptor t, TestResult result ->
+                assertThat(t.descriptor, equalTo(test))
+                assertThat(result.resultType, equalTo(ResultType.SKIPPED))
+                assertThat(result.startTime, equalTo(100L))
+                assertThat(result.endTime, equalTo(200L))
+                assertThat(result.testCount, equalTo(1L))
+                assertThat(result.successfulTestCount, equalTo(0L))
+                assertThat(result.failedTestCount, equalTo(0L))
+            }
+        }
+
+        adapter.started(test, new TestStartEvent(100L))
+        adapter.completed('id', new TestCompleteEvent(200L, ResultType.SKIPPED))
+    }
+
+    @Test
+    public void createsAnAggregateResultForEmptyTestSuite() {
+        TestDescriptorInternal suite = suite('id')
+
+        context.checking {
+            one(listener).beforeSuite(withParam(notNullValue()))
+            will { TestDescriptor t ->
+                assertThat(t.descriptor, equalTo(suite))
+                assertThat(t.parent, nullValue())
+            }
+            one(listener).afterSuite(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptor t, TestResult result ->
+                assertThat(t.descriptor, equalTo(suite))
+                assertThat(result.resultType, equalTo(ResultType.SUCCESS))
+                assertThat(result.startTime, equalTo(100L))
+                assertThat(result.endTime, equalTo(200L))
+                assertThat(result.testCount, equalTo(0L))
+                assertThat(result.successfulTestCount, equalTo(0L))
+                assertThat(result.failedTestCount, equalTo(0L))
+            }
+        }
+
+        adapter.started(suite, new TestStartEvent(100L))
+        adapter.completed('id', new TestCompleteEvent(200L))
+    }
+
+    @Test
+    public void createsAnAggregateResultForTestSuiteWithPassedTest() {
+        TestDescriptorInternal suite = suite('id')
+        TestDescriptorInternal test = test('testid')
+
+        context.checking {
+            one(listener).beforeSuite(withParam(notNullValue()))
+            will { TestDescriptor t ->
+                assertThat(t.descriptor, equalTo(suite))
+                assertThat(t.parent, nullValue())
+            }
+            one(listener).beforeTest(withParam(notNullValue()))
+            will { TestDescriptor t ->
+                assertThat(t.descriptor, equalTo(test))
+                assertThat(t.parent.descriptor, equalTo(suite))
+            }
+            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptor t ->
+                assertThat(t.descriptor, equalTo(test))
+            }
+            one(listener).afterSuite(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptor t, TestResult result ->
+                assertThat(t.descriptor, equalTo(suite))
+                assertThat(result.resultType, equalTo(ResultType.SUCCESS))
+                assertThat(result.testCount, equalTo(1L))
+                assertThat(result.successfulTestCount, equalTo(1L))
+                assertThat(result.failedTestCount, equalTo(0L))
+            }
+        }
+
+        adapter.started(suite, new TestStartEvent(100L))
+        adapter.started(test, new TestStartEvent(100L, 'id'))
+        adapter.completed('testid', new TestCompleteEvent(200L))
+        adapter.completed('id', new TestCompleteEvent(200L))
+    }
+
+    @Test
+    public void createsAnAggregateResultForTestSuiteWithFailedTest() {
+        TestDescriptorInternal suite = suite('id')
+        TestDescriptorInternal ok = test('ok')
+        TestDescriptorInternal broken = test('broken')
+
+        context.checking {
+            one(listener).beforeSuite(withParam(notNullValue()))
+            will { TestDescriptor t ->
+                assertThat(t.descriptor, equalTo(suite))
+                assertThat(t.parent, nullValue())
+            }
+            one(listener).beforeTest(withParam(notNullValue()))
+            will { TestDescriptor t ->
+                assertThat(t.descriptor, equalTo(ok))
+                assertThat(t.parent.descriptor, equalTo(suite))
+            }
+            one(listener).beforeTest(withParam(notNullValue()))
+            will { TestDescriptor t ->
+                assertThat(t.descriptor, equalTo(broken))
+                assertThat(t.parent.descriptor, equalTo(suite))
+            }
+            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
+            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
+            one(listener).afterSuite(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptor t, TestResult result ->
+                assertThat(t.descriptor, equalTo(suite))
+                assertThat(result.resultType, equalTo(ResultType.FAILURE))
+                assertThat(result.testCount, equalTo(2L))
+                assertThat(result.successfulTestCount, equalTo(1L))
+                assertThat(result.failedTestCount, equalTo(1L))
+            }
+        }
+
+        adapter.started(suite, new TestStartEvent(100L))
+        adapter.started(ok, new TestStartEvent(100L, 'id'))
+        adapter.started(broken, new TestStartEvent(100L, 'id'))
+        adapter.completed('ok', new TestCompleteEvent(200L))
+        adapter.failure('broken', new RuntimeException())
+        adapter.completed('broken', new TestCompleteEvent(200L))
+        adapter.completed('id', new TestCompleteEvent(200L))
+    }
+
+    @Test
+    public void createsAnAggregateResultForTestSuiteWithSkippedTest() {
+        TestDescriptorInternal suite = suite('id')
+        TestDescriptorInternal test = test('testid')
+
+        context.checking {
+            one(listener).beforeSuite(withParam(notNullValue()))
+            will { TestDescriptor t ->
+                assertThat(t.descriptor, equalTo(suite))
+                assertThat(t.parent, nullValue())
+            }
+            one(listener).beforeTest(withParam(notNullValue()))
+            will { TestDescriptor t ->
+                assertThat(t.descriptor, equalTo(test))
+                assertThat(t.parent.descriptor, equalTo(suite))
+            }
+            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
+            one(listener).afterSuite(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptor t, TestResult result ->
+                assertThat(t.descriptor, equalTo(suite))
+                assertThat(result.resultType, equalTo(ResultType.SUCCESS))
+                assertThat(result.testCount, equalTo(1L))
+                assertThat(result.successfulTestCount, equalTo(0L))
+                assertThat(result.failedTestCount, equalTo(0L))
+            }
+        }
+
+        adapter.started(suite, new TestStartEvent(100L))
+        adapter.started(test, new TestStartEvent(100L, 'id'))
+        adapter.completed('testid', new TestCompleteEvent(200L, ResultType.SKIPPED))
+        adapter.completed('id', new TestCompleteEvent(200L))
+    }
+
+    @Test
+    public void createsAnAggregateResultForTestSuiteWithNestedSuites() {
+        TestDescriptorInternal root = suite('root')
+        TestDescriptorInternal suite1 = suite('suite1')
+        TestDescriptorInternal suite2 = suite('suite2')
+        TestDescriptorInternal ok = test('ok')
+        TestDescriptorInternal broken = test('broken')
+
+        context.checking {
+            one(listener).beforeSuite(withParam(notNullValue()))
+            one(listener).beforeSuite(withParam(notNullValue()))
+            one(listener).beforeTest(withParam(notNullValue()))
+            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
+            one(listener).beforeSuite(withParam(notNullValue()))
+            one(listener).beforeTest(withParam(notNullValue()))
+            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
+            one(listener).afterSuite(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptor t, TestResult result ->
+                assertThat(t.descriptor, equalTo(suite1))
+                assertThat(result.resultType, equalTo(ResultType.SUCCESS))
+                assertThat(result.testCount, equalTo(1L))
+                assertThat(result.successfulTestCount, equalTo(1L))
+                assertThat(result.failedTestCount, equalTo(0L))
+            }
+            one(listener).afterSuite(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptor t, TestResult result ->
+                assertThat(t.descriptor, equalTo(suite2))
+                assertThat(result.resultType, equalTo(ResultType.FAILURE))
+                assertThat(result.testCount, equalTo(1L))
+                assertThat(result.successfulTestCount, equalTo(0L))
+                assertThat(result.failedTestCount, equalTo(1L))
+            }
+            one(listener).afterSuite(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptor t, TestResult result ->
+                assertThat(t.descriptor, equalTo(root))
+                assertThat(result.resultType, equalTo(ResultType.FAILURE))
+                assertThat(result.testCount, equalTo(2L))
+                assertThat(result.successfulTestCount, equalTo(1L))
+                assertThat(result.failedTestCount, equalTo(1L))
+            }
+        }
+
+        adapter.started(root, new TestStartEvent(100L))
+        adapter.started(suite1, new TestStartEvent(100L, 'root'))
+        adapter.started(ok, new TestStartEvent(100L, 'suite1'))
+        adapter.started(suite2, new TestStartEvent(100L, 'root'))
+        adapter.completed('ok', new TestCompleteEvent(200L))
+        adapter.completed('suite1', new TestCompleteEvent(200L))
+        adapter.started(broken, new TestStartEvent(100L, 'suite2'))
+        adapter.failure('broken', new RuntimeException())
+        adapter.completed('broken', new TestCompleteEvent(200L))
+        adapter.completed('suite2', new TestCompleteEvent(200L))
+        adapter.completed('root', new TestCompleteEvent(200L))
+    }
+
+    @Test
+    public void createsAnAggregateResultForTestSuiteWithFailure() {
+        TestDescriptorInternal suite = suite('id')
+        TestDescriptorInternal test = test('testid')
+        RuntimeException failure = new RuntimeException()
+
+        context.checking {
+            one(listener).beforeSuite(withParam(notNullValue()))
+            one(listener).beforeTest(withParam(notNullValue()))
+            one(listener).afterTest(withParam(notNullValue()), withParam(notNullValue()))
+            one(listener).afterSuite(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptor t, TestResult result ->
+                assertThat(t.descriptor, equalTo(suite))
+                assertThat(result.resultType, equalTo(ResultType.FAILURE))
+                assertThat(result.exception, sameInstance(failure))
+            }
+        }
+
+        adapter.started(suite, new TestStartEvent(100L))
+        adapter.started(test, new TestStartEvent(100L, 'id'))
+        adapter.completed('testid', new TestCompleteEvent(200L, ResultType.SKIPPED))
+        adapter.failure('id', failure)
+        adapter.completed('id', new TestCompleteEvent(200L))
+    }
+
+    private TestDescriptorInternal test(String id) {
+        TestDescriptorInternal test = context.mock(TestDescriptorInternal.class, id)
+        context.checking {
+            allowing(test).getId()
+            will(returnValue(id))
+            allowing(test).isComposite()
+            will(returnValue(false))
+        }
+        return test
+    }
+
+    private TestDescriptorInternal suite(String id) {
+        TestDescriptorInternal test = context.mock(TestDescriptorInternal.class, id)
+        context.checking {
+            allowing(test).getId()
+            will(returnValue(id))
+            allowing(test).isComposite()
+            will(returnValue(true))
+        }
+        return test
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestLoggerTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestLoggerTest.groovy
new file mode 100644
index 0000000..18ad08f
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestLoggerTest.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.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
+
+class TestLoggerTest extends Specification {
+    private final ProgressLoggerFactory factory = Mock()
+    private final ProgressLogger progressLogger = Mock()
+    private final TestDescriptor rootSuite = suite(true)
+    private final TestLogger logger = new TestLogger(factory)
+
+    def startsProgressLoggerWhenRootSuiteIsStartedAndStopsWhenRootSuiteIsCompleted() {
+        when:
+        logger.beforeSuite(rootSuite)
+
+        then:
+        1 * factory.start() >> progressLogger
+
+        when:
+        logger.afterSuite(rootSuite, result())
+
+        then:
+        1 * progressLogger.completed('')
+    }
+
+    def logsCountOfTestsExecuted() {
+        TestDescriptor test1 = test()
+        TestDescriptor test2 = test()
+
+        1 * factory.start() >> 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.start() >> 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 * progressLogger.completed('2 tests completed, 1 failure')
+    }
+
+    def ignoresSuitesOtherThanTheRootSuite() {
+        TestDescriptor suite = suite()
+
+        1 * factory.start() >> 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/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestSummaryListenerTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestSummaryListenerTest.groovy
new file mode 100644
index 0000000..7d0774e
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/results/TestSummaryListenerTest.groovy
@@ -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.api.internal.tasks.testing.results
+
+import org.gradle.api.tasks.testing.TestDescriptor
+import org.gradle.api.tasks.testing.TestResult
+import org.gradle.util.JUnit4GroovyMockery
+import org.jmock.integration.junit4.JMock
+import org.junit.runner.RunWith
+import org.slf4j.Logger
+import org.junit.Test
+import org.junit.Before
+import static org.junit.Assert.*
+import org.gradle.api.internal.tasks.testing.TestSuiteExecutionException
+
+ at RunWith(JMock.class)
+public class TestSummaryListenerTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final Logger logger = context.mock(Logger.class)
+    private final RuntimeException failure = new RuntimeException()
+    private final TestSummaryListener listener = new TestSummaryListener(logger)
+
+    @Before
+    public void setup() {
+        context.checking {
+            ignoring(logger).debug(withParam(anything()), (Object) withParam(anything()))
+        }
+    }
+
+    @Test
+    public void logsSuccessfulTests() {
+        context.checking {
+            one(logger).info('{} PASSED', '<test>')
+        }
+        listener.afterTest(test('<test>'), result(TestResult.ResultType.SUCCESS))
+    }
+
+    @Test
+    public void logsSkippedTests() {
+        context.checking {
+            one(logger).info('{} SKIPPED', '<test>')
+        }
+        listener.afterTest(test('<test>'), result(TestResult.ResultType.SKIPPED))
+    }
+
+    @Test
+    public void logsFailedTestExecution() {
+        context.checking {
+            one(logger).info('{} FAILED: {}', '<test>', failure)
+            one(logger).error('Test {} FAILED', '<class>')
+        }
+        listener.afterTest(test('<test>', '<class>'), result(TestResult.ResultType.FAILURE))
+    }
+
+    @Test
+    public void logsFailedTestExecutionWhenTestHasNoClass() {
+        context.checking {
+            one(logger).error('{} FAILED: {}', '<test>', failure)
+        }
+        listener.afterTest(test('<test>'), result(TestResult.ResultType.FAILURE))
+    }
+
+    @Test
+    public void logsFailedSuiteExecution() {
+        context.checking {
+            one(logger).info('{} FAILED: {}', '<test>', failure)
+            one(logger).error('Test {} FAILED', '<class>')
+        }
+        listener.afterSuite(test('<test>', '<class>'), result(TestResult.ResultType.FAILURE))
+    }
+
+    @Test
+    public void logsFailedSuiteExecutionWhenSuiteHasNoClass() {
+        context.checking {
+            one(logger).error('{} FAILED: {}', '<test>', failure)
+        }
+        listener.afterSuite(test('<test>'), result(TestResult.ResultType.FAILURE))
+    }
+
+    @Test
+    public void logsFailedSuiteExecutionWhenSuiteHasNoException() {
+        listener.afterSuite(test('<test>'), result(TestResult.ResultType.FAILURE, null))
+    }
+
+    @Test
+    public void logsSuiteInternalException() {
+        TestSuiteExecutionException failure = new TestSuiteExecutionException('broken', new RuntimeException())
+        context.checking {
+            one(logger).error('Execution for <test> FAILED', failure)
+        }
+        listener.afterSuite(test('<test>'), result(TestResult.ResultType.FAILURE, failure))
+    }
+
+    @Test
+    public void doesNotLogFailedClassMoreThanOnce() {
+        context.checking {
+            ignoring(logger).info(withParam(anything()), (Object) withParam(anything()), (Object) withParam(anything()))
+            one(logger).error('Test {} FAILED', '<class>')
+        }
+        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))
+    }
+
+    @Test
+    public void usesRootSuiteResultsToDetermineIfTestsHasFailed() {
+        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/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessorTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessorTest.groovy
new file mode 100644
index 0000000..26d38a1
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestClassProcessorTest.groovy
@@ -0,0 +1,328 @@
+/*
+ * 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.testng
+
+import org.gradle.api.GradleException
+import org.gradle.api.internal.tasks.testing.TestDescriptorInternal
+import org.gradle.api.internal.tasks.testing.TestResultProcessor
+
+import org.gradle.api.tasks.testing.TestResult.ResultType
+import org.gradle.api.tasks.testing.testng.TestNGOptions
+import org.gradle.api.internal.tasks.testing.TestClassRunInfo
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.TemporaryFolder
+import org.jmock.Sequence
+import org.jmock.integration.junit4.JMock
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.testng.annotations.AfterClass
+import org.testng.annotations.BeforeClass
+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.junit.Ignore
+import org.gradle.api.internal.tasks.testing.TestCompleteEvent
+import org.gradle.api.internal.tasks.testing.TestStartEvent
+import org.gradle.logging.StandardOutputRedirector
+
+ at RunWith(JMock.class)
+class TestNGTestClassProcessorTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    @Rule public final TemporaryFolder tmpDir = new TemporaryFolder();
+    private final TestResultProcessor resultProcessor = context.mock(TestResultProcessor.class);
+    private final TestNGOptions options = new TestNGOptions(tmpDir.dir)
+    private final TestNGTestClassProcessor processor = new TestNGTestClassProcessor(tmpDir.dir, options, [], new LongIdGenerator(), {} as StandardOutputRedirector);
+
+    @Test
+    public void executesATestClass() {
+        context.checking {
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite, TestStartEvent event ->
+                assertThat(suite.id, equalTo(1L))
+                assertThat(suite.name, equalTo('Gradle test'))
+                assertThat(suite.className, nullValue())
+                assertThat(event.parentId, nullValue())
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test, TestStartEvent event ->
+                assertThat(test.id, equalTo(2L))
+                assertThat(test.name, equalTo('ok'))
+                assertThat(test.className, equalTo(ATestNGClass.class.name))
+                assertThat(event.parentId, equalTo(1L))
+            }
+            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, equalTo(ResultType.SUCCESS))
+            }
+            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+        }
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(ATestNGClass.class));
+        processor.stop();
+    }
+
+    @Test
+    public void executesAFactoryTestClass() {
+        context.checking {
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite ->
+                assertThat(suite.name, equalTo('Gradle test'))
+                assertThat(suite.className, nullValue())
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test ->
+                assertThat(test.name, equalTo('ok'))
+                assertThat(test.className, equalTo(ATestNGClass.class.name))
+            }
+            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, equalTo(ResultType.SUCCESS))
+            }
+            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+        }
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(ATestNGFactoryClass.class));
+        processor.stop();
+    }
+
+    @Test
+    public void executesATestWithExpectedException() {
+        context.checking {
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test ->
+                assertThat(test.id, equalTo(2L))
+                assertThat(test.name, equalTo('ok'))
+                assertThat(test.className, equalTo(ATestNGClassWithExpectedException.class.name))
+            }
+            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, equalTo(ResultType.SUCCESS))
+            }
+            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+        }
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(ATestNGClassWithExpectedException.class));
+        processor.stop();
+    }
+
+    @Test @Ignore
+    public void executesATestClassWithBrokenConstructor() {
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(ATestNGClassWithBrokenConstructor.class));
+        processor.stop();
+        fail()
+    }
+
+    @Test
+    public void executesATestClassWithBrokenSetup() {
+        context.checking {
+            Sequence sequence = context.sequence('seq')
+
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite ->
+                assertThat(suite.name, equalTo('Gradle test'))
+                assertThat(suite.className, nullValue())
+            }
+            inSequence(sequence)
+
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test ->
+                assertThat(test.name, equalTo('beforeMethod'))
+                assertThat(test.className, equalTo(ATestNGClassWithBrokenSetupMethod.class.name))
+            }
+            inSequence(sequence)
+
+            one(resultProcessor).failure(2L, ATestNGClassWithBrokenSetupMethod.failure)
+
+            one(resultProcessor).completed(withParam(equalTo(2L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, equalTo(ResultType.FAILURE))
+            }
+            inSequence(sequence)
+
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test ->
+                assertThat(test.name, equalTo('test'))
+                assertThat(test.className, equalTo(ATestNGClassWithBrokenSetupMethod.class.name))
+            }
+            inSequence(sequence)
+
+            one(resultProcessor).completed(withParam(equalTo(3L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, equalTo(ResultType.SKIPPED))
+            }
+            inSequence(sequence)
+
+            one(resultProcessor).completed(withParam(equalTo(1L)), withParam(notNullValue()))
+            will { id, TestCompleteEvent event ->
+                assertThat(event.resultType, nullValue())
+            }
+            inSequence(sequence)
+        }
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(ATestNGClassWithBrokenSetupMethod.class));
+        processor.stop();
+    }
+
+    @Test
+    public void canIncludeAndExcludeGroups() {
+        context.checking {
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal suite ->
+                assertThat(suite.name, equalTo('Gradle test'))
+                assertThat(suite.className, nullValue())
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test ->
+                assertThat(test.name, equalTo('group1'))
+                assertThat(test.className, equalTo(ATestNGClassWithGroups.class.name))
+            }
+            one(resultProcessor).started(withParam(notNullValue()), withParam(notNullValue()))
+            will { TestDescriptorInternal test ->
+                assertThat(test.name, equalTo('group2'))
+                assertThat(test.className, equalTo(ATestNGClassWithGroups.class.name))
+            }
+            ignoring(resultProcessor).completed(withParam(anything()), withParam(anything()))
+        }
+
+        options.includeGroups('group1', 'group2')
+        options.excludeGroups('group3')
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(testClass(ATestNGClassWithGroups.class));
+        processor.stop();
+    }
+
+    @Test
+    public void failsEarlyForUnknownTestClass() {
+        processor.startProcessing(resultProcessor)
+        try {
+            processor.processTestClass(testClass('unknown'))
+            fail()
+        } catch (GradleException e) {
+            assertThat(e.message, equalTo('Could not load test class \'unknown\'.'))
+        }
+    }
+
+    private TestClassRunInfo testClass(Class<?> type) {
+        return testClass(type.name)
+    }
+
+    private TestClassRunInfo testClass(String testClassName) {
+        TestClassRunInfo runInfo = context.mock(TestClassRunInfo.class)
+        context.checking {
+            allowing(runInfo).getTestClassName()
+            will(returnValue(testClassName))
+        }
+        return runInfo;
+    }
+}
+
+public class ATestNGClass {
+    @BeforeClass
+    public void beforeClass() {
+    }
+
+    @AfterClass
+    public void afterClass() {
+    }
+
+    @BeforeMethod
+    public void beforeMethod() {
+    }
+
+    @org.testng.annotations.Test
+    public void ok() {
+    }
+}
+
+public class ATestNGClassWithExpectedException {
+    @org.testng.annotations.Test(expectedExceptions = RuntimeException.class)
+    public void ok() {
+        throw new RuntimeException()
+    }
+}
+
+public class ATestNGClassWithGroups {
+    @org.testng.annotations.Test(groups="group1")
+    public void group1() {
+    }
+
+    @org.testng.annotations.Test(groups="group2")
+    public void group2() {
+    }
+
+    @org.testng.annotations.Test(groups="group2,group3")
+    public void excluded() {
+    }
+
+    @org.testng.annotations.Test(groups="group4")
+    public void ignored() {
+    }
+}
+
+public static class ATestNGFactoryClass {
+    @Factory
+    public Object[] suite() {
+        return [new ATestNGClass()] as Object[]
+    }
+}
+
+public static class ATestNGClassWithBrokenConstructor {
+    static RuntimeException failure = new RuntimeException()
+
+    def ATestNGClassWithBrokenConstructor() {
+        throw failure
+    }
+
+    @org.testng.annotations.Test
+    public void test() {
+    }
+}
+
+public static class ATestNGClassWithBrokenSetupMethod {
+    static RuntimeException failure = new RuntimeException()
+
+    @BeforeMethod
+    public void beforeMethod() {
+        throw failure
+    }
+
+    @org.testng.annotations.Test
+    public void test() {
+        System.out.println("EXECUTE");
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.java
new file mode 100644
index 0000000..566401c
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/testng/TestNGTestFrameworkTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.testng;
+
+import org.gradle.api.JavaVersion;
+import org.gradle.api.internal.project.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;
+
+import java.io.File;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class TestNGTestFrameworkTest extends AbstractTestFrameworkTest {
+
+    private TestNGTestFramework testNGTestFramework;
+    private TestNGOptions testngOptionsMock;
+    private IdGenerator<?> idGeneratorMock;
+    private ServiceRegistry serviceRegistry;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        testngOptionsMock = context.mock(TestNGOptions.class);
+        idGeneratorMock = context.mock(IdGenerator.class);
+        serviceRegistry = context.mock(ServiceRegistry.class);
+
+        final JavaVersion sourceCompatibility = JavaVersion.VERSION_1_5;
+        context.checking(new Expectations() {{
+            allowing(projectMock).getProjectDir(); will(returnValue(new File("projectDir")));
+            allowing(projectMock).property("sourceCompatibility"); will(returnValue(sourceCompatibility));
+            allowing(testMock).getTestClassesDir();will(returnValue(testClassesDir));
+            allowing(testMock).getClasspath();will(returnValue(classpathMock));
+            allowing(testMock).getTemporaryDir(); will(returnValue(temporaryDir));
+        }});
+    }
+
+    @org.junit.Test
+    public void testInitialize() {
+        testNGTestFramework = new TestNGTestFramework(testMock);
+        setMocks();
+
+        assertNotNull(testNGTestFramework.getOptions());
+    }
+
+    @org.junit.Test
+    public void testCreatesTestProcessor() {
+        testNGTestFramework = new TestNGTestFramework(testMock);
+        setMocks();
+
+        context.checking(new Expectations() {{
+            allowing(testMock).getTestSrcDirs();  will(returnValue(testSrcDirs));
+            allowing(testMock).getTestReportDir(); will(returnValue(testReportDir));
+            allowing(serviceRegistry).get(IdGenerator.class); will(returnValue(idGeneratorMock));
+            one(testngOptionsMock).setTestResources(testSrcDirs);
+            one(testngOptionsMock).getSuites(temporaryDir);
+        }});
+
+        TestClassProcessor processor = testNGTestFramework.getProcessorFactory().create(serviceRegistry);
+        assertThat(processor, instanceOf(TestNGTestClassProcessor.class));
+    }
+
+    private void setMocks() {
+        testNGTestFramework.setOptions(testngOptionsMock);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessorTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessorTest.java
new file mode 100644
index 0000000..cc03409
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/ForkingTestClassProcessorTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.worker;
+
+import org.gradle.api.Action;
+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.messaging.remote.ObjectConnection;
+import org.gradle.process.JavaForkOptions;
+import org.gradle.process.internal.JavaExecHandleBuilder;
+import org.gradle.process.internal.WorkerProcess;
+import org.gradle.process.internal.WorkerProcessBuilder;
+import org.gradle.process.internal.WorkerProcessFactory;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.List;
+
+import static java.util.Arrays.*;
+import static org.hamcrest.Matchers.*;
+
+ at RunWith(JMock.class)
+public class ForkingTestClassProcessorTest {
+    private final JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+    private final WorkerTestClassProcessorFactory processorFactory = context.mock(WorkerTestClassProcessorFactory.class);
+    private final WorkerProcessFactory workerFactory = context.mock(WorkerProcessFactory.class);
+    private final WorkerProcess workerProcess = context.mock(WorkerProcess.class);
+    private final RemoteTestClassProcessor worker = context.mock(RemoteTestClassProcessor.class);
+    private final TestClassRunInfo test1 = context.mock(TestClassRunInfo.class, "test1");
+    private final TestClassRunInfo test2 = context.mock(TestClassRunInfo.class, "test2");
+    private final TestResultProcessor resultProcessor = context.mock(TestResultProcessor.class);
+    private final List<File> appClassPath = asList(new File("classpath.jar"));
+    private final JavaForkOptions options = context.mock(JavaForkOptions.class);
+    private final Action<WorkerProcessBuilder> action = context.mock(Action.class);
+    private final ForkingTestClassProcessor processor = new ForkingTestClassProcessor(workerFactory, processorFactory, options, appClassPath, action);
+
+    @Test
+    public void onFirstTestCaseStartsWorkerProcess() {
+        expectWorkerProcessStarted();
+        context.checking(new Expectations() {{
+            one(worker).processTestClass(test1);
+        }});
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(test1);
+    }
+
+    @Test
+    public void onSubsequentTestCaseForwardsTestToWorkerProcess() {
+        expectWorkerProcessStarted();
+        context.checking(new Expectations() {{
+            one(worker).processTestClass(test1);
+            one(worker).processTestClass(test2);
+        }});
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(test1);
+        processor.processTestClass(test2);
+    }
+
+    @Test
+    public void onEndProcessingWaitsForWorkerProcessToStop() {
+        expectWorkerProcessStarted();
+        context.checking(new Expectations() {{
+            one(worker).processTestClass(test1);
+            one(worker).stop();
+            one(workerProcess).waitForStop();
+        }});
+
+        processor.startProcessing(resultProcessor);
+        processor.processTestClass(test1);
+        processor.stop();
+    }
+
+    @Test
+    public void onEndProcessingDoesNothingIfNoTestsProcessed() {
+        processor.startProcessing(resultProcessor);
+        processor.stop();
+    }
+
+    private void expectWorkerProcessStarted() {
+        context.checking(new Expectations() {{
+            WorkerProcessBuilder builder = context.mock(WorkerProcessBuilder.class);
+            ObjectConnection connection = context.mock(ObjectConnection.class);
+            JavaExecHandleBuilder javaCommandBuilder = context.mock(JavaExecHandleBuilder.class);
+
+            one(workerFactory).newProcess();
+            will(returnValue(builder));
+
+            one(builder).worker(with(notNullValue(TestWorker.class)));
+
+            one(builder).applicationClasspath(appClassPath);
+
+            one(builder).setLoadApplicationInSystemClassLoader(true);
+
+            one(action).execute(builder);
+            
+            allowing(builder).getJavaCommand();
+            will(returnValue(javaCommandBuilder));
+
+            one(options).copyTo(javaCommandBuilder);
+
+            one(builder).build();
+            will(returnValue(workerProcess));
+
+            allowing(workerProcess).getConnection();
+            will(returnValue(connection));
+
+            one(connection).addIncoming(TestResultProcessor.class, resultProcessor);
+            
+            one(connection).addOutgoing(RemoteTestClassProcessor.class);
+            will(returnValue(worker));
+
+            one(workerProcess).start();
+
+            one(worker).startProcessing();
+        }});
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorkerTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorkerTest.groovy
new file mode 100644
index 0000000..154d4e1
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/internal/tasks/testing/worker/TestWorkerTest.groovy
@@ -0,0 +1,98 @@
+/*
+ * 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.worker
+
+import org.gradle.api.internal.tasks.testing.TestResultProcessor
+import org.gradle.api.internal.tasks.testing.TestClassProcessor
+import org.gradle.api.internal.tasks.testing.TestClassRunInfo
+import org.gradle.messaging.remote.ObjectConnection
+import org.gradle.process.internal.WorkerProcessContext
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.MultithreadedTestCase
+import org.jmock.integration.junit4.JMock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import static org.junit.Assert.*
+import static org.hamcrest.Matchers.*
+import org.gradle.api.internal.tasks.testing.WorkerTestClassProcessorFactory
+import org.junit.Rule
+import org.gradle.util.SetSystemProperties
+
+ at RunWith(JMock.class)
+public class TestWorkerTest extends MultithreadedTestCase {
+    @Rule public final SetSystemProperties properties = new SetSystemProperties()
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private final WorkerProcessContext workerContext = context.mock(WorkerProcessContext.class)
+    private final ObjectConnection connection = context.mock(ObjectConnection.class)
+    private final WorkerTestClassProcessorFactory factory = context.mock(WorkerTestClassProcessorFactory.class)
+    private final TestClassProcessor processor = context.mock(TestClassProcessor.class)
+    private final TestClassRunInfo test = context.mock(TestClassRunInfo.class)
+    private final TestResultProcessor resultProcessor = context.mock(TestResultProcessor.class)
+    private final TestWorker worker = new TestWorker(factory)
+
+    @Before
+    public void setup() {
+        context.checking {
+            allowing(workerContext).getWorkerId()
+            will(returnValue('<worker-id>'))
+            
+            ignoring(workerContext).getDisplayName()
+
+            allowing(workerContext).getServerConnection()
+            will(returnValue(connection))
+
+            ignoring(workerContext).getApplicationClassLoader()
+        }
+    }
+
+    @Test
+    public void createsTestProcessorAndBlocksUntilEndOfProcessingReceived() {
+        context.checking {
+            one(factory).create(withParam(notNullValue()))
+            will(returnValue(processor))
+
+            one(connection).addOutgoing(TestResultProcessor.class)
+            will(returnValue(resultProcessor))
+
+            one(connection).addIncoming(RemoteTestClassProcessor.class, worker)
+            will {
+                start {
+                    worker.startProcessing()
+                    worker.processTestClass(test)
+                    syncAt(1)
+                    worker.stop()
+                }
+            }
+
+            ignoring(resultProcessor)
+
+            one(processor).startProcessing(withParam(notNullValue()))
+            one(processor).processTestClass(test)
+            one(processor).stop()
+        }
+
+        run {
+            expectBlocksUntil(1) {
+                worker.execute(workerContext)
+            }
+        }
+
+        assertThat(System.properties['org.gradle.test.worker'], equalTo('<worker-id>'))
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultAttributesTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultAttributesTest.groovy
new file mode 100644
index 0000000..d9bdbe8
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultAttributesTest.groovy
@@ -0,0 +1,70 @@
+/*
+ * 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.java.archives.internal
+
+import spock.lang.Specification
+import org.gradle.api.java.archives.ManifestException
+
+/**
+ * @author Hans Dockter
+ */
+class DefaultAttributesTest extends Specification {
+    DefaultAttributes attributes = new DefaultAttributes();
+
+    def testKeyValidationWithPut() {
+        when:
+        attributes.put(':::', 'someValue')
+
+        then:
+        thrown(ManifestException)
+
+        when:
+        attributes.put(null, 'someValue')
+
+        then:
+        thrown(ManifestException)
+    }
+
+    def testKeyValidationWithPutAll() {
+        when:
+        attributes.putAll(':::': 'someValue')
+
+        then:
+        thrown(ManifestException)
+
+        when:
+        attributes.putAll((null): 'someValue')
+
+        then:
+        thrown(ManifestException)
+    }
+
+    def testValueValidationWithPut() {
+        when:
+        attributes.put('key', null)
+
+        then:
+        thrown(ManifestException)
+    }
+
+    def testValueValidationWithPutAll() {
+        when:
+        attributes.putAll('key': null)
+
+        then:
+        thrown(ManifestException)
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpecTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpecTest.groovy
new file mode 100644
index 0000000..53bcf57
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestMergeSpecTest.groovy
@@ -0,0 +1,155 @@
+/*
+ * 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.java.archives.internal
+
+import org.gradle.api.Action
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Specification
+import org.gradle.api.java.archives.ManifestMergeDetails
+
+/**
+ * @author Hans Dockter
+ */
+
+class DefaultManifestMergeSpecTest extends Specification {
+    def static final MANIFEST_VERSION_MAP = ['Manifest-Version': '1.0']
+
+    DefaultManifestMergeSpec mergeSpec = new DefaultManifestMergeSpec()
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder()
+
+    def from() {
+        expect:
+        mergeSpec.from('some') == mergeSpec
+        mergeSpec.from('someOther')
+        mergeSpec.mergePaths == ['some', 'someOther']
+    }
+
+    def mergeWithFileAndObjectManifest() {
+        def fileResolver = Mock(FileResolver)
+        DefaultManifest baseManifest = new DefaultManifest(fileResolver)
+        def baseMap = baseManifest.attributes + ([key1: 'value1', key2: 'value2', key3: 'value3'] as LinkedHashMap) 
+        def baseSectionMap = [keysec1: 'valueSec1', keysec2: 'valueSec2', keysec3: 'valueSec3']
+        baseManifest.attributes(baseMap)
+        baseManifest.attributes(baseSectionMap, 'section')
+
+        DefaultManifest otherManifest = new DefaultManifest(fileResolver)
+        def otherMap = [key1: 'value1other', key4: 'value4other', key5: 'value5other'] as LinkedHashMap
+        def otherSectionMap = [keysec1: 'value1Secother', keysec4: 'value4Secother', keysec5: 'value5Secother']
+        otherManifest.attributes(otherMap)
+        otherManifest.attributes(otherSectionMap, 'section')
+
+        TestFile manifestFile = tmpDir.file('somefile')
+        fileResolver.resolve(manifestFile) >> manifestFile
+        DefaultManifest fileManifest = new DefaultManifest(fileResolver)
+        def fileMap = [key2: 'value2File', key4: 'value4File', key6: 'value6File'] as LinkedHashMap
+        def fileSectionMap = [keysec2: 'value2Secfile', keysec4: 'value5Secfile', keysec6: 'value6Secfile']
+        fileManifest.attributes(fileMap)
+        fileManifest.attributes(fileSectionMap, 'section')
+        fileManifest.writeTo(manifestFile)
+
+        when:
+        mergeSpec.from(otherManifest, manifestFile)
+        DefaultManifest mergedManifest = mergeSpec.merge(baseManifest, fileResolver)
+
+        then:
+        mergedManifest.attributes == baseMap + otherMap + fileMap
+        mergedManifest.attributes.keySet() as List == ['Manifest-Version', 'key1', 'key2', 'key3', 'key4', 'key5', 'key6']
+        mergedManifest.sections.section == baseSectionMap + otherSectionMap + fileSectionMap
+        mergedManifest.sections.section.keySet() as List == ['keysec1', 'keysec2', 'keysec3', 'keysec4', 'keysec5', 'keysec6']
+    }
+
+    def mergeWithExcludeAction() {
+        DefaultManifest baseManifest = new DefaultManifest(Mock(FileResolver))
+        baseManifest.attributes key1: 'value1', key2: 'value2'
+        mergeSpec.from(new DefaultManifest(Mock(FileResolver)))
+        mergeSpec.eachEntry {ManifestMergeDetails details ->
+            if (details.getKey() == 'key2') {
+                details.exclude()
+            }
+        }
+
+        expect:
+        mergeSpec.merge(baseManifest, Mock(FileResolver)).attributes == [key1: 'value1'] + MANIFEST_VERSION_MAP
+    }
+
+    def mergeWithNestedFrom() {
+        DefaultManifest baseManifest = new DefaultManifest(Mock(FileResolver)).attributes(key1: 'value1')
+        DefaultManifest mergeManifest = new DefaultManifest(Mock(FileResolver)).attributes(key2: 'value2')
+        DefaultManifest nestedMergeManifest = new DefaultManifest(Mock(FileResolver)).attributes(key3: 'value3')
+        mergeManifest.from(nestedMergeManifest)
+        mergeSpec.from(mergeManifest)
+
+        expect:
+        mergeSpec.merge(baseManifest, Mock(FileResolver)).attributes == [key1: 'value1', key2: 'value2', key3: 'value3'] +
+                MANIFEST_VERSION_MAP
+    }
+
+    def mergeWithChangeValueAction() {
+        DefaultManifest baseManifest = new DefaultManifest(Mock(FileResolver))
+        baseManifest.attributes key1: 'value1', key2: 'value2'
+        mergeSpec.from(new DefaultManifest(Mock(FileResolver)))
+        mergeSpec.eachEntry {ManifestMergeDetails details ->
+            if (details.getKey() == 'key2') {
+                details.setValue('newValue2')
+            }
+        }
+
+        expect:
+        mergeSpec.merge(baseManifest, Mock(FileResolver)).attributes == [key1: 'value1', key2: 'newValue2'] + MANIFEST_VERSION_MAP
+    }
+
+    def mergeWithActionOnSection() {
+        DefaultManifest baseManifest = new DefaultManifest(Mock(FileResolver))
+        baseManifest.attributes('section', key1: 'value1')
+        mergeSpec.from(new DefaultManifest(Mock(FileResolver)).attributes('section', key2: 'mergeValue2'))
+        mergeSpec.eachEntry {ManifestMergeDetails details ->
+            if (details.getBaseValue() != null && details.getMergeValue() == null) {
+                details.exclude();
+            }
+        }
+
+        expect:
+        mergeSpec.merge(baseManifest, Mock(FileResolver)).sections.section == [key2: 'mergeValue2']
+    }
+
+    def mergeShouldWinByDefault() {
+        DefaultManifest baseManifest = new DefaultManifest(Mock(FileResolver))
+        baseManifest.attributes key1: 'value1'
+        mergeSpec.from(new DefaultManifest(Mock(FileResolver)).attributes(key1: 'mergeValue1'))
+
+        expect:
+        mergeSpec.merge(baseManifest, Mock(FileResolver)).attributes == [key1: 'mergeValue1'] + MANIFEST_VERSION_MAP
+    }
+
+    def addActionByAnonymousInnerClass() {
+        DefaultManifest baseManifest = new DefaultManifest(Mock(FileResolver))
+        mergeSpec.from(new DefaultManifest(Mock(FileResolver)).attributes(key1: 'value1'))
+        mergeSpec.eachEntry(new Action() {
+            void execute(def mergeDetails) {
+                if (mergeDetails.key == 'key1') {
+                    mergeDetails.exclude()
+                }
+            }
+        })
+
+        expect:
+        mergeSpec.merge(baseManifest, Mock(FileResolver)).attributes == MANIFEST_VERSION_MAP
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestTest.groovy
new file mode 100644
index 0000000..6628942
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/java/archives/internal/DefaultManifestTest.groovy
@@ -0,0 +1,180 @@
+/*
+ * 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.api.java.archives.internal
+
+import org.apache.tools.ant.taskdefs.Manifest
+import org.apache.tools.ant.taskdefs.Manifest.Attribute
+import org.apache.tools.ant.taskdefs.Manifest.Section
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.Rule
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+
+class DefaultManifestTest extends Specification {
+    def static final MANIFEST_VERSION_MAP = ['Manifest-Version': '1.0']
+    def fileResolver = Mock(FileResolver)
+    DefaultManifest gradleManifest = new DefaultManifest(fileResolver)
+
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder()
+
+    def testInitWithFileReolver() {
+        expect:
+        gradleManifest.attributes == MANIFEST_VERSION_MAP
+        !gradleManifest.sections
+        !gradleManifest.mergeSpecs
+    }
+
+    def testInitWithPathFileResolver() {
+        TestFile manifestFile = tmpDir.file('somefile')
+        def fileMap = [Key2: 'value2File', key4: 'value4File', key6: 'value6File']
+        def fileSectionMap = [Keysec2: 'value2Secfile', keysec4: 'value5Secfile', keysec6: 'value6Secfile']
+        String content = ''
+        fileMap.each {key, value ->
+            content += String.format("%s: %s%n", key, value)
+        }
+        content += String.format("Name: %s%n", 'section')
+        fileSectionMap.each {key, value ->
+            content += String.format("%s: %s%n", key, value)
+        }
+        manifestFile.write(content)
+        fileResolver.resolve('file') >> manifestFile
+
+        when:
+        DefaultManifest manifest = new DefaultManifest('file', fileResolver)
+
+        then:
+        manifest.getAttributes() == fileMap + MANIFEST_VERSION_MAP
+        manifest.sections.section == fileSectionMap
+    }
+
+    def testAddMainAttributes() {
+        Map attributes = [key1: 'value1', key2: 'value2']
+        Map attributes2 = [key3: 'value3']
+
+        when:
+        def expectToReturnSelf = gradleManifest.mainAttributes(attributes)
+        gradleManifest.attributes(attributes2)
+
+        then:
+        gradleManifest.is(expectToReturnSelf)
+        gradleManifest.getAttributes() == attributes + attributes2 + MANIFEST_VERSION_MAP
+    }
+
+    def testAddSectionAttributes() {
+        String section1 = 'section1'
+        String section2 = 'section2'
+        Map attributes = [key1: 'value1', key2: 'value2']
+        Map attributes2 = [key3: 'value3']
+        Map attributes3 = [key3: 'value3']
+
+        when:
+        def expectToReturnSelf = gradleManifest.attributes(attributes, section1)
+        gradleManifest.attributes(attributes2, section1)
+        gradleManifest.attributes(attributes3, section2)
+
+        then:
+        gradleManifest.is(expectToReturnSelf)
+        gradleManifest.sections.section1 == attributes + attributes2
+        gradleManifest.sections.section2 == attributes3
+        gradleManifest.getSections() == [section1: attributes + attributes2, section2: attributes3]
+    }
+
+    def clear() {
+        gradleManifest.attributes(key1: 'value1')
+        gradleManifest.attributes('section', key1: 'value1')
+        gradleManifest.from(new DefaultManifest(Mock(FileResolver)))
+
+        when:
+        gradleManifest.clear()
+
+        then:
+        gradleManifest.getAttributes() == MANIFEST_VERSION_MAP
+        !gradleManifest.sections
+        !gradleManifest.mergeSpecs
+    }
+
+    def merge() {
+        gradleManifest.attributes(key1: 'value1')
+        gradleManifest.from(new DefaultManifest(fileResolver).attributes(key2: 'value2', key3: 'value3')) {
+            eachEntry { details ->
+                if (details.key == 'key3') {
+                    details.exclude()
+                }
+            }
+        }
+        gradleManifest.from(new DefaultManifest(fileResolver).attributes(key4: 'value4'))
+
+        expect:
+        gradleManifest.effectiveManifest.getAttributes() == [key1: 'value1', key2: 'value2', key4: 'value4'] + MANIFEST_VERSION_MAP
+    }
+
+    def write() {
+        Map testMainAttributes = [key1: 'value1', key2: 'value2', key3: 'value3', key4: 'value4'] as LinkedHashMap
+        Map testSectionAttributes = [sectionkey1: 'sectionvalue1']
+        String testSection = 'section'
+
+        DefaultManifest gradleManifest = new DefaultManifest(fileResolver)
+        gradleManifest.from(new DefaultManifest(fileResolver).attributes(testMainAttributes))
+        gradleManifest.attributes(testSectionAttributes, testSection)
+
+        Manifest expectedManifest = new Manifest()
+        expectedManifest.addConfiguredAttribute(new Attribute('key1', 'value1'))
+        expectedManifest.addConfiguredAttribute(new Attribute('key2', 'value2'))
+        expectedManifest.addConfiguredAttribute(new Attribute('key3', 'value3'))
+        expectedManifest.addConfiguredAttribute(new Attribute('key4', 'value4'))
+        expectedManifest.addConfiguredAttribute(new Attribute('Manifest-Version', '1.0'))
+        Section section = new Section()
+        section.setName testSection
+        section.addConfiguredAttribute(new Attribute('sectionkey1', 'sectionvalue1'))
+        expectedManifest.addConfiguredSection(section)
+        def StringWriter stringWriter = new StringWriter()
+
+        when:
+        gradleManifest.writeTo(stringWriter)
+        Manifest actualManifest = new Manifest(new StringReader(stringWriter.toString()))
+        def actualOrderedKeys = []
+        actualManifest.getMainSection().getAttributeKeys().each { element ->
+            actualOrderedKeys.add(element)
+        }
+
+        then:
+        actualManifest == expectedManifest
+        actualOrderedKeys == testMainAttributes.keySet() as List
+    }
+
+    def writeWithPath() {
+        TestFile manifestFile = tmpDir.file('someNonexistingDir').file('someFile')
+        DefaultManifest manifest = new DefaultManifest(fileResolver).attributes(key1: 'value1')
+        fileResolver.resolve('file') >> manifestFile
+        
+        when:
+        manifest.writeTo('file')
+        Manifest fileManifest = new Manifest(new FileReader(manifestFile))
+        Manifest expectedManifest = new Manifest()
+        expectedManifest.addConfiguredAttribute(new Attribute('key1', 'value1'))
+        expectedManifest.addConfiguredAttribute(new Attribute('Manifest-Version', '1.0'))
+
+        then:
+        fileManifest.equals(expectedManifest)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/BasePluginConventionTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/BasePluginConventionTest.groovy
new file mode 100644
index 0000000..8dbf227
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/BasePluginConventionTest.groovy
@@ -0,0 +1,52 @@
+/*
+ * 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.api.plugins
+
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.util.HelperUtil
+import org.junit.Before
+import org.junit.Test
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+class BasePluginConventionTest {
+    private DefaultProject project = HelperUtil.createRootProject()
+    private File testDir = project.projectDir
+    private BasePluginConvention convention
+
+    @Before public void setUp() {
+        convention = new BasePluginConvention(project)
+    }
+
+    @Test public void defaultValues() {
+        assertEquals(project.name, convention.archivesBaseName)
+        assertEquals('distributions', convention.distsDirName)
+        assertEquals(new File(project.buildDir, 'distributions'), convention.distsDir)
+        assertEquals('libs', convention.libsDirName)
+        assertEquals(new File(project.buildDir, 'libs'), convention.libsDir)
+    }
+
+    @Test public void dirsRelativeToBuildDir() {
+        project.buildDirName = 'mybuild'
+        convention.distsDirName = 'mydists'
+        assertEquals(project.file('mybuild/mydists'), convention.distsDir)
+        convention.libsDirName = 'mylibs'
+        assertEquals(project.file('mybuild/mylibs'), convention.libsDir)
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/BasePluginTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/BasePluginTest.groovy
new file mode 100644
index 0000000..f8158ed
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/BasePluginTest.groovy
@@ -0,0 +1,162 @@
+/*
+ * 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.DefaultTask
+import org.gradle.api.Project
+import org.gradle.api.artifacts.PublishArtifact
+import org.gradle.api.internal.tasks.DefaultTaskDependency
+import org.gradle.api.tasks.Delete
+import org.gradle.api.tasks.Upload
+import org.gradle.util.HelperUtil
+import org.junit.Test
+import static org.gradle.util.Matchers.*
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+import org.gradle.api.Task
+import org.gradle.api.tasks.bundling.Jar
+import org.gradle.api.tasks.bundling.Zip
+import org.gradle.api.tasks.bundling.Tar
+import org.gradle.api.artifacts.Dependency
+import org.gradle.util.WrapUtil
+import org.gradle.api.internal.artifacts.configurations.Configurations
+
+/**
+ * @author Hans Dockter
+ */
+class BasePluginTest {
+    private final Project project = HelperUtil.createRootProject()
+    private final BasePlugin plugin = new BasePlugin()
+
+    @Test public void addsConventionObject() {
+        plugin.apply(project)
+
+        assertThat(project.convention.plugins.base, instanceOf(BasePluginConvention))
+    }
+
+    @Test public void createsTasksAndAppliesMappings() {
+        plugin.apply(project)
+
+        def task = project.tasks[BasePlugin.CLEAN_TASK_NAME]
+        assertThat(task, instanceOf(Delete))
+        assertThat(task, dependsOn())
+        assertThat(task.targetFiles.files, equalTo([project.buildDir] as Set))
+
+        task = project.tasks[BasePlugin.ASSEMBLE_TASK_NAME]
+        assertThat(task, instanceOf(DefaultTask))
+    }
+
+    @Test public void addsRulesWhenAConfigurationIsAdded() {
+        plugin.apply(project)
+
+        assertThat(project.tasks.rules.size(), equalTo(3))
+    }
+
+    @Test public void addsImplicitTasksForConfiguration() {
+        plugin.apply(project)
+
+        Task producer = [getName: {-> 'producer'}] as Task
+        PublishArtifact artifactStub = [getBuildDependencies: {-> new DefaultTaskDependency().add(producer) }] as PublishArtifact
+
+        project.configurations.getByName('archives').addArtifact(artifactStub)
+
+        def task = project.tasks['buildArchives']
+        assertThat(task, instanceOf(DefaultTask))
+        assertThat(task, dependsOn('producer'))
+
+        task = project.tasks['uploadArchives']
+        assertThat(task, instanceOf(Upload))
+        assertThat(task, dependsOn('producer'))
+
+        project.configurations.add('conf').addArtifact(artifactStub)
+
+        task = project.tasks['buildConf']
+        assertThat(task, instanceOf(DefaultTask))
+        assertThat(task, dependsOn('producer'))
+
+        task = project.tasks['uploadConf']
+        assertThat(task, instanceOf(Upload))
+        assertThat(task, dependsOn('producer'))
+        assertThat(task.configuration, sameInstance(project.configurations.conf))
+    }
+
+    @Test public void addsACleanRule() {
+        plugin.apply(project)
+
+        Task test = project.task('test')
+        test.outputs.files(project.buildDir)
+
+        Task cleanTest = project.tasks['cleanTest']
+        assertThat(cleanTest, instanceOf(Delete))
+        assertThat(cleanTest.delete, equalTo([test.outputs.files] as Set))
+    }
+
+    @Test public void appliesMappingsForArchiveTasks() {
+        plugin.apply(project)
+
+        project.version = '1.0'
+
+        def task = project.tasks.add('someJar', Jar)
+        assertThat(task.destinationDir, equalTo(project.libsDir))
+        assertThat(task.version, equalTo(project.version))
+        assertThat(task.baseName, equalTo(project.archivesBaseName))
+
+        assertThat(project.tasks[BasePlugin.ASSEMBLE_TASK_NAME], dependsOn('someJar'))
+
+        task = project.tasks.add('someZip', Zip)
+        assertThat(task.destinationDir, equalTo(project.distsDir))
+        assertThat(task.version, equalTo(project.version))
+        assertThat(task.baseName, equalTo(project.archivesBaseName))
+
+        assertThat(project.tasks[BasePlugin.ASSEMBLE_TASK_NAME], dependsOn('someJar', 'someZip'))
+
+        task = project.tasks.add('someTar', Tar)
+        assertThat(task.destinationDir, equalTo(project.distsDir))
+        assertThat(task.version, equalTo(project.version))
+        assertThat(task.baseName, equalTo(project.archivesBaseName))
+
+        assertThat(project.tasks[BasePlugin.ASSEMBLE_TASK_NAME], dependsOn('someJar', 'someZip', 'someTar'))
+    }
+
+    @Test public void usesNullVersionWhenProjectVersionNotSpecified() {
+        plugin.apply(project)
+
+        def task = project.tasks.add('someJar', Jar)
+        assertThat(task.version, nullValue())
+
+        project.version = '1.0'
+
+        task = project.tasks.add('someOtherJar', Jar)
+        assertThat(task.version, equalTo('1.0'))
+    }
+
+    @Test public void addsConfigurationsToTheProject() {
+        plugin.apply(project)
+
+        assertThat(project.status, equalTo("integration"))
+
+        def configuration = project.configurations.getByName(Dependency.DEFAULT_CONFIGURATION)
+        assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(WrapUtil.toSet(Dependency.ARCHIVES_CONFIGURATION)))
+        assertTrue(configuration.visible)
+        assertTrue(configuration.transitive)
+
+        configuration = project.configurations.getByName(Dependency.ARCHIVES_CONFIGURATION)
+        assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(WrapUtil.toSet()))
+        assertTrue(configuration.visible)
+        assertTrue(configuration.transitive)
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/GroovyBasePluginTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/GroovyBasePluginTest.groovy
new file mode 100644
index 0000000..8fa73ce
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/GroovyBasePluginTest.groovy
@@ -0,0 +1,90 @@
+/*
+ * 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.plugins
+
+import org.gradle.api.Project
+import org.gradle.api.internal.artifacts.configurations.Configurations
+import org.gradle.api.tasks.compile.GroovyCompile
+import org.gradle.api.tasks.javadoc.Groovydoc
+import org.gradle.util.HelperUtil
+import org.junit.Test
+import static org.gradle.util.Matchers.dependsOn
+import static org.gradle.util.WrapUtil.toLinkedSet
+import static org.gradle.util.WrapUtil.toSet
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+
+class GroovyBasePluginTest {
+    private final Project project = HelperUtil.createRootProject()
+    private final GroovyBasePlugin groovyBasePlugin = new GroovyBasePlugin()
+
+    @Test public void appliesTheJavaBasePluginToTheProject() {
+        groovyBasePlugin.apply(project)
+
+        assertTrue(project.getPlugins().hasPlugin(JavaBasePlugin));
+    }
+
+    @Test public void addsGroovyConfigurationToTheProject() {
+        groovyBasePlugin.apply(project)
+        
+        def configuration = project.configurations.getByName(GroovyBasePlugin.GROOVY_CONFIGURATION_NAME)
+        assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(toSet()))
+        assertFalse(configuration.visible)
+        assertFalse(configuration.transitive)
+    }
+
+    @Test public void appliesMappingsToNewSourceSet() {
+        groovyBasePlugin.apply(project)
+
+        def sourceSet = project.sourceSets.add('custom')
+        assertThat(sourceSet.groovy.displayName, equalTo("custom Groovy source"))
+        assertThat(sourceSet.groovy.srcDirs, equalTo(toLinkedSet(project.file("src/custom/groovy"))))
+    }
+
+    @Test public void addsCompileTaskToNewSourceSet() {
+        groovyBasePlugin.apply(project)
+        
+        project.sourceSets.add('custom')
+
+        def task = project.tasks['compileCustomGroovy']
+        assertThat(task, instanceOf(GroovyCompile.class))
+        assertThat(task.description, equalTo('Compiles the custom Groovy source.'))
+        assertThat(task.defaultSource, equalTo(project.sourceSets.custom.groovy))
+        assertThat(task, dependsOn('compileCustomJava'))
+    }
+
+    @Test public void dependenciesOfJavaPluginTasksIncludeGroovyCompileTasks() {
+        groovyBasePlugin.apply(project)
+
+        project.sourceSets.add('custom')
+        def task = project.tasks['customClasses']
+        assertThat(task, dependsOn(hasItem('compileCustomGroovy')))
+    }
+   
+    @Test public void configuresAdditionalTasksDefinedByTheBuildScript() {
+        groovyBasePlugin.apply(project)
+
+        def task = project.createTask('otherGroovydoc', type: Groovydoc)
+        assertThat(task.destinationDir, equalTo(new File(project.docsDir, 'groovydoc')))
+        assertThat(task.docTitle, equalTo(project.apiDocTitle))
+        assertThat(task.windowTitle, equalTo(project.apiDocTitle))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/GroovyPluginTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/GroovyPluginTest.groovy
new file mode 100644
index 0000000..a38d8fc
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/GroovyPluginTest.groovy
@@ -0,0 +1,107 @@
+/*
+ * 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.plugins
+
+import org.gradle.api.Project
+import org.gradle.api.internal.artifacts.configurations.Configurations
+import org.gradle.api.tasks.compile.GroovyCompile
+import org.gradle.api.tasks.javadoc.Groovydoc
+import org.gradle.util.HelperUtil
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import org.junit.Test
+import static org.gradle.util.Matchers.dependsOn
+import static org.gradle.util.WrapUtil.toLinkedSet
+import static org.gradle.util.WrapUtil.toSet
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+class GroovyPluginTest {
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder()
+    private final Project project = HelperUtil.createRootProject()
+    private final GroovyPlugin groovyPlugin = new GroovyPlugin()
+
+    @Test public void appliesTheJavaPluginToTheProject() {
+        groovyPlugin.apply(project)
+
+        assertTrue(project.getPlugins().hasPlugin(JavaPlugin));
+    }
+
+    @Test public void addsGroovyConfigurationToTheProject() {
+        groovyPlugin.apply(project)
+
+        def configuration = project.configurations.getByName(JavaPlugin.COMPILE_CONFIGURATION_NAME)
+        assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(toSet(GroovyBasePlugin.GROOVY_CONFIGURATION_NAME)))
+        assertFalse(configuration.visible)
+        assertTrue(configuration.transitive)
+    }
+
+    @Test public void addsGroovyConventionToEachSourceSet() {
+        groovyPlugin.apply(project)
+
+        def sourceSet = project.sourceSets.main
+        assertThat(sourceSet.groovy.displayName, equalTo("main Groovy source"))
+        assertThat(sourceSet.groovy.srcDirs, equalTo(toLinkedSet(project.file("src/main/groovy"))))
+
+        sourceSet = project.sourceSets.test
+        assertThat(sourceSet.groovy.displayName, equalTo("test Groovy source"))
+        assertThat(sourceSet.groovy.srcDirs, equalTo(toLinkedSet(project.file("src/test/groovy"))))
+    }
+
+    @Test public void addsCompileTaskForEachSourceSet() {
+        groovyPlugin.apply(project)
+
+        def task = project.tasks['compileGroovy']
+        assertThat(task, instanceOf(GroovyCompile.class))
+        assertThat(task.description, equalTo('Compiles the main Groovy source.'))
+        assertThat(task.defaultSource, equalTo(project.sourceSets.main.groovy))
+        assertThat(task, dependsOn(JavaPlugin.COMPILE_JAVA_TASK_NAME))
+
+        task = project.tasks['compileTestGroovy']
+        assertThat(task, instanceOf(GroovyCompile.class))
+        assertThat(task.description, equalTo('Compiles the test Groovy source.'))
+        assertThat(task.defaultSource, equalTo(project.sourceSets.test.groovy))
+        assertThat(task, dependsOn(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME, JavaPlugin.CLASSES_TASK_NAME))
+    }
+
+    @Test public void dependenciesOfJavaPluginTasksIncludeGroovyCompileTasks() {
+        groovyPlugin.apply(project)
+
+        def task = project.tasks[JavaPlugin.CLASSES_TASK_NAME]
+        assertThat(task, dependsOn(hasItem('compileGroovy')))
+
+        task = project.tasks[JavaPlugin.TEST_CLASSES_TASK_NAME]
+        assertThat(task, dependsOn(hasItem('compileTestGroovy')))
+    }
+    
+    @Test public void addsStandardTasksToTheProject() {
+        groovyPlugin.apply(project)
+
+        project.sourceSets.main.groovy.srcDirs(tmpDir.getDir())
+        tmpDir.file("SomeFile.groovy").touch()
+        def task = project.tasks[GroovyPlugin.GROOVYDOC_TASK_NAME]
+        assertThat(task, instanceOf(Groovydoc.class))
+        assertThat(task.destinationDir, equalTo(new File(project.docsDir, 'groovydoc')))
+        assertThat(task.source.files, equalTo(project.sourceSets.main.groovy.files))
+        assertThat(task.docTitle, equalTo(project.apiDocTitle))
+        assertThat(task.windowTitle, equalTo(project.apiDocTitle))
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/JavaBasePluginTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/JavaBasePluginTest.groovy
new file mode 100644
index 0000000..37189f4
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/JavaBasePluginTest.groovy
@@ -0,0 +1,154 @@
+/*
+ * 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.DefaultTask
+import org.gradle.api.Project
+import org.gradle.api.tasks.Copy
+import org.gradle.api.tasks.bundling.Jar
+import org.gradle.api.tasks.compile.Compile
+import org.gradle.api.tasks.javadoc.Javadoc
+import org.gradle.util.HelperUtil
+import org.gradle.util.Matchers
+import spock.lang.Specification
+import static org.gradle.util.WrapUtil.toLinkedSet
+import org.gradle.api.tasks.testing.Test
+import org.gradle.util.SetSystemProperties
+import org.junit.Rule
+
+/**
+ * @author Hans Dockter
+ */
+
+class JavaBasePluginTest extends Specification {
+    @Rule
+    public SetSystemProperties sysProperties = new SetSystemProperties()
+    private final Project project = HelperUtil.createRootProject()
+    private final JavaBasePlugin javaBasePlugin = new JavaBasePlugin()
+
+    void appliesBasePluginsAndAddsConventionObject() {
+        when:
+        javaBasePlugin.apply(project)
+
+        then:
+        project.getPlugins().hasPlugin(ReportingBasePlugin)
+        project.getPlugins().hasPlugin(BasePlugin)
+        project.convention.plugins.java instanceof JavaPluginConvention
+    }
+
+    void createsTasksAndAppliesMappingsForNewSourceSet() {
+        when:
+        javaBasePlugin.apply(project)
+        project.sourceSets.add('custom')
+        
+        then:
+        def set = project.sourceSets.custom
+        set.java.srcDirs == toLinkedSet(project.file('src/custom/java'))
+        set.resources.srcDirs == toLinkedSet(project.file('src/custom/resources'))
+        set.classesDir == new File(project.buildDir, 'classes/custom')
+        Matchers.builtBy('customClasses').matches(set.classes)
+
+        def task = project.tasks['processCustomResources']
+        task.description == 'Processes the custom resources.'
+        task instanceof Copy
+        Matchers.dependsOn().matches(task)
+        task.destinationDir == project.sourceSets.custom.classesDir
+        task.defaultSource == project.sourceSets.custom.resources
+
+        task = project.tasks['compileCustomJava']
+        task.description == 'Compiles the custom Java source.'
+        task instanceof Compile
+        Matchers.dependsOn().matches(task)
+        task.defaultSource == project.sourceSets.custom.java
+        task.classpath.is(project.sourceSets.custom.compileClasspath)
+        task.destinationDir == project.sourceSets.custom.classesDir
+
+        task = project.tasks['customClasses']
+        task.description == 'Assembles the custom classes.'
+        task instanceof DefaultTask
+        Matchers.dependsOn('processCustomResources', 'compileCustomJava').matches(task)
+    }
+
+    void appliesMappingsToTasksDefinedByBuildScript() {
+        when:
+        javaBasePlugin.apply(project)
+        def task = project.createTask('customCompile', type: Compile)
+
+        then:
+        task.sourceCompatibility == project.sourceCompatibility.toString()
+
+        task = project.createTask('customTest', type: org.gradle.api.tasks.testing.Test)
+        task.workingDir == project.projectDir
+
+        task = project.createTask('customJavadoc', type: Javadoc)
+        task.destinationDir == project.file("$project.docsDir/javadoc")
+        task.title == project.apiDocTitle
+    }
+
+    void appliesMappingsToCustomJarTasks() {
+        when:
+        javaBasePlugin.apply(project)
+        def task = project.createTask('customJar', type: Jar)
+
+        then:
+        Matchers.dependsOn().matches(task)
+        task.destinationDir == project.libsDir
+    }
+
+    void createsLifecycleBuildTasks() {
+        when:
+        javaBasePlugin.apply(project)
+
+        then:
+        def build = project.tasks[JavaBasePlugin.BUILD_TASK_NAME]
+        Matchers.dependsOn(JavaBasePlugin.CHECK_TASK_NAME, BasePlugin.ASSEMBLE_TASK_NAME).matches(build)
+
+        def buildDependent = project.tasks[JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME]
+        Matchers.dependsOn(JavaBasePlugin.BUILD_TASK_NAME).matches(buildDependent)
+
+        def buildNeeded = project.tasks[JavaBasePlugin.BUILD_NEEDED_TASK_NAME]
+        Matchers.dependsOn(JavaBasePlugin.BUILD_TASK_NAME).matches(buildNeeded)
+    }
+
+    def configuresTestTaskWhenDebugSystemPropertyIsSet() {
+        javaBasePlugin.apply(project)
+        def task = project.tasks.add('test', Test.class)
+
+        when:
+        System.setProperty("test.debug", "true")
+        project.projectEvaluationBroadcaster.afterEvaluate(project, null)
+
+        then:
+        task.debug
+    }
+
+    def configuresTestTaskWhenSingleTestSystemPropertyIsSet() {
+        javaBasePlugin.apply(project)
+        def task = project.tasks.add('test', Test.class)
+        task.include 'ignoreme'
+
+        when:
+        System.setProperty("test.single", "pattern")
+        project.projectEvaluationBroadcaster.afterEvaluate(project, null)
+
+        then:
+        task.includes == ['**/pattern*.class'] as Set
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginConventionTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginConventionTest.groovy
new file mode 100644
index 0000000..1927ce3
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginConventionTest.groovy
@@ -0,0 +1,140 @@
+/*
+ * 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.api.plugins
+
+import org.gradle.api.JavaVersion
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.api.internal.tasks.DefaultSourceSetContainer
+import org.gradle.api.java.archives.internal.DefaultManifest
+import org.gradle.util.HelperUtil
+import org.gradle.util.JUnit4GroovyMockery
+import org.gradle.util.TemporaryFolder
+import org.gradle.util.TestFile
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertThat
+
+/**
+ * @author Hans Dockter
+ */
+class JavaPluginConventionTest {
+    private final JUnit4GroovyMockery context = new JUnit4GroovyMockery()
+    private DefaultProject project = HelperUtil.createRootProject()
+    private File testDir = project.projectDir
+    private JavaPluginConvention convention
+
+    @Rule
+    public final TemporaryFolder tmpDir = new TemporaryFolder()
+
+    @Before public void setUp() {
+        project.convention.plugins.reportingBase = new ReportingBasePluginConvention(project)
+        convention = new JavaPluginConvention(project)
+    }
+
+    @Test public void defaultValues() {
+        assertThat(convention.sourceSets, instanceOf(DefaultSourceSetContainer))
+        assertThat(convention.manifest, notNullValue())
+        assertEquals('dependency-cache', convention.dependencyCacheDirName)
+        assertEquals('docs', convention.docsDirName)
+        assertEquals('test-results', convention.testResultsDirName)
+        assertEquals('tests', convention.testReportDirName)
+        assertEquals(JavaVersion.VERSION_1_5, convention.sourceCompatibility)
+        assertEquals(JavaVersion.VERSION_1_5, convention.targetCompatibility)
+    }
+
+    @Test public void canConfigureSourceSets() {
+        File dir = new File('classes-dir')
+        convention.sourceSets {
+            main {
+                classesDir = dir
+            }
+        }
+        assertThat(convention.sourceSets.main.classesDir, equalTo(project.file(dir)))
+    }
+    
+    @Test public void testDefaultDirs() {
+        checkDirs()
+    }
+
+    @Test public void testDynamicDirs() {
+        project.buildDirName = 'mybuild'
+        checkDirs()
+    }
+
+    private void checkDirs() {
+        assertEquals(new File(project.buildDir, convention.dependencyCacheDirName), convention.dependencyCacheDir)
+        assertEquals(new File(project.buildDir, convention.docsDirName), convention.docsDir)
+        assertEquals(new File(project.buildDir, convention.testResultsDirName), convention.testResultsDir)
+        assertEquals(new File(convention.reportsDir, convention.testReportDirName), convention.testReportDir)
+    }
+
+    @Test public void testTestReportDirIsCalculatedRelativeToReportsDir() {
+        assertEquals(new File(project.buildDir, 'reports/tests'), convention.testReportDir)
+
+        project.reportsDirName = 'other-reports-dir'
+        convention.testReportDirName = 'other-test-dir'
+
+        assertEquals(new File(project.buildDir, 'other-reports-dir/other-test-dir'), convention.testReportDir)
+    }
+
+    @Test public void testTargetCompatibilityDefaultsToSourceCompatibilityWhenNotSet() {
+        convention.sourceCompatibility = '1.4'
+        assertEquals(JavaVersion.VERSION_1_4, convention.sourceCompatibility)
+        assertEquals(JavaVersion.VERSION_1_4, convention.targetCompatibility)
+
+        convention.targetCompatibility = '1.2'
+        assertEquals(JavaVersion.VERSION_1_4, convention.sourceCompatibility)
+        assertEquals(JavaVersion.VERSION_1_2, convention.targetCompatibility)
+
+        convention.sourceCompatibility = 6
+        assertEquals(JavaVersion.VERSION_1_6, convention.sourceCompatibility)
+        assertEquals(JavaVersion.VERSION_1_2, convention.targetCompatibility)
+    }
+
+    @Test
+    public void createsManifestWithFileResolvingAndValues() {
+        FileResolver fileResolver = context.mock(FileResolver)
+        project.setFileResolver fileResolver
+        TestFile manifestFile = expectPathResolved(fileResolver, 'file')
+        manifestFile.write("key2: value2")
+        def manifest = convention.manifest {
+            from 'file'
+            attributes(key1: 'value1')
+        }
+        assertThat(manifest, instanceOf(DefaultManifest.class))
+        DefaultManifest mergedManifest = manifest.effectiveManifest
+        assertThat(mergedManifest.attributes, equalTo([key1: 'value1', key2: 'value2', 'Manifest-Version': '1.0']))
+    }
+
+    @Test
+    public void createsEmptyManifest() {
+        assertThat(convention.manifest(), instanceOf(DefaultManifest.class))
+    }
+
+    private TestFile expectPathResolved(FileResolver fileResolver, String path) {
+        TestFile file = tmpDir.file(path)
+        context.checking {
+            one(fileResolver).resolve(path)
+            will(returnValue(file))
+        }
+        return file
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginTest.groovy
new file mode 100644
index 0000000..fb2d930
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/JavaPluginTest.groovy
@@ -0,0 +1,235 @@
+/*
+ * 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.DefaultTask
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.internal.artifacts.configurations.Configurations
+import org.gradle.api.internal.plugins.EmbeddableJavaProject
+import org.gradle.api.internal.project.DefaultProject
+import org.gradle.api.tasks.Copy
+import org.gradle.api.tasks.SourceSet
+import org.gradle.api.tasks.bundling.Jar
+import org.gradle.api.tasks.compile.Compile
+import org.gradle.api.tasks.javadoc.Javadoc
+import org.gradle.util.HelperUtil
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import org.junit.Test
+import static org.gradle.util.Matchers.builtBy
+import static org.gradle.util.Matchers.dependsOn
+import static org.gradle.util.WrapUtil.toLinkedSet
+import static org.gradle.util.WrapUtil.toSet
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+class JavaPluginTest {
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder()
+    private final Project project = HelperUtil.createRootProject()
+    private final JavaPlugin javaPlugin = new JavaPlugin()
+
+    @Test public void appliesBasePluginsAndAddsConventionObject() {
+        javaPlugin.apply(project)
+
+        assertThat(project.convention.plugins.embeddedJavaProject, instanceOf(EmbeddableJavaProject))
+        assertThat(project.convention.plugins.embeddedJavaProject.rebuildTasks, equalTo([BasePlugin.CLEAN_TASK_NAME, JavaBasePlugin.BUILD_TASK_NAME]))
+        assertThat(project.convention.plugins.embeddedJavaProject.buildTasks, equalTo([JavaBasePlugin.BUILD_TASK_NAME]))
+        assertThat(project.convention.plugins.embeddedJavaProject.runtimeClasspath, sameInstance(project.sourceSets.main.runtimeClasspath))
+    }
+
+    @Test public void addsConfigurationsToTheProject() {
+        javaPlugin.apply(project)
+
+        def configuration = project.configurations.getByName(JavaPlugin.COMPILE_CONFIGURATION_NAME)
+        assertFalse(configuration.visible)
+        assertTrue(configuration.transitive)
+
+        configuration = project.configurations.getByName(JavaPlugin.RUNTIME_CONFIGURATION_NAME)
+        assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(toSet(JavaPlugin.COMPILE_CONFIGURATION_NAME)))
+        assertFalse(configuration.visible)
+        assertTrue(configuration.transitive)
+
+        configuration = project.configurations.getByName(JavaPlugin.TEST_COMPILE_CONFIGURATION_NAME)
+        assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(toSet(JavaPlugin.COMPILE_CONFIGURATION_NAME)))
+        assertFalse(configuration.visible)
+        assertTrue(configuration.transitive)
+
+        configuration = project.configurations.getByName(JavaPlugin.TEST_RUNTIME_CONFIGURATION_NAME)
+        assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(toSet(JavaPlugin.TEST_COMPILE_CONFIGURATION_NAME, JavaPlugin.RUNTIME_CONFIGURATION_NAME)))
+        assertFalse(configuration.visible)
+        assertTrue(configuration.transitive)
+
+        configuration = project.configurations.getByName(Dependency.DEFAULT_CONFIGURATION)
+        assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(toSet(Dependency.ARCHIVES_CONFIGURATION, JavaPlugin.RUNTIME_CONFIGURATION_NAME)))
+    }
+
+    @Test public void createsStandardSourceSetsAndAppliesMappings() {
+        javaPlugin.apply(project)
+
+        def set = project.sourceSets[SourceSet.MAIN_SOURCE_SET_NAME]
+        assertThat(set.java.srcDirs, equalTo(toLinkedSet(project.file('src/main/java'))))
+        assertThat(set.resources.srcDirs, equalTo(toLinkedSet(project.file('src/main/resources'))))
+        assertThat(set.compileClasspath, sameInstance(project.configurations.compile))
+        assertThat(set.classesDir, equalTo(new File(project.buildDir, 'classes/main')))
+        assertThat(set.classes, builtBy(JavaPlugin.CLASSES_TASK_NAME))
+        assertThat(set.runtimeClasspath.sourceCollections, hasItem(project.configurations.runtime))
+        assertThat(set.runtimeClasspath, hasItem(new File(project.buildDir, 'classes/main')))
+
+        set = project.sourceSets[SourceSet.TEST_SOURCE_SET_NAME]
+        assertThat(set.java.srcDirs, equalTo(toLinkedSet(project.file('src/test/java'))))
+        assertThat(set.resources.srcDirs, equalTo(toLinkedSet(project.file('src/test/resources'))))
+        assertThat(set.compileClasspath.sourceCollections, hasItem(project.configurations.testCompile))
+        assertThat(set.compileClasspath, hasItem(new File(project.buildDir, 'classes/main')))
+        assertThat(set.classesDir, equalTo(new File(project.buildDir, 'classes/test')))
+        assertThat(set.classes, builtBy(JavaPlugin.TEST_CLASSES_TASK_NAME))
+        assertThat(set.runtimeClasspath.sourceCollections, hasItem(project.configurations.testRuntime))
+        assertThat(set.runtimeClasspath, hasItem(new File(project.buildDir, 'classes/main')))
+        assertThat(set.runtimeClasspath, hasItem(new File(project.buildDir, 'classes/test')))
+    }
+
+    @Test public void createsMappingsForCustomSourceSets() {
+        javaPlugin.apply(project)
+
+        def set = project.sourceSets.add('custom')
+        assertThat(set.java.srcDirs, equalTo(toLinkedSet(project.file('src/custom/java'))))
+        assertThat(set.resources.srcDirs, equalTo(toLinkedSet(project.file('src/custom/resources'))))
+        assertThat(set.compileClasspath, sameInstance(project.configurations.compile))
+        assertThat(set.classesDir, equalTo(new File(project.buildDir, 'classes/custom')))
+        assertThat(set.classes, builtBy('customClasses'))
+        assertThat(set.runtimeClasspath.sourceCollections, hasItem(project.configurations.runtime))
+        assertThat(set.runtimeClasspath, hasItem(new File(project.buildDir, 'classes/custom')))
+    }
+    
+    @Test public void createsStandardTasksAndAppliesMappings() {
+        javaPlugin.apply(project)
+
+        def task = project.tasks[JavaPlugin.PROCESS_RESOURCES_TASK_NAME]
+        assertThat(task, instanceOf(Copy))
+        assertThat(task, dependsOn())
+        assertThat(task.defaultSource, equalTo(project.sourceSets.main.resources))
+        assertThat(task.destinationDir, equalTo(project.sourceSets.main.classesDir))
+
+        task = project.tasks[JavaPlugin.COMPILE_JAVA_TASK_NAME]
+        assertThat(task, instanceOf(Compile))
+        assertThat(task, dependsOn())
+        assertThat(task.defaultSource, equalTo(project.sourceSets.main.java))
+        assertThat(task.classpath, sameInstance(project.sourceSets.main.compileClasspath))
+        assertThat(task.destinationDir, equalTo(project.sourceSets.main.classesDir))
+
+        task = project.tasks[JavaPlugin.CLASSES_TASK_NAME]
+        assertThat(task, instanceOf(DefaultTask))
+        assertThat(task, dependsOn(JavaPlugin.PROCESS_RESOURCES_TASK_NAME, JavaPlugin.COMPILE_JAVA_TASK_NAME))
+
+        task = project.tasks[JavaPlugin.PROCESS_TEST_RESOURCES_TASK_NAME]
+        assertThat(task, instanceOf(Copy))
+        assertThat(task, dependsOn())
+        assertThat(task.defaultSource, equalTo(project.sourceSets.test.resources))
+        assertThat(task.destinationDir, equalTo(project.sourceSets.test.classesDir))
+
+        task = project.tasks[JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME]
+        assertThat(task, instanceOf(Compile))
+        assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
+        assertThat(task.defaultSource, equalTo(project.sourceSets.test.java))
+        assertThat(task.classpath, sameInstance(project.sourceSets.test.compileClasspath))
+        assertThat(task.destinationDir, equalTo(project.sourceSets.test.classesDir))
+
+        task = project.tasks[JavaPlugin.TEST_CLASSES_TASK_NAME]
+        assertThat(task, instanceOf(DefaultTask))
+        assertThat(task, dependsOn(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME, JavaPlugin.PROCESS_TEST_RESOURCES_TASK_NAME))
+
+        task = project.tasks[JavaPlugin.TEST_TASK_NAME]
+        assertThat(task, instanceOf(org.gradle.api.tasks.testing.Test))
+        assertThat(task, dependsOn(JavaPlugin.TEST_CLASSES_TASK_NAME, JavaPlugin.CLASSES_TASK_NAME))
+        assertThat(task.classpath, equalTo(project.sourceSets.test.runtimeClasspath))
+        assertThat(task.testClassesDir, equalTo(project.sourceSets.test.classesDir))
+        assertThat(task.workingDir, equalTo(project.projectDir))
+
+        task = project.tasks[JavaPlugin.JAR_TASK_NAME]
+        assertThat(task, instanceOf(Jar))
+        assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
+        assertThat(task.destinationDir, equalTo(project.libsDir))
+        assertThat(task.copyAction.mainSpec.sourcePaths, equalTo([project.sourceSets.main.classes] as Set))
+        assertThat(task.manifest, notNullValue())
+        assertThat(task.manifest, not(sameInstance(project.manifest)))
+        assertThat(task.manifest.mergeSpecs.size(), equalTo(1))
+        assertThat(task.manifest.mergeSpecs[0].mergePaths[0], sameInstance(project.manifest))
+
+        task = project.tasks[BasePlugin.ASSEMBLE_TASK_NAME]
+        assertThat(task, dependsOn(JavaPlugin.JAR_TASK_NAME))
+
+        task = project.tasks[JavaBasePlugin.CHECK_TASK_NAME]
+        assertThat(task, instanceOf(DefaultTask))
+        assertThat(task, dependsOn(JavaPlugin.TEST_TASK_NAME))
+
+        project.sourceSets.main.java.srcDirs(tmpDir.getDir())
+        tmpDir.file("SomeFile.java").touch()
+        task = project.tasks[JavaPlugin.JAVADOC_TASK_NAME]
+        assertThat(task, instanceOf(Javadoc))
+        assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
+        assertThat(task.source.files, equalTo(project.sourceSets.main.allJava.files))
+        assertThat(task.classpath.sourceCollections, hasItem(project.sourceSets.main.classes))
+        assertThat(task.classpath.sourceCollections, hasItem(project.sourceSets.main.compileClasspath))
+        assertThat(task.destinationDir, equalTo(project.file("$project.docsDir/javadoc")))
+        assertThat(task.title, equalTo(project.apiDocTitle))
+
+        task = project.tasks["buildArchives"]
+        assertThat(task, instanceOf(DefaultTask))
+        assertThat(task, dependsOn(JavaPlugin.JAR_TASK_NAME))
+
+        task = project.tasks[JavaBasePlugin.BUILD_TASK_NAME]
+        assertThat(task, instanceOf(DefaultTask))
+        assertThat(task, dependsOn(BasePlugin.ASSEMBLE_TASK_NAME, JavaBasePlugin.CHECK_TASK_NAME))
+
+        task = project.tasks[JavaBasePlugin.BUILD_NEEDED_TASK_NAME]
+        assertThat(task, instanceOf(DefaultTask))
+        assertThat(task, dependsOn(JavaBasePlugin.BUILD_TASK_NAME))
+
+        task = project.tasks[JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME]
+        assertThat(task, instanceOf(DefaultTask))
+        assertThat(task, dependsOn(JavaBasePlugin.BUILD_TASK_NAME))
+    }
+
+    @Test public void buildOtherProjects() {
+        DefaultProject commonProject = HelperUtil.createChildProject(project, "common");
+        DefaultProject middleProject = HelperUtil.createChildProject(project, "middle");
+        DefaultProject appProject = HelperUtil.createChildProject(project, "app");
+
+        javaPlugin.apply(project);
+        javaPlugin.apply(commonProject);
+        javaPlugin.apply(middleProject);
+        javaPlugin.apply(appProject);
+
+        appProject.dependencies {
+            compile project(path: middleProject.path, configuration: 'compile')
+        }
+        middleProject.dependencies {
+            compile project(path: commonProject.path, configuration: 'compile')
+        }
+
+        Task task = middleProject.tasks[JavaBasePlugin.BUILD_NEEDED_TASK_NAME];
+        assertThat(task.taskDependencies.getDependencies(task)*.path as Set, equalTo([':middle:build', ':common:build'] as Set))
+
+        task = middleProject.tasks[JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME];
+        assertThat(task.taskDependencies.getDependencies(task)*.path as Set, equalTo([':middle:build', ':app:build'] as Set))
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/ProjectReportsPluginTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/ProjectReportsPluginTest.java
new file mode 100644
index 0000000..c7ad6f0
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/ProjectReportsPluginTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.plugins;
+
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.tasks.diagnostics.DependencyReportTask;
+import org.gradle.api.tasks.diagnostics.PropertyReportTask;
+import org.gradle.api.tasks.diagnostics.TaskReportTask;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.WrapUtil;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.gradle.util.Matchers.dependsOn;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+public class ProjectReportsPluginTest {
+    private final Project project = HelperUtil.createRootProject();
+    private final ProjectReportsPlugin plugin = new ProjectReportsPlugin();
+
+    @Test
+    public void appliesBaseReportingPluginAndAddsConventionObject() {
+        plugin.apply(project);
+
+        assertTrue(project.getPlugins().hasPlugin(ReportingBasePlugin.class));
+        assertThat(project.getConvention().getPlugin(ProjectReportsPluginConvention.class), notNullValue());
+    }
+
+    @Test
+    public void addsTasksToProject() {
+        plugin.apply(project);
+
+        Task task = project.getTasks().getByName(ProjectReportsPlugin.TASK_REPORT);
+        assertThat(task, instanceOf(TaskReportTask.class));
+        assertThat(task.property("outputFile"), equalTo((Object) new File(project.getBuildDir(), "reports/project/tasks.txt")));
+        assertThat(task.property("projects"), equalTo((Object) WrapUtil.toSet(project)));
+
+        task = project.getTasks().getByName(ProjectReportsPlugin.PROPERTY_REPORT);
+        assertThat(task, instanceOf(PropertyReportTask.class));
+        assertThat(task.property("outputFile"), equalTo((Object) new File(project.getBuildDir(), "reports/project/properties.txt")));
+        assertThat(task.property("projects"), equalTo((Object) WrapUtil.toSet(project)));
+
+        task = project.getTasks().getByName(ProjectReportsPlugin.DEPENDENCY_REPORT);
+        assertThat(task, instanceOf(DependencyReportTask.class));
+        assertThat(task.property("outputFile"), equalTo((Object) new File(project.getBuildDir(), "reports/project/dependencies.txt")));
+        assertThat(task.property("projects"), equalTo((Object) WrapUtil.toSet(project)));
+        
+        task = project.getTasks().getByName(ProjectReportsPlugin.PROJECT_REPORT);
+        assertThat(task, dependsOn(ProjectReportsPlugin.TASK_REPORT, ProjectReportsPlugin.PROPERTY_REPORT, ProjectReportsPlugin.DEPENDENCY_REPORT));
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginConventionTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginConventionTest.java
new file mode 100644
index 0000000..30da5c8
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginConventionTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.plugins;
+
+import org.gradle.api.Project;
+import static org.hamcrest.Matchers.*;
+
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.project.ProjectInternal;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.Expectations;
+import static org.junit.Assert.*;
+import org.junit.Test;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+ at RunWith(JMock.class)
+public class ReportingBasePluginConventionTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private final ProjectInternal project = context.mock(ProjectInternal.class);
+    private final ReportingBasePluginConvention convention = new ReportingBasePluginConvention(project);
+    private final File buildDir = new File("build-dir");
+
+    @Before
+    public void setUp() {
+        context.checking(new Expectations() {{
+            allowing(project).getBuildDir();
+            will(returnValue(buildDir));
+        }});
+    }
+
+    @Test
+    public void defaultValues() {
+        assertThat(convention.getReportsDirName(), equalTo("reports"));
+    }
+
+    @Test
+    public void calculatesReportsDirFromReportsDirName() {
+        context.checking(new Expectations(){{
+            FileResolver fileResolver = context.mock(FileResolver.class);
+            FileResolver buildDirResolver = context.mock(FileResolver.class, "buildDir");
+            allowing(project).getFileResolver();
+            will(returnValue(fileResolver));
+            one(fileResolver).withBaseDir(buildDir);
+            will(returnValue(buildDirResolver));
+            one(buildDirResolver).resolve("new-reports");
+            will(returnValue(new File(buildDir, "new-reports")));
+        }});
+
+        convention.setReportsDirName("new-reports");
+        assertThat(convention.getReportsDir(), equalTo(new File(buildDir, "new-reports")));
+    }
+
+    @Test
+    public void calculatesApiDocTitleFromProjectNameAndVersion() {
+        context.checking(new Expectations(){{
+            allowing(project).getName();
+            will(returnValue("<name>"));
+            one(project).getVersion();
+            will(returnValue(Project.DEFAULT_VERSION));
+        }});
+        assertThat(convention.getApiDocTitle(), equalTo("<name> API"));
+
+        context.checking(new Expectations(){{
+            one(project).getVersion();
+            will(returnValue("<not-the-default>"));
+        }});
+        assertThat(convention.getApiDocTitle(), equalTo("<name> <not-the-default> API"));
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginTest.java
new file mode 100644
index 0000000..b5dc652
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/ReportingBasePluginTest.java
@@ -0,0 +1,32 @@
+/*
+ * 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.plugins;
+
+import org.gradle.api.Project;
+import org.gradle.util.HelperUtil;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+public class ReportingBasePluginTest {
+    @Test
+    public void addsTasksAndConventionToProject() {
+        Project project = HelperUtil.createRootProject();
+        new ReportingBasePlugin().apply(project);
+
+        assertThat(project.getConvention().getPlugins().get("reportingBase"), instanceOf(ReportingBasePluginConvention.class));
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/WarPluginTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/WarPluginTest.groovy
new file mode 100644
index 0000000..f9a4db1
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/plugins/WarPluginTest.groovy
@@ -0,0 +1,137 @@
+/*
+ * 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.plugins
+
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.artifacts.Dependency
+import org.gradle.api.internal.artifacts.configurations.Configurations
+import org.gradle.api.tasks.bundling.War
+import org.gradle.util.HelperUtil
+import org.junit.Before
+import org.junit.Test
+import static org.gradle.util.Matchers.*
+import static org.gradle.util.WrapUtil.*
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+class WarPluginTest {
+    private Project project // = HelperUtil.createRootProject()
+    private WarPlugin warPlugin// = new WarPlugin()
+
+    @Before
+    public void setUp() {
+        project = HelperUtil.createRootProject()
+        warPlugin = new WarPlugin()
+    }
+
+    @Test public void appliesJavaPluginAndAddsConvention() {
+        warPlugin.apply(project)
+
+        assertTrue(project.getPlugins().hasPlugin(JavaPlugin));
+        assertThat(project.convention.plugins.war, instanceOf(WarPluginConvention))
+    }
+    
+    @Test public void createsConfigurations() {
+        warPlugin.apply(project)
+
+        def configuration = project.configurations.getByName(JavaPlugin.COMPILE_CONFIGURATION_NAME)
+        assertThat(Configurations.getNames(configuration.extendsFrom), equalTo(toSet(WarPlugin.PROVIDED_COMPILE_CONFIGURATION_NAME)))
+        assertFalse(configuration.visible)
+        assertTrue(configuration.transitive)
+
+        configuration = project.configurations.getByName(JavaPlugin.RUNTIME_CONFIGURATION_NAME)
+        assertThat(Configurations.getNames(configuration.extendsFrom), equalTo(toSet(JavaPlugin.COMPILE_CONFIGURATION_NAME, WarPlugin.PROVIDED_RUNTIME_CONFIGURATION_NAME)))
+        assertFalse(configuration.visible)
+        assertTrue(configuration.transitive)
+
+        configuration = project.configurations.getByName(WarPlugin.PROVIDED_COMPILE_CONFIGURATION_NAME)
+        assertThat(Configurations.getNames(configuration.extendsFrom), equalTo(toSet()))
+        assertFalse(configuration.visible)
+        assertTrue(configuration.transitive)
+
+        configuration = project.configurations.getByName(WarPlugin.PROVIDED_RUNTIME_CONFIGURATION_NAME)
+        assertThat(Configurations.getNames(configuration.extendsFrom), equalTo(toSet(WarPlugin.PROVIDED_COMPILE_CONFIGURATION_NAME)))
+        assertFalse(configuration.visible)
+        assertTrue(configuration.transitive)
+    }
+
+    @Test public void addsTasks() {
+        warPlugin.apply(project)
+
+        def task = project.tasks[WarPlugin.WAR_TASK_NAME]
+        assertThat(task, instanceOf(War))
+        assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
+        assertThat(task.destinationDir, equalTo(project.libsDir))
+
+        task = project.tasks[BasePlugin.ASSEMBLE_TASK_NAME]
+        assertThat(task, dependsOn(JavaPlugin.JAR_TASK_NAME, WarPlugin.WAR_TASK_NAME))
+    }
+
+    @Test public void dependsOnRuntimeConfig() {
+        warPlugin.apply(project)
+
+        Project childProject = HelperUtil.createChildProject(project, 'child')
+        JavaPlugin javaPlugin = new JavaPlugin()
+        javaPlugin.apply(childProject)
+
+        project.dependencies {
+            runtime project(path: childProject.path, configuration: 'archives')
+        }
+
+        def task = project.tasks[WarPlugin.WAR_TASK_NAME]
+        assertThat(task.taskDependencies.getDependencies(task)*.path as Set, hasItem(':child:jar'))
+    }
+
+    @Test public void usesRuntimeClasspathExcludingProvidedAsClasspath() {
+        File compileJar = project.file('compile.jar')
+        File runtimeJar = project.file('runtime.jar')
+        File providedJar = project.file('provided.jar')
+
+        warPlugin.apply(project)
+
+        project.dependencies {
+            providedCompile project.files(providedJar)
+            compile project.files(compileJar)
+            runtime project.files(runtimeJar)
+        }
+
+        def task = project.tasks[WarPlugin.WAR_TASK_NAME]
+        assertThat(task.classpath.files as List, equalTo([project.sourceSets.main.classesDir, runtimeJar, compileJar]))
+    }
+
+    @Test public void appliesMappingsToArchiveTasks() {
+        warPlugin.apply(project)
+
+        def task = project.createTask('customWar', type: War)
+        assertThat(task, dependsOn(hasItems(JavaPlugin.CLASSES_TASK_NAME)))
+        assertThat(task.destinationDir, equalTo(project.libsDir))
+
+        assertThat(project.tasks[BasePlugin.ASSEMBLE_TASK_NAME], dependsOn(JavaPlugin.JAR_TASK_NAME, WarPlugin.WAR_TASK_NAME, 'customWar'))
+    }
+
+    @Test public void addsDefaultWarToArchiveConfiguration() {
+        warPlugin.apply(project)
+
+        Configuration archiveConfiguration = project.getConfigurations().getByName(Dependency.ARCHIVES_CONFIGURATION);
+        assertThat(archiveConfiguration.getAllArtifacts().size(), equalTo(1)); 
+        assertThat(archiveConfiguration.getAllArtifacts().iterator().next().getType(), equalTo("war")); 
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/bundling/JarTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/bundling/JarTest.groovy
new file mode 100644
index 0000000..e744868
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/bundling/JarTest.groovy
@@ -0,0 +1,62 @@
+/*
+ * 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.api.tasks.bundling
+
+import org.gradle.api.java.archives.internal.DefaultManifest
+import org.junit.Before
+import org.junit.Test
+import static org.junit.Assert.assertEquals
+import static org.junit.Assert.assertNotNull
+
+/**
+ * @author Hans Dockter
+ */
+class JarTest extends AbstractArchiveTaskTest {
+    Jar jar
+
+    @Before public void setUp()  {
+        super.setUp()
+        jar = createTask(Jar)
+        configure(jar)
+    }
+
+    AbstractArchiveTask getArchiveTask() {
+        jar
+    }
+
+    @Test public void testJar() {
+        assertEquals(Jar.DEFAULT_EXTENSION, jar.extension)
+        assertNotNull(jar.manifest)
+        assertNotNull(jar.metaInf)
+    }
+
+    @Test public void testManifest() {
+        jar.manifest = new DefaultManifest(null);
+        jar.manifest {
+            attributes(key: 'value')
+        }
+        assertEquals(jar.manifest.attributes.key, 'value')
+    }
+
+    @Test public void testManifestWithNullManifest() {
+        jar.manifest = null
+        jar.manifest {
+            attributes(key: 'value')
+        }
+        assertEquals(jar.manifest.attributes.key, 'value')
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/bundling/WarTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/bundling/WarTest.groovy
new file mode 100644
index 0000000..f36c810
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/bundling/WarTest.groovy
@@ -0,0 +1,45 @@
+/*
+ * 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.api.tasks.bundling
+
+import org.junit.Before
+import org.junit.Test
+import static org.junit.Assert.assertEquals
+
+/**
+ * @author Hans Dockter
+ */
+class WarTest extends AbstractArchiveTaskTest {
+
+    War war
+
+    Map filesFromDepencencyManager
+
+    @Before public void setUp() {
+        super.setUp()
+        war = createTask(War)
+        configure(war)
+    }
+
+    AbstractArchiveTask getArchiveTask() {
+        war
+    }
+
+    @Test public void testWar() {
+        assertEquals(War.WAR_EXTENSION, war.extension)
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/AbstractCompileTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/AbstractCompileTest.java
new file mode 100644
index 0000000..814ebde
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/AbstractCompileTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.compile;
+
+import org.gradle.api.internal.file.SimpleFileCollection;
+import org.gradle.api.tasks.AbstractConventionTaskTest;
+import org.gradle.util.WrapUtil;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.List;
+
+import static org.gradle.util.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+public abstract class AbstractCompileTest extends AbstractConventionTaskTest {
+    public static final String TEST_PATTERN_1 = "pattern1";
+    public static final String TEST_PATTERN_2 = "pattern2";
+    public static final String TEST_PATTERN_3 = "pattern3";
+
+    public static final List<File> TEST_DEPENDENCY_MANAGER_CLASSPATH = WrapUtil.toList(new File("jar1"));
+    public static final List<String> TEST_INCLUDES = WrapUtil.toList("incl/*");
+    public static final List<String> TEST_EXCLUDES = WrapUtil.toList("excl/*");
+
+    protected File srcDir;
+    protected File destDir;
+    protected File depCacheDir;
+
+    protected abstract AbstractCompile getCompile();
+
+    @Override
+    public void setUp() {
+        super.setUp();
+        destDir = getProject().file("destDir");
+        depCacheDir = getProject().file("depCache");
+        srcDir = getProject().file("src");
+        srcDir.mkdirs();
+    }
+
+    @Test public void testDefaults() {
+        AbstractCompile compile = getCompile();
+        assertNull(compile.getDestinationDir());
+        assertNull(compile.getSourceCompatibility());
+        assertNull(compile.getTargetCompatibility());
+        assertThat(compile.getSource(), isEmpty());
+    }
+
+    protected void setUpMocksAndAttributes(final AbstractCompile compile) {
+        compile.source(srcDir);
+        compile.setIncludes(TEST_INCLUDES);
+        compile.setExcludes(TEST_EXCLUDES);
+        compile.setSourceCompatibility("1.5");
+        compile.setTargetCompatibility("1.5");
+        compile.setDestinationDir(destDir);
+
+        compile.setClasspath(new SimpleFileCollection(TEST_DEPENDENCY_MANAGER_CLASSPATH));
+    }
+
+    @Test public void testIncludes() {
+        AbstractCompile compile = getCompile();
+
+        assertSame(compile.include(TEST_PATTERN_1, TEST_PATTERN_2), compile);
+        assertEquals(compile.getIncludes(), WrapUtil.toLinkedSet(TEST_PATTERN_1, TEST_PATTERN_2));
+
+        assertSame(compile.include(TEST_PATTERN_3), compile);
+        assertEquals(compile.getIncludes(), WrapUtil.toLinkedSet(TEST_PATTERN_1, TEST_PATTERN_2, TEST_PATTERN_3));
+    }
+
+    @Test public void testExcludes() {
+        AbstractCompile compile = getCompile();
+
+        assertSame(compile.exclude(TEST_PATTERN_1, TEST_PATTERN_2), compile);
+        assertEquals(compile.getExcludes(), WrapUtil.toLinkedSet(TEST_PATTERN_1, TEST_PATTERN_2));
+
+        assertSame(compile.exclude(TEST_PATTERN_3), compile);
+        assertEquals(compile.getExcludes(), WrapUtil.toLinkedSet(TEST_PATTERN_1, TEST_PATTERN_2, TEST_PATTERN_3));
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/AbstractOptionsTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/AbstractOptionsTest.groovy
new file mode 100644
index 0000000..9369600
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/AbstractOptionsTest.groovy
@@ -0,0 +1,50 @@
+/*
+ * 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.tasks.compile
+
+import org.junit.Test
+import static org.gradle.util.Matchers.*
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+public class AbstractOptionsTest {
+    private final TestOptions options = new TestOptions()
+
+    @Test
+    public void hasEmptyOptionsMapWhenEverythingIsNull() {
+        assertThat(options.optionMap(), isEmptyMap())
+    }
+
+    @Test
+    public void optionsMapIncludesNonNullValues() {
+        assertThat(options.optionMap(), isEmptyMap())
+
+        options.intProp = 9
+        Map expected = new LinkedHashMap();
+        expected.intProp = 9
+        assertThat(options.optionMap(), equalTo(expected))
+
+        options.stringProp = 'string'
+        expected.stringProp = 'string'
+        assertThat(options.optionMap(), equalTo(expected))
+    }
+}
+
+class TestOptions extends AbstractOptions {
+    Integer intProp
+    String stringProp
+    Object objectProp
+}
\ No newline at end of file
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/CompileOptionsTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/CompileOptionsTest.groovy
new file mode 100644
index 0000000..be829aa
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/CompileOptionsTest.groovy
@@ -0,0 +1,175 @@
+/*
+ * 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.api.tasks.compile
+
+import org.junit.Before
+import org.junit.Test
+import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.*
+import static org.gradle.util.Matchers.*
+
+/**
+ * @author Hans Dockter
+ */
+class CompileOptionsTest {
+    static final Map TEST_DEBUG_OPTION_MAP = [someDebugOption: 'someDebugOptionValue']
+    static final Map TEST_FORK_OPTION_MAP = [someForkOption: 'someForkOptionValue']
+    static final Map TEST_DEPEND_OPTION_MAP = [someDependOption: 'someDependOptionValue']
+
+    CompileOptions compileOptions
+
+    @Before public void setUp()  {
+        compileOptions = new CompileOptions()
+        compileOptions.debugOptions = [optionMap: {TEST_DEBUG_OPTION_MAP}] as DebugOptions
+        compileOptions.forkOptions = [optionMap: {TEST_FORK_OPTION_MAP}] as ForkOptions
+    }
+
+    @Test public void testCompileOptions() {
+        assertTrue(compileOptions.debug)
+        assertTrue(compileOptions.failOnError)
+        assertTrue(compileOptions.warnings)
+
+        assertFalse(compileOptions.includeJavaRuntime)
+        assertFalse(compileOptions.deprecation)
+        assertFalse(compileOptions.listFiles)
+        assertFalse(compileOptions.verbose)
+        assertFalse(compileOptions.fork)
+
+        assertThat(compileOptions.compilerArgs, isEmpty())
+        assertNull(compileOptions.encoding)
+        assertNull(compileOptions.compiler)
+        assertNull(compileOptions.bootClasspath)
+        assertNull(compileOptions.extensionDirs)
+
+        assertNotNull(compileOptions.forkOptions)
+        assertNotNull(compileOptions.debugOptions)
+    }
+
+    @Test public void testOptionMapForDebugAndForkOptions() {
+        Map optionMap = compileOptions.optionMap()
+        assertEquals(optionMap.subMap(TEST_DEBUG_OPTION_MAP.keySet()), TEST_DEBUG_OPTION_MAP)
+        assertEquals(optionMap.subMap(TEST_FORK_OPTION_MAP.keySet()), TEST_FORK_OPTION_MAP)
+    }
+
+    @Test public void testOptionMapWithNullables() {
+        Map optionMap = compileOptions.optionMap()
+        Map nullables = [
+                encoding: 'encoding',
+                compiler: 'compiler',
+                bootClasspath: 'bootclasspath',
+                extensionDirs: 'extdirs'
+        ]
+        nullables.each {String field, String antProperty ->
+            assertFalse(optionMap.keySet().contains(antProperty))
+        }
+
+        nullables.keySet().each {compileOptions."$it" = "${it}Value"}
+        optionMap = compileOptions.optionMap()
+        nullables.each {String field, String antProperty ->
+            assertEquals("${field}Value" as String, optionMap[antProperty])
+        }
+    }
+
+    @Test public void testOptionMapWithTrueFalseValues() {
+        Map booleans = [
+                failOnError: 'failonerror',
+                verbose: 'verbose',
+                listFiles: 'listfiles',
+                deprecation: 'deprecation',
+                warnings: 'nowarn',
+                debug: 'debug',
+                fork: 'fork',
+                includeJavaRuntime: 'includeJavaRuntime'
+        ]
+        booleans.keySet().each {compileOptions."$it" = true}
+        Map optionMap = compileOptions.optionMap()
+        booleans.values().each {
+            if (it.equals('nowarn')) {
+                assertEquals(false, optionMap[it])
+            } else {
+                assertEquals(true, optionMap[it])
+            }
+        }
+        booleans.keySet().each {compileOptions."$it" = false}
+        optionMap = compileOptions.optionMap()
+        booleans.values().each {
+            if (it.equals('nowarn')) {
+                assertEquals(true, optionMap[it])
+            } else {
+                assertEquals(false, optionMap[it])
+            }
+        }
+    }
+
+    @Test public void testWithExcludeFieldsFromOptionMap() {
+      compileOptions.compilerArgs = [[value: 'something']]
+        Map optionMap = compileOptions.optionMap()
+        ['debugOptions', 'forkOptions', 'compilerArgs'].each {
+            assertFalse(optionMap.containsKey(it))
+        }
+    }
+
+    @Test public void testFork() {
+        compileOptions.fork = false
+        boolean forkUseCalled = false
+        compileOptions.forkOptions = [define: {Map args ->
+            forkUseCalled = true
+            assertEquals(TEST_FORK_OPTION_MAP, args)
+        }] as ForkOptions
+        assert compileOptions.fork(TEST_FORK_OPTION_MAP).is(compileOptions)
+        assertTrue(compileOptions.fork)
+        assertTrue(forkUseCalled)
+    }
+
+    @Test public void testDebug() {
+        compileOptions.debug = false
+        boolean debugUseCalled = false
+        compileOptions.debugOptions = [define: {Map args ->
+            debugUseCalled = true
+            assertEquals(TEST_DEBUG_OPTION_MAP, args)
+        }] as DebugOptions
+        assert compileOptions.debug(TEST_DEBUG_OPTION_MAP).is(compileOptions)
+        assertTrue(compileOptions.debug)
+        assertTrue(debugUseCalled)
+    }
+
+    @Test public void testDepend() {
+        compileOptions.useDepend = false
+        boolean dependUseCalled = false
+        compileOptions.dependOptions = [define: {Map args ->
+            dependUseCalled = true
+            assertEquals(TEST_DEPEND_OPTION_MAP, args)
+        }] as DependOptions
+        assert compileOptions.depend(TEST_DEPEND_OPTION_MAP).is(compileOptions)
+        assertTrue(compileOptions.useDepend)
+        assertTrue(dependUseCalled)
+    }
+
+    @Test public void testDefine() {
+        compileOptions.debug = false
+        compileOptions.compiler = null
+        compileOptions.bootClasspath = 'xxxx'
+        compileOptions.fork = false
+        compileOptions.useDepend = false
+        compileOptions.define(debug: true, compiler: 'compiler', bootClasspath: null)
+        assertTrue(compileOptions.debug)
+        assertEquals('compiler', compileOptions.compiler)
+        assertNull(compileOptions.bootClasspath)
+        assertFalse(compileOptions.fork)
+        assertFalse(compileOptions.useDepend)
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/CompileTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/CompileTest.java
new file mode 100644
index 0000000..198ea5b
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/CompileTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.api.tasks.compile;
+
+import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.internal.tasks.compile.JavaCompiler;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.util.GFileUtils;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+import static org.gradle.util.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(org.jmock.integration.junit4.JMock.class)
+public class CompileTest extends AbstractCompileTest {
+    private Compile compile;
+
+    private JavaCompiler compilerMock;
+
+    private Mockery context = new Mockery();
+
+    @Before public void setUp()  {
+        super.setUp();
+        compile = createTask(Compile.class);
+        compilerMock = context.mock(JavaCompiler.class);
+        compile.setJavaCompiler(compilerMock);
+
+        GFileUtils.touch(new File(srcDir, "incl/file.java"));
+    }
+           
+    public ConventionTask getTask() {
+        return compile;
+    }
+
+    public void testExecute(final int numFilesCompiled) {
+        setUpMocksAndAttributes(compile);
+        context.checking(new Expectations() {{
+            WorkResult result = context.mock(WorkResult.class);
+
+            one(compilerMock).setSource(with(hasSameItems(compile.getSource())));
+            one(compilerMock).setClasspath(compile.getClasspath());
+            one(compilerMock).setDestinationDir(compile.getDestinationDir());
+            one(compilerMock).setDependencyCacheDir(compile.getDependencyCacheDir());
+            one(compilerMock).setSourceCompatibility(compile.getSourceCompatibility());
+            one(compilerMock).setTargetCompatibility(compile.getTargetCompatibility());
+            one(compilerMock).execute();
+            will(returnValue(result));
+            allowing(result).getDidWork();
+            will(returnValue(numFilesCompiled > 0));
+        }});
+        compile.compile();
+    }
+
+    @Test
+    public void testExecuteDoingWork() {
+        testExecute(7);
+        assertTrue(compile.getDidWork());
+    }
+
+    @Test
+    public void testExecuteNotDoingWork() {
+        testExecute(0);
+        assertFalse(compile.getDidWork());
+    }
+
+    // todo We need to do this to make the compiler happy. We need to file a Jira to Groovy.
+    public Compile getCompile() {
+        return compile;
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/DebugOptionsTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/DebugOptionsTest.groovy
new file mode 100644
index 0000000..a90aee6
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/DebugOptionsTest.groovy
@@ -0,0 +1,56 @@
+/*
+ * 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.api.tasks.compile
+
+import static org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test;
+
+/**
+ * @author Hans Dockter
+ */
+class DebugOptionsTest {
+    static final String TEST_DEBUG_LEVEL = 'testDebugLevel'
+    static final String DEBUG_LEVEL_PROPERTY_NAME = 'debugLevel'
+    static final String DEBUG_LEVEL_ANT_PROPERTY_NAME = 'debuglevel'
+
+    DebugOptions debugOptions
+
+    @Before public void setUp()  {
+        debugOptions = new DebugOptions()
+    }
+
+    @Test public void testDebugOptions() {
+        assertNull(debugOptions.debugLevel)
+    }
+
+    @Test public void testOptionMap() {
+        Map optionMap = debugOptions.optionMap()
+        assertEquals(0, optionMap.size())
+
+        debugOptions.debugLevel = TEST_DEBUG_LEVEL
+        optionMap = debugOptions.optionMap()
+        assertEquals(1, optionMap.size())
+        assertEquals(optionMap[DEBUG_LEVEL_ANT_PROPERTY_NAME], TEST_DEBUG_LEVEL)
+    }
+
+    @Test public void testDefine() {
+        debugOptions.debugLevel = null
+        debugOptions.define((DEBUG_LEVEL_PROPERTY_NAME): TEST_DEBUG_LEVEL)
+        assertEquals(TEST_DEBUG_LEVEL, debugOptions.debugLevel)
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/ForkOptionsTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/ForkOptionsTest.groovy
new file mode 100644
index 0000000..61c086f
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/ForkOptionsTest.groovy
@@ -0,0 +1,58 @@
+/*
+ * 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.api.tasks.compile
+
+import static org.junit.Assert.*
+import org.junit.Before
+import org.junit.Test;
+
+/**
+ * @author Hans Dockter
+ */
+class ForkOptionsTest {
+    static final Map PROPS = [executable: 'executable', memoryInitialSize: 'memoryInitialSize', memoryMaximumSize: 'memoryMaximumSize', tempDir: 'tempdir']
+    
+    ForkOptions forkOptions
+
+    @Before public void setUp()  {
+        forkOptions = new ForkOptions()
+    }
+
+    @Test public void testCompileOptions() {
+        assertNull(forkOptions.executable)
+        assertNull(forkOptions.memoryInitialSize)
+        assertNull(forkOptions.memoryMaximumSize)
+        assertNull(forkOptions.tempDir)
+    }
+
+    @Test public void testOptionMap() {
+        Map optionMap = forkOptions.optionMap()
+        assertEquals(0, optionMap.size())
+        PROPS.keySet().each { forkOptions."$it" = "${it}Value" }
+        optionMap = forkOptions.optionMap()
+        assertEquals(4, optionMap.size())
+        PROPS.keySet().each {assertEquals("${it}Value" as String, optionMap[PROPS[it]])}
+    }
+
+    @Test public void testDefine() {
+        forkOptions.define(PROPS.keySet().inject([:]) { Map map, String prop ->
+            map[prop] = "${prop}Value"
+            map
+        })
+        PROPS.keySet().each {assertEquals("${it}Value" as String, forkOptions."${it}")}
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileOptionsTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileOptionsTest.groovy
new file mode 100644
index 0000000..cc32a27
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileOptionsTest.groovy
@@ -0,0 +1,118 @@
+/*
+ * 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.tasks.compile
+
+import org.junit.Test
+import org.junit.Before
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+class GroovyCompileOptionsTest {
+    static final Map TEST_FORK_OPTION_MAP = [someForkOption: 'someForkOptionValue']
+
+    GroovyCompileOptions compileOptions
+
+    @Before public void setUp()  {
+        compileOptions = new GroovyCompileOptions()
+        compileOptions.forkOptions = [optionMap: {TEST_FORK_OPTION_MAP}] as GroovyForkOptions
+    }
+
+    @Test public void testCompileOptions() {
+        assertTrue(compileOptions.failOnError)
+        assertFalse(compileOptions.includeJavaRuntime)
+        assertFalse(compileOptions.stacktrace)
+        assertFalse(compileOptions.listFiles)
+        assertFalse(compileOptions.verbose)
+        assertTrue(compileOptions.fork)
+        assertNull(compileOptions.encoding)
+        assertNotNull(compileOptions.forkOptions)
+    }
+
+    @Test public void testOptionMapForForkOptions() {
+        Map optionMap = compileOptions.optionMap()
+        assertEquals(optionMap.subMap(TEST_FORK_OPTION_MAP.keySet()), TEST_FORK_OPTION_MAP)
+    }
+
+    @Test public void testOptionMapWithNullables() {
+        Map optionMap = compileOptions.optionMap()
+        Map nullables = [
+                encoding: 'encoding'
+        ]
+        nullables.each {String field, String antProperty ->
+            assertFalse(optionMap.keySet().contains(antProperty))
+        }
+
+        nullables.keySet().each {compileOptions."$it" = "${it}Value"}
+        optionMap = compileOptions.optionMap()
+        nullables.each {String field, String antProperty ->
+            assertEquals("${field}Value" as String, optionMap[antProperty])
+        }
+    }
+
+    @Test public void testOptionMapWithTrueFalseValues() {
+        Map booleans = [
+                failOnError: 'failonerror',
+                verbose: 'verbose',
+                listFiles: 'listfiles',
+                fork: 'fork',
+                includeJavaRuntime: 'includeJavaRuntime'
+        ]
+        booleans.keySet().each {compileOptions."$it" = true}
+        Map optionMap = compileOptions.optionMap()
+        booleans.values().each {
+            if (it.equals('nowarn')) {
+                assertEquals(false, optionMap[it])
+            } else {
+                assertEquals(true, optionMap[it])
+            }
+        }
+        booleans.keySet().each {compileOptions."$it" = false}
+        optionMap = compileOptions.optionMap()
+        booleans.values().each {
+            if (it.equals('nowarn')) {
+                assertEquals(true, optionMap[it])
+            } else {
+                assertEquals(false, optionMap[it])
+            }
+        }
+    }
+
+    @Test public void testFork() {
+        compileOptions.fork = false
+        boolean forkUseCalled = false
+        compileOptions.forkOptions = [define: {Map args ->
+            forkUseCalled = true
+            assertEquals(TEST_FORK_OPTION_MAP, args)
+        }] as GroovyForkOptions
+        assert compileOptions.fork(TEST_FORK_OPTION_MAP).is(compileOptions)
+        assertTrue(compileOptions.fork)
+        assertTrue(forkUseCalled)
+    }
+
+    @Test public void testDefine() {
+        compileOptions.stacktrace = false
+        compileOptions.verbose = false
+        compileOptions.encoding = 'xxxx'
+        compileOptions.fork = false
+        compileOptions.define(stacktrace: true, encoding: 'encoding')
+        assertTrue(compileOptions.stacktrace)
+        assertEquals('encoding', compileOptions.encoding)
+        assertFalse(compileOptions.verbose)
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileTest.java
new file mode 100644
index 0000000..5ce614c
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyCompileTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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.tasks.compile;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.internal.tasks.compile.GroovyJavaJointCompiler;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.util.GFileUtils;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+import static org.gradle.util.Matchers.*;
+import static org.gradle.util.WrapUtil.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(org.jmock.integration.junit4.JMock.class)
+public class GroovyCompileTest extends AbstractCompileTest {
+    static final List TEST_GROOVY_CLASSPATH = toList(new File("groovy.jar"));
+
+    private GroovyCompile testObj;
+
+    GroovyJavaJointCompiler groovyCompilerMock;
+
+    JUnit4Mockery context = new JUnit4Mockery();
+
+    public AbstractCompile getCompile() {
+        return testObj;
+    }
+
+    @Before
+    public void setUp() {
+        super.setUp();
+        testObj = createTask(GroovyCompile.class);
+        groovyCompilerMock = context.mock(GroovyJavaJointCompiler.class);
+        testObj.setCompiler(groovyCompilerMock);
+
+        GFileUtils.touch(new File(srcDir, "incl/file.groovy"));
+    }
+
+    public ConventionTask getTask() {
+        return testObj;
+    }
+
+    public void testExecute(final int numFilesCompiled) {
+        setUpMocksAndAttributes(testObj, TEST_GROOVY_CLASSPATH);
+        context.checking(new Expectations(){{
+            WorkResult result = context.mock(WorkResult.class);
+
+            one(groovyCompilerMock).setSource(with(hasSameItems(testObj.getSource())));
+            one(groovyCompilerMock).setDestinationDir(testObj.getDestinationDir());
+            one(groovyCompilerMock).setClasspath(testObj.getClasspath());
+            one(groovyCompilerMock).setSourceCompatibility(testObj.getSourceCompatibility());
+            one(groovyCompilerMock).setTargetCompatibility(testObj.getTargetCompatibility());
+            one(groovyCompilerMock).setGroovyClasspath(TEST_GROOVY_CLASSPATH);
+            one(groovyCompilerMock).execute();
+            will(returnValue(result));
+            allowing(result).getDidWork();
+            will(returnValue(numFilesCompiled > 0));
+        }});
+
+        testObj.compile();
+    }
+
+    @Test
+    public void testExecuteDoingWork() {
+        testExecute(7);
+        assertTrue(testObj.getDidWork());
+    }
+
+    @Test
+    public void testExecuteNotDoingWork() {
+        testExecute(0);
+        assertFalse(testObj.getDidWork());
+    }
+
+    @Test
+    public void testExecuteWithEmptyGroovyClasspath() {
+        setUpMocksAndAttributes(testObj, Collections.emptyList());
+        try {
+            testObj.compile();
+        } catch (InvalidUserDataException e) {
+            return;
+        }
+        Assert.fail();
+    }
+
+    void setUpMocksAndAttributes(GroovyCompile compile, final List groovyClasspath) {
+        super.setUpMocksAndAttributes(compile);
+
+        final FileCollection groovyClasspathCollection = context.mock(FileCollection.class);
+        context.checking(new Expectations(){{
+            allowing(groovyClasspathCollection).getFiles();
+            will(returnValue(new LinkedHashSet<File>(groovyClasspath)));
+        }});
+
+        compile.setGroovyClasspath(groovyClasspathCollection);
+        compile.source(srcDir);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyForkOptionsTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyForkOptionsTest.groovy
new file mode 100644
index 0000000..fb33392
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/compile/GroovyForkOptionsTest.groovy
@@ -0,0 +1,55 @@
+/*
+ * 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.tasks.compile
+
+import org.junit.Test
+import org.junit.Before;
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+public class GroovyForkOptionsTest {
+    static final Map PROPS = [memoryInitialSize: 'memoryInitialSize', memoryMaximumSize: 'memoryMaximumSize']
+
+    GroovyForkOptions forkOptions
+
+    @Before public void setUp()  {
+        forkOptions = new GroovyForkOptions()
+    }
+
+    @Test public void testCompileOptions() {
+        assertNull(forkOptions.memoryInitialSize)
+        assertNull(forkOptions.memoryMaximumSize)
+    }
+
+    @Test public void testOptionMap() {
+        Map optionMap = forkOptions.optionMap()
+        assertEquals(0, optionMap.size())
+        PROPS.keySet().each { forkOptions."$it" = "${it}Value" }
+        optionMap = forkOptions.optionMap()
+        assertEquals(2, optionMap.size())
+        PROPS.keySet().each {assertEquals("${it}Value" as String, optionMap[PROPS[it]])}
+    }
+
+    @Test public void testDefine() {
+        forkOptions.define(PROPS.keySet().inject([:]) { Map map, String prop ->
+            map[prop] = "${prop}Value"
+            map
+        })
+        PROPS.keySet().each {assertEquals("${it}Value" as String, forkOptions."${it}")}
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/javadoc/GroovydocTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/javadoc/GroovydocTest.java
new file mode 100644
index 0000000..c1b1e1e
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/javadoc/GroovydocTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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.javadoc;
+
+import org.gradle.api.InvalidUserDataException;
+import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.tasks.AbstractConventionTaskTest;
+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.util.Set;
+
+/**
+ * @author Hans Dockter
+ */
+public class GroovydocTest extends AbstractConventionTaskTest {
+    private Groovydoc groovydoc;
+
+    @Before
+    public void setUp() {
+        super.setUp();
+        groovydoc = createTask(Groovydoc.class);
+    }
+
+    public ConventionTask getTask() {
+        return groovydoc;
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void addLinkWithNullUrl() {
+        groovydoc.link(null, "package");
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void addLinkWithEmptyPackageList() {
+        groovydoc.link("http://www.abc.de");
+    }
+
+    @Test(expected = InvalidUserDataException.class)
+    public void addLinkWithNullPackage() {
+        groovydoc.link("http://www.abc.de", "package", null);
+    }
+
+    @Test
+    public void addLink() {
+        String url1 = "http://www.url1.de";
+        String url2 = "http://www.url2.de";
+        String package1 = "package1";
+        String package2 = "package2";
+        String package3 = "package3";
+
+        groovydoc.link(url1, package1, package2);
+        groovydoc.link(url2, package3);
+
+        assertThat(groovydoc.getLinks(), equalTo(WrapUtil.toSet(
+                new Groovydoc.Link(url1, package1, package2),
+                new Groovydoc.Link(url2, package3))));
+    }
+
+    @Test
+    public void setLinks() {
+        String url1 = "http://www.url1.de";
+        String url2 = "http://www.url2.de";
+        String package1 = "package1";
+        String package2 = "package2";
+
+        groovydoc.link(url1, package1);
+        Set<Groovydoc.Link> newLinkSet = WrapUtil.toSet(new Groovydoc.Link(url2, package2));
+        groovydoc.setLinks(newLinkSet);
+
+        assertThat(groovydoc.getLinks(), equalTo(newLinkSet));
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/javadoc/JavadocTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/javadoc/JavadocTest.java
new file mode 100644
index 0000000..8274065
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/javadoc/JavadocTest.java
@@ -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.tasks.javadoc;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.internal.file.SimpleFileCollection;
+import org.gradle.api.tasks.AbstractConventionTaskTest;
+import org.gradle.external.javadoc.JavadocExecHandleBuilder;
+import org.gradle.external.javadoc.StandardJavadocDocletOptions;
+import org.gradle.process.internal.ExecAction;
+import org.gradle.process.internal.ExecException;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.TestFile;
+import org.gradle.util.WrapUtil;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.Set;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(org.jmock.integration.junit4.JMock.class)
+public class JavadocTest extends AbstractConventionTaskTest {
+    private final JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+    private final TestFile testDir = tmpDir.getDir();
+    private final File destDir = new File(testDir, "dest");
+    private final File srcDir = new File(testDir, "srcdir");
+    private final Set<File> classpath = WrapUtil.toSet(new File("classpath"));
+    private JavadocExecHandleBuilder javadocExecHandleBuilderMock = context.mock(JavadocExecHandleBuilder.class);
+    private ExecAction execActionMock = context.mock(ExecAction.class);
+    private Javadoc task;
+    private FileCollection configurationMock = new SimpleFileCollection(classpath);
+    private String executable = "somepath";
+
+    @Before
+    public void setUp() {
+        super.setUp();
+        task = createTask(Javadoc.class);
+        task.setClasspath(configurationMock);
+        task.setExecutable(executable);
+        task.setJavadocExecHandleBuilder(javadocExecHandleBuilderMock);
+        GFileUtils.touch(new File(srcDir, "file.java"));
+    }
+
+    public ConventionTask getTask() {
+        return task;
+    }
+
+    private void expectJavadocExecHandle() {
+        context.checking(new Expectations(){{
+            one(javadocExecHandleBuilderMock).execDirectory(getProject().getRootDir());
+            will(returnValue(javadocExecHandleBuilderMock));
+            one(javadocExecHandleBuilderMock).options(task.getOptions());
+            will(returnValue(javadocExecHandleBuilderMock));
+            one(javadocExecHandleBuilderMock).optionsFile(new File(getProject().getBuildDir(), "tmp/taskname/javadoc.options"));
+            will(returnValue(javadocExecHandleBuilderMock));
+            one(javadocExecHandleBuilderMock).getExecHandle();
+            will(returnValue(execActionMock));
+            one(javadocExecHandleBuilderMock).setExecutable(executable);
+        }});
+    }
+
+    @Test
+    public void defaultExecution() {
+        task.setDestinationDir(destDir);
+        task.source(srcDir);
+
+        expectJavadocExecHandle();
+        expectJavadocExec();
+
+        task.execute();
+    }
+
+    private void expectJavadocExec() {
+        context.checking(new Expectations(){{
+            one(execActionMock).execute();
+        }});
+    }
+
+    @Test
+    public void wrapsExecutionFailure() {
+        final ExecException failure = new ExecException(null);
+
+        task.setDestinationDir(destDir);
+        task.source(srcDir);
+
+        expectJavadocExecHandle();
+        context.checking(new Expectations(){{
+            one(execActionMock).execute();
+            will(throwException(failure));
+        }});
+
+        try {
+            task.generate();
+            fail();
+        } catch (GradleException e) {
+            assertThat(e.getMessage(), endsWith("Javadoc generation failed."));
+            assertThat(e.getCause(), sameInstance((Throwable) failure));
+        }
+    }
+
+    @Test
+    public void executionWithOptionalAtributes() {
+        task.setDestinationDir(destDir);
+        task.source(srcDir);
+        task.setMaxMemory("max-memory");
+        task.setVerbose(true);
+
+        expectJavadocExecHandle();
+        expectJavadocExec();
+
+        task.execute();
+    }
+
+    @Test
+    public void setsTheWindowAndDocTitleIfNotSet() {
+        task.setDestinationDir(destDir);
+        task.source(srcDir);
+        task.setTitle("title");
+
+        expectJavadocExecHandle();
+        expectJavadocExec();
+
+        task.execute();
+        StandardJavadocDocletOptions options = (StandardJavadocDocletOptions) task.getOptions();
+        assertThat(options.getDocTitle(), equalTo("title"));
+        assertThat(options.getWindowTitle(), equalTo("title"));
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/testing/AbstractTestFrameworkOptionsTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/testing/AbstractTestFrameworkOptionsTest.java
new file mode 100644
index 0000000..b33f432
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/testing/AbstractTestFrameworkOptionsTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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.tasks.testing;
+
+import org.gradle.api.internal.tasks.testing.TestFramework;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class AbstractTestFrameworkOptionsTest<T extends TestFramework> {
+    protected JUnit4GroovyMockery context = new JUnit4GroovyMockery();
+
+    protected T testFrameworkMock;
+
+    protected void setUp(Class<T> testFrameworkClass) throws Exception
+    {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+
+        testFrameworkMock = context.mock(testFrameworkClass);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTest.java
new file mode 100644
index 0000000..0df765d
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/testing/TestTest.java
@@ -0,0 +1,305 @@
+/*
+ * 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.testing;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.file.ConfigurableFileTree;
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.internal.file.SimpleFileCollection;
+import org.gradle.api.internal.tasks.testing.TestFramework;
+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.results.TestListenerAdapter;
+import org.gradle.api.tasks.AbstractConventionTaskTest;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.HelperUtil;
+import org.gradle.util.TestClosure;
+import org.hamcrest.Description;
+import org.jmock.Expectations;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.Collections;
+
+import static org.gradle.util.Matchers.*;
+import static org.gradle.util.WrapUtil.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(JMock.class)
+public class TestTest extends AbstractConventionTaskTest {
+    static final String TEST_PATTERN_1 = "pattern1";
+    static final String TEST_PATTERN_2 = "pattern2";
+    static final String TEST_PATTERN_3 = "pattern3";
+
+    private File classesDir;
+    private File resultsDir;
+    private File reportDir;
+
+    private JUnit4Mockery context = new JUnit4Mockery() {{
+        setImposteriser(ClassImposteriser.INSTANCE);
+    }};
+
+    TestFramework testFrameworkMock = context.mock(TestFramework.class);
+    TestExecuter testExecuterMock = context.mock(TestExecuter.class);
+    private FileCollection classpathMock = new SimpleFileCollection(new File("classpath"));
+    private Test test;
+
+    @Before
+    public void setUp() {
+        super.setUp();
+
+        File rootDir = getProject().getProjectDir();
+        classesDir = new File(rootDir, "testClassesDir");
+        File classfile = new File(classesDir, "FileTest.class");
+        GFileUtils.touch(classfile);
+        resultsDir = new File(rootDir, "resultDir");
+        reportDir = new File(rootDir, "report/tests");
+
+        test = createTask(Test.class);
+    }
+
+    public ConventionTask getTask() {
+        return test;
+    }
+
+    @org.junit.Test
+    public void testInit() {
+        assertThat(test.getTestFramework(), instanceOf(JUnitTestFramework.class));
+        assertNull(test.getTestClassesDir());
+        assertNull(test.getClasspath());
+        assertNull(test.getTestResultsDir());
+        assertNull(test.getTestReportDir());
+        assertThat(test.getIncludes(), isEmpty());
+        assertThat(test.getExcludes(), isEmpty());
+        assertFalse(test.isIgnoreFailures());
+    }
+
+    @org.junit.Test
+    public void testExecute() {
+        configureTask();
+        expectTestsExecuted();
+
+        test.executeTests();
+    }
+
+    @org.junit.Test
+    public void testExecuteWithTestFailuresAndStopAtFailures() {
+        configureTask();
+        expectTestsFail();
+        try {
+            test.executeTests();
+            fail();
+        } catch (GradleException e) {
+            assertThat(e.getMessage(), startsWith("There were failing tests. See the report at"));
+        }
+    }
+
+    @org.junit.Test
+    public void testExecuteWithTestFailuresAndIgnoreFailures() {
+        configureTask();
+        test.setIgnoreFailures(true);
+        expectTestsFail();
+        test.executeTests();
+    }
+
+    @org.junit.Test
+    public void testScansForTestClassesInTheTestClassesDir() {
+        configureTask();
+        test.include("include");
+        test.exclude("exclude");
+
+        FileTree classFiles = test.getCandidateClassFiles();
+        assertThat(classFiles, instanceOf(ConfigurableFileTree.class));
+        ConfigurableFileTree files = (ConfigurableFileTree) classFiles;
+        assertThat(files.getDir(), equalTo(classesDir));
+        assertThat(files.getIncludes(), equalTo(toSet("include")));
+        assertThat(files.getExcludes(), equalTo(toSet("exclude")));
+    }
+
+    @org.junit.Test
+    public void testAddsDefaultIncludeAndExcludePatternsWhenTestScanningIsOff() {
+        configureTask();
+        test.setScanForTestClasses(false);
+
+        ConfigurableFileTree files = (ConfigurableFileTree) test.getCandidateClassFiles();
+        assertThat(files.getDir(), equalTo(classesDir));
+        assertThat(files.getIncludes(), equalTo(toSet("**/*Tests.class", "**/*Test.class")));
+        assertThat(files.getExcludes(), equalTo(toSet("**/Abstract*.class")));
+    }
+
+    @org.junit.Test
+    public void notifiesListenerOfEvents() {
+        final TestListener listener = context.mock(TestListener.class);
+        test.addTestListener(listener);
+
+        final TestDescriptor testDescriptor = context.mock(TestDescriptor.class);
+
+        context.checking(new Expectations() {{
+            one(listener).beforeSuite(testDescriptor);
+        }});
+
+        test.getTestListenerBroadcaster().getSource().beforeSuite(testDescriptor);
+    }
+
+    @org.junit.Test
+    public void notifiesListenerBeforeSuite() {
+        final TestClosure closure = context.mock(TestClosure.class);
+        test.beforeSuite(HelperUtil.toClosure(closure));
+
+        final TestDescriptor testDescriptor = context.mock(TestDescriptor.class);
+
+        context.checking(new Expectations() {{
+            one(closure).call(testDescriptor);
+        }});
+
+        test.getTestListenerBroadcaster().getSource().beforeSuite(testDescriptor);
+    }
+
+    @org.junit.Test
+    public void notifiesListenerAfterSuite() {
+        final TestClosure closure = context.mock(TestClosure.class);
+        test.afterSuite(HelperUtil.toClosure(closure));
+
+        final TestDescriptor testDescriptor = context.mock(TestDescriptor.class);
+        final TestResult result = context.mock(TestResult.class);
+
+        context.checking(new Expectations() {{
+            one(closure).call(testDescriptor);
+        }});
+
+        test.getTestListenerBroadcaster().getSource().afterSuite(testDescriptor, result);
+    }
+
+    @org.junit.Test
+    public void notifiesListenerBeforeTest() {
+        final TestClosure closure = context.mock(TestClosure.class);
+        test.beforeTest(HelperUtil.toClosure(closure));
+
+        final TestDescriptor testDescriptor = context.mock(TestDescriptor.class);
+
+        context.checking(new Expectations() {{
+            one(closure).call(testDescriptor);
+        }});
+
+        test.getTestListenerBroadcaster().getSource().beforeTest(testDescriptor);
+    }
+
+    @org.junit.Test
+    public void notifiesListenerAfterTest() {
+        final TestClosure closure = context.mock(TestClosure.class);
+        test.afterTest(HelperUtil.toClosure(closure));
+
+        final TestDescriptor testDescriptor = context.mock(TestDescriptor.class);
+        final TestResult result = context.mock(TestResult.class);
+
+        context.checking(new Expectations() {{
+            one(closure).call(testDescriptor);
+        }});
+
+        test.getTestListenerBroadcaster().getSource().afterTest(testDescriptor, result);
+    }
+
+    @org.junit.Test
+    public void testIncludes() {
+        assertSame(test, test.include(TEST_PATTERN_1, TEST_PATTERN_2));
+        assertEquals(toLinkedSet(TEST_PATTERN_1, TEST_PATTERN_2), test.getIncludes());
+        test.include(TEST_PATTERN_3);
+        assertEquals(toLinkedSet(TEST_PATTERN_1, TEST_PATTERN_2, TEST_PATTERN_3), test.getIncludes());
+    }
+
+    @org.junit.Test
+    public void testExcludes() {
+        assertSame(test, test.exclude(TEST_PATTERN_1, TEST_PATTERN_2));
+        assertEquals(toLinkedSet(TEST_PATTERN_1, TEST_PATTERN_2), test.getExcludes());
+        test.exclude(TEST_PATTERN_3);
+        assertEquals(toLinkedSet(TEST_PATTERN_1, TEST_PATTERN_2, TEST_PATTERN_3), test.getExcludes());
+    }
+
+    private void expectOptionsBuilt() {
+        context.checking(new Expectations() {{
+            TestFrameworkOptions testOptions = context.mock(TestFrameworkOptions.class);
+            allowing(testFrameworkMock).getOptions();
+            will(returnValue(testOptions));
+        }});
+    }
+
+    private void expectTestsExecuted() {
+        expectOptionsBuilt();
+        context.checking(new Expectations() {{
+            one(testExecuterMock).execute(with(sameInstance(test)), with(notNullValue(TestListenerAdapter.class)));
+            one(testFrameworkMock).report();
+        }});
+    }
+
+    private void expectTestsFail() {
+        expectOptionsBuilt();
+
+        context.checking(new Expectations() {{
+            final TestResult result = context.mock(TestResult.class);
+            allowing(result).getResultType();
+            will(returnValue(TestResult.ResultType.FAILURE));
+            ignoring(result);
+
+            final TestDescriptor testDescriptor = context.mock(TestDescriptor.class);
+            allowing(testDescriptor).getName();
+            will(returnValue("test"));
+            allowing(testDescriptor).getParent();
+            will(returnValue(null));
+
+            ignoring(testDescriptor);
+
+            one(testExecuterMock).execute(with(sameInstance(test)), with(notNullValue(TestListenerAdapter.class)));
+            will(new Action() {
+                public void describeTo(Description description) {
+                    description.appendText("fail tests");
+                }
+
+                public Object invoke(Invocation invocation) throws Throwable {
+                    TestTest.this.test.getTestListenerBroadcaster().getSource().beforeSuite(testDescriptor);
+                    TestTest.this.test.getTestListenerBroadcaster().getSource().afterSuite(testDescriptor, result);
+                    return null;
+                }
+            });
+
+            one(testFrameworkMock).report();
+        }});
+    }
+
+    private void configureTask() {
+        test.useTestFramework(testFrameworkMock);
+        test.setTestExecuter(testExecuterMock);
+        
+        test.setTestClassesDir(classesDir);
+        test.setTestResultsDir(resultsDir);
+        test.setTestReportDir(reportDir);
+        test.setClasspath(classpathMock);
+        test.setTestSrcDirs(Collections.<File>emptyList());
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/testing/testng/TestNGOptionsTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/testing/testng/TestNGOptionsTest.groovy
new file mode 100644
index 0000000..fe2a0c9
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/api/tasks/testing/testng/TestNGOptionsTest.groovy
@@ -0,0 +1,109 @@
+/*
+ * 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.testing.testng
+
+import org.junit.Before
+import org.junit.Test
+import static org.junit.Assert.*;
+import static org.hamcrest.Matchers.*
+import org.gradle.api.tasks.testing.AbstractTestFrameworkOptionsTest
+import org.gradle.api.JavaVersion
+import org.gradle.api.internal.tasks.testing.testng.TestNGTestFramework
+
+/**
+ * @author Tom Eyckmans
+ */
+
+public class TestNGOptionsTest extends AbstractTestFrameworkOptionsTest<TestNGTestFramework> {
+
+    TestNGOptions testngOptions;
+
+    String[] groups = ['fast', 'unit']
+
+    @Before public void setUp()
+    {
+        super.setUp(TestNGTestFramework)
+
+        testngOptions = new TestNGOptions(new File("projectDir"))
+    }
+
+    @Test public void verifyDefaults()
+    {
+        assertFalse(testngOptions.javadocAnnotations)
+        assertEquals(TestNGOptions.JDK_ANNOTATIONS, testngOptions.annotations)
+
+        assertNull(testngOptions.testResources)
+
+        assertNotNull(testngOptions.includeGroups)
+        assertTrue(testngOptions.includeGroups.empty)
+
+        assertNotNull(testngOptions.excludeGroups)
+        assertTrue(testngOptions.excludeGroups.empty)
+
+        assertNotNull(testngOptions.listeners)
+        assertTrue(testngOptions.listeners.empty)
+
+        assertNull(testngOptions.parallel)
+
+        assertEquals(testngOptions.threadCount, 1)
+
+        assertEquals('Gradle suite', testngOptions.suiteName)
+
+        assertEquals('Gradle test', testngOptions.testName)
+    }
+
+    @Test public void jdk14SourceCompatibilityAnnotationsDefaulting()
+    {
+        testngOptions.setAnnotationsOnSourceCompatibility(JavaVersion.VERSION_1_4)
+        assertEquals(testngOptions.annotations, TestNGOptions.JAVADOC_ANNOTATIONS)
+    }
+
+    @Test public void jdk15SourceCompatibilityAnnotationsDefaulting()
+    {
+        testngOptions.setAnnotationsOnSourceCompatibility(JavaVersion.VERSION_1_5)
+        assertEquals(testngOptions.annotations, TestNGOptions.JDK_ANNOTATIONS)
+    }
+
+    @Test public void jdk16SourceCompatibilityAnnotationsDefaulting()
+    {
+        testngOptions.setAnnotationsOnSourceCompatibility(JavaVersion.VERSION_1_6)
+        assertEquals(testngOptions.annotations, TestNGOptions.JDK_ANNOTATIONS)
+    }
+
+    @Test public void testIncludeGroups()
+    {
+        assertTrue(testngOptions.excludeGroups.empty);
+        assertTrue(testngOptions.includeGroups.empty);
+
+        testngOptions.includeGroups(groups);
+
+        assertFalse(testngOptions.includeGroups.empty)
+        assertThat(testngOptions.includeGroups, hasItems(groups))
+        assertTrue(testngOptions.excludeGroups.empty);
+    }
+
+    @Test public void testExcludeGroups()
+    {
+        assertTrue(testngOptions.excludeGroups.empty);
+        assertTrue(testngOptions.includeGroups.empty);
+
+        testngOptions.excludeGroups(groups)
+
+        assertFalse(testngOptions.excludeGroups.empty)
+        assertThat(testngOptions.excludeGroups, hasItems(groups))
+        assertTrue(testngOptions.includeGroups.empty);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/JavadocExecHandleBuilderTest.groovy b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/JavadocExecHandleBuilderTest.groovy
new file mode 100644
index 0000000..240ae70
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/JavadocExecHandleBuilderTest.groovy
@@ -0,0 +1,99 @@
+/*
+ * 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.external.javadoc;
+
+
+import org.gradle.util.Jvm
+import org.junit.Test
+import spock.lang.Specification
+import static org.junit.Assert.assertTrue
+
+/**
+ * @author Tom Eyckmans
+ */
+public class JavadocExecHandleBuilderTest extends Specification {
+
+    private JavadocExecHandleBuilder javadocExecHandleBuilder = new JavadocExecHandleBuilder()
+
+    def setup() {
+        MinimalJavadocOptions minimalJavadocOptions = Mock()
+        javadocExecHandleBuilder.options = minimalJavadocOptions
+        javadocExecHandleBuilder.optionsFile = new File(".")
+    }
+
+    def testCheckExecutableAfterInit() {
+        expect:
+        javadocExecHandleBuilder.execHandle.executable == Jvm.current().javadocExecutable.absolutePath
+    }
+
+    def testCheckCustomExecutable() {
+        String executable = "somepath"
+        javadocExecHandleBuilder.executable = executable
+        
+        expect:
+        javadocExecHandleBuilder.execHandle.executable == executable
+    }
+
+    def testSetNullExecDirectory() {
+        when:
+        javadocExecHandleBuilder.execDirectory(null)
+
+        then:
+        thrown(java.lang.IllegalArgumentException)
+    }
+
+    def testSetNotExistingDirectory() {
+        when:
+        javadocExecHandleBuilder.execDirectory(new File(".notExistingTestDirectoryX"));
+
+        then:
+        thrown(java.lang.IllegalArgumentException)
+    }
+
+    def testSetExistingExecDirectory() {
+        File existingDirectory = new File(".existingDirectory");
+        assertTrue(existingDirectory.mkdir());
+
+        expect:
+        try {
+            javadocExecHandleBuilder.execDirectory(existingDirectory);
+        }
+        finally {
+            existingDirectory.delete()
+        }
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSetNullOptions() {
+        when:
+        javadocExecHandleBuilder.options(null);
+
+        then:
+        thrown(java.lang.IllegalArgumentException)
+    }
+
+    @Test
+    public void testSetNotNullOptions() {
+        MinimalJavadocOptions options = Mock()
+
+        when:
+        javadocExecHandleBuilder.options(options)
+
+        then:
+        true // no error
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptionsTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptionsTest.java
new file mode 100644
index 0000000..78fbe97
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/StandardJavadocDocletOptionsTest.java
@@ -0,0 +1,514 @@
+/*
+ * 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.external.javadoc;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.After;
+import static org.junit.Assert.*;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.jmock.Expectations;
+import org.gradle.external.javadoc.optionfile.JavadocOptionFile;
+import org.gradle.external.javadoc.optionfile.LinksOfflineJavadocOptionFileOption;
+import org.gradle.external.javadoc.optionfile.GroupsJavadocOptionFileOption;
+
+import java.util.*;
+import java.io.File;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class StandardJavadocDocletOptionsTest {
+
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private StandardJavadocDocletOptions options;
+
+    @Before
+    public void setUp() {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+
+        options = new StandardJavadocDocletOptions();
+    }
+
+    @Test
+    public void testDefaults() {
+        // core javadoc options
+        assertNull(options.getOverview());
+        assertNull(options.getMemberLevel());
+        assertNull(options.getDoclet());
+        assertEmpty(options.getDocletpath());
+        assertNull(options.getSource());
+        assertEmpty(options.getClasspath());
+        assertEmpty(options.getBootClasspath());
+        assertEmpty(options.getExtDirs());
+        assertEquals(options.getOutputLevel(), JavadocOutputLevel.QUIET);
+        assertFalse(options.isBreakIterator());
+        assertNull(options.getLocale());
+        assertNull(options.getEncoding());
+        assertEmpty(options.getJFlags());
+        assertEmpty(options.getSourceNames());
+        assertEmpty(options.getOptionFiles());
+        // standard doclet options
+        assertNull(options.getDestinationDirectory());
+        assertFalse(options.isUse());
+        assertFalse(options.isVersion());
+        assertFalse(options.isAuthor());
+        assertFalse(options.isSplitIndex());
+        assertNull(options.getWindowTitle());
+        assertNull(options.getDocTitle());
+        assertNull(options.getFooter());
+        assertNull(options.getBottom());
+        assertEmpty(options.getLinks());
+        assertEmpty(options.getLinksOffline());
+        assertFalse(options.isLinkSource());
+        assertEmpty(options.getGroups());
+        assertFalse(options.isNoDeprecated());
+        assertFalse(options.isNoDeprecatedList());
+        assertFalse(options.isNoSince());
+        assertFalse(options.isNoTree());
+        assertFalse(options.isNoIndex());
+        assertFalse(options.isNoHelp());
+        assertFalse(options.isNoNavBar());
+        assertNull(options.getHelpFile());
+        assertNull(options.getStylesheetFile());
+        assertFalse(options.isSerialWarn());
+        assertNull(options.getCharSet());
+        assertNull(options.getDocEncoding());
+        assertFalse(options.isKeyWords());
+        assertEmpty(options.getTags());
+        assertEmpty(options.getTagletPath());
+        assertFalse(options.isDocFilesSubDirs());
+        assertEmpty(options.getExcludeDocFilesSubDir());
+        assertEmpty(options.getNoQualifiers());
+        assertFalse(options.isNoTimestamp());
+        assertFalse(options.isNoComment());
+    }
+
+    @Test
+    public void testConstructor() {
+        final JavadocOptionFile optionFileMock = context.mock(JavadocOptionFile.class);
+
+        context.checking(new Expectations(){{
+            // core options
+            one(optionFileMock).addStringOption("overview");
+            one(optionFileMock).addEnumOption("memberLevel");
+            one(optionFileMock).addStringOption("doclet");
+            one(optionFileMock).addPathOption("docletclasspath");
+            one(optionFileMock).addStringOption("source");
+            one(optionFileMock).addPathOption("sourcepath");
+            one(optionFileMock).addPathOption("classpath");
+            one(optionFileMock).addStringsOption("subpackages", ";");
+            one(optionFileMock).addStringsOption("exclude", ":");
+            one(optionFileMock).addPathOption("bootclasspath");
+            one(optionFileMock).addPathOption("extdirs");
+            one(optionFileMock).addEnumOption("outputLevel", JavadocOutputLevel.QUIET);
+            one(optionFileMock).addBooleanOption("breakiterator");
+            one(optionFileMock).addStringOption("locale");
+            one(optionFileMock).addStringOption("encoding");
+            // standard doclet options
+            one(optionFileMock).addFileOption("d");
+            one(optionFileMock).addBooleanOption("use");
+            one(optionFileMock).addBooleanOption("version");
+            one(optionFileMock).addBooleanOption("author");
+            one(optionFileMock).addBooleanOption("splitindex");
+            one(optionFileMock).addStringOption("windowtitle");
+            one(optionFileMock).addStringOption("doctitle");
+            one(optionFileMock).addStringOption("footer");
+            one(optionFileMock).addStringOption("bottom");
+            one(optionFileMock).addStringOption("link");
+            allowing(optionFileMock).addOption(new LinksOfflineJavadocOptionFileOption("linkoffline"));
+            one(optionFileMock).addBooleanOption("linksource");
+            one(optionFileMock).addOption(new GroupsJavadocOptionFileOption("group"));
+            one(optionFileMock).addBooleanOption("nodeprecated");
+            one(optionFileMock).addBooleanOption("nodeprecatedlist");
+            one(optionFileMock).addBooleanOption("nosince");
+            one(optionFileMock).addBooleanOption("notree");
+            one(optionFileMock).addBooleanOption("noindex");
+            one(optionFileMock).addBooleanOption("nohelp");
+            one(optionFileMock).addBooleanOption("nonavbar");
+            one(optionFileMock).addFileOption("helpfile");
+            one(optionFileMock).addFileOption("stylesheetfile");
+            one(optionFileMock).addBooleanOption("serialwarn");
+            one(optionFileMock).addStringOption("charset");
+            one(optionFileMock).addStringOption("docencoding");
+            one(optionFileMock).addBooleanOption("keywords");
+            one(optionFileMock).addStringOption("tags");
+            one(optionFileMock).addPathOption("tagletpath");
+            one(optionFileMock).addBooleanOption("docfilessubdirs");
+            one(optionFileMock).addStringsOption("excludedocfilessubdir", ":");
+            one(optionFileMock).addStringsOption("noqualifier", ":");
+            one(optionFileMock).addBooleanOption("notimestamp");
+            one(optionFileMock).addBooleanOption("nocomment");
+        }});
+
+        options = new StandardJavadocDocletOptions();
+    }
+
+    @Test
+    public void testFluentOverview() {
+        final String overviewValue = "overview";
+        assertEquals(options, options.overview(overviewValue));
+        assertEquals(overviewValue, options.getOverview());
+    }
+
+    @Test
+    public void testShowAll() {
+        assertEquals(options, options.showAll());
+        assertEquals(JavadocMemberLevel.PRIVATE, options.getMemberLevel());
+    }
+
+    @Test
+    public void testShowFromPublic() {
+        assertEquals(options, options.showFromPublic());
+        assertEquals(JavadocMemberLevel.PUBLIC, options.getMemberLevel());
+    }
+
+    @Test
+    public void testShowFromPackage() {
+        assertEquals(options, options.showFromPackage());
+        assertEquals(JavadocMemberLevel.PACKAGE, options.getMemberLevel());
+    }
+
+    @Test
+    public void testShowFromProtected() {
+        assertEquals(options, options.showFromProtected());
+        assertEquals(JavadocMemberLevel.PROTECTED, options.getMemberLevel());
+    }
+
+    @Test
+    public void testShowFromPrivate() {
+        assertEquals(options, options.showFromPrivate());
+        assertEquals(JavadocMemberLevel.PRIVATE, options.getMemberLevel());
+    }
+
+    @Test
+    public void testFluentDocletClass() {
+        final String docletValue = "org.gradle.CustomDocletClass";
+        assertEquals(options, options.doclet(docletValue));
+        assertEquals(docletValue, options.getDoclet());
+    }
+
+    @Test
+    public void testFluentDocletClasspath() {
+        final File[] docletClasspathValue = new File[]{new File("doclet.jar"), new File("doclet-dep.jar")};
+        assertEquals(options, options.docletpath(docletClasspathValue));
+        assertArrayEquals(docletClasspathValue, options.getDocletpath().toArray());
+    }
+
+    @Test
+    public void testFluentSource() {
+        final String sourceValue = "1.5";
+        assertEquals(options, options.source(sourceValue));
+        assertEquals(sourceValue, options.getSource());
+    }
+
+    @Test
+    public void testFluentClasspath() {
+        final File[] classpathValue = new File[]{new File("classpath.jar"), new File("classpath-dir")};
+        assertEquals(options, options.classpath(classpathValue));
+        assertArrayEquals(classpathValue, options.getClasspath().toArray());
+    }
+
+    @Test
+    public void testFluentBootclasspath() {
+        final File[] bootClasspathValue = new File[]{new File("bootclasspath.jar"), new File("bootclasspath2.jar")};
+        assertEquals(options, options.bootClasspath(bootClasspathValue));
+        assertArrayEquals(bootClasspathValue, options.getBootClasspath().toArray());
+    }
+
+    @Test
+    public void testFluentExtDirs() {
+        final File[] extDirsValue = new File[]{new File("extDirOne"), new File("extDirTwo")};
+        assertEquals(options, options.extDirs(extDirsValue));
+        assertArrayEquals(extDirsValue, options.getExtDirs().toArray());
+    }
+
+    @Test
+    public void testQuietOutputLevel() {
+        assertEquals(options, options.quiet());
+        assertEquals(JavadocOutputLevel.QUIET, options.getOutputLevel());
+    }
+
+    @Test
+    public void testVerboseOutputLevel() {
+        assertEquals(options, options.verbose());
+        assertEquals(JavadocOutputLevel.VERBOSE, options.getOutputLevel());
+        assertTrue(options.isVerbose());
+    }
+
+    @Test
+    public void testFluentBreakIterator() {
+        assertEquals(options, options.breakIterator());
+        assertTrue(options.isBreakIterator());
+    }
+
+    @Test
+    public void testFluentLocale() {
+        final String localeValue = "nl";
+        assertEquals(options, options.locale(localeValue));
+        assertEquals(localeValue, options.getLocale());
+    }
+
+    @Test
+    public void testFluentEncoding() {
+        final String encodingValue = "UTF-8";
+        assertEquals(options, options.encoding(encodingValue));
+        assertEquals(encodingValue, options.getEncoding());
+    }
+
+    @Test
+    public void testFluentDirectory() {
+        final File directoryValue = new File("testOutput");
+        assertEquals(options, options.destinationDirectory(directoryValue));
+        assertEquals(directoryValue, options.getDestinationDirectory());
+    }
+
+    @Test
+    public void testFluentUse() {
+        assertEquals(options, options.use());
+        assertTrue(options.isUse());
+    }
+
+    @Test
+    public void testFluentVersion() {
+        assertEquals(options, options.version());
+        assertTrue(options.isVersion());
+    }
+
+    @Test
+    public void testFluentAuthor() {
+        assertEquals(options, options.author());
+        assertTrue(options.isAuthor());
+    }
+
+    @Test
+    public void testFluentSplitIndex() {
+        assertEquals(options, options.splitIndex());
+        assertTrue(options.isSplitIndex());
+    }
+
+    @Test
+    public void testFluentWindowTitle() {
+        final String windowTitleValue = "windowTitleValue";
+        assertEquals(options, options.windowTitle(windowTitleValue));
+        assertEquals(windowTitleValue, options.getWindowTitle());
+    }
+
+    @Test
+    public void testFluentDocTitle() {
+        final String docTitleValue = "docTitleValue";
+        assertEquals(options, options.docTitle(docTitleValue));
+        assertEquals(docTitleValue, options.getDocTitle());
+    }
+
+    @Test
+    public void testFluentFooter() {
+        final String footerValue = "footerValue";
+        assertEquals(options, options.footer(footerValue));
+        assertEquals(footerValue, options.getFooter());
+    }
+
+    @Test
+    public void testFluentBottom() {
+        final String bottomValue = "bottomValue";
+        assertEquals(options, options.bottom(bottomValue));
+        assertEquals(bottomValue, options.getBottom());
+    }
+
+    @Test
+    public void testFluentLink() {
+        final String[] linkValue = new String[]{"http://otherdomain.org/javadoc"};
+        assertEquals(options, options.links(linkValue));
+        assertArrayEquals(linkValue, options.getLinks().toArray());
+    }
+
+    @Test
+    public void testFluentLinkOffline() {
+        final String extDocUrl = "http://otherdomain.org/javadoc";
+        final String packageListLoc = "/home/someuser/used-lib-local-javadoc-list";
+        assertEquals(options, options.linksOffline(extDocUrl, packageListLoc));
+        assertEquals(extDocUrl, options.getLinksOffline().get(0).getExtDocUrl());
+        assertEquals(packageListLoc, options.getLinksOffline().get(0).getPackagelistLoc());
+    }
+
+    @Test
+    public void testFluentLinkSource() {
+        assertEquals(options, options.linkSource());
+        assertTrue(options.isLinkSource());
+    }
+
+    @Test
+    public void testFluentGroup() {
+        final String groupOneName = "groupOneName";
+        final String[] groupOnePackages = new String[]{"java.lang", "java.io"};
+
+        final String groupTwoName = "gradle";
+        final String[] groupTwoPackages = new String[]{"org.gradle"};
+
+        assertEquals(options, options.group(groupOneName, groupOnePackages));
+        assertEquals(options, options.group(groupTwoName, groupTwoPackages));
+        assertEquals(2, options.getGroups().size());
+        assertArrayEquals(groupOnePackages, options.getGroups().get(groupOneName).toArray());
+        assertArrayEquals(groupTwoPackages, options.getGroups().get(groupTwoName).toArray());
+    }
+
+    @Test
+    public void testFluentNoDeprecated() {
+        assertEquals(options, options.noDeprecated());
+        assertTrue(options.isNoDeprecated());
+    }
+
+    @Test
+    public void testFluentNoDeprecatedList() {
+        assertEquals(options, options.noDeprecatedList());
+        assertTrue(options.isNoDeprecatedList());
+    }
+
+    @Test
+    public void testFluentNoSince() {
+        assertEquals(options, options.noSince());
+        assertTrue(options.isNoSince());
+    }
+
+    @Test
+    public void testFluentNoTree() {
+        assertEquals(options, options.noTree());
+        assertTrue(options.isNoTree());
+    }
+
+    @Test
+    public void testFluentNoIndex() {
+        assertEquals(options, options.noIndex());
+        assertTrue(options.isNoIndex());
+    }
+
+    @Test
+    public void testFluentNoNavBar() {
+        assertEquals(options, options.noNavBar());
+        assertTrue(options.isNoNavBar());
+    }
+
+    @Test
+    public void testFluentHelpFile() {
+        final File helpFileValue = new File("help-file.txt");
+        assertEquals(options, options.helpFile(helpFileValue));
+        assertEquals(helpFileValue, options.getHelpFile());
+    }
+
+    @Test
+    public void testFluentStylesheetFile() {
+        final File stylesheetFileValue = new File("stylesheet.css");
+        assertEquals(options, options.stylesheetFile(stylesheetFileValue));
+        assertEquals(stylesheetFileValue, options.getStylesheetFile());
+    }
+
+    @Test
+    public void testFluentSerialWarn() {
+        assertEquals(options, options.serialWarn());
+        assertTrue(options.isSerialWarn());
+    }
+
+    @Test
+    public void testFluentCharset() {
+        final String charsetValue = "dummy-charset";
+        assertEquals(options, options.charSet(charsetValue));
+        assertEquals(charsetValue, options.getCharSet());
+    }
+
+    @Test
+    public void testFluentDocEncoding() {
+        final String docEncodingValue = "UTF-16";
+        assertEquals(options, options.docEncoding(docEncodingValue));
+        assertEquals(docEncodingValue, options.getDocEncoding());
+    }
+
+    @Test
+    public void testFluentKeywords() {
+        assertEquals(options, options.keyWords());
+        assertTrue(options.isKeyWords());
+    }
+
+    @Test
+    public void testFluentTagsAndTaglets() {
+        final String[] tagletsValue = new String[]{"com.sun.tools.doclets.ToDoTaglet"};
+        final String[] tagsValue = new String[]{"param", "return", "todo:a:\"To Do:\""};
+
+        final List<String> tempList = new ArrayList<String>();
+        tempList.addAll(Arrays.asList(tagletsValue));
+        tempList.addAll(Arrays.asList(tagsValue));
+
+        final Object[] totalTagsValue = tempList.toArray();
+        assertEquals(options, options.taglets(tagletsValue));
+        assertEquals(options, options.tags(tagsValue));
+        assertArrayEquals(totalTagsValue, options.getTags().toArray());
+    }
+
+    @Test
+    public void testFluentTagletPath() {
+        final File[] tagletPathValue = new File[]{new File("tagletOne.jar"), new File("tagletTwo.jar")};
+        assertEquals(options, options.tagletPath(tagletPathValue));
+        assertArrayEquals(tagletPathValue, options.getTagletPath().toArray());
+    }
+
+    @Test
+    public void testFluentDocFilesSubDirs() {
+        assertEquals(options, options.docFilesSubDirs());
+        assertTrue(options.isDocFilesSubDirs());
+    }
+
+    @Test
+    public void testFluentExcludeDocFilesSubDir() {
+        final String[] excludeDocFilesSubDirValue = new String[]{".hg",".svn",".bzr", ".git"};
+        assertEquals(options, options.excludeDocFilesSubDir(excludeDocFilesSubDirValue));
+        assertArrayEquals(excludeDocFilesSubDirValue, options.getExcludeDocFilesSubDir().toArray());
+    }
+
+    @Test
+    public void testFluentNoQualifier() {
+        String[] noQualifierValue = new String[]{"java.lang", "java.io"};
+        assertEquals(options, options.noQualifiers(noQualifierValue));
+        assertArrayEquals(noQualifierValue, options.getNoQualifiers().toArray());
+    }
+
+    @Test
+    public void testFluetNoTimestamp() {
+        assertEquals(options, options.noTimestamp());
+        assertTrue(options.isNoTimestamp());
+    }
+
+    @Test
+    public void testFluentNoComment() {
+        assertEquals(options, options.noComment());
+        assertTrue(options.isNoComment());
+    }
+
+    @After
+    public void tearDown() {
+        options = null;
+    }
+
+    public static void assertEmpty(Collection shouldBeEmptyCollection) {
+        assertNotNull(shouldBeEmptyCollection);
+        assertTrue(shouldBeEmptyCollection.isEmpty());
+    }
+
+    public static void assertEmpty(Map shouldBeEmptyMap) {
+        assertNotNull(shouldBeEmptyMap);
+        assertTrue(shouldBeEmptyMap.isEmpty());
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/BooleanJavadocOptionFileOptionTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/BooleanJavadocOptionFileOptionTest.java
new file mode 100644
index 0000000..84e202d
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/BooleanJavadocOptionFileOptionTest.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.external.javadoc.optionfile;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.jmock.Expectations;
+
+import java.io.IOException;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class BooleanJavadocOptionFileOptionTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private JavadocOptionFileWriterContext writerContextMock;
+    private final String optionName = "testOption";
+    private BooleanJavadocOptionFileOption booleanOption;
+
+    @Before
+    public void setUp() {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        writerContextMock = context.mock(JavadocOptionFileWriterContext.class);
+
+        booleanOption = new BooleanJavadocOptionFileOption(optionName);
+    }
+
+    @Test
+    public void testWriteNullValue() throws IOException {
+        booleanOption.write(writerContextMock);
+    }
+
+    @Test
+    public void testWriteFalseValue() throws IOException {
+        booleanOption.setValue(false);
+
+        booleanOption.write(writerContextMock);
+    }
+
+    @Test
+    public void testWriteTrueValue() throws IOException {
+        booleanOption.setValue(true);
+
+        context.checking(new Expectations() {{
+            one(writerContextMock).writeOption(optionName);
+        }});
+
+        booleanOption.write(writerContextMock);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/EnumJavadocOptionFileOptionTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/EnumJavadocOptionFileOptionTest.java
new file mode 100644
index 0000000..8ea1574
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/EnumJavadocOptionFileOptionTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.jmock.Expectations;
+import org.junit.Before;
+import org.junit.Test;
+import org.gradle.external.javadoc.JavadocMemberLevel;
+
+import java.io.IOException;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class EnumJavadocOptionFileOptionTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private JavadocOptionFileWriterContext writerContextMock;
+    private final String optionName = "testOption";
+    private EnumJavadocOptionFileOption<JavadocMemberLevel> enumOption;
+
+    @Before
+    public void setUp() {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        writerContextMock = context.mock(JavadocOptionFileWriterContext.class);
+
+        enumOption = new EnumJavadocOptionFileOption<JavadocMemberLevel>(optionName);
+    }
+
+    @Test
+    public void testWriteNullValue() throws IOException {
+        enumOption.write(writerContextMock);
+    }
+
+    @Test
+    public void testWriteNoneNullValue() throws IOException {
+        enumOption.setValue(JavadocMemberLevel.PUBLIC);
+
+        context.checking(new Expectations() {{
+            one(writerContextMock).writeOption("public");
+        }});
+
+        enumOption.write(writerContextMock);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/FileJavadocOptionFileOptionTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/FileJavadocOptionFileOptionTest.java
new file mode 100644
index 0000000..7fa88b0
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/FileJavadocOptionFileOptionTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.jmock.Expectations;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.File;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class FileJavadocOptionFileOptionTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private JavadocOptionFileWriterContext writerContextMock;
+    private final String optionName = "testOption";
+    private FileJavadocOptionFileOption fileOption;
+
+    @Before
+    public void setUp() {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        writerContextMock = context.mock(JavadocOptionFileWriterContext.class);
+
+        fileOption = new FileJavadocOptionFileOption(optionName);
+    }
+
+    @Test
+    public void testWriteNullValue() throws IOException {
+        fileOption.write(writerContextMock);
+    }
+
+    @Test
+    public void testWriteNoneNullValue() throws IOException {
+        final File testValue = new File("dummyTestFileValue");
+
+        context.checking(new Expectations() {{
+            one(writerContextMock).writeValueOption(optionName, testValue.getAbsolutePath());
+        }});
+        
+        fileOption.write(writerContextMock);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/GroupsJavadocOptionFileOptionTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/GroupsJavadocOptionFileOptionTest.java
new file mode 100644
index 0000000..95a042f
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/GroupsJavadocOptionFileOptionTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.jmock.Expectations;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Arrays;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class GroupsJavadocOptionFileOptionTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private JavadocOptionFileWriterContext writerContextMock;
+    private final String optionName = "testOption";
+
+    private GroupsJavadocOptionFileOption groupsFile;
+
+    @Before
+    public void setUp() {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        writerContextMock = context.mock(JavadocOptionFileWriterContext.class);
+
+        groupsFile = new GroupsJavadocOptionFileOption(optionName);
+    }
+
+    @Test
+    public void testWriteNullValue() throws IOException {
+        groupsFile.write(writerContextMock);
+    }
+
+    @Test
+    public void testWriteNotNullValue() throws IOException {
+        final String groupName = "testGroup";
+        final List<String> groupElements = Arrays.asList("java.lang", "java.util*");
+
+        groupsFile.getValue().put(groupName, groupElements);
+
+        context.checking(new Expectations() {{
+            one(writerContextMock).writeOptionHeader(optionName);
+            one(writerContextMock).write("\"testGroup\"");
+            one(writerContextMock).write(" ");
+            one(writerContextMock).write("\"java.lang:java.util*\"");
+            one(writerContextMock).newLine();
+        }});
+
+        groupsFile.write(writerContextMock);
+    }
+
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/JavadocOptionFileTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/JavadocOptionFileTest.java
new file mode 100644
index 0000000..7b5f0f3
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/JavadocOptionFileTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.jmock.Expectations;
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class JavadocOptionFileTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private JavadocOptionFileOption optionFileOptionMock;
+    private final String optionName = "testOption";
+    
+
+    private JavadocOptionFile optionFile;
+
+    @Before
+    public void setUp() {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+
+        optionFileOptionMock = context.mock(JavadocOptionFileOption.class);
+
+        optionFile = new JavadocOptionFile();
+    }
+
+    @Test
+    public void testDefaults() {
+        assertNotNull(optionFile.getOptions());
+        assertTrue(optionFile.getOptions().isEmpty());
+
+        assertNotNull(optionFile.getSourceNames());
+        assertNotNull(optionFile.getSourceNames().getValue());
+        assertTrue(optionFile.getSourceNames().getValue().isEmpty());
+    }
+
+    @Test
+    public void testAddOption() {
+        context.checking(new Expectations() {{
+            one(optionFileOptionMock).getOption();
+            returnValue(optionName);
+        }});
+
+        optionFile.addOption(optionFileOptionMock);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/JavadocOptionFileWriterContextTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/JavadocOptionFileWriterContextTest.java
new file mode 100644
index 0000000..566adb0
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/JavadocOptionFileWriterContextTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.jmock.Expectations;
+import org.junit.Before;
+import org.junit.Test;
+import org.gradle.util.WrapUtil;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class JavadocOptionFileWriterContextTest {
+
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private BufferedWriter bufferedWriterMock;
+
+    private JavadocOptionFileWriterContext writerContext;
+
+    @Before
+    public void setUp() {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        bufferedWriterMock = context.mock(BufferedWriter.class);
+
+        writerContext = new JavadocOptionFileWriterContext(bufferedWriterMock);
+    }
+
+    @Test
+    public void testWrite() throws IOException {
+        final String writeValue = "dummy";
+
+        context.checking(new Expectations() {{
+            one(bufferedWriterMock).write(writeValue);
+        }});
+
+        writerContext.write(writeValue);
+    }
+
+    @Test
+    public void testNewLine() throws IOException {
+        context.checking(new Expectations() {{
+            one(bufferedWriterMock).newLine();
+        }});
+
+        writerContext.newLine();
+    }
+
+    @Test
+    public void quotesAndEscapesOptionValue() throws IOException {
+        context.checking(new Expectations(){{
+            one(bufferedWriterMock).write("-");
+            one(bufferedWriterMock).write("key");
+            one(bufferedWriterMock).write(" ");
+            one(bufferedWriterMock).write("'");
+            one(bufferedWriterMock).write("1\\\\2\\\\");
+            one(bufferedWriterMock).write("'");
+            one(bufferedWriterMock).newLine();
+        }});
+
+        writerContext.writeValueOption("key", "1\\2\\");
+    }
+
+    @Test
+    public void quotesAndEscapesOptionValues() throws IOException {
+        context.checking(new Expectations(){{
+            one(bufferedWriterMock).write("-");
+            one(bufferedWriterMock).write("key");
+            one(bufferedWriterMock).write(" ");
+            one(bufferedWriterMock).write("'");
+            one(bufferedWriterMock).write("a\\\\b:c");
+            one(bufferedWriterMock).write("'");
+            one(bufferedWriterMock).newLine();
+        }});
+
+        writerContext.writeValuesOption("key", WrapUtil.toList("a\\b", "c"), ":");
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/LinksOfflineJavadocOptionFileOptionTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/LinksOfflineJavadocOptionFileOptionTest.java
new file mode 100644
index 0000000..4a6664f
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/LinksOfflineJavadocOptionFileOptionTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.jmock.Expectations;
+import org.junit.Before;
+import org.junit.Test;
+import org.gradle.external.javadoc.JavadocOfflineLink;
+
+import java.io.IOException;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class LinksOfflineJavadocOptionFileOptionTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private JavadocOptionFileWriterContext writerContextMock;
+    private final String optionName = "testOption";
+
+    private LinksOfflineJavadocOptionFileOption linksOfflineOption;
+
+    @Before
+    public void setUp() {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        writerContextMock = context.mock(JavadocOptionFileWriterContext.class);
+
+        linksOfflineOption = new LinksOfflineJavadocOptionFileOption(optionName);
+    }
+
+    @Test
+    public void writeNullValue() throws IOException {
+        linksOfflineOption.write(writerContextMock);
+    }
+
+    @Test
+    public void writeNoneNullValue() throws IOException {
+        final String extDocUrl = "extDocUrl";
+        final String packageListLoc = "packageListLoc";
+
+        linksOfflineOption.getValue().add(new JavadocOfflineLink(extDocUrl, packageListLoc));
+
+        context.checking(new Expectations() {{
+            one(writerContextMock).writeValueOption(optionName, extDocUrl + "' '" + packageListLoc);
+        }});
+
+        linksOfflineOption.write(writerContextMock);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/MultilineStringsJavadocOptionFileOptionTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/MultilineStringsJavadocOptionFileOptionTest.java
new file mode 100644
index 0000000..58451c9
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/MultilineStringsJavadocOptionFileOptionTest.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.external.javadoc.optionfile;
+
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.jmock.Expectations;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Melanie Pfautz
+ */
+public class MultilineStringsJavadocOptionFileOptionTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private JavadocOptionFileWriterContext writerContextMock;
+    private final String optionName = "testOption";
+
+    private MultilineStringsJavadocOptionFileOption linksOption;
+
+    @Before
+    public void setUp() {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        writerContextMock = context.mock(JavadocOptionFileWriterContext.class);
+        linksOption = new MultilineStringsJavadocOptionFileOption(optionName);
+    }
+
+    @Test
+    public void writeNullValue() throws IOException {
+        linksOption.writeCollectionValue(writerContextMock);
+    }
+
+    @Test
+    public void writeNonNullValue() throws IOException {
+        final String extDocUrl = "extDocUrl";
+
+        linksOption.getValue().add(extDocUrl);
+        context.checking(new Expectations() {{
+            final List<String> tempList = new ArrayList<String>();
+            tempList.add(extDocUrl);
+            one(writerContextMock).writeMultilineValuesOption(optionName, tempList);
+        }});
+
+        linksOption.writeCollectionValue(writerContextMock);
+    }
+
+    @Test
+    public void writeMultipleValues() throws IOException {
+        final List<String> tempList = new ArrayList<String>();
+        final String docUrl1 = "docUrl1";
+        final String docUrl2 = "docUrl2";
+
+        linksOption.getValue().add(docUrl1);
+        linksOption.getValue().add(docUrl2);
+        context.checking(new Expectations() {{
+            tempList.add(docUrl1);
+            tempList.add(docUrl2);
+            one(writerContextMock).writeMultilineValuesOption(optionName, tempList);
+        }});
+       
+        linksOption.writeCollectionValue(writerContextMock);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/OptionLessStringsJavadocOptionFileOptionTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/OptionLessStringsJavadocOptionFileOptionTest.java
new file mode 100644
index 0000000..3a2bb15
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/OptionLessStringsJavadocOptionFileOptionTest.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.external.javadoc.optionfile;
+
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.jmock.Expectations;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class OptionLessStringsJavadocOptionFileOptionTest {
+
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private JavadocOptionFileWriterContext writerContextMock;
+    private final String optionName = "testOption";
+
+    private OptionLessStringsJavadocOptionFileOption optionLessStringsOption;
+
+    @Before
+    public void setUp() {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        writerContextMock = context.mock(JavadocOptionFileWriterContext.class);
+
+        optionLessStringsOption = new OptionLessStringsJavadocOptionFileOption();
+    }
+
+    @Test
+    public void writeNullValue() throws IOException {
+        final String firstValue = "firstValue";
+        final String secondValue = "secondValue";
+
+        context.checking(new Expectations() {{
+            one(writerContextMock).write(firstValue);
+            one(writerContextMock).newLine();
+            one(writerContextMock).write(secondValue);
+            one(writerContextMock).newLine();
+        }});
+
+        optionLessStringsOption.write(writerContextMock);
+    }
+
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/PathJavadocOptionFileOptionTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/PathJavadocOptionFileOptionTest.java
new file mode 100644
index 0000000..1da2f6c
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/PathJavadocOptionFileOptionTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.jmock.Expectations;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.File;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class PathJavadocOptionFileOptionTest {
+
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private JavadocOptionFileWriterContext writerContextMock;
+    private final String optionName = "testOption";
+    private final String joinBy = ";";
+
+    private PathJavadocOptionFileOption pathOption;
+
+    @Before
+    public void setUp() {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        writerContextMock = context.mock(JavadocOptionFileWriterContext.class);
+
+        pathOption = new PathJavadocOptionFileOption(optionName, joinBy);
+    }
+
+    @Test
+    public void testWriteNullValue() throws IOException {
+        pathOption.write(writerContextMock);
+    }
+
+    @Test
+    public void testWriteNoneNullValue() throws IOException {
+        final File fileOne = new File("fileOne");
+        final File fileTwo = new File("fileTwo");
+
+        pathOption.getValue().add(fileOne);
+        pathOption.getValue().add(fileTwo);
+
+        context.checking(new Expectations() {{
+            one(writerContextMock).writePathOption(optionName, pathOption.getValue(), joinBy);
+        }});
+
+        pathOption.write(writerContextMock);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/StringJavadocOptionFileOptionTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/StringJavadocOptionFileOptionTest.java
new file mode 100644
index 0000000..2f7bed2
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/StringJavadocOptionFileOptionTest.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.external.javadoc.optionfile;
+
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.jmock.Expectations;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class StringJavadocOptionFileOptionTest {
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private JavadocOptionFileWriterContext writerContextMock;
+    private final String optionName = "testOption";
+
+    private StringJavadocOptionFileOption stringOption;
+
+    @Before
+    public void setUp() {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        writerContextMock = context.mock(JavadocOptionFileWriterContext.class);
+
+        stringOption = new StringJavadocOptionFileOption(optionName);
+    }
+
+    @Test
+    public void testWriteNullValue() throws IOException {
+        stringOption.write(writerContextMock);
+    }
+
+    @Test
+    public void testWriteNoneNullValue() throws IOException {
+        final String testValue = "testValue";
+
+        stringOption.setValue(testValue);
+
+        context.checking(new Expectations() {{
+            one(writerContextMock).writeValueOption(optionName, testValue);
+        }});
+
+        stringOption.write(writerContextMock);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/StringsJavadocOptionFileOptionTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/StringsJavadocOptionFileOptionTest.java
new file mode 100644
index 0000000..b76f371
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/StringsJavadocOptionFileOptionTest.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.external.javadoc.optionfile;
+
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.jmock.Expectations;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class StringsJavadocOptionFileOptionTest {
+
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private JavadocOptionFileWriterContext writerContextMock;
+    private final String optionName = "testOption";
+    private final String joinBy = ";";
+
+    private StringsJavadocOptionFileOption stringsOption;
+
+    @Before
+    public void setUp() {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        writerContextMock = context.mock(JavadocOptionFileWriterContext.class);
+
+        stringsOption = new StringsJavadocOptionFileOption(optionName, joinBy);
+    }
+
+    @Test
+    public void writeNullValue() throws IOException {
+        stringsOption.write(writerContextMock);
+    }
+
+    @Test
+    public void writeNoneNullValue() throws IOException {
+        final String valueOne = "valueOne";
+        final String valueTwo = "valueTwo";
+
+        stringsOption.getValue().add(valueOne);
+        stringsOption.getValue().add(valueTwo);
+
+        context.checking(new Expectations() {{
+            one(writerContextMock).writeValuesOption(optionName, stringsOption.getValue(), joinBy);
+        }});
+
+        stringsOption.write(writerContextMock);
+    }
+}
diff --git a/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/TagsJavadocOptionFileOptionTest.java b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/TagsJavadocOptionFileOptionTest.java
new file mode 100644
index 0000000..5405755
--- /dev/null
+++ b/subprojects/gradle-plugins/src/test/groovy/org/gradle/external/javadoc/optionfile/TagsJavadocOptionFileOptionTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.external.javadoc.optionfile;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.jmock.Expectations;
+
+import java.io.IOException;
+
+/**
+ * @author Tom Eyckmans
+ */
+public class TagsJavadocOptionFileOptionTest {
+
+    private final JUnit4Mockery context = new JUnit4Mockery();
+    private JavadocOptionFileWriterContext writerContextMock;
+    private final String optionName = "testOption";
+    private final String joinBy = ";";
+    
+    private TagsJavadocOptionFileOption tagsOption;
+
+    @Before
+    public void setUp() {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        writerContextMock = context.mock(JavadocOptionFileWriterContext.class);
+
+        tagsOption = new TagsJavadocOptionFileOption(optionName);
+    }
+
+    @Test
+    public void writeNullValue() throws IOException {
+        tagsOption.write(writerContextMock);
+    }
+
+    @Test
+    public void writeNoneNullValue() throws IOException {
+        tagsOption.getValue().add("testTag");
+        tagsOption.getValue().add("testTaglet:testTagletOne");
+        tagsOption.getValue().add("testTaglet\"testTagletTwo");
+
+        context.checking(new Expectations() {{
+            one(writerContextMock).writeValueOption("taglet", "testTag");
+            one(writerContextMock).writeValueOption("tag", "testTaglet:testTagletOne");
+            one(writerContextMock).writeValueOption("tag", "testTaglet\"testTagletTwo");
+        }});
+
+        tagsOption.write(writerContextMock);
+    }
+
+}
diff --git a/subprojects/gradle-scala/scala.gradle b/subprojects/gradle-scala/scala.gradle
new file mode 100644
index 0000000..0d3af1b
--- /dev/null
+++ b/subprojects/gradle-scala/scala.gradle
@@ -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.
+ */
+dependencies {
+    groovy libraries.groovy_depends
+
+    compile project(':core')
+    compile project(':plugins')
+
+    testCompile project(path: ':core', configuration: 'testFixtures')
+    testCompile project(path: ':plugins', configuration: 'testFixtures')
+    testCompile libraries.slf4j_api
+    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+}
+
+
+
diff --git a/subprojects/gradle-scala/src/main/groovy/org/gradle/api/internal/tasks/DefaultScalaSourceSet.java b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/internal/tasks/DefaultScalaSourceSet.java
new file mode 100644
index 0000000..5e0b7cf
--- /dev/null
+++ b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/internal/tasks/DefaultScalaSourceSet.java
@@ -0,0 +1,57 @@
+/*
+ * 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.tasks;
+
+import groovy.lang.Closure;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.SourceDirectorySet;
+import org.gradle.api.internal.file.DefaultSourceDirectorySet;
+import org.gradle.api.internal.file.FileResolver;
+import org.gradle.api.internal.file.UnionFileTree;
+import org.gradle.api.tasks.ScalaSourceSet;
+import org.gradle.api.tasks.util.PatternFilterable;
+import org.gradle.api.tasks.util.PatternSet;
+import org.gradle.util.ConfigureUtil;
+
+public class DefaultScalaSourceSet implements ScalaSourceSet {
+    private final SourceDirectorySet scala;
+    private final UnionFileTree allScala;
+    private final PatternFilterable scalaPatterns = new PatternSet();
+
+    public DefaultScalaSourceSet(String displayName, FileResolver fileResolver) {
+        scala = new DefaultSourceDirectorySet(String.format("%s Scala source", displayName), fileResolver);
+        scala.getFilter().include("**/*.java", "**/*.scala");
+        scalaPatterns.include("**/*.scala");
+        allScala = new UnionFileTree(String.format("%s Scala source", displayName), scala.matching(scalaPatterns));
+    }
+
+    public SourceDirectorySet getScala() {
+        return scala;
+    }
+
+    public ScalaSourceSet scala(Closure configureClosure) {
+        ConfigureUtil.configure(configureClosure, getScala());
+        return this;
+    }
+
+    public PatternFilterable getScalaSourcePatterns() {
+        return scalaPatterns;
+    }
+
+    public FileTree getAllScala() {
+        return allScala;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-scala/src/main/groovy/org/gradle/api/internal/tasks/scala/AntScalaCompiler.groovy b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/internal/tasks/scala/AntScalaCompiler.groovy
new file mode 100644
index 0000000..50e3045
--- /dev/null
+++ b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/internal/tasks/scala/AntScalaCompiler.groovy
@@ -0,0 +1,73 @@
+/*
+ * 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.tasks.scala
+
+import org.gradle.api.file.FileCollection
+import org.gradle.api.internal.project.IsolatedAntBuilder
+import org.gradle.api.tasks.WorkResult
+import org.gradle.api.tasks.scala.ScalaCompileOptions
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+class AntScalaCompiler implements ScalaCompiler {
+    private static Logger logger = LoggerFactory.getLogger(AntScalaCompiler)
+
+    private final IsolatedAntBuilder antBuilder
+    private final Iterable<File> bootclasspathFiles
+    private final Iterable<File> extensionDirs
+    FileCollection source
+    File destinationDir
+    Iterable<File> classpath
+    Iterable<File> scalaClasspath
+    ScalaCompileOptions scalaCompileOptions = new ScalaCompileOptions()
+    
+    def AntScalaCompiler(IsolatedAntBuilder antBuilder) {
+        this.antBuilder = antBuilder
+        this.bootclasspathFiles = []
+        this.extensionDirs = []
+    }
+
+    def AntScalaCompiler(IsolatedAntBuilder antBuilder, Iterable<File> bootclasspathFiles, Iterable<File> extensionDirs) {
+        this.antBuilder = antBuilder
+        this.bootclasspathFiles = bootclasspathFiles
+        this.extensionDirs = extensionDirs
+    }
+
+    WorkResult execute() {
+        Map options = ['destDir': destinationDir] + scalaCompileOptions.optionMap()
+        String taskName = scalaCompileOptions.useCompileDaemon ? 'fsc' : 'scalac'
+
+        antBuilder.withClasspath(scalaClasspath).execute { ant ->
+            taskdef(resource: 'scala/tools/ant/antlib.xml')
+
+            "${taskName}"(options) {
+                source.addToAntBuilder(ant, 'src', FileCollection.AntType.MatchingTask)
+                bootclasspathFiles.each {file ->
+                    bootclasspath(location: file)
+                }
+                extensionDirs.each {dir ->
+                    extdirs(location: dir)
+                }
+                classpath.each {file ->
+                    classpath(location: file)
+                }
+            }
+        }
+
+        return { true } as WorkResult
+    }
+
+}
diff --git a/subprojects/gradle-scala/src/main/groovy/org/gradle/api/internal/tasks/scala/DefaultScalaJavaJointCompiler.java b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/internal/tasks/scala/DefaultScalaJavaJointCompiler.java
new file mode 100644
index 0000000..5ba9b92
--- /dev/null
+++ b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/internal/tasks/scala/DefaultScalaJavaJointCompiler.java
@@ -0,0 +1,91 @@
+/*
+ * 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.scala;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.internal.tasks.compile.JavaCompiler;
+import org.gradle.api.tasks.WorkResult;
+import org.gradle.api.tasks.compile.CompileOptions;
+import org.gradle.api.tasks.scala.ScalaCompileOptions;
+import org.gradle.api.tasks.util.PatternFilterable;
+import org.gradle.api.tasks.util.PatternSet;
+
+import java.io.File;
+
+public class DefaultScalaJavaJointCompiler implements ScalaJavaJointCompiler {
+    private final ScalaCompiler scalaCompiler;
+    private final JavaCompiler javaCompiler;
+    private FileCollection source;
+
+    public DefaultScalaJavaJointCompiler(ScalaCompiler scalaCompiler, JavaCompiler javaCompiler) {
+        this.scalaCompiler = scalaCompiler;
+        this.javaCompiler = javaCompiler;
+    }
+
+    public ScalaCompileOptions getScalaCompileOptions() {
+        return scalaCompiler.getScalaCompileOptions();
+    }
+
+    public void setScalaClasspath(Iterable<File> classpath) {
+        scalaCompiler.setScalaClasspath(classpath);
+    }
+
+    public void setSource(FileCollection source) {
+        this.source = source;
+        scalaCompiler.setSource(source);
+    }
+
+    public void setDestinationDir(File destinationDir) {
+        scalaCompiler.setDestinationDir(destinationDir);
+        javaCompiler.setDestinationDir(destinationDir);
+    }
+
+    public void setClasspath(Iterable<File> classpath) {
+        scalaCompiler.setClasspath(classpath);
+        javaCompiler.setClasspath(classpath);
+    }
+
+    public CompileOptions getCompileOptions() {
+        return javaCompiler.getCompileOptions();
+    }
+
+    public void setSourceCompatibility(String sourceCompatibility) {
+        javaCompiler.setSourceCompatibility(sourceCompatibility);
+    }
+
+    public void setTargetCompatibility(String targetCompatibility) {
+        javaCompiler.setTargetCompatibility(targetCompatibility);
+    }
+
+    public WorkResult execute() {
+        scalaCompiler.execute();
+
+        PatternFilterable patternSet = new PatternSet();
+        patternSet.include("**/*.java");
+        FileTree javaSource = source.getAsFileTree().matching(patternSet);
+        if (!javaSource.isEmpty()) {
+            javaCompiler.setSource(javaSource);
+            javaCompiler.execute();
+        }
+
+        return new WorkResult() {
+            public boolean getDidWork() {
+                return true;
+            }
+        };
+    }
+}
diff --git a/subprojects/gradle-scala/src/main/groovy/org/gradle/api/internal/tasks/scala/IncrementalScalaCompiler.java b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/internal/tasks/scala/IncrementalScalaCompiler.java
new file mode 100644
index 0000000..4b0bde7
--- /dev/null
+++ b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/internal/tasks/scala/IncrementalScalaCompiler.java
@@ -0,0 +1,47 @@
+/*
+ * 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.scala;
+
+import org.gradle.api.internal.TaskOutputsInternal;
+import org.gradle.api.internal.tasks.compile.IncrementalJavaSourceCompiler;
+import org.gradle.api.internal.tasks.compile.SimpleStaleClassCleaner;
+import org.gradle.api.internal.tasks.compile.StaleClassCleaner;
+import org.gradle.api.tasks.scala.ScalaCompileOptions;
+
+import java.io.File;
+
+public class IncrementalScalaCompiler extends IncrementalJavaSourceCompiler<ScalaJavaJointCompiler>
+        implements ScalaJavaJointCompiler {
+    private final TaskOutputsInternal taskOutputs;
+
+    public IncrementalScalaCompiler(ScalaJavaJointCompiler compiler, TaskOutputsInternal taskOutputs) {
+        super(compiler);
+        this.taskOutputs = taskOutputs;
+    }
+
+    public ScalaCompileOptions getScalaCompileOptions() {
+        return getCompiler().getScalaCompileOptions();
+    }
+
+    public void setScalaClasspath(Iterable<File> classpath) {
+        getCompiler().setScalaClasspath(classpath);
+    }
+
+    @Override
+    protected StaleClassCleaner createCleaner() {
+        return new SimpleStaleClassCleaner(taskOutputs);
+    }
+}
diff --git a/subprojects/gradle-scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaCompiler.java b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaCompiler.java
new file mode 100644
index 0000000..854ce4b
--- /dev/null
+++ b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaCompiler.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.api.internal.tasks.scala;
+
+import org.gradle.api.internal.tasks.compile.Compiler;
+import org.gradle.api.tasks.scala.ScalaCompileOptions;
+
+import java.io.File;
+
+public interface ScalaCompiler extends Compiler {
+    ScalaCompileOptions getScalaCompileOptions();
+
+    void setScalaClasspath(Iterable<File> classpath);
+}
diff --git a/subprojects/gradle-scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaJavaJointCompiler.java b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaJavaJointCompiler.java
new file mode 100644
index 0000000..2b3a309
--- /dev/null
+++ b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/internal/tasks/scala/ScalaJavaJointCompiler.java
@@ -0,0 +1,21 @@
+/*
+ * 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.scala;
+
+import org.gradle.api.internal.tasks.compile.JavaSourceCompiler;
+
+public interface ScalaJavaJointCompiler extends ScalaCompiler, JavaSourceCompiler {
+}
diff --git a/subprojects/gradle-scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaBasePlugin.groovy b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaBasePlugin.groovy
new file mode 100644
index 0000000..000b5fb
--- /dev/null
+++ b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaBasePlugin.groovy
@@ -0,0 +1,76 @@
+/*
+ * 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.plugins.scala;
+
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.file.FileTreeElement
+import org.gradle.api.internal.tasks.DefaultScalaSourceSet
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.plugins.JavaPluginConvention
+import org.gradle.api.tasks.SourceSet
+import org.gradle.api.tasks.scala.ScalaCompile
+import org.gradle.api.tasks.scala.ScalaDoc
+
+public class ScalaBasePlugin implements Plugin<Project> {
+    // public configurations
+    public static final String SCALA_TOOLS_CONFIGURATION_NAME = "scalaTools";
+
+    public void apply(Project project) {
+        JavaBasePlugin javaPlugin = project.plugins.apply(JavaBasePlugin.class);
+
+        project.configurations.add(SCALA_TOOLS_CONFIGURATION_NAME).setVisible(false).setTransitive(true).
+                setDescription("The Scala tools libraries to be used for this Scala project.");
+
+        configureCompileDefaults(project, javaPlugin)
+        configureSourceSetDefaults(project, javaPlugin)
+        configureScaladoc(project);
+    }
+
+    private void configureSourceSetDefaults(Project project, JavaBasePlugin javaPlugin) {
+        project.convention.getPlugin(JavaPluginConvention.class).sourceSets.allObjects {SourceSet sourceSet ->
+            sourceSet.convention.plugins.scala = new DefaultScalaSourceSet(sourceSet.displayName, project.fileResolver)
+            sourceSet.scala.srcDir { project.file("src/$sourceSet.name/scala")}
+            sourceSet.allJava.add(sourceSet.scala.matching(sourceSet.java.filter))
+            sourceSet.allSource.add(sourceSet.scala)
+            sourceSet.resources.filter.exclude { FileTreeElement element -> sourceSet.scala.contains(element.file) }
+
+            String taskName = sourceSet.getCompileTaskName('scala')
+            ScalaCompile scalaCompile = project.tasks.add(taskName, ScalaCompile.class);
+            scalaCompile.dependsOn sourceSet.compileJavaTaskName
+            javaPlugin.configureForSourceSet(sourceSet, scalaCompile);
+            scalaCompile.description = "Compiles the $sourceSet.scala.";
+            scalaCompile.conventionMapping.defaultSource = { sourceSet.scala }
+
+            project.tasks[sourceSet.classesTaskName].dependsOn(taskName)
+        }
+    }
+
+    private void configureCompileDefaults(final Project project, JavaBasePlugin javaPlugin) {
+        project.tasks.withType(ScalaCompile.class).allTasks {ScalaCompile compile ->
+            compile.scalaClasspath = project.configurations[SCALA_TOOLS_CONFIGURATION_NAME]
+        }
+    }
+
+    private void configureScaladoc(final Project project) {
+        project.getTasks().withType(ScalaDoc.class).allTasks {ScalaDoc scalaDoc ->
+            scalaDoc.conventionMapping.destinationDir = { project.file("$project.docsDir/scaladoc") }
+            scalaDoc.conventionMapping.title = { project.apiDocTitle }
+            scalaDoc.scalaClasspath = project.configurations[SCALA_TOOLS_CONFIGURATION_NAME]
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaPlugin.groovy b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaPlugin.groovy
new file mode 100644
index 0000000..484c175
--- /dev/null
+++ b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/plugins/scala/ScalaPlugin.groovy
@@ -0,0 +1,45 @@
+/*
+ * 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.plugins.scala;
+
+
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.tasks.scala.ScalaDoc
+import org.gradle.api.plugins.JavaBasePlugin
+
+public class ScalaPlugin implements Plugin<Project> {
+    // tasks
+    public static final String SCALA_DOC_TASK_NAME = "scaladoc";
+
+    public void apply(Project project) {
+        project.plugins.apply(ScalaBasePlugin.class);
+        project.plugins.apply(JavaPlugin.class);
+
+        configureScaladoc(project);
+    }
+
+    private void configureScaladoc(final Project project) {
+        project.getTasks().withType(ScalaDoc.class).allTasks {ScalaDoc scalaDoc ->
+            scalaDoc.conventionMapping.classpath = { project.sourceSets.main.classes + project.sourceSets.main.compileClasspath }
+            scalaDoc.conventionMapping.defaultSource = { project.sourceSets.main.scala }
+        }
+        ScalaDoc scalaDoc = project.tasks.add(SCALA_DOC_TASK_NAME, ScalaDoc.class)
+        scalaDoc.description = "Generates scaladoc for the source code.";
+        scalaDoc.group = JavaBasePlugin.DOCUMENTATION_GROUP
+    }
+}
diff --git a/subprojects/gradle-scala/src/main/groovy/org/gradle/api/tasks/ScalaSourceSet.java b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/tasks/ScalaSourceSet.java
new file mode 100644
index 0000000..64d119e
--- /dev/null
+++ b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/tasks/ScalaSourceSet.java
@@ -0,0 +1,50 @@
+/*
+ * 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.tasks;
+
+import groovy.lang.Closure;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.file.SourceDirectorySet;
+
+/**
+ * A {@code ScalaSourceSetConvention} defines the properties and methods added to a {@link org.gradle.api.tasks.SourceSet}
+ * by the {@link org.gradle.api.plugins.scala.ScalaPlugin}.
+ */
+public interface ScalaSourceSet {
+    /**
+     * Returns the source to be compiled by the Scala compiler for this source set. This may contain both Java
+     * and Scala source files.
+     *
+     * @return The Scala source. Never returns null.
+     */
+    SourceDirectorySet getScala();
+
+    /**
+     * Configures the Scala source for this set. The given closure is used to configure the {@code SourceDirectorySet}
+     * which contains the Scala source.
+     *
+     * @param configureClosure The closure to use to configure the Scala source.
+     * @return this
+     */
+    ScalaSourceSet scala(Closure configureClosure);
+
+    /**
+     * All Scala source for this source set.
+     *
+     * @return the Scala source. Never returns null.
+     */
+    FileTree getAllScala();
+}
\ No newline at end of file
diff --git a/subprojects/gradle-scala/src/main/groovy/org/gradle/api/tasks/scala/AntScalaDoc.groovy b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/tasks/scala/AntScalaDoc.groovy
new file mode 100644
index 0000000..7402d73
--- /dev/null
+++ b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/tasks/scala/AntScalaDoc.groovy
@@ -0,0 +1,62 @@
+/*
+ * 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.tasks.scala
+
+import org.gradle.api.file.FileCollection
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.gradle.api.internal.project.IsolatedAntBuilder
+
+class AntScalaDoc {
+    private static Logger logger = LoggerFactory.getLogger(AntScalaDoc)
+
+    private final IsolatedAntBuilder antBuilder
+    private final Iterable<File> bootclasspathFiles
+    private final Iterable<File> extensionDirs
+
+    def AntScalaDoc(IsolatedAntBuilder antBuilder) {
+        this.antBuilder = antBuilder
+        this.bootclasspathFiles = []
+        this.extensionDirs = []
+    }
+
+    def AntScalaDoc(IsolatedAntBuilder antBuilder, Iterable<File> bootclasspathFiles, Iterable<File> extensionDirs) {
+        this.antBuilder = antBuilder
+        this.bootclasspathFiles = bootclasspathFiles
+        this.extensionDirs = extensionDirs
+    }
+
+    void execute(FileCollection source, File targetDir, Iterable<File> classpathFiles, Iterable<File> scalaClasspath, ScalaDocOptions docOptions) {
+        antBuilder.withClasspath(scalaClasspath).execute { ant ->
+            taskdef(resource: 'scala/tools/ant/antlib.xml')
+
+            Map options = ['destDir': targetDir] + docOptions.optionMap()
+
+            scaladoc(options) {
+                source.addToAntBuilder(ant, 'src', FileCollection.AntType.MatchingTask)
+                bootclasspathFiles.each {file ->
+                    bootclasspath(location: file)
+                }
+                extensionDirs.each {dir ->
+                    extdirs(location: dir)
+                }
+                classpathFiles.each {file ->
+                    classpath(location: file)
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaCompile.java b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaCompile.java
new file mode 100644
index 0000000..c3f866f
--- /dev/null
+++ b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaCompile.java
@@ -0,0 +1,82 @@
+/*
+ * 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.tasks.scala;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.file.FileTree;
+import org.gradle.api.internal.project.AntBuilderFactory;
+import org.gradle.api.internal.project.IsolatedAntBuilder;
+import org.gradle.api.internal.tasks.compile.AntJavaCompiler;
+import org.gradle.api.internal.tasks.compile.JavaCompiler;
+import org.gradle.api.internal.tasks.scala.*;
+import org.gradle.api.tasks.InputFiles;
+import org.gradle.api.tasks.Nested;
+import org.gradle.api.tasks.compile.AbstractCompile;
+import org.gradle.api.tasks.compile.CompileOptions;
+
+/**
+ * Task to perform scala compilation.
+ */
+public class ScalaCompile extends AbstractCompile {
+    private FileCollection scalaClasspath;
+
+    private ScalaJavaJointCompiler compiler;
+
+    public ScalaCompile() {
+        ScalaCompiler scalaCompiler = new AntScalaCompiler(getServices().get(IsolatedAntBuilder.class));
+        JavaCompiler javaCompiler = new AntJavaCompiler(getServices().get(AntBuilderFactory.class));
+        compiler = new IncrementalScalaCompiler(new DefaultScalaJavaJointCompiler(scalaCompiler, javaCompiler), getOutputs());
+    }
+
+    @InputFiles
+    public FileCollection getScalaClasspath() {
+        return scalaClasspath;
+    }
+
+    public void setScalaClasspath(FileCollection scalaClasspath) {
+        this.scalaClasspath = scalaClasspath;
+    }
+
+    public ScalaJavaJointCompiler getCompiler() {
+        return compiler;
+    }
+
+    public void setCompiler(ScalaJavaJointCompiler compiler) {
+        this.compiler = compiler;
+    }
+
+    @Nested
+    public ScalaCompileOptions getScalaCompileOptions() {
+        return compiler.getScalaCompileOptions();
+    }
+
+    @Nested
+    public CompileOptions getOptions() {
+        return compiler.getCompileOptions();
+    }
+
+    @Override
+    protected void compile() {
+        FileTree source = getSource();
+        compiler.setSource(source);
+        compiler.setDestinationDir(getDestinationDir());
+        compiler.setClasspath(getClasspath());
+        compiler.setScalaClasspath(getScalaClasspath());
+        compiler.setSourceCompatibility(getSourceCompatibility());
+        compiler.setTargetCompatibility(getTargetCompatibility());
+        compiler.execute();
+    }
+}
diff --git a/subprojects/gradle-scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaCompileOptions.groovy b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaCompileOptions.groovy
new file mode 100644
index 0000000..225928e
--- /dev/null
+++ b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaCompileOptions.groovy
@@ -0,0 +1,146 @@
+/*
+ * 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.tasks.scala
+
+import org.gradle.api.tasks.compile.AbstractOptions
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.Optional
+
+public class ScalaCompileOptions extends AbstractOptions {
+
+    /**
+     * Whether to use the fsc compile daemon.
+     */
+    boolean useCompileDaemon = false
+
+    // NOTE: Does not work for scalac 2.7.1 due to a bug in the ant task
+    /**
+     * Server (host:port) on which the compile daemon is running.
+     * The host must share disk access with the client process.
+     * If not specified, launches the daemon on the localhost.
+     * This parameter can only be specified if useCompileDaemon is true.
+     */
+    String daemonServer;
+
+    /**
+     * Fail the build on compilation errors.
+     */
+    boolean failOnError = true
+
+    /**
+     * Generate deprecation information.
+     */
+    boolean deprecation = true
+
+    /**
+     * Generate unchecked information.
+     */
+    boolean unchecked = true
+
+    /**
+     * Generate debugging information.
+     * Legal values: none, source, line, vars, notailcalls
+     */
+    @Input @Optional
+    String debugLevel
+
+    /**
+     * Run optimisations.
+     */
+    @Input
+    boolean optimize = false
+
+    /**
+     * Encoding of source files.
+     */
+    @Input @Optional
+    String encoding = null
+
+    /**
+     * Whether to force the compilation of all files.
+     * Legal values:
+     * - never (only compile modified files)
+     * - changed (compile all files when at least one file is modified)
+     * - always (always recompile all files)
+     */
+    String force = "never"
+
+    /**
+     * Specifies which backend to use.
+     * Legal values: 1.4, 1.5
+     */
+    @Input
+    String targetCompatibility = '1.5'
+
+    /**
+     * Additional parameters passed to the compiler.
+     * Each parameter must start with '-'.
+     */
+    List<String> additionalParameters
+
+    /**
+     * List files to be compiled.
+     */
+    boolean listFiles
+
+    /**
+     * Specifies the amount of logging.
+     * Legal values:  none, verbose, debug
+     */
+    String loggingLevel
+
+    /**
+     * Phases of the compiler to log.
+     * Legal values: namer, typer, pickler, uncurry, tailcalls, transmatch, explicitouter, erasure,
+     *               lambdalift, flatten, constructors, mixin, icode, jvm, terminal.
+     */
+    List<String> loggingPhases
+
+
+    Map fieldName2AntMap() {
+        [
+                failOnError: 'failonerror',
+                loggingLevel: 'logging',
+                loggingPhases: 'logPhase',
+                targetCompatibility: 'target',
+                optimize: 'optimise',
+                daemonServer: 'server',
+                listFiles: 'scalacDebugging',
+                debugLevel: 'debugInfo',
+                additionalParameters: 'addParams'
+        ]
+    }
+
+    Map fieldValue2AntMap() {
+        [
+                deprecation: {toOnOffString(deprecation)},
+                unchecked: {toOnOffString(unchecked)},
+                optimize: {toOnOffString(optimize)},
+                targetCompatibility: {"jvm-${targetCompatibility}"},
+                loggingPhases: {loggingPhases.isEmpty() ? ' ' : loggingPhases.join(',')},
+                additionalParameters: {additionalParameters.isEmpty() ? ' ' : additionalParameters.join(' ')},
+        ]
+    }
+
+    List excludedFieldsFromOptionMap() {
+        ['useCompileDaemon']
+    }
+
+    private String toOnOffString(value) {
+        return value ? 'on' : 'off'
+    }
+
+}
diff --git a/subprojects/gradle-scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaDoc.java b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaDoc.java
new file mode 100644
index 0000000..695a79c
--- /dev/null
+++ b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaDoc.java
@@ -0,0 +1,109 @@
+/*
+ * 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.tasks.scala;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.project.IsolatedAntBuilder;
+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.util.GUtil;
+
+import java.io.File;
+
+/**
+ * Task to generate Scala API documentation.
+ */
+public class ScalaDoc extends SourceTask {
+
+    private File destinationDir;
+
+    private FileCollection classpath;
+    private FileCollection scalaClasspath;
+    private AntScalaDoc antScalaDoc = new AntScalaDoc(getServices().get(IsolatedAntBuilder.class));
+    private ScalaDocOptions scalaDocOptions = new ScalaDocOptions();
+    private String title;
+
+    public AntScalaDoc getAntScalaDoc() {
+        return antScalaDoc;
+    }
+
+    public void setAntScalaDoc(AntScalaDoc antScalaDoc) {
+        this.antScalaDoc = antScalaDoc;
+    }
+
+    @OutputDirectory
+    public File getDestinationDir() {
+        return destinationDir;
+    }
+
+    public void setDestinationDir(File destinationDir) {
+        this.destinationDir = destinationDir;
+    }
+
+    /**
+     * <p>Returns the classpath to use to locate classes referenced by the documented source.</p>
+     *
+     * @return The classpath.
+     */
+    @InputFiles
+    public Iterable<File> getClasspath() {
+        return classpath;
+    }
+
+    public void setClasspath(FileCollection classpath) {
+        this.classpath = classpath;
+    }
+
+    @InputFiles
+    public FileCollection getScalaClasspath() {
+        return scalaClasspath;
+    }
+
+    public void setScalaClasspath(FileCollection scalaClasspath) {
+        this.scalaClasspath = scalaClasspath;
+    }
+
+    public ScalaDocOptions getScalaDocOptions() {
+        return scalaDocOptions;
+    }
+
+    public void setScalaDocOptions(ScalaDocOptions scalaDocOptions) {
+        this.scalaDocOptions = scalaDocOptions;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    @TaskAction
+    protected void generate() {
+        ScalaDocOptions options = getScalaDocOptions();
+        if (!GUtil.isTrue(options.getDocTitle())) {
+            options.setDocTitle(getTitle());
+        }
+        if (!GUtil.isTrue(options.getWindowTitle())) {
+            options.setWindowTitle(getTitle());
+        }
+        getAntScalaDoc().execute(getSource(), getDestinationDir(), getClasspath(), getScalaClasspath(), options);
+    }
+
+}
diff --git a/subprojects/gradle-scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaDocOptions.groovy b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaDocOptions.groovy
new file mode 100644
index 0000000..7e30ab9
--- /dev/null
+++ b/subprojects/gradle-scala/src/main/groovy/org/gradle/api/tasks/scala/ScalaDocOptions.groovy
@@ -0,0 +1,92 @@
+/*
+ * 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.tasks.scala
+
+import org.gradle.api.tasks.compile.AbstractOptions
+
+public class ScalaDocOptions extends AbstractOptions {
+
+    /**
+     * Generate deprecation information.
+     */
+    boolean deprecation = true
+
+    /**
+     * Generate unchecked information.
+     */
+    boolean unchecked = true
+
+    /**
+     * Text to appear in the window title.
+     */
+    String windowTitle
+
+    /**
+     * Html text to appear in the main frame title.
+     */
+    String docTitle
+
+    /**
+     * Html text to appear in the header for each page.
+     */
+    String header
+
+    /**
+     * Html text to appear in the footer for each page.
+     */
+    String footer
+
+    /**
+     * Html text to appear in the top text for each page.
+     */
+    String top
+
+    /**
+     * Html text to appear in the bottom text for each page.
+     */
+    String bottom
+
+    /**
+     * Style sheet to override default style.
+     */
+    File styleSheet
+
+    /**
+     * Additional parameters passed to the compiler.
+     * Each parameter must start with '-'.
+     */
+    List<String> additionalParameters
+
+
+    Map fieldName2AntMap() {
+        [
+                additionalParameters: 'addParams'
+        ]
+    }
+
+    Map fieldValue2AntMap() {
+        [
+                deprecation: {toOnOffString(deprecation)},
+                unchecked: {toOnOffString(unchecked)},
+                additionalParameters: {additionalParameters.isEmpty() ? ' ' : additionalParameters.join(' ')},
+        ]
+    }
+
+    private String toOnOffString(value) {
+        return value ? 'on' : 'off'
+    }
+
+}
diff --git a/subprojects/gradle-scala/src/main/resources/META-INF/gradle-plugins/scala-base.properties b/subprojects/gradle-scala/src/main/resources/META-INF/gradle-plugins/scala-base.properties
new file mode 100644
index 0000000..ebabb57
--- /dev/null
+++ b/subprojects/gradle-scala/src/main/resources/META-INF/gradle-plugins/scala-base.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+implementation-class=org.gradle.api.plugins.scala.ScalaBasePlugin
diff --git a/subprojects/gradle-scala/src/main/resources/META-INF/gradle-plugins/scala.properties b/subprojects/gradle-scala/src/main/resources/META-INF/gradle-plugins/scala.properties
new file mode 100644
index 0000000..2011420
--- /dev/null
+++ b/subprojects/gradle-scala/src/main/resources/META-INF/gradle-plugins/scala.properties
@@ -0,0 +1,16 @@
+#
+# 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.
+#
+implementation-class=org.gradle.api.plugins.scala.ScalaPlugin
diff --git a/subprojects/gradle-scala/src/test/groovy/org/gradle/api/internal/tasks/DefaultScalaSourceSetTest.groovy b/subprojects/gradle-scala/src/test/groovy/org/gradle/api/internal/tasks/DefaultScalaSourceSetTest.groovy
new file mode 100644
index 0000000..eb3dac1
--- /dev/null
+++ b/subprojects/gradle-scala/src/test/groovy/org/gradle/api/internal/tasks/DefaultScalaSourceSetTest.groovy
@@ -0,0 +1,49 @@
+/*
+ * 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.tasks
+
+import org.gradle.api.internal.file.DefaultSourceDirectorySet
+import org.gradle.api.internal.file.FileResolver
+import org.gradle.api.internal.file.UnionFileTree
+import org.junit.Test
+import static org.gradle.util.Matchers.*
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.*
+
+class DefaultScalaSourceSetTest {
+    private final DefaultScalaSourceSet sourceSet = new DefaultScalaSourceSet("<set-display-name>", [resolve: {it as File}] as FileResolver)
+
+    @Test
+    public void defaultValues() {
+        assertThat(sourceSet.scala, instanceOf(DefaultSourceDirectorySet))
+        assertThat(sourceSet.scala, isEmpty())
+        assertThat(sourceSet.scala.displayName, equalTo('<set-display-name> Scala source'))
+
+        assertThat(sourceSet.scalaSourcePatterns.includes, equalTo(['**/*.scala'] as Set))
+        assertThat(sourceSet.scalaSourcePatterns.excludes, isEmpty())
+
+        assertThat(sourceSet.allScala, instanceOf(UnionFileTree))
+        assertThat(sourceSet.allScala, isEmpty())
+        assertThat(sourceSet.allScala.displayName, equalTo('<set-display-name> Scala source'))
+        assertThat(sourceSet.allScala.sourceTrees, not(isEmpty()))
+    }
+
+    @Test
+    public void canConfigureScalaSource() {
+        sourceSet.scala { srcDir 'src/scala' }
+        assertThat(sourceSet.scala.srcDirs, equalTo([new File('src/scala')] as Set))
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-scala/src/test/groovy/org/gradle/api/internal/tasks/scala/DefaultScalaJavaJointCompilerTest.groovy b/subprojects/gradle-scala/src/test/groovy/org/gradle/api/internal/tasks/scala/DefaultScalaJavaJointCompilerTest.groovy
new file mode 100644
index 0000000..f55713b
--- /dev/null
+++ b/subprojects/gradle-scala/src/test/groovy/org/gradle/api/internal/tasks/scala/DefaultScalaJavaJointCompilerTest.groovy
@@ -0,0 +1,61 @@
+/*
+ * 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.scala
+
+import spock.lang.Specification
+import org.gradle.api.internal.tasks.compile.JavaCompiler
+import org.gradle.api.file.FileCollection
+import org.gradle.api.file.FileTree
+
+class DefaultScalaJavaJointCompilerTest extends Specification {
+    private final ScalaCompiler scalaCompiler = Mock()
+    private final JavaCompiler javaCompiler = Mock()
+    private final FileCollection source = Mock()
+    private final FileTree sourceTree = Mock()
+    private final FileTree javaSource = Mock()
+    private final DefaultScalaJavaJointCompiler compiler = new DefaultScalaJavaJointCompiler(scalaCompiler, javaCompiler)
+
+    def executesScalaCompilerThenJavaCompiler() {
+        compiler.source = source
+
+        when:
+        def result = compiler.execute()
+
+        then:
+        result.didWork
+        1 * scalaCompiler.execute()
+        1 * source.getAsFileTree() >> sourceTree
+        1 * sourceTree.matching(!null) >> javaSource
+        _ * javaSource.isEmpty() >> false
+        1 * javaCompiler.setSource(!null)
+        1 * javaCompiler.execute()
+    }
+
+    def doesNotInvokeJavaCompilerWhenNoJavaSource() {
+        compiler.source = source
+
+        when:
+        def result = compiler.execute()
+
+        then:
+        result.didWork
+        1 * scalaCompiler.execute()
+        1 * source.getAsFileTree() >> sourceTree
+        1 * sourceTree.matching(!null) >> javaSource
+        _ * javaSource.isEmpty() >> true
+        0 * javaCompiler._
+    }
+}
diff --git a/subprojects/gradle-scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaBasePluginTest.groovy b/subprojects/gradle-scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaBasePluginTest.groovy
new file mode 100644
index 0000000..06c1328
--- /dev/null
+++ b/subprojects/gradle-scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaBasePluginTest.groovy
@@ -0,0 +1,96 @@
+/*
+ * 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.plugins.scala
+
+import static org.hamcrest.Matchers.*
+
+import org.gradle.api.Project
+import org.gradle.api.internal.artifacts.configurations.Configurations
+import org.gradle.api.plugins.JavaBasePlugin
+import org.gradle.api.tasks.scala.ScalaCompile
+import org.gradle.api.tasks.scala.ScalaDoc
+import org.gradle.util.HelperUtil
+import org.junit.Test
+import static org.gradle.util.Matchers.*
+import static org.gradle.util.WrapUtil.*
+import static org.junit.Assert.*
+
+public class ScalaBasePluginTest {
+
+    private final Project project = HelperUtil.createRootProject()
+    private final ScalaBasePlugin scalaPlugin = new ScalaBasePlugin()
+
+    @Test public void appliesTheJavaPluginToTheProject() {
+        scalaPlugin.apply(project)
+        assertTrue(project.getPlugins().hasPlugin(JavaBasePlugin))
+    }
+
+    @Test public void addsScalaToolsConfigurationToTheProject() {
+        scalaPlugin.apply(project)
+        def configuration = project.configurations.getByName(ScalaBasePlugin.SCALA_TOOLS_CONFIGURATION_NAME)
+        assertThat(Configurations.getNames(configuration.extendsFrom, false), equalTo(toSet()))
+        assertFalse(configuration.visible)
+        assertTrue(configuration.transitive)
+    }
+
+    @Test public void addsScalaConventionToNewSourceSet() {
+        scalaPlugin.apply(project)
+
+        def sourceSet = project.sourceSets.add('custom')
+        assertThat(sourceSet.scala.displayName, equalTo("custom Scala source"))
+        assertThat(sourceSet.scala.srcDirs, equalTo(toLinkedSet(project.file("src/custom/scala"))))
+    }
+
+    @Test public void addsCompileTaskForNewSourceSet() {
+        scalaPlugin.apply(project)
+
+        project.sourceSets.add('custom')
+        def task = project.tasks['compileCustomScala']
+        assertThat(task, instanceOf(ScalaCompile.class))
+        assertThat(task.description, equalTo('Compiles the custom Scala source.'))
+        assertThat(task.classpath, equalTo(project.sourceSets.custom.compileClasspath))
+        assertThat(task.scalaClasspath, equalTo(project.configurations[ScalaBasePlugin.SCALA_TOOLS_CONFIGURATION_NAME]))
+        assertThat(task.defaultSource, equalTo(project.sourceSets.custom.scala))
+        assertThat(task, dependsOn('compileCustomJava'))
+    }
+    
+    @Test public void dependenciesOfJavaPluginTasksIncludeScalaCompileTasks() {
+        scalaPlugin.apply(project)
+
+        project.sourceSets.add('custom')
+        def task = project.tasks['customClasses']
+        assertThat(task, dependsOn(hasItem('compileCustomScala')))
+    }
+
+    @Test public void configuresCompileTasksDefinedByTheBuildScript() {
+        scalaPlugin.apply(project)
+
+        def task = project.createTask('otherCompile', type: ScalaCompile)
+        assertThat(task.defaultSource, nullValue())
+        assertThat(task.scalaClasspath, equalTo(project.configurations[ScalaBasePlugin.SCALA_TOOLS_CONFIGURATION_NAME]))
+        assertThat(task, dependsOn())
+    }
+
+    @Test public void configuresScalaDocTasksDefinedByTheBuildScript() {
+        scalaPlugin.apply(project)
+
+        def task = project.createTask('otherScaladoc', type: ScalaDoc)
+        assertThat(task.destinationDir, equalTo(project.file("$project.docsDir/scaladoc")))
+        assertThat(task.title, equalTo(project.apiDocTitle))
+        assertThat(task.scalaClasspath, equalTo(project.configurations[ScalaBasePlugin.SCALA_TOOLS_CONFIGURATION_NAME]))
+        assertThat(task, dependsOn())
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaPluginTest.groovy b/subprojects/gradle-scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaPluginTest.groovy
new file mode 100644
index 0000000..b0c3035
--- /dev/null
+++ b/subprojects/gradle-scala/src/test/groovy/org/gradle/api/plugins/scala/ScalaPluginTest.groovy
@@ -0,0 +1,101 @@
+/*
+ * 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.plugins.scala
+
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.tasks.scala.ScalaCompile
+import org.gradle.api.tasks.scala.ScalaDoc
+import org.gradle.util.HelperUtil
+import org.junit.Test
+import static org.gradle.util.Matchers.dependsOn
+import static org.gradle.util.WrapUtil.toLinkedSet
+import static org.hamcrest.Matchers.*
+import static org.junit.Assert.assertThat
+import static org.junit.Assert.assertTrue
+
+public class ScalaPluginTest {
+
+    private final Project project = HelperUtil.createRootProject()
+    private final ScalaPlugin scalaPlugin = new ScalaPlugin()
+
+    @Test public void appliesTheJavaPluginToTheProject() {
+        scalaPlugin.apply(project)
+        assertTrue(project.getPlugins().hasPlugin(JavaPlugin))
+    }
+
+    @Test public void addsScalaConventionToEachSourceSetAndAppliesMappings() {
+        scalaPlugin.apply(project)
+
+        def sourceSet = project.sourceSets.main
+        assertThat(sourceSet.scala.displayName, equalTo("main Scala source"))
+        assertThat(sourceSet.scala.srcDirs, equalTo(toLinkedSet(project.file("src/main/scala"))))
+
+        sourceSet = project.sourceSets.test
+        assertThat(sourceSet.scala.displayName, equalTo("test Scala source"))
+        assertThat(sourceSet.scala.srcDirs, equalTo(toLinkedSet(project.file("src/test/scala"))))
+    }
+
+    @Test public void addsCompileTaskForEachSourceSet() {
+        scalaPlugin.apply(project)
+
+        def task = project.tasks['compileScala']
+        assertThat(task, instanceOf(ScalaCompile.class))
+        assertThat(task.description, equalTo('Compiles the main Scala source.'))
+        assertThat(task.classpath, equalTo(project.sourceSets.main.compileClasspath))
+        assertThat(task.defaultSource, equalTo(project.sourceSets.main.scala))
+        assertThat(task, dependsOn(JavaPlugin.COMPILE_JAVA_TASK_NAME))
+
+        task = project.tasks['compileTestScala']
+        assertThat(task, instanceOf(ScalaCompile.class))
+        assertThat(task.description, equalTo('Compiles the test Scala source.'))
+        assertThat(task.classpath, equalTo(project.sourceSets.test.compileClasspath))
+        assertThat(task.defaultSource, equalTo(project.sourceSets.test.scala))
+        assertThat(task, dependsOn(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME, JavaPlugin.CLASSES_TASK_NAME))
+    }
+
+    @Test public void dependenciesOfJavaPluginTasksIncludeScalaCompileTasks() {
+        scalaPlugin.apply(project)
+
+        def task = project.tasks[JavaPlugin.CLASSES_TASK_NAME]
+        assertThat(task, dependsOn(hasItem('compileScala')))
+
+        task = project.tasks[JavaPlugin.TEST_CLASSES_TASK_NAME]
+        assertThat(task, dependsOn(hasItem('compileTestScala')))
+    }
+    
+    @Test public void addsScalaDocTasksToTheProject() {
+        scalaPlugin.apply(project)
+
+        def task = project.tasks[ScalaPlugin.SCALA_DOC_TASK_NAME]
+        assertThat(task, instanceOf(ScalaDoc.class))
+        assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
+        assertThat(task.destinationDir, equalTo(project.file("$project.docsDir/scaladoc")))
+        assertThat(task.defaultSource, equalTo(project.sourceSets.main.scala))
+        assertThat(task.classpath.sourceCollections, hasItem(project.sourceSets.main.classes))
+        assertThat(task.classpath.sourceCollections, hasItem(project.sourceSets.main.compileClasspath))
+        assertThat(task.title, equalTo(project.apiDocTitle))
+    }
+
+    @Test public void configuresScalaDocTasksDefinedByTheBuildScript() {
+        scalaPlugin.apply(project)
+
+        def task = project.createTask('otherScaladoc', type: ScalaDoc)
+        assertThat(task, dependsOn(JavaPlugin.CLASSES_TASK_NAME))
+        assertThat(task.classpath.sourceCollections, hasItem(project.sourceSets.main.classes))
+        assertThat(task.classpath.sourceCollections, hasItem(project.sourceSets.main.compileClasspath))
+    }
+}
diff --git a/subprojects/gradle-scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaCompileOptionsTest.groovy b/subprojects/gradle-scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaCompileOptionsTest.groovy
new file mode 100644
index 0000000..441b685
--- /dev/null
+++ b/subprojects/gradle-scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaCompileOptionsTest.groovy
@@ -0,0 +1,138 @@
+/*
+ * 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.tasks.scala
+
+import org.junit.Before
+import org.junit.Test
+import static org.hamcrest.Matchers.equalTo
+import static org.junit.Assert.*
+
+public class ScalaCompileOptionsTest {
+
+    private ScalaCompileOptions compileOptions;
+
+    @Before public void setUp() {
+        compileOptions = new ScalaCompileOptions()
+    }
+
+    @Test public void testOptionMapDoesNotContainCompileDaemon() {
+        String antProperty = 'useCompileDaemon'
+        assertFalse(compileOptions.useCompileDaemon)
+        assertFalse(compileOptions.optionMap().containsKey(antProperty))
+
+        compileOptions.useCompileDaemon = true
+        assertFalse(compileOptions.optionMap().containsKey(antProperty))
+    }
+
+    @Test public void testOptionMapContainsDaemonServerIfSpecified() {
+        assertSimpleStringValue('daemonServer', 'server', null, 'host:9000')
+    }
+
+    @Test public void testOptionMapContainsFailOnError() {
+        assertBooleanValue('failOnError', 'failonerror', true)
+    }
+
+    @Test public void testOptionMapContainsDeprecation() {
+        assertOnOffValue('deprecation', 'deprecation', true)
+    }
+
+    @Test public void testOptionMapContainsUnchecked() {
+        assertOnOffValue('unchecked', 'unchecked', true)
+    }
+
+    @Test public void testOptionMapContainsDebugLevelIfSpecified() {
+        assertSimpleStringValue('debugLevel', 'debugInfo', null, 'line')
+    }
+
+    @Test public void testOptionMapContainsOptimize() {
+        assertOnOffValue('optimize', 'optimise', false)
+    }
+
+    @Test public void testOptionMapContainsEncodingIfSpecified() {
+        assertSimpleStringValue('encoding', 'encoding', null, 'utf8')
+    }
+
+    @Test public void testOptionMapContainsForce() {
+        assertSimpleStringValue('force', 'force', 'never', 'changed')
+    }
+
+    @Test public void testOptionMapContainsTargetCompatibility() {
+        String antProperty = 'target'
+        assertThat(compileOptions.targetCompatibility as String, equalTo('1.5'))
+        assertThat(compileOptions.optionMap()[antProperty] as String, equalTo('jvm-1.5'))
+        compileOptions.targetCompatibility = '1.4'
+        assertThat(compileOptions.optionMap()['target'] as String, equalTo('jvm-1.4'))
+    }
+
+    @Test public void testOptionMapContainsValuesForAdditionalParameters() {
+        String antProperty = 'addParams'
+        assertNull(compileOptions.additionalParameters)
+        assertFalse(compileOptions.optionMap().containsKey(antProperty))
+
+        compileOptions.additionalParameters = ['-opt1', '-opt2']
+        assertThat(compileOptions.optionMap()[antProperty] as String, equalTo('-opt1 -opt2' as String))
+    }
+
+    @Test public void testOptionMapContainsListFiles() {
+        assertBooleanValue('listFiles', 'scalacDebugging', false)
+    }
+
+    @Test public void testOptionMapContainsLoggingLevelIfSpecified() {
+        assertSimpleStringValue('loggingLevel', 'logging', null, 'verbose')
+    }
+
+    @Test public void testOptionMapContainsValueForLoggingPhase() {
+        String antProperty = 'logPhase'
+        Map optionMap = compileOptions.optionMap()
+        assertFalse(optionMap.containsKey(antProperty))
+
+        compileOptions.loggingPhases = ['pickler', 'tailcalls']
+        optionMap = compileOptions.optionMap()
+        assertThat(optionMap[antProperty] as String, equalTo('pickler,tailcalls' as String))
+    }
+
+    private def assertBooleanValue(String fieldName, String antProperty, boolean defaultValue) {
+        assertThat(compileOptions."$fieldName" as boolean, equalTo(defaultValue))
+
+        compileOptions."$fieldName" = true
+        assertThat(compileOptions.optionMap()[antProperty] as String, equalTo('true'))
+
+        compileOptions."$fieldName" = false
+        assertThat(compileOptions.optionMap()[antProperty] as String, equalTo('false'))
+    }
+
+    private def assertOnOffValue(String fieldName, String antProperty, boolean defaultValue) {
+        assertThat(compileOptions."$fieldName" as boolean, equalTo(defaultValue))
+
+        compileOptions."$fieldName" = true
+        assertThat(compileOptions.optionMap()[antProperty] as String, equalTo('on'))
+
+        compileOptions."$fieldName" = false
+        assertThat(compileOptions.optionMap()[antProperty] as String, equalTo('off'))
+    }
+
+    private def assertSimpleStringValue(String fieldName, String antProperty, String defaultValue, String testValue) {
+        assertThat(compileOptions."${fieldName}" as String, equalTo(defaultValue))
+        if (defaultValue == null) {
+            assertFalse(compileOptions.optionMap().containsKey(antProperty))
+        } else {
+            assertThat(compileOptions.optionMap()[antProperty] as String, equalTo(defaultValue))
+        }
+        compileOptions."${fieldName}" = testValue
+        assertThat(compileOptions.optionMap()[antProperty] as String, equalTo(testValue))
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaCompileTest.java b/subprojects/gradle-scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaCompileTest.java
new file mode 100644
index 0000000..8feeca2
--- /dev/null
+++ b/subprojects/gradle-scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaCompileTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.tasks.scala;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.ConventionTask;
+import org.gradle.api.internal.tasks.scala.ScalaJavaJointCompiler;
+import org.gradle.api.tasks.compile.AbstractCompile;
+import org.gradle.api.tasks.compile.AbstractCompileTest;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.gradle.util.Matchers.*;
+
+public class ScalaCompileTest extends AbstractCompileTest {
+
+    private ScalaCompile scalaCompile;
+
+    private ScalaJavaJointCompiler scalaCompiler;
+    private JUnit4Mockery context = new JUnit4GroovyMockery();
+
+    @Override
+    public AbstractCompile getCompile() {
+        return scalaCompile;
+    }
+
+    @Override
+    public ConventionTask getTask() {
+        return scalaCompile;
+    }
+
+    @Override
+    @Before
+    public void setUp() {
+        super.setUp();
+        scalaCompile = createTask(ScalaCompile.class);
+        scalaCompiler = context.mock(ScalaJavaJointCompiler.class);
+        scalaCompile.setCompiler(scalaCompiler);
+
+        GFileUtils.touch(new File(srcDir, "incl/file.scala"));
+        GFileUtils.touch(new File(srcDir, "incl/file.java"));
+    }
+
+    @Test
+    public void testExecuteDoingWork() {
+        setUpMocksAndAttributes(scalaCompile);
+        context.checking(new Expectations() {{
+            one(scalaCompiler).setSource(with(hasSameItems(scalaCompile.getSource())));
+            one(scalaCompiler).setDestinationDir(scalaCompile.getDestinationDir());
+            one(scalaCompiler).setClasspath(scalaCompile.getClasspath());
+            one(scalaCompiler).setScalaClasspath(scalaCompile.getScalaClasspath());
+            one(scalaCompiler).setSourceCompatibility(scalaCompile.getSourceCompatibility());
+            one(scalaCompiler).setTargetCompatibility(scalaCompile.getTargetCompatibility());
+            one(scalaCompiler).execute();
+        }});
+
+        scalaCompile.compile();
+    }
+
+    protected void setUpMocksAndAttributes(final ScalaCompile compile) {
+        compile.source(srcDir);
+        compile.setIncludes(TEST_INCLUDES);
+        compile.setExcludes(TEST_EXCLUDES);
+        compile.setSourceCompatibility("1.5");
+        compile.setTargetCompatibility("1.5");
+        compile.setDestinationDir(destDir);
+        compile.setScalaClasspath(context.mock(FileCollection.class));
+
+        final FileCollection configuration = context.mock(FileCollection.class);
+        context.checking(new Expectations(){{
+            allowing(configuration).iterator();
+            will(returnIterator(TEST_DEPENDENCY_MANAGER_CLASSPATH));
+        }});
+
+        compile.setClasspath(configuration);
+    }
+
+}
diff --git a/subprojects/gradle-scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaDocOptionsTest.groovy b/subprojects/gradle-scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaDocOptionsTest.groovy
new file mode 100644
index 0000000..ec8bd60
--- /dev/null
+++ b/subprojects/gradle-scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaDocOptionsTest.groovy
@@ -0,0 +1,97 @@
+/*
+ * 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.tasks.scala
+
+import org.junit.Test
+import static org.hamcrest.Matchers.equalTo
+import static org.junit.Assert.*
+
+public class ScalaDocOptionsTest {
+
+    private ScalaDocOptions docOptions = new ScalaDocOptions()
+
+    @Test public void testOptionMapContainsDeprecation() {
+        assertOnOffValue('deprecation', 'deprecation', true)
+    }
+
+    @Test public void testOptionMapContainsUnchecked() {
+        assertOnOffValue('unchecked', 'unchecked', true)
+    }
+
+    @Test public void testOptionMapContainsWindowTitleIfSpecified() {
+        assertSimpleStringValue('windowTitle', 'windowTitle', null, 'title-value')
+    }
+
+    @Test public void testOptionMapContainsDocTitleIfSpecified() {
+        assertSimpleStringValue('docTitle', 'docTitle', null, 'title-value')
+    }
+
+    @Test public void testOptionMapContainsHeaderIfSpecified() {
+        assertSimpleStringValue('header', 'header', null, 'header-value')
+    }
+
+    @Test public void testOptionMapContainsFooterIfSpecified() {
+        assertSimpleStringValue('footer', 'footer', null, 'footer-value')
+    }
+
+    @Test public void testOptionMapContainsTopIfSpecified() {
+        assertSimpleStringValue('top', 'top', null, 'top-value')
+    }
+
+    @Test public void testOptionMapContainsBottomIfSpecified() {
+        assertSimpleStringValue('bottom', 'bottom', null, 'bottom-value')
+    }
+
+    @Test public void testOptionMapContainsStyleSheetIfSpecified() {
+        String antProperty = 'styleSheet'
+        assertNull(docOptions.styleSheet)
+        assertFalse(docOptions.optionMap().containsKey(antProperty))
+        File file = new File('abc')
+        docOptions.styleSheet = file
+        assertThat(docOptions.optionMap()[antProperty] as File, equalTo(file))
+    }
+
+    @Test public void testOptionMapContainsValuesForAdditionalParameters() {
+        String antProperty = 'addParams'
+        assertNull(docOptions.additionalParameters)
+        assertFalse(docOptions.optionMap().containsKey(antProperty))
+
+        docOptions.additionalParameters = ['-opt1', '-opt2']
+        assertThat(docOptions.optionMap()[antProperty] as String, equalTo('-opt1 -opt2' as String))
+    }
+
+    private def assertOnOffValue(String fieldName, String antProperty, boolean defaultValue) {
+        assertThat(docOptions."$fieldName" as boolean, equalTo(defaultValue))
+
+        docOptions."$fieldName" = true
+        assertThat(docOptions.optionMap()[antProperty] as String, equalTo('on'))
+
+        docOptions."$fieldName" = false
+        assertThat(docOptions.optionMap()[antProperty] as String, equalTo('off'))
+    }
+
+    private def assertSimpleStringValue(String fieldName, String antProperty, String defaultValue, String testValue) {
+        assertThat(docOptions."${fieldName}" as String, equalTo(defaultValue))
+        if (defaultValue == null) {
+            assertFalse(docOptions.optionMap().containsKey(antProperty))
+        } else {
+            assertThat(docOptions.optionMap()[antProperty] as String, equalTo(defaultValue))
+        }
+        docOptions."${fieldName}" = testValue
+        assertThat(docOptions.optionMap()[antProperty] as String, equalTo(testValue))
+    }
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaDocTest.java b/subprojects/gradle-scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaDocTest.java
new file mode 100644
index 0000000..386204a
--- /dev/null
+++ b/subprojects/gradle-scala/src/test/groovy/org/gradle/api/tasks/scala/ScalaDocTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.tasks.scala;
+
+import org.gradle.api.file.FileCollection;
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.tasks.AbstractTaskTest;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.JUnit4GroovyMockery;
+import org.gradle.util.WrapUtil;
+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 static org.gradle.api.tasks.compile.AbstractCompileTest.*;
+import static org.gradle.util.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+ at RunWith(JMock.class)
+public class ScalaDocTest extends AbstractTaskTest {
+    private ScalaDoc scalaDoc;
+    private AntScalaDoc antScalaDocMock;
+    private JUnit4Mockery context = new JUnit4GroovyMockery();
+    private File destDir;
+    private File srcDir;
+
+    @Override
+    public AbstractTask getTask() {
+        return scalaDoc;
+    }
+
+    @Override
+    @Before
+    public void setUp() {
+        super.setUp();
+        destDir = getProject().file("destDir");
+        srcDir = getProject().file("src");
+        GFileUtils.touch(new File(srcDir, "file.scala"));
+        scalaDoc = createTask(ScalaDoc.class);
+        antScalaDocMock = context.mock(AntScalaDoc.class);
+        scalaDoc.setAntScalaDoc(antScalaDocMock);
+    }
+
+    @Test
+    public void testExecutesAntScalaDoc() {
+        setUpMocksAndAttributes(scalaDoc);
+        scalaDoc.generate();
+    }
+
+    @Test
+    public void testScalaIncludes() {
+        assertSame(scalaDoc.include(TEST_PATTERN_1, TEST_PATTERN_2), scalaDoc);
+        assertEquals(scalaDoc.getIncludes(), WrapUtil.toLinkedSet(TEST_PATTERN_1, TEST_PATTERN_2));
+
+        assertSame(scalaDoc.include(TEST_PATTERN_3), scalaDoc);
+        assertEquals(scalaDoc.getIncludes(), WrapUtil.toLinkedSet(TEST_PATTERN_1, TEST_PATTERN_2, TEST_PATTERN_3));
+    }
+
+    @Test
+    public void testScalaExcludes() {
+        assertSame(scalaDoc.exclude(TEST_PATTERN_1, TEST_PATTERN_2), scalaDoc);
+        assertEquals(scalaDoc.getExcludes(), WrapUtil.toLinkedSet(TEST_PATTERN_1, TEST_PATTERN_2));
+
+        assertSame(scalaDoc.exclude(TEST_PATTERN_3), scalaDoc);
+        assertEquals(scalaDoc.getExcludes(), WrapUtil.toLinkedSet(TEST_PATTERN_1, TEST_PATTERN_2, TEST_PATTERN_3));
+    }
+
+    @Test
+    public void testSetsDocTitleAndWindowTitleIfNotSet() {
+        setUpMocksAndAttributes(scalaDoc);
+        scalaDoc.setTitle("title");
+
+        scalaDoc.generate();
+
+        assertThat(scalaDoc.getScalaDocOptions().getDocTitle(), equalTo("title"));
+        assertThat(scalaDoc.getScalaDocOptions().getWindowTitle(), equalTo("title"));
+    }
+
+    private void setUpMocksAndAttributes(final ScalaDoc docTask) {
+        docTask.source(srcDir);
+        docTask.setDestinationDir(destDir);
+        docTask.setScalaClasspath(context.mock(FileCollection.class));
+        docTask.setClasspath(context.mock(FileCollection.class));
+
+        context.checking(new Expectations() {{
+            one(antScalaDocMock).execute(
+                    with(hasSameItems(scalaDoc.getSource())),
+                    with(equalTo(scalaDoc.getDestinationDir())),
+                    with(equalTo(scalaDoc.getClasspath())),
+                    with(equalTo(scalaDoc.getScalaClasspath())),
+                    with(sameInstance(scalaDoc.getScalaDocOptions())));
+        }});
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/CommandLineAssistant.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/CommandLineAssistant.java
new file mode 100644
index 0000000..360b337
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/CommandLineAssistant.java
@@ -0,0 +1,183 @@
+/*
+ * 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.foundation;
+
+import org.gradle.initialization.DefaultCommandLine2StartParameterConverter;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * Some helpful functions for manipulating command line arguments.
+ *
+ * @author mhunsicker
+ */
+public class CommandLineAssistant {
+    private DefaultCommandLine2StartParameterConverter commandLine2StartParameterConverter;
+
+    public CommandLineAssistant() {
+        commandLine2StartParameterConverter = new DefaultCommandLine2StartParameterConverter();
+    }
+
+    public DefaultCommandLine2StartParameterConverter getCommandLine2StartParameterConverter() {
+        return commandLine2StartParameterConverter;
+    }
+
+    /**
+     * This breaks up the full command line string into space-delimited and/or quoted command line arguments. This
+     * currently does not handle escaping characters such as quotes.
+     *
+     * @param fullCommandLine the full command line
+     * @return a string array of the separate command line arguments.
+     */
+    public static String[] breakUpCommandLine(String fullCommandLine) {
+        List<String> commandLineArguments = new ArrayList<String>();
+
+        boolean isInsideQuotes = false;
+        StringBuffer currentOption = new StringBuffer();
+
+        for (int index = 0; index < fullCommandLine.length(); index++) {
+            char c = fullCommandLine.charAt(index);
+            if (Character.isSpaceChar(c) && !isInsideQuotes) {
+                currentOption.trimToSize();
+                if (currentOption.length() > 0) {
+                    commandLineArguments.add(currentOption.toString());
+                }
+
+                currentOption = new StringBuffer();
+            } else {
+                if (c == '"') {
+                    isInsideQuotes = !isInsideQuotes;
+                }
+
+                currentOption.append(c);
+            }
+        }
+
+        currentOption.trimToSize();
+        if (currentOption.length() > 0) {
+            commandLineArguments.add(currentOption.toString());
+        }
+
+        return commandLineArguments.toArray(new String[commandLineArguments.size()]);
+    }
+
+    public boolean hasLogLevelDefined(String[] commandLineArguments) {
+        return hasCommandLineOptionsDefined(commandLineArguments, new CommandLineSearch() {
+            public boolean contains(String commandLine) {
+
+                return commandLine2StartParameterConverter.getLogLevel(commandLine) != null;
+            }
+        });
+    }
+
+    public boolean hasShowStacktraceDefined(String[] commandLineArguments) {
+        return hasCommandLineOptionsDefined(commandLineArguments, new CommandLineSearch() {
+            public boolean contains(String commandLine) {
+                return commandLine2StartParameterConverter.getShowStacktrace(commandLine) != null;
+            }
+        });
+    }
+
+    public interface CommandLineSearch {
+        public boolean contains(String commandLine);
+    }
+
+    /**
+     * This determines if one of the sought options is defined on the command line. We're only looking for options that
+     * are prefixed with a single '-'. Note: this IS case-sensitive.
+     *
+     * @param commandLineOptions the command line options
+     * @param commandLineSearch the options we're looking for. This won't have the prefixed dash in them (just "s", "d",
+     * etc.).
+     * @return true if one of the sought options exists in the
+     */
+    private boolean hasCommandLineOptionsDefined(String[] commandLineOptions, CommandLineSearch commandLineSearch) {
+        for (
+                int commandLineOptionsIndex = 0; commandLineOptionsIndex < commandLineOptions.length;
+                commandLineOptionsIndex++) {
+            String commandLineOption = commandLineOptions[commandLineOptionsIndex];
+
+            if (commandLineOption != null && commandLineOption.length() > 1 && commandLineOption.charAt(0) == '-') {
+                //everything minus the dash must be equivalent to the sought option.
+                String remainder = commandLineOption.substring(1);
+
+                if (commandLineSearch.contains(remainder)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * This appends additional command line options to a task name to generate a full command line option.
+     *
+     * @param task the task to execute
+     * @param additionCommandLineOptions the additional options
+     * @return a single command line string.
+     */
+    public static String appendAdditionalCommandLineOptions(TaskView task, String... additionCommandLineOptions) {
+        if (additionCommandLineOptions == null || additionCommandLineOptions.length == 0) {
+            return task.getFullTaskName();
+        }
+
+        StringBuilder builder = new StringBuilder(task.getFullTaskName());
+        builder.append(' ');
+
+        appendAdditionalCommandLineOptions(builder, additionCommandLineOptions);
+
+        return builder.toString();
+    }
+
+    /*
+   combines the tasks into a single command
+    */
+
+    public static String combineTasks(List<TaskView> tasks, String... additionCommandLineOptions) {
+        if (tasks == null || tasks.isEmpty()) {
+            return null;
+        }
+
+        StringBuilder builder = new StringBuilder();
+        Iterator<TaskView> iterator = tasks.iterator();
+        while (iterator.hasNext()) {
+            TaskView taskView = iterator.next();
+            builder.append(taskView.getFullTaskName());
+            if (iterator.hasNext()) {
+                builder.append(' ');
+            }
+        }
+
+        appendAdditionalCommandLineOptions(builder, additionCommandLineOptions);
+
+        return builder.toString();
+    }
+
+    public static void appendAdditionalCommandLineOptions(StringBuilder builder, String... additionCommandLineOptions) {
+        if (additionCommandLineOptions != null) {
+            for (int index = 0; index < additionCommandLineOptions.length; index++) {
+                String additionCommandLineOption = additionCommandLineOptions[index];
+                builder.append(additionCommandLineOption);
+                if (index + 1 < additionCommandLineOptions.length) {
+                    builder.append(' ');
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/PathParserPortion.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/PathParserPortion.java
new file mode 100644
index 0000000..a86269c
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/PathParserPortion.java
@@ -0,0 +1,60 @@
+/*
+ * 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.foundation;
+
+/**
+ * Small helper class that aids walking a full task path which can be multiple projects deep with a task on the end.
+ *
+ * @author mhunsicker
+ */
+public class PathParserPortion {
+    private String firstPart;
+    private String remainder;
+
+    public PathParserPortion(String path) {
+        if( path != null && path.length() > 0 )
+        {
+            if( path.startsWith( ":" ) )    //skip the first character, if its a colon. This is optional and makes it absolute, vs relative.
+            {
+                path = path.substring( 1 );
+            }
+
+            int indexOfColon = path.indexOf(':');
+            if (indexOfColon == -1) {
+                firstPart = path;
+            } else {
+                firstPart = path.substring(0, indexOfColon);  //get everyting up to the colon
+                remainder = path.substring(indexOfColon + 1); //everything else
+            }
+        }
+    }
+
+    public String getFirstPart() {
+        return firstPart;
+    }
+
+    public String getRemainder() {
+        return remainder;
+    }
+
+    public boolean hasRemainder() {
+        return remainder != null;
+    }
+
+    public String toString() {
+        return firstPart + " -> " + remainder;
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ProjectConverter.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ProjectConverter.java
new file mode 100644
index 0000000..3a0166d
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ProjectConverter.java
@@ -0,0 +1,161 @@
+/*
+ * 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.foundation;
+
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This converts Gradle's projects into ProjectView objects. These can be safely reused unlike Gradle's projects.
+ *
+ * @author mhunsicker
+ */
+public class ProjectConverter {
+    private List<ProjectView> rootLevelResultingProjects = new ArrayList<ProjectView>();
+    private HashMap<Project, ProjectView> projectMap = new HashMap<Project, ProjectView>();
+    private final Logger logger = Logging.getLogger(ProjectConverter.class);
+
+    public ProjectConverter() {
+    }
+
+    /**
+     * Call this to convert the projects.
+     *
+     * @param rootProject the root project.
+     */
+    public List<ProjectView> convertProjects(Project rootProject) {
+        rootLevelResultingProjects.clear();
+        projectMap.clear();
+
+        addRootLevelProject(rootProject);
+
+        buildDependencies();
+
+        return rootLevelResultingProjects;
+    }
+
+    /**
+     * This adds the specified poject as a root level projects. It then adds all tasks and recursively adds all sub
+     * projects.
+     *
+     * @param rootLevelProject a root level project.
+     */
+    public void addRootLevelProject(Project rootLevelProject) {
+        ProjectView rootLevelProjectView = new ProjectView(rootLevelProject.getName(), rootLevelProject.getBuildFile());
+        projectMap.put(rootLevelProject, rootLevelProjectView);
+
+        rootLevelResultingProjects.add(rootLevelProjectView);
+
+        addSubProjects(rootLevelProject, rootLevelProjectView, 1);
+
+        addTasks(rootLevelProject, rootLevelProjectView);
+
+        rootLevelProjectView.sortSubProjectsAndTasks();
+    }
+
+    /**
+     * Adds all sub projects of the specifed GradleProject.
+     *
+     * @param parentProject the source parent project. Where we get the sub projects from.
+     * @param parentProjectView the destination of the sub projects from parentProject.
+     */
+    private void addSubProjects(Project parentProject, ProjectView parentProjectView, int currentDepth) {
+        Set<Project> subProjects = parentProject.getSubprojects();
+        Iterator<Project> iterator = subProjects.iterator();
+        while (iterator.hasNext()) {
+            Project subProject = iterator.next();
+            int depth = subProject.getDepth();
+            if (depth
+                    == currentDepth)   //at the root, we seem to be getting all projects regardless of their depth (that is we'll get root:subproject1:subproject2 as the root's subproject). We'll ignore these and then add them to our hierarchy when we get to the correct depth.
+            {
+                ProjectView projectView = new ProjectView(subProject.getName(), subProject.getBuildFile());
+                projectMap.put(subProject, projectView);
+
+                parentProjectView.addSubProject(projectView);
+
+                addTasks(subProject, projectView);
+
+                projectView.sortSubProjectsAndTasks();
+
+                addSubProjects(subProject, projectView, currentDepth + 1);
+            }
+        }
+    }
+
+    /**
+     * Adds the tasks from the project to the GradleProject.
+     *
+     * @param project the source parent project. Where we get the sub projects from.
+     * @param projectView the destination of the tasks from project.
+     */
+    private void addTasks(Project project, ProjectView projectView) {
+        List<String> defaultTasks = project.getDefaultTasks();
+        Set<Task> tasks = project.getTasks().getAll();
+        for (Task task : tasks) {
+            String taskName = task.getName();
+
+            boolean isDefault = defaultTasks.contains(taskName);
+
+            projectView.createTask(taskName, task.getDescription(), isDefault);
+        }
+    }
+
+    /**
+     * This sets the dependencies on the ProjectViews. We ask the gradle projects for the dependencies and then convert
+     * them to ProjectViews. Obviously, this must be done after converting all Projects to ProjectViews.
+     */
+    private void buildDependencies() {
+        Iterator<Project> projectIterator = projectMap.keySet().iterator();
+        while (projectIterator.hasNext()) {
+            Project project = projectIterator.next();
+
+            ProjectView projectView = projectMap.get(project);
+
+            List<ProjectView> projectViewList = getProjectViews(project.getDependsOnProjects());
+
+            projectView.setDependsOnProjects(projectViewList);
+        }
+    }
+
+    /**
+     * Converts a set of projects to the existing project views. This does not actually instantiate new ProjectView
+     * objects.
+     */
+    private List<ProjectView> getProjectViews(Set<Project> projects) {
+        List<ProjectView> views = new ArrayList<ProjectView>();
+
+        Iterator<Project> projectIterator = projects.iterator();
+        while (projectIterator.hasNext()) {
+            Project project = projectIterator.next();
+            ProjectView projectView = projectMap.get(project);
+            if (projectView == null) {
+                logger.error("Missing project: " + project.getName());
+            } else {
+                views.add(projectView);
+            }
+        }
+
+        return views;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ProjectView.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ProjectView.java
new file mode 100644
index 0000000..53f0e52
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ProjectView.java
@@ -0,0 +1,243 @@
+/*
+ * 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.foundation;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Analog to gradle's Project but more light-weight and is better suited for using the gradle API from an IDE plugin. It
+ * is also easily serializable for passing across a socket. A project is a collection of source files that have tasks
+ * associated with them. The tasks build the project. Projects can contain other projects. This is immutable and
+ * ultimately comes from gradle files.
+ *
+ * @author mhunsicker
+ */
+public class ProjectView implements Comparable<ProjectView>, Serializable {
+    private String name;
+    private ProjectView parentProject;
+            //will be null for any project until it is added as a sub project to another project. It is null for the root project always.
+    private List<ProjectView> subProjects = new ArrayList<ProjectView>();
+    private List<TaskView> tasks = new ArrayList<TaskView>();
+    private List<ProjectView> dependsOnProjects = new ArrayList<ProjectView>();
+
+    private File buildFile;
+
+    /**
+     * Instantiates an immutable view of a project. This is only meant to be called internally whenever generating a
+     * hierachy of projects and tasks.
+     */
+    /*package*/ ProjectView(String name, File buildFile) {
+        this.name = name;
+        this.buildFile = buildFile;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public File getBuildFile() {
+        return buildFile;
+    }
+
+    public String toString() {
+        return name;
+    }
+
+    public ProjectView getParentProject() {
+        return parentProject;
+    }
+
+    protected void setParentProject(ProjectView parentProject) {
+        this.parentProject = parentProject;
+    }
+
+    public int compareTo(ProjectView otherProject) {
+        return name.compareTo(otherProject.name);
+    }
+
+    /**
+     * creates a task for this project. This is only meant to be called internally whenever generating a hierachy of
+     * projects and tasks.
+     */
+    /*package*/ void createTask(String name, String description, boolean isDefault) {
+        TaskView taskView = new TaskView(this, name, description, isDefault);
+        tasks.add(taskView);
+    }
+
+    /**
+     * Adds the specified project as a sub project of this project. This is only meant to be called internally whenever
+     * generating a hierachy of projects and tasks.
+     */
+    /*package*/ void addSubProject(ProjectView subProject) {
+        subProject.setParentProject(this);
+        subProjects.add(subProject);
+    }
+
+    /**
+     * Sets the project that this project depends on. This is only meant to be called internally whenever generating a
+     * hierachy of projects and tasks.
+     */
+    /*package*/ void setDependsOnProjects(List<ProjectView> newDependsOnProjects) {
+        if( newDependsOnProjects == null ) {
+            return;
+        }
+
+        this.dependsOnProjects.clear();
+        this.dependsOnProjects.addAll( newDependsOnProjects );
+    }
+
+    public List<ProjectView> getDependsOnProjects() {
+        return dependsOnProjects;
+    }
+
+    public List<TaskView> getTasks() {
+        return Collections.unmodifiableList(tasks);
+    }
+
+    public List<ProjectView> getSubProjects() {
+        return Collections.unmodifiableList(subProjects);
+    }
+
+    public void sortSubProjectsAndTasks() {
+        Collections.sort(tasks);
+        Collections.sort(subProjects);
+    }
+
+    public ProjectView getSubProject(String name) {
+        Iterator<ProjectView> iterator = subProjects.iterator();
+        while (iterator.hasNext()) {
+            ProjectView subProject = iterator.next();
+            if (name.equals(subProject.getName())) {
+                return subProject;
+            }
+        }
+
+        return null;
+    }
+
+    public TaskView getTask(String name) {
+        Iterator<TaskView> iterator = tasks.iterator();
+        while (iterator.hasNext()) {
+            TaskView task = iterator.next();
+            if (name.equals(task.getName())) {
+                return task;
+            }
+        }
+
+        return null;
+    }
+
+    public ProjectView getSubProjectFromFullPath(String fullProjectName) {
+        if (fullProjectName == null) {
+            return null;
+        }
+
+        PathParserPortion portion = new PathParserPortion(fullProjectName);
+
+        ProjectView subProject = getSubProject(portion.getFirstPart());
+
+        if (!portion
+                .hasRemainder()) //if we have no remainder, then the path is just a sub project's name. We're done (even if subProject is null).
+        {
+            return subProject;
+        }
+
+        if (subProject == null) {
+            return null;
+        }   //the path may be invalid
+
+        return subProject.getSubProjectFromFullPath(portion.getRemainder());
+    }
+
+    /**
+     * This gets the task based on the given full path. This recursively calls this same function with sub projects
+     * until it finds the task or no matches are found.
+     *
+     * @param fullTaskName the full task name (root_project:sub_project:sub_sub_project:task.).
+     * @return the task or null if not found.
+     */
+    public TaskView getTaskFromFullPath(String fullTaskName) {
+        if (fullTaskName == null) {
+            return null;
+        }
+
+        PathParserPortion portion = new PathParserPortion(fullTaskName);
+        if (!portion.hasRemainder()) //if we have no remainder, then this is for a task.
+        {
+            return getTask(portion.getFirstPart());
+        }
+
+        ProjectView subProject = getSubProject(portion.getFirstPart());
+        if (subProject == null) {
+            return null;
+        }
+
+        //let the sub project figure it out.
+        return subProject.getTaskFromFullPath(portion.getRemainder());
+    }
+
+    /**
+     * This generates this project's full name. This is a colon-separated string of this project and its parent
+     * projects.
+     *
+     * Example: root_project:sub_project:sub_sub_project.
+     */
+    public String getFullProjectName() {
+        ProjectView ancestorProject = getParentProject();
+        if (ancestorProject == null) {
+            return "";
+        } //if we're the root, our full project name is nothing.
+
+        StringBuilder builder = new StringBuilder(name);
+        while (ancestorProject != null
+                && ancestorProject.getParentProject() != null)   //we don't want to include the 'root' project
+        {
+            builder.insert(0, ancestorProject.getName() + ':');
+            ancestorProject = ancestorProject.getParentProject();
+        }
+
+        return builder.toString();
+    }
+
+    /**
+     * Builds a list of default tasks. These are defined by specifying
+     *
+     * defaultTasks 'task name'
+     *
+     * in the gradle file. There can be multiple default tasks. This only returns default tasks directly for this
+     * project and does not return them for subprojects.
+     *
+     * @return a list of default tasks or an empty list if none exist
+     */
+    public List<TaskView> getDefaultTasks() {
+        List<TaskView> defaultTasks = new ArrayList<TaskView>();
+
+        Iterator<TaskView> taskIterator = tasks.iterator();
+        while (taskIterator.hasNext()) {
+            TaskView taskView = taskIterator.next();
+            if (taskView.isDefault()) {
+                defaultTasks.add(taskView);
+            }
+        }
+
+        return defaultTasks;
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/TaskView.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/TaskView.java
new file mode 100644
index 0000000..94ce92d
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/TaskView.java
@@ -0,0 +1,102 @@
+/*
+ * 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.foundation;
+
+import java.io.Serializable;
+
+/**
+ * Analog to gradle's Task but more light-weight and better suited for using the gradle API from an IDE plugin. It is
+ * also easily serializable for passing across a socket. A task is something you can execute and is part of a project.
+ * This is immutable and ultimately comes from gradle files.
+ *
+ * @author mhunsicker
+ */
+public class TaskView implements Comparable<TaskView>, Serializable {
+    private ProjectView project;
+    private String name;
+    private String description;
+    private boolean isDefault;
+            //whether or not this is one of potentially manny default tasks for its project.
+
+    /**
+     * Instantiates an immutable view of a task. This is only meant to be called internally whenever generating a hierachy
+     * of projects and tasks.
+     */
+    /*package*/ TaskView(ProjectView project, String name, String description, boolean isDefault) {
+        this.project = project;
+        this.name = name;
+        this.isDefault = isDefault;
+
+        if (description == null || description.trim().equals("")) {
+            this.description
+                    = "";  //just make a blank or null description empty so we don't have to check for null everywhere.
+        } else {
+            this.description = description;
+        }
+    }
+
+    public ProjectView getProject() {
+        return project;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public boolean hasDescription() {
+        return !description.equals("");
+    }
+
+    /**
+     * returns whether or not this is a default task for its parent project. These are defined by specifying
+     *
+     * defaultTasks 'task name'
+     *
+     * in the gradle file. There can be multiple default tasks.
+     *
+     * @return true if its a default task, false if not.
+     */
+    public boolean isDefault() {
+        return isDefault;
+    }
+
+    public int compareTo(TaskView otherTask) {
+        //sort by project name first, then by task name.
+        int projectComparison = project.compareTo(otherTask.getProject());
+        if (projectComparison != 0) {
+            return projectComparison;
+        }
+
+        return name.compareTo(otherTask.name);
+    }
+
+    public String toString() {
+        return name;
+    }
+
+    /**
+     * This generates this task's full name. This is a colon-separated string of this task and its parent projects.
+     *
+     * Example: root_project:sub_project:sub_sub_project:task.
+     */
+    public String getFullTaskName() {
+        return project.getFullProjectName() + ':' + name;
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/common/ListReorderer.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/common/ListReorderer.java
new file mode 100644
index 0000000..78c8288
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/common/ListReorderer.java
@@ -0,0 +1,354 @@
+/*
+ * 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.foundation.common;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * Utility class that allows lists to be reordered.
+ *
+ * @author Chris Hampton
+ */
+public class ListReorderer {
+    /**
+     * Moves the object down one position in the group list.
+     *
+     * @param sourceList The list whose elements we want to reorder.
+     * @param object The object to move.
+     */
+    public static boolean moveBefore(List sourceList, Object object) {
+        // Get the old index of the object
+        int previousIndex = sourceList.indexOf(object);
+        // If the index of the object is 0 it can't go any lower. If it's
+        // -1 it's not even in the list. In these cases we do nothing.
+        if (previousIndex < 1) {
+            return false;
+        }
+        // Remove the object from it's old position in the list, this shifts all
+        // elements above it down by 1
+        sourceList.remove(object);
+        // Add the object back at 1 less than it's original index, the old
+        // element at this position is shifted to right in the lise
+        sourceList.add(previousIndex - 1, object);
+        // If we get here we assume we moved it.
+        return true;
+    }
+
+    /**
+     * Moves a list of elements in this list while keeping their relative positions. When the first element reaches the
+     * beginning it goes no further and the other elements in the list will continue to be shifted on subsequent calls
+     * as long as they don't overwrite previously moved elements. This means that elements with other elements between
+     * them will continue move with the same distance between them but will 'bunch up' toward the beginning of the
+     * list.
+     *
+     * NOTE: The order of the list of moved elements is important. They have to be added in order from lowest index to
+     * highest.
+     *
+     * @param sourceList The list whose elements we want to reorder.
+     * @param objectsToMove List of elements to move.
+     */
+    public static void moveBefore(List sourceList, List objectsToMove) {
+        sortMoveListByRelativeOrder(sourceList, objectsToMove);
+        // Create a new list to put elements in we try to move
+        List triedToMove = new ArrayList();
+        // Now iterate through the elements to move and attempt to move them
+        Iterator iterator = objectsToMove.iterator();
+        while (iterator.hasNext()) {
+            // Get the next object to move
+            Object objectToMove = iterator.next();
+            // Get the index of the object to move
+            int currentPosition = sourceList.indexOf(objectToMove);
+            // Only move the element if it's not already at the front of the list
+            if (currentPosition > 0) {
+                // Get the element at the position we want to move to and make sure it's not
+                // an element in the list that we'vd already moved
+                Object occupyingObject = sourceList.get(currentPosition - 1);
+                if (currentPosition < sourceList.size() && !triedToMove.contains(occupyingObject)) {
+                    moveBefore(sourceList, objectToMove);
+                }
+            }
+            // If we get here we have at least tried to move the object,
+            // so stick it in the tried list
+            triedToMove.add(objectToMove);
+        }
+    }
+
+    /**
+     * Moves a list of objects to a new index location.
+     *
+     * @param sourceList The list where the move will occur.
+     * @param moveList The objects to move.
+     * @param index The object's new location in the list.
+     */
+    public static void moveTo(List sourceList, List moveList, int index) {
+        // First make sure the move is valid
+        if (index < 0 || index >= sourceList.size()) {
+            return;
+        }
+        // Store the object at the index to move to
+        Object moveBeforeObject = sourceList.get(index);
+
+        //This fixes a bug. This happens if the user selects things and moves them to an index where something is already selected. I select 1, 2, and 4 and I say move to 2. 2 is already selected. This makes no sense, but its happened in the field.
+        if (moveList.contains(moveBeforeObject)) //just remove the item from the move list.
+        {
+            List newMoveList = new ArrayList(
+                    moveList);   //we don't want to actually affect the move list. Callers use it for visually selecting items after the move. So we'll make a duplicate and just recursively call ourselves again.
+            newMoveList.remove(moveBeforeObject);
+            moveTo(sourceList, newMoveList, index + 1);   //skip over the one we took out
+            return;
+        }
+
+        // Remove the object from it's old position
+        sourceList.removeAll(moveList);
+        // Get the new index value after shifts
+        index = sourceList.indexOf(moveBeforeObject);
+
+        //make sure the index is within bounds.
+        if (index < 0) {
+            index = 0;
+        }
+        if (index > sourceList.size() - 1) {
+            index = sourceList.size() - 1;
+        }
+
+        // Add the element to the new location
+        sourceList.addAll(index, moveList);
+    }
+
+    /**
+     * Moves an object to the front of the list.
+     *
+     * @param sourceList The list the object is in.
+     * @param object The object to move.
+     * @return True if the object was in the list and it was moved.
+     */
+    public static boolean moveToFront(List sourceList, Object object) {
+        boolean moved = false;
+        // If we can remove it, then it was in the list
+        if (sourceList.remove(object)) {
+            sourceList.add(0, object); // This is a void method, so it doesn't set our flag
+            moved = true;
+        }
+
+        return moved;
+    }
+
+    /**
+     * Moves a list of objects to the front of the list.
+     *
+     * @param sourceList The list the object is in.
+     * @param objectsToMove The object to move.
+     */
+    public static void moveToFront(List sourceList, List objectsToMove) {
+        sortMoveListByRelativeOrder(sourceList, objectsToMove);
+        for (int i = objectsToMove.size() - 1; i >= 0; i--) {
+            Object object = objectsToMove.get(i);
+            if (sourceList.remove(object)) {
+                sourceList.add(0, object);
+            }
+        }
+    }
+
+    /**
+     * Moves the object up one index position in the list.
+     *
+     * @param sourceList The list whose elements we want to reorder.
+     * @param object The object to move.
+     */
+    public static boolean moveAfter(List sourceList, Object object) {
+        // Get the old index of the object
+        int previousIndex = sourceList.indexOf(object);
+        // If the index of the object is 0 it can't go any higher. If it's
+        // -1 it's not even in the list. In these cases we do nothing.
+        if (previousIndex >= sourceList.size() - 1 || previousIndex == -1) {
+            return false;
+        }
+        // Remove the object from it's old position in the list, this shifts all
+        // elements above it down by 1
+        sourceList.remove(object);
+        // Add the object back at 2 higher than it's original index, if we only
+        // add one than we just place it back where it was since everything shifted
+        // down when we removed it
+        sourceList.add(previousIndex + 1, object);
+        // If we get here we assume we moved it.
+        return true;
+    }
+
+    /**
+     * Moves the objects in the list up one index position in this list while maintaining their relative position. When
+     * an element reaches the end of the list it can go no farther, but the other elements continue to move each call
+     * without overwriting previously moved elements. This causes moved elements to 'bunch up' at the end of the list.
+     *
+     * NOTE: The order of the list of moved elements is important. They have to be added in order from lowest index to
+     * highest.
+     *
+     * @param sourceList The list whose elements we want to reorder.
+     * @param objectsToMove List of elements to move.
+     */
+    public static void moveAfter(List sourceList, List objectsToMove) {
+        sortMoveListByRelativeOrder(sourceList, objectsToMove);
+        List triedToMove = new ArrayList();
+        // Since we are moving elements to a greater index in the list,
+        // we iterate through the list backwards to move the highest indexed
+        // element first
+        for (int i = objectsToMove.size() - 1; i >= 0; i--) {
+            Object objectToMove = objectsToMove.get(i);
+            // Get the index of the object to move
+            int currentPosition = sourceList.indexOf(objectToMove);
+            // Make sure the element we want to move isn't already at the end of
+            // the list
+            if (currentPosition < sourceList.size() - 1) {
+                // Now get the index of the elment occupying the spot we want
+                // to move to and only move the current objectToMove if it
+                // does not overwite a previously moved element
+                Object occupyingObject = sourceList.get(currentPosition + 1);
+                if (!triedToMove.contains(occupyingObject)) {
+                    moveAfter(sourceList, objectToMove);
+                }
+            }
+            // If we get here we have at least tried to move the object,
+            // so stick it in the tried list
+            triedToMove.add(objectToMove);
+        }
+    }
+
+    /**
+     * Moves an object to the back of the list.
+     *
+     * @param sourceList The list the object is in.
+     * @param object The object to move.
+     * @return True if the object was in the list and it was moved.
+     */
+    public static boolean moveToBack(List sourceList, Object object) {
+        boolean moved = false;
+
+        // If we can remove it, then it was in the list
+        if (sourceList.remove(object)) {
+            moved = sourceList.add(object);
+        }
+
+        return moved;
+    }
+
+    /**
+     * Moves a list of objects to the front of the list.
+     *
+     * @param sourceList The list the object is in.
+     * @param objectsToMove The object to move.
+     */
+    public static void moveToBack(List sourceList, List objectsToMove) {
+        sortMoveListByRelativeOrder(sourceList, objectsToMove);
+        for (int i = 0; i < objectsToMove.size(); i++) {
+            Object object = objectsToMove.get(i);
+            if (sourceList.remove(object)) {
+                sourceList.add(object);
+            }
+        }
+    }
+
+    /**
+     * Sorts a child list by position in a parent list to preserve relative ordering of the elements.
+     *
+     * @param parentList .
+     * @param childList .
+     */
+    public static void sortMoveListByRelativeOrder(final List parentList, List childList) {
+        Collections.sort(childList, new Comparator() {
+            public int compare(Object o, Object o1) {
+                int index = parentList.indexOf(o);
+                int index1 = parentList.indexOf(o1);
+                return (index < index1) ? -1 : (index > index1) ? 1 : 0;
+            }
+        });
+    }
+
+    /**
+     * Returns true if all the elements of the check list are at the end of the source list.
+     *
+     * @param sourceList .
+     * @param checkList .
+     * @return .
+     */
+    public static boolean allElementsInFront(List sourceList, List checkList) {
+        // Quick check, if the source list doesn't contain all elements of the checklist,
+        // abort and return false
+        if (!sourceList.containsAll(checkList)) {
+            return false;
+        }
+        // Get the last index of the source list
+        int sourceIndex = checkList.size();
+        // Iterate thru the check list. Find the index of the element
+        // in the source list; and check it's index against the index
+        // we should be on to match the source.
+        for (int index = 0; index < checkList.size(); index++) {
+            Object element = checkList.get(index);
+            int checkIndex = sourceList.indexOf(element);
+            if (checkIndex >= sourceIndex) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public static boolean allElementsInBack(List sourceList, List checkList) {
+        // Quick check, if the source list doesn't contain all elements of the checklist,
+        // abort and return false
+        if (!sourceList.containsAll(checkList)) {
+            return false;
+        }
+        // Get the last index of the source list
+        int sourceIndex = sourceList.size() - checkList.size();
+        // Iterate thru the check list. Find the index of the element
+        // in the source list; and check it's index against the index
+        // we should be on to match the source.
+        for (int index = checkList.size() - 1; index >= 0; index--) {
+            Object element = checkList.get(index);
+            int checkIndex = sourceList.indexOf(element);
+            if (checkIndex < sourceIndex) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * This is mainly used for after doing a move. It gives you the current index of all the moved elements. This is
+     * useful for UIs that need to reselect the new items.
+     *
+     * @param sourceList the source list
+     * @param objectsToMove the elements to move
+     * @return an integer array of the items to select.
+     */
+    public static int[] getIndices(List sourceList, List objectsToMove) {
+        int[] newIndices = new int[objectsToMove.size()];
+
+        for (int index = 0; index < objectsToMove.size(); index++) {
+            Object elementToMove = objectsToMove.get(index);
+            int sourceIndexOfElement = sourceList.indexOf(elementToMove);
+
+            newIndices[index] = sourceIndexOfElement;
+        }
+
+        return newIndices;
+    }
+}
+
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/common/ObserverLord.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/common/ObserverLord.java
new file mode 100644
index 0000000..a5bc7ae
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/common/ObserverLord.java
@@ -0,0 +1,174 @@
+/*
+ * 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.foundation.common;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+import javax.swing.SwingUtilities;
+import java.awt.EventQueue;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This is a Swing-friendly observer manager class. Swing-friendly, but can be used by non-Swing classes. Its meant to
+ * abstract the fact that you probably need to be in the Event Dispatch Thread when receiving notifications inside
+ * Swing-related classes.
+ *
+ * To use this class, add it as a member variable (don't derive from this!) of a class that you want to be observered.
+ * You can have multiple instances of this if you want to allow for a finer granularity of observing (similar to
+ * components having mouse move listeners and mouse (click) listeners). Next, create an interface for the observers. Now
+ * implement add and remove observer functions that call the add and remove functions here. Lastly, implement
+ * ObserverNotification and have it call the aforementioned observer interface appropriately. Note: you should actually
+ * implement ObserverNotification for each "message" you want to send. Example: One that would tell a view a node was
+ * added. One that would tell a view a node was deleted, etc. While you have multiple notification classes, you only
+ * need 1 (or few) actual observer interfaces, containing all the possible functions called by all notifications.
+ *
+ * @author mhunsicker
+ */
+
+public class ObserverLord<E> {
+    private List<E> regularObservers = new ArrayList<E>();
+    private List<E> eventQueueObservers = new ArrayList<E>();
+
+    private final Logger logger = Logging.getLogger(ObserverLord.class);
+
+    /**
+     * Implement this for each call to ObserverLord.notifyObservers. The notify function usually just has a single call
+     * to a function on the observer.
+     *
+     * Example:
+     * <pre>
+     * public void notify( MyObserver observer )
+     * {
+     * observer.myfunction();
+     * }
+     * </pre>
+     */
+    public interface ObserverNotification<E> {
+        public void notify(E observer);
+    }
+
+    /**
+     * Adds an observer to our messaging system.
+     *
+     * <!       Name        Description  >
+     *
+     * @param observer observer to add.
+     * @param inEventQueue true to notify this observer only in the event queue, false to notify it immediately.
+     */
+
+    public void addObserver(E observer, boolean inEventQueue) {
+        if (!inEventQueue) {
+            addIfNew(observer, regularObservers);
+        } else {
+            addIfNew(observer, eventQueueObservers);
+        }
+    }
+
+    private void addIfNew(E observer, List<E> destinationList) {
+        if (!destinationList.contains(observer)) {
+            destinationList.add(observer);
+        }
+    }
+
+    /**
+     * Deletes an observer in our messaging system.
+     *
+     * <!       Name     Dir   Description  >
+     *
+     * @param observer in,
+     */
+    public void removeObserver(E observer) {
+        regularObservers.remove(observer);
+        eventQueueObservers.remove(observer);
+    }
+
+    public void removeAllObservers() {
+        regularObservers.clear();
+        eventQueueObservers.clear();
+    }
+
+    /**
+     * Messaging method that handles telling each observer that something happen to the observable.
+     *
+     * <!       Name        Dir   Description  >
+     *
+     * @param notification in,  notification sent to the observer
+     */
+    public void notifyObservers(ObserverNotification<E> notification) {
+        //notify all the non-event queue observers now.
+        notifyObserversInternal(regularObservers, notification);
+        notifyObserversInEventQueueThread(notification);
+    }
+
+    /**
+     * Here is where we notify all the event queue observers. To notify the event queue observers we have to make sure
+     * it occurs in the event queue thread. If we're not in the event queue, we'll wrap it in an invoke and wait.
+     *
+     * <!       Name        Dir   Description  > <!       Name        Dir   Description  >
+     *
+     * @param notification in,  notification sent to the observer
+     */
+    private void notifyObserversInEventQueueThread(final ObserverNotification notification) {
+        if (eventQueueObservers.size() == 0) //if we have no event queue observsers, we're done
+        {
+            return;
+        }
+
+        if (EventQueue.isDispatchThread()) {
+            notifyObserversInternal(eventQueueObservers, notification);
+        } else {
+            try {
+                SwingUtilities.invokeAndWait(new Runnable() {
+                    public void run() {
+                        notifyObserversInternal(eventQueueObservers, notification);
+                    }
+                });
+            } catch (Exception e) {
+                logger.error("notifyObservers exception", e);
+            }
+        }
+    }
+
+    /**
+     * The internal mechanism that actually notifies the observers. We just iterate though each observer and pass it to
+     * the notification mechanism.
+     *
+     *
+     * <!       Name         Dir  Description  >
+     *
+     * @param observers in,  objects that changed (observable)
+     * @param notification in,  notification sent to the observer
+     */
+    private void notifyObserversInternal(List<E> observers, ObserverNotification notification) {
+        Iterator<E> iterator = observers.iterator();
+        while (iterator.hasNext()) {
+            E observer = iterator.next();
+            try {
+                notification.notify(observer);
+            } catch (Exception e) //this is so an error in the notification doesn't stop the entire process.
+            {
+                logger.error("error notifying observers", e);
+            }
+        }
+    }
+
+    public String toString() {
+        return regularObservers.size() + " regular observers, " + eventQueueObservers.size() + " event queue observers";
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/common/ReorderableList.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/common/ReorderableList.java
new file mode 100644
index 0000000..603bd41
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/common/ReorderableList.java
@@ -0,0 +1,566 @@
+/*
+ * 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.foundation.common;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+
+/*
+   Class to store a list whose elements can be reordered. This list is
+   meant to be generic so it can be reused.
+
+   The copy method allows for a 'deep copy' of all the list elements. For the
+   copy to work properly, all elements stored in the list must implement the
+   Copyable interface.
+
+   Unfortunately, we can't use Java's Object.clone() method because it is
+   protected and must be overridden as public to be used. So we can't call
+   obj.clone() on an Object instance.
+
+   @author champton
+*/
+
+public class ReorderableList<E> implements List<E> {
+    /**
+     * Internal list to store the real elements.
+     */
+    protected List<E> elements;
+
+    /*
+       Parameterized Constructor. This constructor uses an ArrayList by default.
+       Use this constructor if iterating through the elements in order is a
+       high priority.
+    */
+
+    public ReorderableList() {
+        // I chose ArrayList over LinkedList simply because of performance.
+        // LinkedLists are supposed to have better performance when you move
+        // the elements around, as you can do to this list when it is being
+        // edited. But editing will happen only rarely and the list will
+        // be used heavily when displaying the elements to the user.
+        elements = new ArrayList<E>();
+    }
+
+    /*
+       Parameterized Constructor. This is an alternative where the list-type
+       can be passed in. If reordering the list occurs often and has a higher
+       priority than in-order iteration. A LinkedList() can be passed in for
+       the elementList parameter.
+
+       CAUTION: When using this constructor you should probably make the call
+                like the following to prevent the list from being modified
+                outside of the ReorderableList instance:
+
+                List list = ReorderableList(  new LinkedList() );
+
+       @param  elementList The list instance used to store elements.
+    */
+
+    public ReorderableList(List<E> elementList) {
+        this.elements = elementList;
+    }
+
+    /*
+       Add a object to this ReorderableList.
+       @param  object   The object to add.
+    */
+
+    public boolean add(E object) {
+        return elements.add(object);
+    }
+
+    /*
+       Retrieve an object from the ReorderableList.
+       @param  index      The position of the element to retrieve.
+       @return Object - element in the list.
+    */
+
+    public E get(int index) {
+        return elements.get(index);
+    }
+
+    /*
+       Add another list to this ReorderableList.
+       @param  list   The object to add.
+    */
+
+    public void addAll(List<E> list) {
+        elements.addAll(list);
+    }
+
+    /*
+       Remove a object from the ReorderableList.
+       @param  object   The object to remove.
+       @return True if the ReorderableList contained the object, false
+               otherwise.
+    */
+
+    public boolean remove(Object object) {
+        return elements.remove(object);
+    }
+
+    /*
+       Retrieves the index of the element in the ReorderableList.
+       @param  object   The object whose index we want.
+       @return The index of the object in the list or -1 if the object is
+               not contained in the list.
+    */
+
+    public int indexOf(Object object) {
+        return elements.indexOf(object);
+    }
+
+    /*
+       Returns the number of elements in this ReorderableList.
+       @return The number of elements as an int.
+    */
+
+    public int size() {
+        return elements.size();
+    }
+
+    /*
+       Test to see if the ReorderableList has elements or not.
+       @return True if there are no elements in the list, false otherwise.
+    */
+
+    public boolean isEmpty() {
+        return elements.isEmpty();
+    }
+
+    /*
+       Moves the object down one position in the list. If the object reaches the
+       beginning of the list it obviously can go no farther so subsequent calls
+       will have no effect.
+       @param  objectToMove    The object to move.
+       @return Returns true if the object was in the list and was moved.
+    */
+
+    public boolean moveBefore(Object objectToMove) {
+        return ListReorderer.moveBefore(elements, objectToMove);
+    }
+
+    /*
+       Moves a list of elements in this list while keeping their relative positions.
+       When the first element reaches the beginning it goes no further and the
+       other elements in the list will continue to be shifted on subsequent calls
+       as long as they don't overwrite previously moved elements. This means that
+       elements with other elements between them will continue move with the same
+       distance between them but will 'bunch up' toward the beginning of the
+       list.
+
+       NOTE: The order of the list of moved elements is important. They have
+             to be added in order from lowest index to highest.
+
+       @param  elementsToMove       List of elements to move.
+    */
+
+    public void moveBefore(List elementsToMove) {
+        ListReorderer.moveBefore(elements, elementsToMove);
+    }
+
+    /*
+       Move a list of objects to a new specified location.
+       @param  objectsToMove The objects to move.
+       @param  newIndex     The new location of the object.
+    */
+
+    public void moveTo(List objectsToMove, int newIndex) {
+        ListReorderer.moveTo(elements, objectsToMove, newIndex);
+    }
+
+    /*
+       Moves a single object to the front of the list.
+       @param  objectToMove The object to move in the list.
+       @return True if the object was moved, false otherwise.
+    */
+
+    public boolean moveToFront(Object objectToMove) {
+        return ListReorderer.moveToFront(elements, objectToMove);
+    }
+
+    /*
+       Moves a list of objects to the front of the list.
+       @param  elementsToMove  The list of objects to move in the list.
+    */
+
+    public void moveToFront(List elementsToMove) {
+        ListReorderer.moveToFront(elements, elementsToMove);
+    }
+
+    /*
+       Moves the object up one index position higher in the list. If the object
+       reaches the end of the list it obviously can go no farther so subsequent
+       calls will have no effect.
+       @param  objectToMove    The object to move.
+       @return Returns true if the object was in the list and was moved.
+    */
+
+    public boolean moveAfter(Object objectToMove) {
+        return ListReorderer.moveAfter(elements, objectToMove);
+    }
+
+    /*
+       Moves the objects in the list up one index position in this list while
+       maintaining their relative position. When an element reaches the end
+       of the list it can go no farther, but the other elements continue to
+       move each call without overwriting previously moved elements. This
+       causes moved elements to 'bunch up' at the end of the list.
+
+       NOTE: The order of the list of moved elements is important. They have
+             to be added in order from lowest index to highest.
+
+       @param  elementsToMove     List of elements to move.
+    */
+
+    public void moveAfter(List elementsToMove) {
+        ListReorderer.moveAfter(elements, elementsToMove);
+    }
+
+    /*
+       Moves an object to the back of the list.
+       @param  objectToMove The object to move.
+       @return Returns true if the object was in the list and was moved.
+    */
+
+    public boolean moveToBack(Object objectToMove) {
+        return ListReorderer.moveToBack(elements, objectToMove);
+    }
+
+    /*
+       Moves a list of objects to the back of the list.
+       @param  elementsToMove The list of objects to move.
+    */
+
+    public void moveToBack(List elementsToMove) {
+        ListReorderer.moveToBack(elements, elementsToMove);
+    }
+
+    /*
+       @param  checkList  The list of elements to check against our ordered list.
+       @return True if the list of passed in elements are all at the front of the
+               list, false otherwise.
+    */
+
+    public boolean allElementsInFront(List checkList) {
+        return ListReorderer.allElementsInFront(elements, checkList);
+    }
+
+    /*
+       @param  checkList  The list of elements to check against our ordered list.
+       @return True if the list of passed in elements are all at the back of the
+               list, false otherwise.
+    */
+
+    public boolean allElementsInBack(List checkList) {
+        return ListReorderer.allElementsInBack(elements, checkList);
+    }
+
+    /*
+       Returns an Iterator object to iterate through the elements in the
+       ReorderableList.
+       @return Iterator of Objects. It's up to the caller to cast the elements
+               to the appropriate type.
+    */
+
+    public Iterator<E> iterator() {
+        return elements.iterator();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+    //////////////////////////// List Implementation ///////////////////////////
+    ////////////////////////////////////////////////////////////////////////////
+
+    /**
+     * Removes all of the elements from this list (optional operation).  This list will be empty after this call returns
+     * (unless it throws an exception).
+     *
+     * @throws UnsupportedOperationException if the <tt>clear</tt> method is not supported by this list.
+     */
+    public void clear() {
+        elements.clear();
+    }
+
+    /**
+     * Returns an array containing all of the elements in this list in proper sequence.  Obeys the general contract of
+     * the <tt>Collection.toArray</tt> method.
+     *
+     * @return an array containing all of the elements in this list in proper sequence.
+     */
+    public Object[] toArray() {
+        return elements.toArray();
+    }
+
+    /**
+     * Removes the element at the specified position in this list (optional operation).  Shifts any subsequent elements
+     * to the left (subtracts one from their indices).  Returns the element that was removed from the list.
+     *
+     * @param index the index of the element to removed.
+     * @return the element previously at the specified position.
+     * @throws UnsupportedOperationException if the <tt>remove</tt> method is not supported by this list.
+     * @throws IndexOutOfBoundsException if the index is out of range (index < 0 || index >= size()).
+     */
+    public E remove(int index) {
+        return elements.remove(index);
+    }
+
+    /**
+     * Inserts the specified element at the specified position in this list (optional operation).  Shifts the element
+     * currently at that position (if any) and any subsequent elements to the right (adds one to their indices).
+     *
+     * @param index index at which the specified element is to be inserted.
+     * @param element element to be inserted.
+     * @throws UnsupportedOperationException if the <tt>add</tt> method is not supported by this list.
+     * @throws ClassCastException if the class of the specified element prevents it from being added to this list.
+     * @throws NullPointerException if the specified element is null and this list does not support null elements.
+     * @throws IllegalArgumentException if some aspect of the specified element prevents it from being added to this
+     * list.
+     * @throws IndexOutOfBoundsException if the index is out of range (index < 0 || index > size()).
+     */
+    public void add(int index, E element) {
+        elements.add(index, element);
+    }
+
+    /**
+     * Returns the index in this list of the last occurrence of the specified element, or -1 if this list does not
+     * contain this element. More formally, returns the highest index <tt>i</tt> such that <tt>(o==null ? get(i)==null :
+     * o.equals(get(i)))</tt>, or -1 if there is no such index.
+     *
+     * @param object element to search for.
+     * @return the index in this list of the last occurrence of the specified element, or -1 if this list does not
+     *         contain this element.
+     * @throws ClassCastException if the type of the specified element is incompatible with this list (optional).
+     * @throws NullPointerException if the specified element is null and this list does not support null elements
+     * (optional).
+     */
+    public int lastIndexOf(Object object) {
+        return elements.lastIndexOf(object);
+    }
+
+    /**
+     * Returns <tt>true</tt> if this list contains the specified element. More formally, returns <tt>true</tt> if and
+     * only if this list contains at least one element <tt>e</tt> such that <tt>(o==null ? e==null : o.equals(e))</tt>.
+     *
+     * @param object element whose presence in this list is to be tested.
+     * @return <tt>true</tt> if this list contains the specified element.
+     * @throws ClassCastException if the type of the specified element is incompatible with this list (optional).
+     * @throws NullPointerException if the specified element is null and this list does not support null elements
+     * (optional).
+     */
+    public boolean contains(Object object) {
+        return elements.contains(object);
+    }
+
+    /**
+     * Inserts all of the elements in the specified collection into this list at the specified position (optional
+     * operation).  Shifts the element currently at that position (if any) and any subsequent elements to the right
+     * (increases their indices).  The new elements will appear in this list in the order that they are returned by the
+     * specified collection's iterator.  The behavior of this operation is unspecified if the specified collection is
+     * modified while the operation is in progress.  (Note that this will occur if the specified collection is this
+     * list, and it's nonempty.)
+     *
+     * @param index index at which to insert first element from the specified collection.
+     * @param c elements to be inserted into this list.
+     * @return <tt>true</tt> if this list changed as a result of the call.
+     * @throws UnsupportedOperationException if the <tt>addAll</tt> method is not supported by this list.
+     * @throws ClassCastException if the class of one of elements of the specified collection prevents it from being
+     * added to this list.
+     * @throws NullPointerException if the specified collection contains one or more null elements and this list does
+     * not support null elements, or if the specified collection is <tt>null</tt>.
+     * @throws IllegalArgumentException if some aspect of one of elements of the specified collection prevents it from
+     * being added to this list.
+     * @throws IndexOutOfBoundsException if the index is out of range (index < 0 || index > size()).
+     */
+    public boolean addAll(int index, Collection<? extends E> c) {
+        return elements.addAll(index, c);
+    }
+
+    /**
+     * Appends all of the elements in the specified collection to the end of this list, in the order that they are
+     * returned by the specified collection's iterator (optional operation).  The behavior of this operation is
+     * unspecified if the specified collection is modified while the operation is in progress.  (Note that this will
+     * occur if the specified collection is this list, and it's nonempty.)
+     *
+     * @param c collection whose elements are to be added to this list.
+     * @return <tt>true</tt> if this list changed as a result of the call.
+     * @throws UnsupportedOperationException if the <tt>addAll</tt> method is not supported by this list.
+     * @throws ClassCastException if the class of an element in the specified collection prevents it from being added to
+     * this list.
+     * @throws NullPointerException if the specified collection contains one or more null elements and this list does
+     * not support null elements, or if the specified collection is <tt>null</tt>.
+     * @throws IllegalArgumentException if some aspect of an element in the specified collection prevents it from being
+     * added to this list.
+     * @see #add(Object)
+     */
+    public boolean addAll(Collection<? extends E> c) {
+        return elements.addAll(c);
+    }
+
+    /**
+     * Returns <tt>true</tt> if this list contains all of the elements of the specified collection.
+     *
+     * @param c collection to be checked for containment in this list.
+     * @return <tt>true</tt> if this list contains all of the elements of the specified collection.
+     * @throws ClassCastException if the types of one or more elements in the specified collection are incompatible with
+     * this list (optional).
+     * @throws NullPointerException if the specified collection contains one or more null elements and this list does
+     * not support null elements (optional).
+     * @throws NullPointerException if the specified collection is <tt>null</tt>.
+     * @see #contains(Object)
+     */
+    public boolean containsAll(Collection<?> c) {
+        return elements.containsAll(c);
+    }
+
+    /**
+     * Removes from this list all the elements that are contained in the specified collection (optional operation).
+     *
+     * @param c collection that defines which elements will be removed from this list.
+     * @return <tt>true</tt> if this list changed as a result of the call.
+     * @throws UnsupportedOperationException if the <tt>removeAll</tt> method is not supported by this list.
+     * @throws ClassCastException if the types of one or more elements in this list are incompatible with the specified
+     * collection (optional).
+     * @throws NullPointerException if this list contains one or more null elements and the specified collection does
+     * not support null elements (optional).
+     * @throws NullPointerException if the specified collection is <tt>null</tt>.
+     * @see #remove(Object)
+     * @see #contains(Object)
+     */
+    public boolean removeAll(Collection<?> c) {
+        return elements.removeAll(c);
+    }
+
+    /**
+     * Retains only the elements in this list that are contained in the specified collection (optional operation).  In
+     * other words, removes from this list all the elements that are not contained in the specified collection.
+     *
+     * @param c collection that defines which elements this set will retain.
+     * @return <tt>true</tt> if this list changed as a result of the call.
+     * @throws UnsupportedOperationException if the <tt>retainAll</tt> method is not supported by this list.
+     * @throws ClassCastException if the types of one or more elements in this list are incompatible with the specified
+     * collection (optional).
+     * @throws NullPointerException if this list contains one or more null elements and the specified collection does
+     * not support null elements (optional).
+     * @throws NullPointerException if the specified collection is <tt>null</tt>.
+     * @see #remove(Object)
+     * @see #contains(Object)
+     */
+    public boolean retainAll(Collection<?> c) {
+        return elements.retainAll(c);
+    }
+
+    /**
+     * Returns a view of the portion of this list between the specified <tt>fromIndex</tt>, inclusive, and
+     * <tt>toIndex</tt>, exclusive.  (If <tt>fromIndex</tt> and <tt>toIndex</tt> are equal, the returned list is empty.)
+     *  The returned list is backed by this list, so non-structural changes in the returned list are reflected in this
+     * list, and vice-versa. The returned list supports all of the optional list operations supported by this list.<p>
+     * <p/> This method eliminates the need for explicit range operations (of the sort that commonly exist for arrays).
+     *  Any operation that expects a list can be used as a range operation by passing a subList view instead of a whole
+     * list.  For example, the following idiom removes a range of elements from a list:
+     * <pre>
+     * 	    list.subList(from, to).clear();
+     * </pre>
+     * Similar idioms may be constructed for <tt>indexOf</tt> and <tt>lastIndexOf</tt>, and all of the algorithms in the
+     * <tt>Collections</tt> class can be applied to a subList.<p> <p/> The semantics of the list returned by this method
+     * become undefined if the backing list (i.e., this list) is <i>structurally modified</i> in any way other than via
+     * the returned list.  (Structural modifications are those that change the size of this list, or otherwise perturb
+     * it in such a fashion that iterations in progress may yield incorrect results.)
+     *
+     * @param fromIndex low endpoint (inclusive) of the subList.
+     * @param toIndex high endpoint (exclusive) of the subList.
+     * @return a view of the specified range within this list.
+     * @throws IndexOutOfBoundsException for an illegal endpoint index value (fromIndex < 0 || toIndex > size ||
+     * fromIndex > toIndex).
+     */
+    public List<E> subList(int fromIndex, int toIndex) {
+        return elements.subList(fromIndex, toIndex);
+    }
+
+    /**
+     * Returns a list iterator of the elements in this list (in proper sequence).
+     *
+     * @return a list iterator of the elements in this list (in proper sequence).
+     */
+    public ListIterator<E> listIterator() {
+        return elements.listIterator();
+    }
+
+    /**
+     * Returns a list iterator of the elements in this list (in proper sequence), starting at the specified position in
+     * this list.  The specified index indicates the first element that would be returned by an initial call to the
+     * <tt>next</tt> method.  An initial call to the <tt>previous</tt> method would return the element with the
+     * specified index minus one.
+     *
+     * @param index index of first element to be returned from the list iterator (by a call to the <tt>next</tt>
+     * method).
+     * @return a list iterator of the elements in this list (in proper sequence), starting at the specified position in
+     *         this list.
+     * @throws IndexOutOfBoundsException if the index is out of range (index < 0 || index > size()).
+     */
+    public ListIterator<E> listIterator(int index) {
+        return elements.listIterator(index);
+    }
+
+    /**
+     * Replaces the element at the specified position in this list with the specified element (optional operation).
+     *
+     * @param index index of element to replace.
+     * @param element element to be stored at the specified position.
+     * @return the element previously at the specified position.
+     * @throws UnsupportedOperationException if the <tt>set</tt> method is not supported by this list.
+     * @throws ClassCastException if the class of the specified element prevents it from being added to this list.
+     * @throws NullPointerException if the specified element is null and this list does not support null elements.
+     * @throws IllegalArgumentException if some aspect of the specified element prevents it from being added to this
+     * list.
+     * @throws IndexOutOfBoundsException if the index is out of range (index < 0 || index >= size()).
+     */
+    public E set(int index, E element) {
+        return elements.set(index, element);
+    }
+
+    /**
+     * Returns an array containing all of the elements in this list in proper sequence; the runtime type of the returned
+     * array is that of the specified array.  Obeys the general contract of the <tt>Collection.toArray(Object[])</tt>
+     * method.
+     *
+     * @param objectArray the array into which the elements of this list are to be stored, if it is big enough;
+     * otherwise, a new array of the same runtime type is allocated for this purpose.
+     * @return an array containing the elements of this list.
+     * @throws ArrayStoreException if the runtime type of the specified array is not a supertype of the runtime type of
+     * every element in this list.
+     * @throws NullPointerException if the specified array is <tt>null</tt>.
+     */
+    public <T> T[] toArray(T[] objectArray) {
+        return elements.toArray(objectArray);
+    }
+
+    /*
+    This is mainly used for after doing a move. It gives you the current
+    index of all the moved elements. This is useful for UIs that need to
+    reselect the new items.
+
+    @param  elementsToMove the elements that were moved
+    @return                an integer array of the items to select.
+    @author mhunsicker
+    */
+
+    public int[] getIndices(List elementsToMove) {
+        return ListReorderer.getIndices(elements, elementsToMove);
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/basic/ClientProcess.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/basic/ClientProcess.java
new file mode 100644
index 0000000..83f6281
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/basic/ClientProcess.java
@@ -0,0 +1,164 @@
+/*
+ * 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.foundation.ipc.basic;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.ConnectException;
+import java.net.Socket;
+
+/**
+ * The client of what the ProcessLauncherServer launches. The client makes a connection to the server and sends messages
+ * to it. The server responds to those messages, but does not initiate communications otherwise. You implement the
+ * Protocol interface to handle the specifics of the communications.
+ *
+ * @author mhunsicker
+ */
+public class ClientProcess {
+    private final Logger logger = Logging.getLogger(ClientProcess.class);
+
+    /**
+     * Implement this to define the behavior of the communication on the client side.
+     */
+    public interface Protocol {
+        /**
+         * Gives your protocol a chance to store this client so it can access its functions.
+         */
+        public void initialize(ClientProcess client);
+
+        /**
+         * Notification that we have connected to the server.
+         *
+         * @return true if we should continue the connection, false if not.
+         */
+        public boolean serverConnected(Socket clientSocket);
+
+        /**
+         * @return true if we should keep the connection alive. False if we should stop communicaiton.
+         */
+        public boolean continueConnection();
+    }
+
+    private ObjectSocketWrapper socketWrapper;
+    private Protocol protocol;
+
+    public ClientProcess(Protocol protocol) {
+        this.protocol = protocol;
+        protocol.initialize(this);
+    }
+
+    /**
+     * Call this to attempt to connect to the server.
+     *
+     * @param port where the server is listening. Since it launched this client, it should have either been passed to it
+     * on the command line or via a system property (-D).
+     * @return true if we connected to the server, false if not.
+     */
+    public boolean start(int port) {
+        Socket clientSocket = null;
+        try {
+            clientSocket = new Socket((String) null, port);
+            socketWrapper = new ObjectSocketWrapper(clientSocket);
+            if (protocol.serverConnected(clientSocket)) {
+                return true;
+            }
+
+            logger.error("Failed to connect to server (might not have returned correct connection string): " + port);
+        }
+        catch (ConnectException e) {
+            logger.error("Failed to connect to server: " + port);
+        }
+        catch (Exception e) {
+            logger.error("Failed to connect to server: " + port, e);
+        }
+
+        try {
+            if (clientSocket != null) {
+                clientSocket.close();
+            }
+            socketWrapper = null;
+        }
+        catch (IOException e1) {
+            logger.error("Failed to close socket", e1);
+        }
+        return false;
+    }
+
+    /**
+     * Call this to stop communications with the server.
+     */
+    public void stop() {
+        if (socketWrapper != null) {
+            socketWrapper.close();
+         }
+    }
+
+    /**
+     * Call this to send a message with some binary data. The protocal and the server must understand the message,
+     * message type, and data.
+     *
+     * @param messageType the message type. Whatever the client and server want.
+     * @param message     the message being sent
+     * @param data        the data being sent. Must be serializable.
+     * @return true if we sent the message, false if not.
+     */
+    public boolean sendMessage(String messageType, String message, Serializable data) {
+        return socketWrapper.sendObject(new MessageObject(messageType, message, data));
+    }
+
+    public boolean sendMessage(String messageType, String message) {
+        return sendMessage(messageType, message, null);
+    }
+
+    /**
+     * Call this to send a message with some binary data and wait for the server's acknowledgement. The protocol and the
+     * server must understand the message, message type, and data.
+     *
+     * @param messageType the message type. Whatever the client and server want.
+     * @param message     the message being sent
+     * @param data        the data being sent. Must be serializable.
+     * @return the reply from the server
+     */
+    public MessageObject sendMessageWaitForReply(String messageType, String message, Serializable data) {
+        if (!socketWrapper.sendObject(new MessageObject(messageType, message, data))) {
+            return null;
+        }
+
+        return readMessage();
+    }
+
+    /**
+     * Call this to listen for a message from the server. This is really only meant to be a response from the server as
+     * a response to our message.
+     *
+     * @return the message returned.
+     */
+    public MessageObject readMessage() {
+        Object object = socketWrapper.readObject();
+        if (object == null) {
+            return null;
+        }
+
+        if (object instanceof MessageObject) {
+            return (MessageObject) object;
+        }
+
+        return new MessageObject("?", object.toString(), null);
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/basic/ExecutionInfo.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/basic/ExecutionInfo.java
new file mode 100644
index 0000000..390bd52
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/basic/ExecutionInfo.java
@@ -0,0 +1,48 @@
+/*
+ * 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.foundation.ipc.basic;
+
+import java.io.File;
+import java.util.HashMap;
+
+/**
+ * @author mhunsicker
+ */
+/*
+   Fill this in with whatever you need to launch your external process.
+   @author mhunsicker
+*/
+public interface ExecutionInfo {
+    /**
+     * @return the command line arguments passed to gradle.
+     */
+    public String[] getCommandLineArguments();
+
+    /**
+     * @return the working directory from which the JVM will execute.
+     */
+    public File getWorkingDirectory();
+
+    /**
+     * @return a hashmap of environment variables and their values that are passed to the JVM that is created.
+     */
+    public HashMap<String, String> getEnvironmentVariables();
+
+    /**
+     * Notification that execution has completed. This provides a place to cleanup any init scripts you have created.
+     */
+    public void processExecutionComplete();
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/basic/MessageObject.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/basic/MessageObject.java
new file mode 100644
index 0000000..6a04d44
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/basic/MessageObject.java
@@ -0,0 +1,70 @@
+/*
+ * 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.foundation.ipc.basic;
+
+import java.io.Serializable;
+import java.io.IOException;
+import java.io.ObjectStreamException;
+
+/**
+ * A holder for a message that is sent over a socket.
+ *
+ * @author mhunsicker
+ */
+public class MessageObject implements Serializable {
+    private String messageType;
+    private String message;
+    private Serializable data;
+
+    public MessageObject(String messageType, String message, Serializable data) {
+        this.messageType = messageType;
+        this.message = message;
+        this.data = data;
+    }
+
+    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
+        out.writeObject(messageType);
+        out.writeObject(message);
+        out.writeObject(data);
+    }
+
+    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
+        messageType = (String) in.readObject();
+        message = (String) in.readObject();
+        data = (Serializable) in.readObject();
+    }
+
+    private void readObjectNoData() throws ObjectStreamException {
+
+    }
+
+    @Override
+    public String toString() {
+        return "Type='" + messageType + '\'' + " Message='" + message + '\'' + " data=" + data;
+    }
+
+    public String getMessageType() {
+        return messageType;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public Serializable getData() {
+        return data;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/basic/ObjectSocketWrapper.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/basic/ObjectSocketWrapper.java
new file mode 100644
index 0000000..f1f3b1e
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/basic/ObjectSocketWrapper.java
@@ -0,0 +1,124 @@
+/*
+ * 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.foundation.ipc.basic;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.Socket;
+import java.net.SocketException;
+
+/**
+ * Wrapper around a java.net.Socket just to simplify usage.
+ *
+ * @author mhunsicker
+ */
+public class ObjectSocketWrapper {
+    private Socket socket;
+    private final Logger logger = Logging.getLogger(ObjectSocketWrapper.class);
+
+    public ObjectSocketWrapper(Socket socket) {
+        this.socket = socket;
+    }
+
+    public void setTimeout(int timeoutMilliseconds) {
+        try {
+            socket.setSoTimeout(timeoutMilliseconds);
+        }
+        catch (SocketException e) {
+            logger.error("Failed to set timeout", e);
+        }
+    }
+
+    public Object readObject() {
+
+        ObjectInputStream reader = null;
+
+        try {
+            reader = new ObjectInputStream(socket.getInputStream());
+        } catch (SocketException e) {
+            if (!isIgnorableException(e)) {
+                logger.error("Reading Object", e);
+        }
+            return null;
+        }
+        catch (Exception e) {
+            logger.error("Reading Object", e);
+            return null;
+        }
+
+        try {
+            return reader.readObject();
+        }
+        catch (SocketException e) {
+            //a connection reset is normal if the client quits, so don't dump out this exception and just return null.
+            if (!isIgnorableException(e)) {
+                logger.error("Reading Object", e);
+            }
+            return null;
+        }
+        catch (Exception e) {
+            logger.error("Reading Object", e);
+        }
+
+        return null;
+    }
+
+    private boolean isIgnorableException(SocketException e) {
+        //a connection reset is normal if the client quits.
+        return "Connection reset".equalsIgnoreCase(e.getMessage());
+    }
+
+   /**
+    * Synchronizing this prevents multiple threads from sending messages at the same
+    * time which corrupts the socket. 
+     */
+    public synchronized boolean sendObject(Object object) {
+        ObjectOutputStream writer = null;
+        try {
+            writer = new ObjectOutputStream(socket.getOutputStream());
+        }
+        catch (IOException e) {
+            logger.error("Exception when creating writer sending object: " + object, e);
+            return false;
+        }
+
+        try {
+            writer.reset();
+            writer.flush();
+            writer.writeObject(object);
+            writer.flush();
+
+            return true;
+        }
+        catch (Exception e) {
+            logger.error("Exception when sending object: " + object, e);
+            return false;
+        }
+    }
+
+    public void close() {
+        try {
+            socket.close();
+        }
+        catch (IOException e) {
+            logger.error("Closing", e);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/basic/ProcessLauncherServer.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/basic/ProcessLauncherServer.java
new file mode 100644
index 0000000..9ccd11a
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/basic/ProcessLauncherServer.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.foundation.ipc.basic;
+
+import org.gradle.foundation.common.ObserverLord;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.process.ExecResult;
+import org.gradle.process.internal.ExecHandle;
+import org.gradle.process.internal.ExecHandleBuilder;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * This launches an application as a separate process then listens for messages from it. You implement the Protocol
+ * interface to handle the specifics of the communications. To use this, instantiate it, then call start. When the
+ * communications are finished, call requestShutdown(). Your server's protocol can call sendMessage once communication
+ * is started to respond to client's messages.
+ *
+ * @author mhunsicker
+ */
+public class ProcessLauncherServer extends Server<ProcessLauncherServer.Protocol, ProcessLauncherServer.ServerObserver> {
+    private volatile ExecHandle externalProcess;
+
+    private final Logger logger = Logging.getLogger(ProcessLauncherServer.class);
+
+    /**
+     * Implement this to define the behavior of the communication on the server side.
+     */
+    public interface Protocol extends Server.Protocol<ProcessLauncherServer> {
+        public void aboutToKillProcess();
+
+        /**
+         * Fill in the ExecutionInfo object with information needed to execute the other process.
+         * @param serverPort the port the server is listening on. The client should send messages here
+         * @return an executionInfo object containing information about what we execute.
+         */
+        public ExecutionInfo getExecutionInfo(int serverPort );
+
+        /**
+         * Notification that the client has shutdown. Note: this can occur before communications has ever started. You
+         * SHOULD get this notification before receiving serverExited, even if the client fails to launch or locks up.
+         *
+         * @param result the return code of the client application
+         * @param output the standard error and standard output of the client application
+         */
+        public void clientExited(int result, String output);
+    }
+
+    public interface ServerObserver extends Server.ServerObserver {
+        /**
+         * Notification that the client has shutdown. Note: this can occur before communications has ever started. You
+         * SHOULD get this notification before receiving serverExited, even if the client fails to launch or locks up.
+         *
+         * @param result the return code of the client application
+         * @param output the standard error and standard output of the client application
+         */
+        public void clientExited(int result, String output);
+    }
+
+    public ProcessLauncherServer(Protocol protocol) {
+        super(protocol);
+    }
+
+    @Override
+    protected void communicationsStarted() {
+        launchExternalProcess();
+    }
+
+    /**
+     * This launches an external process in a thread and waits for it to exit.
+     */
+    private void launchExternalProcess() {
+        Thread thread = new Thread(new Runnable() {
+            public void run() {
+
+                ExecutionInfo executionInfo = null;
+                ExecHandle execHandle = null;
+                ByteArrayOutputStream output = null;
+                try {
+                    
+                    executionInfo = protocol.getExecutionInfo(getPort() );
+
+                    ExecHandleBuilder builder = new ExecHandleBuilder();
+                    builder.workingDir(executionInfo.getWorkingDirectory());
+                    builder.commandLine((Object[]) executionInfo.getCommandLineArguments());
+                    builder.environment(executionInfo.getEnvironmentVariables());
+                    output = new ByteArrayOutputStream();
+                    builder.setStandardOutput(output);
+                    builder.setErrorOutput(output);
+                    execHandle = builder.build();
+                    setExternalProcess(execHandle);
+
+                    execHandle.start();
+                }
+                catch (Throwable e) {
+                    logger.error("Starting external process", e);
+                    notifyClientExited( -1, e.getMessage() );
+                    setExternalProcess(null);
+                    return;
+                }
+
+                ExecResult result = execHandle.waitForFinish();
+
+                setExternalProcess(null);   //clear our external process member variable (we're using our local variable below). This is so we know the process has already stopped.
+
+                executionInfo.processExecutionComplete();
+                notifyClientExited( result.getExitValue(), output.toString() );
+            }
+        });
+
+        thread.start();
+    }
+
+    public void stop() {
+        super.stop();
+        killProcess(); //if the process is still running, shut it down
+    }
+
+    public void setExternalProcess(ExecHandle externalProcess) {
+        this.externalProcess = externalProcess;
+    }
+
+    /**
+     * Call this to violently kill the external process. This is NOT a good way to stop it. It is preferrable to ask the
+     * thread to stop. However, gradle has no way to do that, so we'll be killing it.
+     */
+    public synchronized void killProcess() {
+        if (externalProcess != null) {
+            requestShutdown();
+            protocol.aboutToKillProcess();
+            externalProcess.abort();
+            setExternalProcess(null);
+            notifyClientExited(-1, "Process Canceled");
+        }
+    }
+
+    private void notifyClientExited(final int result, final String output) {
+        protocol.clientExited(result, output);
+
+        observerLord.notifyObservers(new ObserverLord.ObserverNotification<ServerObserver>() {
+            public void notify(ServerObserver observer) {
+                observer.clientExited(result, output);
+            }
+        });
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/basic/Server.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/basic/Server.java
new file mode 100644
index 0000000..ce55f8a
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/basic/Server.java
@@ -0,0 +1,308 @@
+/*
+ * 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.foundation.ipc.basic;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.foundation.common.ObserverLord;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+/**
+ * This is a server that talks to a client via sockets (Rudimentary form of Inter-Process Communication (IPC)). This
+ * does the work of locating a free socket and starting the connection. To use this, you really only have to define a
+ * Protocol that handles the actual messages. You'll want to make your client startup a ClientProcess object that
+ * implements a corresponding Protocol.
+ *
+ * @author mhunsicker
+  */
+public class Server<P extends Server.Protocol, O extends Server.ServerObserver>
+{
+   private final Logger logger = Logging.getLogger( Server.class );
+
+   private ServerSocket serverSocket;
+   private boolean isServerRunning;
+   private boolean hasRequestedShutdown;
+
+   private ObjectSocketWrapper clientSocket;
+   protected P protocol;
+   private Thread communicationThread;
+   private int port;
+
+   protected ObserverLord<O> observerLord = new ObserverLord<O>();
+
+   //
+
+    /**
+     * Implement this to define the behavior of the communication on the server side.
+     */
+    public interface Protocol<S extends Server>
+    {
+        /**
+         * Gives your protocol a chance to store this server so it can access its functions.
+         */
+        public void initialize( S server );
+
+        /**
+         * Notification that the connection was accepted by the client.
+         */
+        public void connectionAccepted();
+
+        /**
+         * @return true if we should keep the connection alive. False if we should stop communicaiton.
+         */
+        public boolean continueConnection();
+
+        /**
+         * Notification that a message has been received.
+         *
+         * @param message the message that was received.
+         */
+        public void messageReceived( MessageObject message );
+
+        /**
+         * Notification that the client has stopped all communications.
+         */
+        public void clientCommunicationStopped();
+
+        /**
+         * Notification that a read failure occurred. This really only exists for debugging purposes when things
+         * go wrong.
+         */
+        void readFailureOccurred();
+    }
+
+    //
+    public interface ServerObserver
+    {
+        /**
+         * Notification that the server has shutdown.
+         */
+        public void serverExited();
+    }
+
+   public Server( P protocol )
+   {
+      this.protocol = protocol;
+      protocol.initialize( this );
+   }
+
+   public int getPort() { return port; }
+
+   /**
+     * Call this to start the server.
+     *
+     * @return true if we started, false if not.
+   */
+   public boolean start()
+   {
+      port = connect();
+        if (port == -1) {
+         return false;
+        }
+
+      communicationThread = new Thread( new Runnable()
+      {
+         public void run()
+         {
+            listenForConnections();
+         }
+      });
+
+      communicationThread.start();
+
+      communicationsStarted();
+
+      return true;
+   }
+
+   /**
+   * this exists solely so it can be overridden. Its an internal notification that communcations have started.
+   * You may want to do some extra processing now.
+   */
+
+    protected void communicationsStarted() {
+
+   }
+
+   /**
+     * This attempts to open a free port. We'll search for an open port until we find one.
+     *
+     * @return the port we opened or -1 if we couldn't open one.
+   */
+   private int connect()
+   {
+       try {
+           serverSocket = new ServerSocket(0);
+           return serverSocket.getLocalPort();
+       } catch (IOException e) {
+           logger.error( "Could not listen on port: " + port, e );
+           return -1;
+       }
+   }
+
+   /**
+     * This sits in a loop and listens for connections. Once a connection has been made, we'll call another function to
+     * process it.
+   */
+   private void listenForConnections()
+   {
+      int consecutiveFailures = 0;
+      while( !hasRequestedShutdown )
+      {
+         Socket socket = null;
+         try
+         {
+            serverSocket.setSoTimeout( 2000 );  //attempt to connect for a few seconds, then try again (so we'll get any shutdown requests).
+            socket = serverSocket.accept();
+
+            clientSocket = new ObjectSocketWrapper( socket );
+            protocol.connectionAccepted();
+            consecutiveFailures = 0;   //reset our consecutive failures.
+            serverSocket.setSoTimeout( 0 );
+
+            processCommunications();
+
+            clientSocket.close();
+         }
+         catch( IOException e )
+         {
+            consecutiveFailures++;
+            if( consecutiveFailures >= 20 )  //if we fail too many times, we'll request to shutdown. It's obviously not working. This is an arbitrary number.
+            {
+               requestShutdown();
+            }
+
+            if( consecutiveFailures > 8 )    //the first few usually fail while we're waiting for the process to startup.
+            {
+               logger.error( "Accept failed (" + consecutiveFailures + ")." );
+            }
+         }
+         catch( Throwable t ) {  //something really bad happened, shut down
+            logger.error( "Listening for connections", t );
+            requestShutdown();
+         }
+      }
+
+      isServerRunning = false;
+
+      stop();
+      notifyServerExited();
+   }
+
+   /**
+     * This is called once a connection is made. We'll listen for messages from the client, notifying the protocol of
+     * them to do whatever it needs.
+   */
+   private void processCommunications()
+   {
+      boolean hasClientStopped = false;
+      int failureCount = 0;
+      while( !hasClientStopped && protocol.continueConnection() && !hasRequestedShutdown )
+      {
+         Object object = clientSocket.readObject();
+
+         if( object == null )
+         {
+            if( !hasRequestedShutdown )   //if we're trying to shutdown, we can get errors here. Just ignore them and move on
+            {
+               failureCount++;
+               protocol.readFailureOccurred();
+               if( failureCount == 3 ) //after 3 failures, assume the client went away.
+               {
+                  hasClientStopped = true;
+                  protocol.clientCommunicationStopped();
+               }
+            }
+         }
+         else
+         {
+            failureCount = 0; //reset our failures
+
+                if (object instanceof String) {
+               protocol.messageReceived( new MessageObject( "?", object.toString(), null ) );
+                } else if (object instanceof MessageObject) {
+                  protocol.messageReceived( (MessageObject) object );
+                }
+          }
+        }
+    }
+
+   public void requestShutdown() { hasRequestedShutdown = true; }
+
+   public boolean isServerRunning() { return isServerRunning; }
+
+   /**
+     * Call this to send a message. The protocal and the client must understand the message and message type.
+     *
+     * @param messageType the message type. Whatever the client and server want.
+     * @param message the message being sent.
+   */
+   public void sendMessage( String messageType, String message )
+   {
+      clientSocket.sendObject( new MessageObject( messageType, message, null ) );
+   }
+
+   /**
+     * Call this to send a message with some binary data. The protocal and the client must understand the message,
+     * message type, and data.
+     *
+     * @param messageType the message type. Whatever the client and server want.
+     * @param message the message being sent
+     * @param data the data being sent. Must be serializable.
+   */
+   public void sendMessage( String messageType, String message, Serializable data )
+   {
+      clientSocket.sendObject( new MessageObject( messageType, message, data ) );
+   }
+
+
+   public void stop()
+   {
+      try
+      {
+         serverSocket.close();
+      }
+      catch( IOException e )
+      {
+         logger.error( "Closing socket", e );
+      }
+   }
+
+   private void notifyServerExited()
+   {
+      observerLord.notifyObservers( new ObserverLord.ObserverNotification<O>()
+      {
+         public void notify( ServerObserver observer )
+         {
+            observer.serverExited();
+         }
+      } );
+   }
+
+   public void addServerObserver( O observer, boolean inEventQueue )
+   {
+      observerLord.addObserver( observer, inEventQueue );
+   }
+
+   public void removeServerObserver( O observer )
+   {
+      observerLord.removeObserver( observer );
+   }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/AbstractGradleServerProtocol.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/AbstractGradleServerProtocol.java
new file mode 100644
index 0000000..b7e1c0f
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/AbstractGradleServerProtocol.java
@@ -0,0 +1,481 @@
+/*
+ * 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.foundation.ipc.gradle;
+
+import org.apache.commons.io.IOUtils;
+import org.gradle.initialization.DefaultCommandLine2StartParameterConverter;
+import org.gradle.StartParameter;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.foundation.CommandLineAssistant;
+import org.gradle.foundation.ipc.basic.MessageObject;
+import org.gradle.foundation.ipc.basic.ProcessLauncherServer;
+import org.gradle.foundation.ipc.basic.ExecutionInfo;
+import org.gradle.foundation.ipc.basic.ClientProcess;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * This defines the basic behavior of all gradle protocols for interprocess communication. It manages handshaking,
+ * detecting if the client executed prematurely, as well as executing alternate external processes. All you need to do
+ * is extend this, implement the abstract functions, and make sure you call setHasReceivedBuildCompleteNotification()
+ * when whatever you were doing is complete (so we know any exiting is not premature).
+ *
+ * @author mhunsicker
+  */
+public abstract class AbstractGradleServerProtocol implements ProcessLauncherServer.Protocol
+{
+   private static final String INIT_SCRIPT_EXTENSION = ".gradle";
+
+   private final Logger logger = Logging.getLogger( AbstractGradleServerProtocol.class );
+
+   protected ProcessLauncherServer server;
+   private boolean continueConnection;
+   private boolean waitingOnHandshakeCompletion;
+   private boolean hasCompletedConnection;
+
+   private boolean hasReceivedBuildCompleteNotification;
+
+   private File currentDirectory;
+   private File gradleHomeDirectory;
+
+   private File customGradleExecutor;
+   private String commandLine;
+   private LogLevel logLevel;
+
+   //all of this is just so we can get gradle to kill itself when we cancel
+   private int killGradleServerPort;
+   private KillGradleClientProtocol killGradleClientProcotol;
+   private ClientProcess killGradleClient;
+
+   protected MessageObject lastMessageReceived; //just for debugging purposes
+
+   /**
+     * @return true if we should keep the connection alive. False if we should stop communicaiton.
+   */
+   public boolean continueConnection()
+   {
+      return continueConnection;
+   }
+
+   private StartParameter.ShowStacktrace stackTraceLevel;
+
+   public AbstractGradleServerProtocol( File currentDirectory, File gradleHomeDirectory, File customGradleExecutor, String fullCommandLine, LogLevel logLevel, StartParameter.ShowStacktrace stackTraceLevel)
+   {
+      this.currentDirectory = currentDirectory;
+      this.gradleHomeDirectory = gradleHomeDirectory;
+      this.customGradleExecutor = customGradleExecutor;
+      this.commandLine = fullCommandLine;
+      this.logLevel = logLevel;
+      this.stackTraceLevel = stackTraceLevel;
+   }
+
+   /**
+     * Notification that the connection was accepted by the client.
+   */
+   public void connectionAccepted()
+   {
+      //let's make sure we're talking to the right client with some tiny handshaking.
+      server.sendMessage( ProtocolConstants.HANDSHAKE_TYPE, ProtocolConstants.HANDSHAKE_SERVER );
+      continueConnection = true;
+      waitingOnHandshakeCompletion = true;
+   }
+
+   /**
+     * Gives your protocol a chance to store this server so it can access its functions.
+   */
+   public void initialize( ProcessLauncherServer server ) { this.server = server; }
+
+   /**
+     * Call this to stop communication
+   */
+   protected void closeConnection()
+   {
+      this.continueConnection = false;
+   }
+
+   /**
+     * Notification that a message has been received. If we just connected, we'll do a quick handshake to verify the
+     * client, then we just pass the rest on our our output panel.
+     *
+     * @param message the message that was received.
+   */
+   public void messageReceived( MessageObject message )
+   {
+      lastMessageReceived = message;
+      if( waitingOnHandshakeCompletion )  //are we still handshaking?
+      {
+         if( ProtocolConstants.HANDSHAKE_CLIENT.equalsIgnoreCase( message.getMessage() ) )
+         {
+            waitingOnHandshakeCompletion = false;  //we've received what we expected
+            hasCompletedConnection = true;         //and we're now connected
+            if( message.getData() != null )
+            {
+               killGradleServerPort = (Integer) message.getData();
+               killGradleClientProcotol = new KillGradleClientProtocol();
+               killGradleClient = new ClientProcess( killGradleClientProcotol );
+               killGradleClient.start( killGradleServerPort );
+               handShakeCompleted();
+            }
+            else
+            {
+               addStatus( "Invalid handshaking. Missing port number. Stopping connection" );
+               server.sendMessage( "?", "Invalid client handshake protocol!" );
+               closeConnection();
+            }
+         }
+         else
+         {
+            addStatus( "Invalid handshaking. Stopping connection" );
+            server.sendMessage( "?", "Invalid client handshake protocol!" );
+            closeConnection();
+         }
+      }
+      else  //otherwise, its just a normal message, the protocol should handle it.
+      {
+         try
+         {
+            handleMessageReceived( message );
+         }
+         catch( Throwable e )
+         {
+            logger.error( "Problem while handing message :\n" + message, e );
+         }
+      }
+   }
+
+   /**
+     * This provides you with a chance to do something when the handshaking is complete
+    */
+   protected void handShakeCompleted()
+   {
+
+   }
+
+   /**
+     * Notification that a message was received that we didn't process. Implement this to handle the specifics of your
+     * protocol. Basically, the base class handles the handshake. The rest of the conversation is up to you.
+     *
+     * @param message the message we received.
+     * @return true if we handled the message, false if not. If we don't know it, we won't return an acknowlegement.
+   */
+   protected abstract boolean handleMessageReceived( MessageObject message );
+
+   /**
+     * Call this to mark the build as completed (whether successfully or not). This is used to determine if the client
+     * has exited prematurely which indicates a problem.
+   */
+   public void setHasReceivedBuildCompleteNotification() { this.hasReceivedBuildCompleteNotification = true; }
+
+   /**
+     * Notification of any status that might be helpful to the user.
+     *
+     * @param status a status message
+   */
+   protected abstract void addStatus(String status );
+
+            public class MyExecutionInfo implements ExecutionInfo {
+                public String[] commandLineArguments;
+                public File workingDirectory;
+                public HashMap<String, String> environmentVariables = new HashMap<String, String>();
+                public File initStriptPath;
+
+                public String[] getCommandLineArguments() {
+                    return commandLineArguments;
+                }
+
+                public File getWorkingDirectory() {
+                    return workingDirectory;
+                }
+
+                public HashMap<String, String> getEnvironmentVariables() {
+                    return environmentVariables;
+                }
+
+                public void setCommandLineArguments(String[] commandLineArguments) {
+                    this.commandLineArguments = commandLineArguments;
+                }
+
+                public void setWorkingDirectory(File workingDirectory) {
+                    this.workingDirectory = workingDirectory;
+                }
+
+                public void addEnvironmentVariable(String name, String value) {
+                    this.environmentVariables.put(name, value);
+                }
+
+                public void processExecutionComplete() {
+                    if( initStriptPath != null ) {
+                        initStriptPath.delete();
+                    }
+                }
+            }
+
+
+   /**
+     * Fill in the ExecutionInfo object with information needed to execute the other process.
+         * @param serverPort the port the server is listening on. The client should send messages here
+         * @return an executionInfo object containing information about what we execute.
+   */
+   public ExecutionInfo getExecutionInfo( int serverPort )
+   {
+      MyExecutionInfo executionInfo = new MyExecutionInfo();
+
+      //set some environment variables that need to be passed to the script.
+      executionInfo.addEnvironmentVariable( "GRADLE_HOME", getGradleHomeDirectory().getAbsolutePath() );
+      executionInfo.addEnvironmentVariable( "JAVA_HOME", System.getProperty( "java.home" ) );
+
+      executionInfo.setWorkingDirectory( currentDirectory );
+
+      List<String> executionCommandLine = new ArrayList<String>();
+
+      //put the file to execute on the command line
+      File gradleExecutableFile = getGradleExecutableFile();
+      if( gradleExecutableFile == null ) {
+          throw new RuntimeException( "Gradle executable not specified" );
+      }
+      if( !gradleExecutableFile.exists() ) {
+          throw new RuntimeException( "Missing gradle executable. Expected it at: " + gradleExecutableFile );
+      }
+       executionCommandLine.add( gradleExecutableFile.getAbsolutePath() );
+
+      //add the port number we're listenening on
+      executionCommandLine.add( "-D" + ProtocolConstants.PORT_NUMBER_SYSTEM_PROPERTY + "=" + Integer.toString( serverPort ) );
+
+      CommandLineAssistant commandLineAssistant = new CommandLineAssistant();
+
+      //add whatever the user ran
+      String[] individualCommandLineArguments = commandLineAssistant.breakUpCommandLine( commandLine );
+      executionCommandLine.addAll( Arrays.asList( individualCommandLineArguments ) );
+
+      File initStriptPath = getInitScriptFile();
+      if( initStriptPath != null )
+      {
+          executionCommandLine.add( "-" + DefaultCommandLine2StartParameterConverter.INIT_SCRIPT );
+          executionCommandLine.add( initStriptPath.getAbsolutePath() );
+          executionInfo.initStriptPath = initStriptPath;
+      }
+
+      //add the log level if its not present
+        if (!commandLineAssistant.hasLogLevelDefined(individualCommandLineArguments)) {
+            String logLevelText = commandLineAssistant.getCommandLine2StartParameterConverter().getLogLevelCommandLine(
+                    logLevel);
+            if (logLevelText != null && !"".equals(logLevelText)) {
+            executionCommandLine.add( '-' + logLevelText );
+      }
+        }
+
+      //add the stack trace level if its not present
+        if (!commandLineAssistant.hasShowStacktraceDefined(individualCommandLineArguments)) {
+            String stackTraceLevelText = commandLineAssistant.getCommandLine2StartParameterConverter()
+                    .getShowStacktraceCommandLine(stackTraceLevel);
+            if (stackTraceLevelText != null) {
+            executionCommandLine.add( '-' + stackTraceLevelText );
+      }
+        }
+
+      executionInfo.setCommandLineArguments( executionCommandLine.toArray( new String[ executionCommandLine.size() ] ) );
+      return executionInfo;
+   }
+
+   /**
+     * @return the file that should be used to execute gradle. If we've been given a custom file, we use that, otherwise,
+     *         we use the batch or shell script inside the gradle home's bin directory.
+   */
+    protected File getGradleExecutableFile() {
+        if (customGradleExecutor != null) {
+         return customGradleExecutor;
+        }
+
+      return new File( gradleHomeDirectory, "bin" + File.separator + getDefaultGradleExecutableName() );
+   }
+
+   /**
+     * This determines what we're going to execute. Its different based on the OS.
+     *
+     * @return whatever we're going to execute.
+   */
+   private String getDefaultGradleExecutableName()
+   {
+      String osName = System.getProperty("os.name");
+        if (osName.indexOf("indows") >= 0) {
+            return "gradle.bat";
+        } //windoes uses a batch file
+      return "gradle";        //all others use a shell script
+   }
+
+   /**
+     * Notification that the client has stopped all communications.
+   */
+   public void clientCommunicationStopped()
+   {
+      //we don't really care
+   }
+
+   /**
+     * Notification that the client has shutdown. Note: this can occur before communciations has ever started. You SHOULD
+     * get this notification before receiving serverExited, even if the client fails to launch or locks up.
+     *
+     * @param returnCode the return code of the client application
+     * @param output the standard error and standard output of the client application
+    */
+   public void clientExited( int returnCode, String output )
+   {
+      server.requestShutdown();
+
+      boolean wasPremature = false;
+      String message;
+
+      if( !hasCompletedConnection ) //if we never connected, report it
+      {
+         message = "Failed to connect to gradle process for command '" + commandLine +"'\n" + output;
+         wasPremature = true;
+      }
+      else
+         if( !hasReceivedBuildCompleteNotification  )  //this may happen if the client doesn't execute properly or it was killed/canceled. This is just so we don't lose our output (which may yeild clues to the problem).
+         {
+            message = output;
+            wasPremature = true;
+         }
+         else
+         {
+            message = output;
+         }
+
+      reportClientExit( wasPremature, returnCode, message );
+   }
+
+   /**
+     * This is called if the client exits prematurely. That is, we never connected to it or it didn't finish. This can
+     * happen because of setup issues or errors that occur in gradle.
+     *
+     * @param returnCode the return code of the application
+     * @param message Whatever information we can gleen about what went wrong.
+    */
+   protected abstract void reportClientExit( boolean wasPremature, int returnCode, String output );
+
+   /**
+     * This is called before we execute a command. Here, return an init script for this protocol. An init script is a
+     * gradle script that gets run before the other scripts are processed. This is useful here for initiating the gradle
+     * client that talks to the server.
+     *
+     * @return The path to an init script. Null if you have no init script.
+   */
+   public abstract File getInitScriptFile();
+
+   /**
+     * If you do have an init script that's a resource, this will extract it based on the name and write it to a
+     * temporary file and delete it on exit.
+     *
+     * @param resourceClass the class associated with the resource
+     * @param resourceName the name (minus extension or '.') of the resource
+    */
+   protected File extractInitScriptFile( Class resourceClass, String resourceName )
+   {
+       File file = null;
+       try
+       {
+           file = File.createTempFile( resourceName, INIT_SCRIPT_EXTENSION );
+       }
+       catch (IOException e)
+       {
+           logger.error( "Creating init script file temp file", e );
+           return null;
+       }
+       file.deleteOnExit();
+
+        if (extractResourceAsFile(resourceClass, resourceName + INIT_SCRIPT_EXTENSION, file)) {
+         return file;
+        }
+
+      logger.error( "Internal error! Failed to extract init script for executing commands!" );
+
+      return null;
+   }
+
+   /**
+     * This extracts the given class' resource to the specified file if it doesn't already exist.
+     *
+     * @param resourceClass the class associated with the resource
+     * @param name the resource's name
+     * @param file where to put the resource
+     * @return true if successful, false if not.
+   */
+   public boolean extractResourceAsFile( Class resourceClass, String name, File file )
+   {
+      InputStream stream = resourceClass.getResourceAsStream( name );
+        if (stream == null) {
+         return false;
+        }
+
+      byte[] bytes = new byte[0];
+      try
+      {
+         bytes = IOUtils.toByteArray( stream );
+      }
+      catch( IOException e )
+      {
+         logger.error( "Extracting resource as file", e );
+         return false;
+      }
+
+      FileOutputStream fileOutputStream = null;
+      try
+      {
+         fileOutputStream = new FileOutputStream( file );
+         IOUtils.write( bytes, fileOutputStream );
+         return true;
+      }
+      catch( IOException e )
+      {
+         logger.error( "Extracting resource as file (writing bytes)", e );
+         return false;
+      }
+      finally
+      {
+         IOUtils.closeQuietly( fileOutputStream );
+      }
+   }
+
+   protected File getGradleHomeDirectory() { return gradleHomeDirectory; }
+
+   /**
+     * Notification that a read failure occurred. This really only exists for debugging purposes when things go wrong.
+    */
+   public void readFailureOccurred()
+   {
+      logger.debug( "Last message received: " + lastMessageReceived );
+   }
+
+   public void aboutToKillProcess()
+   {
+      killGradle();
+   }
+
+    public void killGradle() {
+        if (killGradleClientProcotol != null) {
+         killGradleClientProcotol.sendKillMessage();
+         }
+   }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandClientProtocol.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandClientProtocol.java
new file mode 100644
index 0000000..d02826a
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandClientProtocol.java
@@ -0,0 +1,240 @@
+/*
+ * 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.foundation.ipc.gradle;
+
+import org.gradle.BuildListener;
+import org.gradle.BuildResult;
+import org.gradle.api.Task;
+import org.gradle.api.execution.TaskExecutionGraph;
+import org.gradle.api.execution.TaskExecutionGraphListener;
+import org.gradle.api.execution.TaskExecutionListener;
+import org.gradle.api.initialization.Settings;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.logging.StandardOutputListener;
+import org.gradle.api.tasks.TaskState;
+import org.gradle.foundation.ipc.basic.ClientProcess;
+import org.gradle.foundation.ipc.basic.MessageObject;
+import org.gradle.foundation.ipc.basic.Server;
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+
+import java.net.Socket;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * This manages the communication between the UI and an externally-launched copy of Gradle when using socket-based
+ * inter-process communication. This is the client (gradle) side used when executing commands (the most common case). We
+ * add gradle listeners and send their notifications as messages back to the server.
+ *
+ * @author mhunsicker
+ */
+public class ExecuteGradleCommandClientProtocol implements ClientProcess.Protocol {
+    private final Logger logger = Logging.getLogger(ExecuteGradleCommandClientProtocol.class);
+    private ClientProcess client;
+    private boolean continueConnection = true;
+    private Gradle gradle;
+
+    private Server localServer;   //this is our server that will listen to the process that started us.
+
+    public ExecuteGradleCommandClientProtocol(Gradle gradle) {
+        this.gradle = gradle;
+    }
+
+    /**
+     * Gives your protocol a chance to store this client so it can access its functions.
+     */
+    public void initialize(ClientProcess client) {
+        this.client = client;
+
+       gradle.addListener( new IPCExecutionListener( client ) );
+    }
+
+    /**
+     * Notification that we have connected to the server. Do minimum handshaking.
+     *
+     * @return true if we should continue the connection, false if not.
+     */
+    public boolean serverConnected(Socket clientSocket) {
+        MessageObject message = client.readMessage();
+        if (message == null) {
+            return false;
+        }
+
+        if (!ProtocolConstants.HANDSHAKE_TYPE.equalsIgnoreCase(message.getMessageType())) {
+            logger.error("Incorrect server handshaking.");
+            return false;
+        }
+
+        localServer = new Server(new KillGradleServerProtocol());
+        localServer.start();
+
+        client.sendMessage(ProtocolConstants.HANDSHAKE_TYPE, ProtocolConstants.HANDSHAKE_CLIENT, localServer.getPort());
+
+        return true;
+    }
+
+    /**
+     * We just keep a flag around for this.
+     *
+     * @return true if we should keep the connection alive. False if we should stop communicaiton.
+     */
+    public boolean continueConnection() {
+        return continueConnection;
+    }
+
+    public void shutdown() {
+        continueConnection = false;
+    }
+
+
+    /**
+     * This converts gradle messages to messages that we send to our server over
+     * a socket. It also tracks the live output and periodically sends it to the
+       server.
+      *
+     */
+    private class IPCExecutionListener implements BuildListener, StandardOutputListener, TaskExecutionGraphListener, TaskExecutionListener
+    {
+        private ClientProcess client;
+
+        private StringBuffer allOutputText = new StringBuffer(); //this is potentially threaded, so use StringBuffer instead of StringBuilder
+        private StringBuffer bufferedLiveOutput = new StringBuffer();
+        private Timer liveOutputTimer;
+        private float totalTasksToExecute;
+        private float totalTasksExecuted;
+        private float percentComplete;
+
+        public IPCExecutionListener(ClientProcess client) {
+            this.client = client;
+
+            //start a timer to periodically send our live output to our server
+            liveOutputTimer = new Timer();
+            liveOutputTimer.scheduleAtFixedRate(new TimerTask() {
+                @Override
+                public void run() {
+                    sendLiveOutput();
+                }
+            }, 500, 500);
+        }
+
+        public void buildStarted(Gradle build) {
+            //we'll never get this message because execution has started before we were instantiated (and before we were able to add our listener).
+        }
+
+        public void graphPopulated( TaskExecutionGraph taskExecutionGraph) {
+           List<Task> taskList = taskExecutionGraph.getAllTasks();
+
+           this.totalTasksToExecute = taskList.size();
+           client.sendMessage( ProtocolConstants.NUMBER_OF_TASKS_TO_EXECUTE, null, new Integer( taskList.size() ) );
+       }
+
+       /**
+        <p>Called when the build settings have been loaded and evaluated. The settings object is fully configured and is
+        ready to use to load the build projects.</p>
+
+        @param settings The settings. Never null.
+        */
+       public void settingsEvaluated( Settings settings ) {
+          //we don't really care
+       }
+
+       /**
+        <p>Called when the projects for the build have been created from the settings. None of the projects have been
+        evaluated.</p>
+
+        @param gradle The build which has been loaded. Never null.
+        */
+       public void projectsLoaded( Gradle gradle ) {
+          //we don't really care
+       }
+
+       /**
+        <p>Called when all projects for the build have been evaluated. The project objects are fully configured and are
+        ready to use to populate the task graph.</p>
+
+        @param gradle The build which has been evaluated. Never null.
+        */
+       public void projectsEvaluated( Gradle gradle ) {
+          //we don't really care
+       }
+
+       public void beforeExecute(Task task) {
+          String currentTaskName = task.getProject().getName() + ":" + task.getName();
+          client.sendMessage(ProtocolConstants.TASK_STARTED_TYPE, currentTaskName, new Float(percentComplete));
+        }
+
+       public void afterExecute(Task task, TaskState state) {
+          totalTasksExecuted++;
+          percentComplete = (totalTasksExecuted / totalTasksToExecute) * 100;
+          String currentTaskName = task.getProject().getName() + ":" + task.getName();
+          client.sendMessage(ProtocolConstants.TASK_COMPLETE_TYPE, currentTaskName, new Float(percentComplete));
+        }
+
+        /**
+        * Called when some output is written by the logging system.
+        *
+        * @param output The text.
+        */
+        public synchronized void onOutput(CharSequence output) {
+            String text = output.toString();
+            this.allOutputText.append( text );
+            this.bufferedLiveOutput.append(text);
+        }
+
+       /**
+        Called on a timer to send the live output to the process that started us. We only send whatever
+        is there since we've last sent output. It was causing some socket problems to send the output
+        immediately upon receiving it. I suspect due to numerous threads adding output. So its now only
+        done periodically and in a more thread-safe manner.
+        */
+        private synchronized void sendLiveOutput() {
+            if (bufferedLiveOutput.length() == 0) {
+                return;  //nothing to send
+            }
+            String text = bufferedLiveOutput.toString();
+            bufferedLiveOutput = new StringBuffer();
+
+            client.sendMessage(ProtocolConstants.LIVE_OUTPUT_TYPE, text);
+        }
+
+        /**
+         * <p>Called when the build is completed. All selected tasks have been executed.</p>
+         * <p>We remove our Log4JAppender as well as our task execution listener. Lastly,
+         * we report the build results.</p>
+         */
+        public void buildFinished(BuildResult buildResult) {
+
+            boolean wasSuccessful = buildResult.getFailure() == null;
+            String output = allOutputText.toString();
+            liveOutputTimer.cancel();  //stop our timer and send whatever live output we have
+            sendLiveOutput();
+
+            //we can't send the exception itself because it might not be serializable (it can include anything from anywhere inside gradle
+            //or one of its dependencies). So format it as text.
+            String details = GradlePluginLord.getGradleExceptionMessage(buildResult.getFailure(), gradle.getStartParameter().getShowStacktrace());
+            output += details;
+
+            client.sendMessage(ProtocolConstants.EXECUTION_COMPLETED_TYPE, output, new Boolean(wasSuccessful));
+
+            client.sendMessage(ProtocolConstants.EXITING, null, null);
+            client.stop();
+        }
+    }
+}
+
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandServerProtocol.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandServerProtocol.java
new file mode 100644
index 0000000..ec001c7
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/ExecuteGradleCommandServerProtocol.java
@@ -0,0 +1,152 @@
+/*
+ * 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.foundation.ipc.gradle;
+
+import org.gradle.StartParameter;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.foundation.ipc.basic.MessageObject;
+
+import java.io.File;
+
+/**
+ * This manages the communication between the UI and an externally-launched copy of Gradle when using socket-based
+ * inter-process communication. This is the server side for executing a gradle command. This listens for messages from
+ * the gradle client.
+ *
+ * @author mhunsicker
+ */
+public class ExecuteGradleCommandServerProtocol extends AbstractGradleServerProtocol {
+    private static final String INIT_SCRIPT_NAME = "execute-command-init-script";
+
+    private ExecutionInteraction executionInteraction;
+
+    public interface ExecutionInteraction {
+        /**
+         * Notification that gradle has started execution. This may not get called if some error occurs that prevents
+         * gradle from running.
+        */
+        void reportExecutionStarted();
+
+       /**
+        * Notification of the total number of tasks that will be executed. This is
+        * called after reportExecutionStarted and before any tasks are executed.
+        * @param size the total number of tasks.
+        */
+        void reportNumberOfTasksToExecute( int size );
+        /**
+         * Notification that execution has finished. Note: if the client fails
+         * to launch at all, this should still be called.
+         *
+         * @param  wasSuccessful true if gradle was successful (returned 0)
+         * @param  message       the output of gradle if it ran. If it didn't, an error message.
+         * @param  throwable     an exception if one occurred
+        */
+        void reportExecutionFinished(boolean wasSuccessful, String message, Throwable throwable);
+
+        void reportTaskStarted(String message, float percentComplete);
+
+        void reportTaskComplete(String message, float percentComplete);
+
+        void reportLiveOutput(String message);
+    }
+
+    public ExecuteGradleCommandServerProtocol(File currentDirectory, File gradleHomeDirectory, File customGradleExecutor, String fullCommandLine, LogLevel logLevel, StartParameter.ShowStacktrace stackTraceLevel, ExecutionInteraction executionInteraction) {
+        super(currentDirectory, gradleHomeDirectory, customGradleExecutor, fullCommandLine, logLevel, stackTraceLevel);
+        this.executionInteraction = executionInteraction;
+    }
+
+    /**
+     * Notification that a message was received that we didn't process. Implement this to handle the specifics of your
+     * protocol. Basically, the base class handles the handshake. The rest of the conversation is up to you.
+     *
+     * @param message the message we received.
+     */
+    @Override
+    protected boolean handleMessageReceived(MessageObject message) {
+        if (ProtocolConstants.EXECUTION_COMPLETED_TYPE.equals(message.getMessageType())) {
+            setHasReceivedBuildCompleteNotification();
+            return true;
+        }
+
+        if (ProtocolConstants.TASK_STARTED_TYPE.equals(message.getMessageType())) {
+            Float percentComplete = (Float) message.getData();
+            executionInteraction.reportTaskStarted(message.getMessage(), percentComplete);
+            return true;
+        }
+
+        if (ProtocolConstants.TASK_COMPLETE_TYPE.equals(message.getMessageType())) {
+            Float percentComplete = (Float) message.getData();
+            executionInteraction.reportTaskComplete(message.getMessage(), percentComplete);
+            return true;
+        }
+
+        if (ProtocolConstants.LIVE_OUTPUT_TYPE.equals(message.getMessageType())) {
+            executionInteraction.reportLiveOutput(message.getMessage());
+            return true;
+        }
+
+        if( ProtocolConstants.NUMBER_OF_TASKS_TO_EXECUTE.equals( message.getMessageType() ) ) {
+            Integer total = (Integer) message.getData();
+            executionInteraction.reportNumberOfTasksToExecute( total.intValue() );
+        }
+
+
+        if (ProtocolConstants.EXITING.equals(message.getMessageType())) {
+            closeConnection();   //the client is done.
+            return true;
+        }
+
+        return false;
+    }
+
+   /**
+    * This is called when when the client exits. This does not mean it succeeded.
+    * This is probably the only way you'll get ALL of the
+    * client's output as it continues to output things like error messages after it sends
+    * us an executionFinished message.
+
+    * @param returnCode the return code of the application
+    * @param output     its total output
+    */
+   protected void reportClientExit( boolean wasPremature, int returnCode, String output )
+   {
+       //Note: we're relying on clientExited to be called to mark a task as complete.
+       //This is because even though gradle sends us a message that it has completed
+       //the build, it hasn't yet output all of its information which is very useful
+       //for debugging.
+       executionInteraction.reportExecutionFinished(returnCode == 0, output, null);
+   }
+
+    /**
+     * Notification of any status that might be helpful to the user.
+     *
+     * @param  status     a status message
+    */
+    protected void addStatus(String status) {
+        executionInteraction.reportLiveOutput(status);
+    }
+
+    /**
+     * This is called before we execute a command. Here, return an init script for this protocol. An init script is a
+     * gradle script that gets run before the other scripts are processed. This is useful here for initiating the gradle
+     * client that talks to the server.
+     *
+     * @return The path to an init script. Null if you have no init script.
+    */
+    public File getInitScriptFile() {
+        return extractInitScriptFile(ExecuteGradleCommandServerProtocol.class, INIT_SCRIPT_NAME);
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/GradleClient.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/GradleClient.java
new file mode 100644
index 0000000..492bfde
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/GradleClient.java
@@ -0,0 +1,96 @@
+/*
+ * 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.foundation.ipc.gradle;
+
+import org.gradle.foundation.ipc.basic.ClientProcess;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+import java.io.Serializable;
+
+/**
+ * <p>This is used to send information from a one process to another process. This one is used by the launched process
+ * where the server (the process that launched us) is listening for our messages over a socket connection. The server
+ * typically sets the port to listen to via java system properties.</p> <p>To use this, instantiate it, then call start
+ * passing in a protocol (which defines the actual communication messages).</p>
+ */
+public class GradleClient {
+    private ClientProcess clientProcess;
+    private final Logger logger = Logging.getLogger(GradleClient.class);
+
+    public GradleClient() {
+    }
+
+    /**
+     * Call this to start the client. This version gets the port number as a system property. It does nothing if this
+     * property isn't defined.
+     *
+     * @param protocol the protocol to use to communicate with the server.
+     * @return true if successful, false if not.
+     */
+    public boolean start(ClientProcess.Protocol protocol) {
+        //make sure we've been given the port number to use
+        String portText = System.getProperty(ProtocolConstants.PORT_NUMBER_SYSTEM_PROPERTY);
+        if (portText == null) {
+            logger.error("No port number specified. Cannot run client");
+            return false;
+        }
+
+        try {
+            int port = Integer.parseInt(portText);
+            return start(protocol, port);
+        } catch (NumberFormatException e) {
+            logger.error("Parsing port '" + portText + "'", e);
+            return false;
+        }
+    }
+
+    /**
+     * Call this to start the client.
+     *
+     * @param protocol the protocol to use to communicate with the server.
+     * @param port the port the server is listening on
+     * @return true if successful, false if not.
+     */
+    public boolean start(ClientProcess.Protocol protocol, int port) {
+        clientProcess = new ClientProcess(protocol);
+
+        if (!clientProcess.start(port)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Call this to send a message and wait for the server to acknowledge siad message.
+     */
+    public boolean sendMessage(String messageType, String message, Serializable data) {
+        return clientProcess.sendMessage(messageType, message, data);
+    }
+
+    public boolean sendMessage(String messageType, String message) {
+        return sendMessage(messageType, message, null);
+    }
+
+    /**
+     * Call this to stop communications with the server.
+     */
+    public void stop() {
+        clientProcess.stop();
+    }
+}
+
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/IPCUtilities.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/IPCUtilities.java
new file mode 100644
index 0000000..db05e75
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/IPCUtilities.java
@@ -0,0 +1,89 @@
+/*
+ * 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.foundation.ipc.gradle;
+
+import org.gradle.api.invocation.Gradle;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+/**
+ * Just some convenience functions to startup a GradleClient. See GradleClient for more information.
+ *
+ * @author mhunsicker
+ */
+public class IPCUtilities {
+    private static final Logger LOGGER = Logging.getLogger(IPCUtilities.class);
+
+    /**
+     * This starts a gradle client for doing regular execution of a command. It expects the port number to set as a
+     * system property. Note: this is using gradle to find the port. See getPort().
+     *
+     * @param gradle the gradle object.
+     */
+    public static void invokeExecuteGradleClient(Gradle gradle) {
+        Integer port = getPort(gradle);
+        if (port == null) {
+            return;
+        }
+
+        ExecuteGradleCommandClientProtocol protocol = new ExecuteGradleCommandClientProtocol(gradle);
+        GradleClient client = new GradleClient();
+        client.start(protocol, port.intValue());
+    }
+
+    /**
+     * This gets the port out of the start parameters. Why? Because this is meant to be run from the init script and the
+     * system properties haven't been set yet. That is due to how gradle is run from the bat file/shell script. It has
+     * to manually set the java system properties (-D). I don't this is a desired side-effect.
+     *
+     * @param gradle the gradle object
+     * @return an integer or null if we didn't get the port.
+     */
+    private static Integer getPort(Gradle gradle) {
+        String portText = gradle.getStartParameter().getSystemPropertiesArgs().get(
+                ProtocolConstants.PORT_NUMBER_SYSTEM_PROPERTY);
+        if (portText == null) {
+            LOGGER.error("Failed to set " + ProtocolConstants.PORT_NUMBER_SYSTEM_PROPERTY + " system property");
+            return null;
+        }
+
+        try {
+            int port = Integer.parseInt(portText);
+            return new Integer(port);
+        } catch (NumberFormatException e) {
+            LOGGER.error("Invalid " + ProtocolConstants.PORT_NUMBER_SYSTEM_PROPERTY + " system property", e);
+            return null;
+        }
+    }
+
+    /**
+     * This starts a gradle client that sends a task list back to the server. It expects the port number to set as a
+     * system property. You probably should be executing the "-t" command. Note: this is using gradle to find the port.
+     * See getPort().
+     *
+     * @param gradle the gradle launcher object.
+     */
+    public static void invokeTaskListGradleClient(Gradle gradle) {
+        Integer port = getPort(gradle);
+        if (port == null) {
+            return;
+        }
+
+        TaskListClientProtocol protocol = new TaskListClientProtocol(gradle);
+        GradleClient client = new GradleClient();
+        client.start(protocol, port.intValue());
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/KillGradleClientProtocol.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/KillGradleClientProtocol.java
new file mode 100644
index 0000000..de08cdc
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/KillGradleClientProtocol.java
@@ -0,0 +1,47 @@
+/*
+ * 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.foundation.ipc.gradle;
+
+import org.gradle.foundation.ipc.basic.ClientProcess;
+
+import java.net.Socket;
+
+/**
+ * This protocol is used by the process that launches gradle (the launching server - but in this case its the client) so
+ * that it can tell gradle to kill itself. This is used to cancel gradle execution. There is no other clean way to do it
+ * but kill it. All this does is send a 'kill' message.
+ *
+ * @author mhunsicker
+ */
+public class KillGradleClientProtocol implements ClientProcess.Protocol {
+    private ClientProcess client;
+
+    public void initialize(ClientProcess client) {
+        this.client = client;
+    }
+
+    public boolean serverConnected(Socket clientSocket) {
+        return true;
+    }
+
+    public boolean continueConnection() {
+        return true;
+    }
+
+    public void sendKillMessage() {
+        client.sendMessage(ProtocolConstants.KILL, ProtocolConstants.KILL);
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/KillGradleServerProtocol.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/KillGradleServerProtocol.java
new file mode 100644
index 0000000..cb77860
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/KillGradleServerProtocol.java
@@ -0,0 +1,58 @@
+/*
+ * 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.foundation.ipc.gradle;
+
+import org.gradle.foundation.ipc.basic.Server;
+import org.gradle.foundation.ipc.basic.MessageObject;
+
+/**
+ * This protocol is used by a client that launches its own server. See KillGradleClientProtocol.
+ *
+ * @author mhunsicker
+ */
+public class KillGradleServerProtocol implements Server.Protocol<Server> {
+    private Server server;
+
+    public void initialize(Server server) {
+        this.server = server;
+    }
+
+    public void connectionAccepted() {
+
+    }
+
+    public boolean continueConnection() {
+        return true;
+    }
+
+    public void messageReceived(MessageObject message) {
+        if (ProtocolConstants.KILL.equals(message.getMessageType())) {
+            killProcess();
+        }
+    }
+
+    private void killProcess() {
+        System.exit(-1);
+    }
+
+    public void clientCommunicationStopped() {
+        killProcess();
+    }
+
+    public void readFailureOccurred() {
+
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/ProtocolConstants.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/ProtocolConstants.java
new file mode 100644
index 0000000..45afd46
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/ProtocolConstants.java
@@ -0,0 +1,43 @@
+/*
+ * 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.foundation.ipc.gradle;
+
+/**
+ * Constants related to interprocess communication between gradle and the gradle UI.
+ *
+ * @author mhunsicker
+ */
+public class ProtocolConstants {
+    //these are the message types we'll receive from the client.
+    public static final String EXECUTION_COMPLETED_TYPE = "ExecutionCompleted";
+    public static final String NUMBER_OF_TASKS_TO_EXECUTE = "NumberOfTasksToExecute";
+    public static final String TASK_STARTED_TYPE = "TaskStarted";
+    public static final String TASK_COMPLETE_TYPE = "TaskComplete";
+    public static final String LIVE_OUTPUT_TYPE = "LiveOutput";
+
+    public static final String HANDSHAKE_TYPE = "connected";
+    public static final String HANDSHAKE_SERVER = "server-reply";
+    public static final String HANDSHAKE_CLIENT = "client-reply";
+
+    public static final String PORT_NUMBER_SYSTEM_PROPERTY = "PortNumber";
+
+    public static final String TASK_LIST_COMPLETED_SUCCESSFULLY_TYPE = "TaskListCompletedSuccessfully";
+    public static final String TASK_LIST_COMPLETED_WITH_ERRORS_TYPE = "TaskListCompletedWithErrors";
+
+    public static final String EXITING = "exiting";
+
+    public static final String KILL = "kill";
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/TaskListClientProtocol.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/TaskListClientProtocol.java
new file mode 100644
index 0000000..e27a6a7
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/TaskListClientProtocol.java
@@ -0,0 +1,185 @@
+/*
+ * 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.foundation.ipc.gradle;
+
+import org.gradle.BuildListener;
+import org.gradle.BuildResult;
+import org.gradle.api.initialization.Settings;
+import org.gradle.api.invocation.Gradle;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.logging.StandardOutputListener;
+import org.gradle.foundation.ProjectConverter;
+import org.gradle.foundation.ProjectView;
+import org.gradle.foundation.ipc.basic.ClientProcess;
+import org.gradle.foundation.ipc.basic.MessageObject;
+import org.gradle.foundation.ipc.basic.Server;
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+
+import java.io.Serializable;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This manages the communication between the UI and an externally-launched copy
+ * of Gradle when using socket-based inter-process communication. This is the
+ * client (gradle) side used to build a task list (tree actually). We add gradle
+ * listeners and send their notifications as messages back to the server.
+ *
+ * @author mhunsicker
+ */
+public class TaskListClientProtocol implements ClientProcess.Protocol {
+    private final Logger logger = Logging.getLogger(TaskListClientProtocol.class);
+    private ClientProcess client;
+    private boolean continueConnection = true;
+    private Gradle gradle;
+
+    private Server localServer;   //this is our server that will listen to the process that started us.
+
+    public TaskListClientProtocol(Gradle gradle) {
+        this.gradle = gradle;
+    }
+
+    /**
+     * Gives your protocol a chance to store this client so it can access its functions.
+     */
+    public void initialize(ClientProcess client) {
+        this.client = client;
+       this.gradle.addListener( new RefreshTaskListBuildListener( client ) );
+    }
+
+    /**
+     * Listener used to delegate gradle messages to our listeners.
+     *
+     */
+    private class RefreshTaskListBuildListener implements BuildListener, StandardOutputListener
+    {
+        private ClientProcess client;
+        private StringBuffer allOutputText = new StringBuffer(); //this is potentially threaded, so use StringBuffer instead of StringBuilder
+
+        public RefreshTaskListBuildListener(ClientProcess client) {
+            this.client = client;
+        }
+
+        public synchronized void onOutput(CharSequence output) {
+            String text = output.toString();
+            allOutputText.append(text);
+        }
+
+       /**
+        * <p>Called when the build is started.</p>
+        *
+        * @param gradle The build which is being started. Never null.
+        */
+       public void buildStarted( Gradle gradle ) { }
+
+       /**
+        * <p>Called when the build settings have been loaded and evaluated. The settings object is fully configured and is
+        * ready to use to load the build projects.</p>
+        *
+        * @param settings The settings. Never null.
+        */
+       public void settingsEvaluated( Settings settings ) { }
+
+       /**
+        * <p>Called when the projects for the build have been created from the settings. None of the projects have been
+        * evaluated.</p>
+        *
+        * @param gradle The build which has been loaded. Never null.
+        */
+       public void projectsLoaded( Gradle gradle ) { }
+
+       /**
+        * <p>Called when all projects for the build have been evaluated. The project objects are fully configured and are
+        * ready to use to populate the task graph.</p>
+        *
+        * @param gradle The build which has been evaluated. Never null.
+        */
+       public void projectsEvaluated( Gradle gradle ) { }
+
+       /**
+        * <p>Called when the build is completed. All selected tasks have been executed.</p>
+        *
+        * @param result The result of the build. Never null.
+        */
+       public void buildFinished( BuildResult buildResult )
+       {
+         boolean wasSuccessful = buildResult.getFailure() == null;
+         String output = allOutputText.toString();
+
+         if (!wasSuccessful) //if we fail, send the results, otherwise, we'll send the projects.
+         {
+             //we can't send the exception itself because it might not be serializable (it can include anything from anywhere inside gradle
+             //or one of its dependencies). So format it as text.
+             String details = GradlePluginLord.getGradleExceptionMessage(buildResult.getFailure(), gradle.getStartParameter().getShowStacktrace());
+             output += details;
+
+             client.sendMessage(ProtocolConstants.TASK_LIST_COMPLETED_WITH_ERRORS_TYPE, output, new Boolean(wasSuccessful));
+         } else {
+             ProjectConverter buildExecuter = new ProjectConverter();
+             List<ProjectView> projects = new ArrayList<ProjectView>();
+             projects.addAll(buildExecuter.convertProjects(buildResult.getGradle().getRootProject()));
+
+             client.sendMessage(ProtocolConstants.TASK_LIST_COMPLETED_SUCCESSFULLY_TYPE, output, (Serializable) projects);
+         }
+
+         //tell the server we're going to exit.
+         client.sendMessage(ProtocolConstants.EXITING, null, null);
+
+         client.stop();
+       }
+    }
+
+    /**
+     * Notification that we have connected to the server. Do minimum handshaking.
+     *
+     * @return true if we should continue the connection, false if not.
+     */
+    public boolean serverConnected(Socket clientSocket) {
+        MessageObject message = client.readMessage();
+        if (message == null)
+        {
+           return false;
+        }
+
+       if (!ProtocolConstants.HANDSHAKE_TYPE.equalsIgnoreCase(message.getMessageType())) {
+            logger.error("Incorrect server handshaking.");
+            return false;
+        }
+
+        localServer = new Server(new KillGradleServerProtocol());
+        localServer.start();
+
+        client.sendMessage(ProtocolConstants.HANDSHAKE_TYPE, ProtocolConstants.HANDSHAKE_CLIENT, localServer.getPort());
+
+        return true;
+    }
+
+    /**
+     * We just keep a flag around for this.
+     *
+     * @return true if we should keep the connection alive. False if we should
+     *         stop communicaiton.
+     */
+    public boolean continueConnection() {
+        return continueConnection;
+    }
+
+    public void shutdown() {
+        continueConnection = false;
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/TaskListServerProtocol.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/TaskListServerProtocol.java
new file mode 100644
index 0000000..d62ddf2
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/ipc/gradle/TaskListServerProtocol.java
@@ -0,0 +1,141 @@
+/*
+ * 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.foundation.ipc.gradle;
+
+import org.gradle.StartParameter;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.foundation.ProjectView;
+import org.gradle.foundation.ipc.basic.MessageObject;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * This manages the communication between the UI and an externally-launched copy of Gradle when using socket-based
+ * inter-process communication. This is the server side for building a task list. This listens for messages from the
+ * gradle client.
+ *
+ * @author mhunsicker
+ */
+public class TaskListServerProtocol extends AbstractGradleServerProtocol {
+    private final Logger logger = Logging.getLogger(TaskListServerProtocol.class);
+
+    private static final String INIT_SCRIPT_NAME = "refresh-tasks-init-script";
+
+    private ExecutionInteraction executionInteraction;
+
+   public interface ExecutionInteraction {
+        /**
+        * Notification that gradle has started execution. This may not get called
+        * if some error occurs that prevents gradle from running.
+        */
+        void reportExecutionStarted();
+
+        /**
+        * Notification that execution has finished. Note: if the client fails
+        * to launch at all, this should still be called.
+        *
+        * @param  wasSuccessful true if gradle was successful (returned 0)
+        * @param  message       the output of gradle if it ran. If it didn't, an error message.
+        * @param  throwable     an exception if one occurred
+        * @param  projects      a hierachical list of projects. This is the final result.
+        */
+        void reportExecutionFinished(boolean wasSuccessful, String message, Throwable throwable);
+
+        void projectsPopulated( List<ProjectView> projects );
+
+        void reportLiveOutput(String message);
+    }
+
+    public TaskListServerProtocol(File currentDirectory, File gradleHomeDirectory, File customGradleExecutor, String fullCommandLine, LogLevel logLevel, StartParameter.ShowStacktrace stackTraceLevel, ExecutionInteraction executionInteraction) {
+        super(currentDirectory, gradleHomeDirectory, customGradleExecutor, fullCommandLine, logLevel, stackTraceLevel);
+        this.executionInteraction = executionInteraction;
+    }
+
+    /**
+    * Notification that a message was received that we didn't process. Implement
+    * this to handle the specifics of your protocol. Basically, the base class
+    * handles the handshake. The rest of the conversation is up to you.
+    *
+    * @param message the message we received.
+    */
+    @Override
+    protected boolean handleMessageReceived(MessageObject message) {
+        if (ProtocolConstants.TASK_LIST_COMPLETED_WITH_ERRORS_TYPE.equals(message.getMessageType())) {  //if we were NOT successful, we'll have a BuildResultsWrapper instead of a project list as our data.
+            setHasReceivedBuildCompleteNotification();
+
+            return true;
+        }
+
+        if (ProtocolConstants.TASK_LIST_COMPLETED_SUCCESSFULLY_TYPE.equals(message.getMessageType())) {  //if we were successful, we'll have a project list instead of a BuildResultsWrapper as our data.
+            setHasReceivedBuildCompleteNotification();
+
+           List<ProjectView> projects = (List<ProjectView>) message.getData();
+           executionInteraction.projectsPopulated( projects );
+
+            return true;
+        }
+
+        if (ProtocolConstants.EXITING.equals(message.getMessageType())) {
+            closeConnection();   //the client is done.
+            return true;
+        }
+
+        return false;
+    }
+
+   /**
+    * This is called when when the client exits. This does not mean it succeeded.
+    * This is probably the only way you'll get ALL of the
+    * client's output as it continues to output things like error messages after it sends
+    * us an executionFinished message.
+    *
+    * @param returnCode the return code of the application
+    * @param output     its total output
+    */
+   protected void reportClientExit( boolean wasPremature, int returnCode, String output )
+   {
+        /**
+        * Note: we're relying on clientExited to be called to mark a task as complete.
+        * This is because even though gradle sends us a message that it has completed
+        * the build, it hasn't yet output all of its information which is very useful
+        * for debugging.
+        */
+        executionInteraction.reportExecutionFinished(returnCode == 0, output, null );
+    }
+
+    /**
+     * Notification of any status that might be helpful to the user.
+     * @param  status     a status message
+    */
+    protected void addStatus(String status) {
+        executionInteraction.reportLiveOutput(status);
+    }
+
+    /**
+     * This is called before we execute a command. Here, return an init script
+     * for this protocol. An init script is a gradle script that gets run before
+     * the other scripts are processed. This is useful here for initiating
+     * the gradle client that talks to the server.
+     *
+     * @return The path to an init script. Null if you have no init script.
+     */
+    public File getInitScriptFile() {
+        return this.extractInitScriptFile(TaskListServerProtocol.class, INIT_SCRIPT_NAME);
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/FileLink.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/FileLink.java
new file mode 100644
index 0000000..401c0b2
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/FileLink.java
@@ -0,0 +1,114 @@
+/*
+ * 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.foundation.output;
+
+import org.gradle.foundation.output.definitions.FileLinkDefinition;
+
+import java.io.File;
+
+/**
+ This represents a link to a file inside gradle's output. This is so the gradle UI/plugins can
+ open the file. This is useful for a user when gradle displays a build error, test failure or
+ compile error.
+
+ @author mhunsicker
+*/
+public class FileLink
+{
+   private File file;
+   private int lineNumber;
+   private int startingIndex;
+   private int endingIndex;
+   private FileLinkDefinition matchingDefinition;  //useful for debugging.
+
+   public FileLink( File file, int startingIndex, int endingIndex, int lineNumber, FileLinkDefinition matchingDefinition )
+   {
+      this.file = file;
+      this.startingIndex = startingIndex;
+      this.endingIndex = endingIndex;
+      this.lineNumber = lineNumber;
+      this.matchingDefinition = matchingDefinition;
+   }
+
+   public FileLink( File file, int startingIndex, int endingIndex, int lineNumber )
+   {
+      this( file, startingIndex, endingIndex, lineNumber, null );
+   }
+
+   @Override
+   public String toString()
+   {
+      return "file='" + file + "' startingIndex=" + startingIndex + " endingIndex=" + endingIndex + " line: " + lineNumber + ( matchingDefinition != null ? ( " definition: " + matchingDefinition.getName() ) : "" );
+   }
+
+   public int getLength()
+   {
+      return endingIndex - startingIndex;
+   }
+
+   /**
+      @return the file
+   */
+   public File getFile() { return file; }
+
+   /**
+      @return the line number into the file. May be -1 if not specified
+   */
+   public int getLineNumber() { return lineNumber; }
+
+   /**
+      @return the index into the source text where this FileLink begins.
+   */
+   public int getStartingIndex() { return startingIndex; }
+
+   /**
+      @return the index into the source text where this FileLink ends.
+   */
+   public int getEndingIndex() { return endingIndex; }
+
+   /**
+    This moves the starting and ending index by the specified amount. This is
+    useful if you're searching within a portion of larger text. This corrects
+    the original indices.
+    @param amountToMove how much to move it.
+    */
+   /*package*/ void move( int amountToMove )
+   {
+      startingIndex += amountToMove;
+      endingIndex += amountToMove;
+   }
+
+   @Override
+   public boolean equals( Object obj )
+   {
+      if( !( obj instanceof FileLink ) )
+      {
+         return false;
+      }
+
+      FileLink otherFileLink = (FileLink) obj;
+      return otherFileLink.endingIndex == endingIndex &&
+             otherFileLink.startingIndex == startingIndex &&
+             otherFileLink.lineNumber == lineNumber &&
+             otherFileLink.file.equals( file );
+      //we do NOT want to compare the FileLinkDefinition. These aren't set usually for tests and we don't have easy access to them anyway.
+   }
+
+    @Override
+    public int hashCode() {
+        return endingIndex ^ startingIndex ^ lineNumber ^ file.hashCode();
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/FileLinkDefinitionLord.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/FileLinkDefinitionLord.java
new file mode 100644
index 0000000..33020f7
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/FileLinkDefinitionLord.java
@@ -0,0 +1,215 @@
+/*
+ * 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.foundation.output;
+
+import org.gradle.foundation.output.definitions.*;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class holds on FileLinkDefinitions used for searching output.
+ *
+ * @author mhunsicker
+
+ */
+public class FileLinkDefinitionLord {
+    private List<String> extensions = new ArrayList<String>( );
+
+    //these are definitions where the file is between known tokens.
+    private Map<Pattern,FileLinkDefinition> complexFileLinkDefinitions = new LinkedHashMap<Pattern,FileLinkDefinition>();
+
+    //these are definitions where we only try to match based on the file extension.
+    private Map<Pattern,FileLinkDefinition> extensionFileLinkDefinitions = new LinkedHashMap<Pattern,FileLinkDefinition>();
+
+    private Pattern combinedSearchPattern;  //search pattern consisting of all of our sub search patterns
+
+    public FileLinkDefinitionLord() {
+      //this is where we define what files we find.
+
+      //add all the file extension definitions
+      addFileExtension( ".java", ":" );     //the colon handles compile errors with line numbers
+      addFileExtension( ".groovy", ":" );
+      addFileExtension( ".gradle", ":" );
+      addFileExtension( ".xml", ":" ); //I don't think I've ever seen an xml or html file specified with a line number delimiter, but we'll try it anyway
+      addFileExtension( ".html", ":" );
+      addFileExtension( ".htm", ":" );
+
+      //now add the more complex ones
+      addPrefixedFileLink( "Ant Compiler Error", "[ant:javac]", ".java", ":" );       //handles java compiler errors
+      addPrefixedFileLink( "Compiler Warning", "Note:", ".java", null );               //handles java compiler warnings such as deprecated APIs
+      addCustomComplexFileLink( new OptionalLineNumberFileLinkDefinition( "Build File Errors", "Build file '",".gradle", "line:" ) );       //handles errors in a gradle build file
+      addPrefixedFileLink( "Ant Checkstyle Error/Warning", "[ant:checkstyle]", ".java", ":" );//handles checkstyle errors/warnings
+      addPrefixedFileLink( "Checkstyle Error (report xml)", "See the report at", ".xml", null );   //handles checkstyle errors. Links to the report xml file.
+      addPrefixedFileLink( "Codenarc Error", "See the report at", ".html", null );      //handles Codenarc errors. Links to the report file.
+      addCustomComplexFileLink( new TestReportFileLinkDefinition() );
+    }
+
+    /**
+     Call this to add file extensions to look for in the output. This assumes the file path
+     is the first thing on the line.
+     @param extension the file extension
+     @param lineNumberDelimiter optional delimiter text for line number. Whatever is after
+             this will be assumed to be a line number. We'll only parse the numbers after
+             this so there can be other stuff after the line number. Pass in null to ignore.
+     */
+    public void addFileExtension( String extension, String lineNumberDelimiter )
+    {
+       if( !extension.startsWith( "."  ) ) {
+           extension = "." + extension;
+       }
+
+        extension = extension.toLowerCase();
+       if( extensions.contains( extension ) ) //don't add extensions already added
+       {
+          return;
+       }
+
+       extensions.add( extension );
+
+       String name = extension + " Files";
+       ExtensionFileLinkDefinition linkDefinition = new ExtensionFileLinkDefinition(name, extension, lineNumberDelimiter);
+       addToMap(extensionFileLinkDefinitions, linkDefinition );
+    }
+
+    /**
+     Creates a file link definition to find file paths in the output that have a known prefix and extension.
+     It also allows for an optional line number after a delimiter. This is useful if you know a certain
+     message always precedes a file path.
+     @param name  the name of this file link definition. Used by tests mostly.
+     @param prefix  the text that is before the file path. It should be enough to make it fairly unique
+     @param extension  the expected file extension. If we don't find this extension, we do not consider
+             the text a file's path. If there are multiple extensions, you'll have to add multiples of these.
+     @param lineNumberDelimiter optional delimiter text for line number. Whatever is after
+             this will be assumed to be a line number. We'll only parse the numbers after
+             this so there can be other stuff after the line number. Pass in null to ignore.
+
+     */
+    public void addPrefixedFileLink( String name, String prefix, String extension, String lineNumberDelimiter )
+    {
+        PrefixedFileLinkDefinition linkDefinition = new PrefixedFileLinkDefinition(name, prefix, extension, lineNumberDelimiter);
+        addToMap( complexFileLinkDefinitions, linkDefinition );
+    }
+
+    private void addCustomComplexFileLink( FileLinkDefinition fileLinkDefinition )
+    {
+        addToMap( complexFileLinkDefinitions, fileLinkDefinition );
+    }
+
+    private void addToMap( Map<Pattern,FileLinkDefinition> destinationMap, FileLinkDefinition fileLinkDefinition )
+    {
+        //if you change anything, we'll destroy our combined search pattern. This will recreate it with the
+        //latest settings when its next asked for.
+        combinedSearchPattern = null;
+
+        String searchExpression = fileLinkDefinition.getSearchExpression();
+        Pattern pattern = Pattern.compile( searchExpression, getSearchPatternFlags() );
+        destinationMap.put( pattern, fileLinkDefinition);
+    }
+
+    /**
+     @return a list of known file extensions that are searched for in the output.
+     */
+    public List<String> getFileExtensions() { return Collections.unmodifiableList( extensions ); }
+
+    /**
+      @return a list of our FileLinkDefinitions
+     */
+    public List<FileLinkDefinition> getFileLinkDefinitions()
+    {
+        List<FileLinkDefinition> fileLinkDefinitions = new ArrayList<FileLinkDefinition>();
+
+        fileLinkDefinitions.addAll( complexFileLinkDefinitions.values() );
+        fileLinkDefinitions.addAll( extensionFileLinkDefinitions.values() );
+
+        return Collections.unmodifiableList( fileLinkDefinitions );
+    }
+
+   private int getSearchPatternFlags() { return Pattern.CASE_INSENSITIVE; }
+
+    /**
+     * This returns the FileLinkDefinition whose search pattern 'matches' (as in 'finds', not 'equals') the
+     * specified text. The tricky thing here is that multiple FileLinkDefinitions can match the text. To
+     * assist this we've done two things: we first try to match it with the complex patterns (the ones that
+     * try to match prefixed and suffixed text around a file's path), then if we don't find one, we'll match
+     * it with a simple extension FileLinkDefinitions. The other thing is that we search the definitions in
+     * order. This means the order in which the FileLinkDefinitions are added can be important. Add the more
+     * definitive ones first.
+     * @param text the text to use to find a match.
+     * @return a FileLinkDefinition that matches the text
+     */
+   public FileLinkDefinition getMatchingFileLinkDefinition( String text )
+   {
+       FileLinkDefinition fileLinkDefinition = getMatchingFileLinkDefinition( text, complexFileLinkDefinitions );
+       if( fileLinkDefinition == null ) {
+           fileLinkDefinition = getMatchingFileLinkDefinition( text, extensionFileLinkDefinitions );
+       }
+
+       return fileLinkDefinition;
+   }
+
+   private static FileLinkDefinition getMatchingFileLinkDefinition( String text, Map<Pattern,FileLinkDefinition> map )
+   {
+      Iterator<Pattern> iterator = map.keySet().iterator();
+      while( iterator.hasNext() )
+      {
+         Pattern pattern = iterator.next();
+         Matcher matcher = pattern.matcher( text );
+         if( matcher.find( 0 ) )
+         {
+            return map.get( pattern );
+         }
+      }
+
+      return null;
+   }
+
+    public Pattern getSearchPattern()
+    {
+        if( combinedSearchPattern == null ) //only build it if we need to.
+        {
+            combinedSearchPattern = buildSearchPattern();
+        }
+
+        return combinedSearchPattern;
+    }
+
+    /**
+    This iterates through all the FileLinkDefinitions and builds one giant single RegEx search pattern. This is more
+    efficient than using multiple search patterns.
+    */
+   private Pattern buildSearchPattern()
+   {
+      StringBuilder criteria = new StringBuilder();
+      Iterator<FileLinkDefinition> iterator = getFileLinkDefinitions().iterator();
+      while( iterator.hasNext() )
+      {
+         FileLinkDefinition fileLinkDefinition = iterator.next();
+         String searchExpression = fileLinkDefinition.getSearchExpression();
+
+         criteria.append( "(" ).append( searchExpression ).append( ")" );
+
+         if( iterator.hasNext() )
+         {
+            criteria.append( "|" );
+         }
+      }
+
+      return Pattern.compile( criteria.toString(), getSearchPatternFlags() );
+   }
+
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/LiveOutputParser.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/LiveOutputParser.java
new file mode 100644
index 0000000..f9139b8
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/LiveOutputParser.java
@@ -0,0 +1,122 @@
+/*
+ * 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.foundation.output;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.Collections;
+
+/**
+ This is a special type of OutputParser. It handles tracking live output. The unique thing about live
+ output is that we're not guaranteed to get whole lines. Also, we don't want to parse parts that have
+ already been parsed. This holds onto the output until a newline is reached, then parses it. It also
+ tracks the overall index into the output (even though its only parsing a part of it).
+
+ @author mhunsicker
+ */
+public class LiveOutputParser
+{
+   private OutputParser parser;
+   private List<FileLink> fileLinks = new ArrayList<FileLink>();
+   private StringBuilder totalTextToParse = new StringBuilder();
+   private int lastNewline;
+
+   public LiveOutputParser( FileLinkDefinitionLord fileLinkDefinitionLord, boolean verifyFileExists )
+   {
+       parser = new OutputParser( fileLinkDefinitionLord, verifyFileExists );
+   }
+
+   /**
+    Removes all text and FileLinks. This is so you can use this on new text
+    */
+   public void reset()
+   {
+       //recreate the parser because its possible the definitions have changed.
+      boolean verifyFileExists = parser.isVerifyFileExists();
+      FileLinkDefinitionLord fileLinkDefinitionLord = parser.getFileLinkDefinitionLord();
+      parser = new OutputParser( fileLinkDefinitionLord, verifyFileExists );
+       
+      lastNewline = 0;
+      totalTextToParse.setLength( 0 );
+      fileLinks.clear();
+   }
+
+   public List<FileLink> appendText( String text )
+   {
+      int oldTotalSize = totalTextToParse.length();
+
+      totalTextToParse.append( text );
+      int indexOfNewline = text.lastIndexOf( '\n' );
+      if( indexOfNewline == -1 )
+      {
+         return Collections.emptyList();  //nothing to search yet
+      }
+
+      //compensate the index for the total size
+      indexOfNewline += oldTotalSize;
+
+      //get everything between the last newline and this one
+      String textToParse = totalTextToParse.substring( lastNewline, indexOfNewline );
+
+      //search it
+      List<FileLink> subFileLinks = parser.parseText( textToParse );
+
+      //for each FileLink we have, we have to correct it's offsets because we didn't search from the beginning.
+      Iterator<FileLink> iterator = subFileLinks.iterator();
+      while( iterator.hasNext() )
+      {
+         FileLink fileLink = iterator.next();
+         fileLink.move( lastNewline );
+      }
+
+      fileLinks.addAll( subFileLinks );
+
+      lastNewline = indexOfNewline;
+
+      return subFileLinks;
+   }
+
+   public List<FileLink> getFileLinks() { return Collections.unmodifiableList( fileLinks ); }
+
+   /**
+    This gets the fileLink at the specified index in the text.
+    @param index the index into the overall text.
+    @return a FileLink if one exists at the index. null if not.
+    */
+   public FileLink getFileLink( int index )
+   {
+      if( index < 0 || index >= totalTextToParse.length() )
+      {
+         return null;
+      }
+
+      Iterator<FileLink> iterator = fileLinks.iterator();
+
+      while( iterator.hasNext() )
+      {
+         FileLink fileLink = iterator.next();
+         if( fileLink.getStartingIndex() <= index && fileLink.getEndingIndex() >= index )
+         {
+            return fileLink;
+         }
+      }
+
+      return null;
+   }
+
+   public String getText() { return totalTextToParse.toString(); }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/OutputParser.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/OutputParser.java
new file mode 100644
index 0000000..2f85d1d
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/OutputParser.java
@@ -0,0 +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.foundation.output;
+
+import org.gradle.foundation.output.definitions.*;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ This parses gradle's output text looking for links to files. We use RegEx to
+ do the bulk of  the matching. However, we want this to be 'pluggable' (to a
+ degree) so new file links can be added easily. To accomplish this, this works
+ with FileLinkDefinitions. They require a basic RegEx pattern to match some
+ initial part of the a file link, however, they can be implemented to do more
+ advanced parsing of the text. The definitions are built-up and held by
+ FileLinkDefinitionLord. This just handles the tedium of matching the
+ all-inclusive pattern with text, managing indices, and calling the matching
+ FileLinkDefinition to refine the match.
+
+ @author mhunsicker
+ */
+public class OutputParser
+{
+    private FileLinkDefinitionLord fileLinkDefinitionLord;
+    private boolean verifyFileExists;   //this is really only for testing where the file will not exist.
+
+    public OutputParser( FileLinkDefinitionLord fileLinkDefinitionLord, boolean verifyFileExists )
+   {
+       this.fileLinkDefinitionLord = fileLinkDefinitionLord;
+       this.verifyFileExists = verifyFileExists;
+   }
+
+    public boolean isVerifyFileExists() { return verifyFileExists; }
+    public FileLinkDefinitionLord getFileLinkDefinitionLord() { return fileLinkDefinitionLord; }
+
+   /**
+    This parses the text looking for file links
+    @param text the text to parse
+    @return a list of FileLinks for each file that was found in the text.
+    */
+   public List<FileLink> parseText( String text )
+   {
+      List<FileLink> fileLinks = new ArrayList<FileLink>();
+
+      Pattern combinedSearchPattern = fileLinkDefinitionLord.getSearchPattern();
+      Matcher matcher = combinedSearchPattern.matcher( text );
+
+      int index = 0;
+
+      boolean foundAMatch = matcher.find( index );
+      while( foundAMatch )
+      {
+         // Retrieve matching string
+         String matchedText = matcher.group();
+
+         // Retrieve indices of matching string
+         int start = matcher.start();
+         int end = matcher.end();
+
+         int nextStarting = start;
+
+         //now that we have a match, we have to find the one FileLinkDefinition that actually matches so it
+         //can determine the actual file. This makes the matcher more plugable.
+         FileLinkDefinition fileLinkDefinition = fileLinkDefinitionLord.getMatchingFileLinkDefinition( matchedText );
+         if( fileLinkDefinition != null )
+         {
+            nextStarting = fileLinkDefinition.parseFileLink( text, matchedText, start, end, verifyFileExists, fileLinks );
+         }
+         else
+         {
+            //this is probably a serious problem that needs to be reported. However, we'll continue as if nothing bad happened.
+            System.out.println( "We found a match but didn't find the matching definition. Matched text:\n" + text );
+         }
+
+         if( nextStarting == -1 || nextStarting < start )
+         {
+            nextStarting = start;
+         }
+
+         index = nextStarting + 1;
+         if( index < text.length() )
+         {
+            foundAMatch = matcher.find( index );
+         }
+         else
+         {
+            foundAMatch = false; //don't continue searching if we've found the end
+         }
+      }
+
+      return fileLinks;
+   }
+}
+
+
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/definitions/ExtensionFileLinkDefinition.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/definitions/ExtensionFileLinkDefinition.java
new file mode 100644
index 0000000..5f11229
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/definitions/ExtensionFileLinkDefinition.java
@@ -0,0 +1,140 @@
+/*
+ * 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.foundation.output.definitions;
+
+import org.gradle.foundation.output.FileLink;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ This is a basic FileLinkDefinition that uses a file's extension and assumes the file is at the beginning of the line.
+ This also allows you to specify an optional line number delimiter. This handles a delimiter after the path to specify a
+ line number (the delimiter cannot be before the path).
+
+ Here's a sample line output from a compile error:
+       /home/someguy/path/etc/etc.java:186: cannot find symbol
+
+ @author mhunsicker
+ */
+public class ExtensionFileLinkDefinition implements FileLinkDefinition
+{
+   private String expression;
+   private String lineNumberDelimiter;
+   private String extension;
+   private String name;
+
+   public ExtensionFileLinkDefinition( String name, String extension )
+   {
+      this( name, extension, null );
+   }
+
+   public ExtensionFileLinkDefinition( String name, String extension, String lineNumberDelimiter )
+   {
+      this.name = name;
+      this.lineNumberDelimiter = lineNumberDelimiter;
+      this.extension = extension;
+
+      this.expression = ".*\\" + extension;  //the ending slashes here are to escape the dot on the extension
+
+      if( lineNumberDelimiter != null )
+      {
+         this.expression += generateLineNumberExpression( lineNumberDelimiter );
+      }
+   }
+
+   public String getName() { return name; }
+
+   protected String generateLineNumberExpression( String lineNumberDelimiter )
+   {
+      //there may be a space before the delimiter so we quote the delimiter, possible space,
+      //followed by numbers
+      return PrefixedFileLinkDefinition.quoteLiteral( lineNumberDelimiter ) + "\\s*\\d*";
+   }
+
+   public String getSearchExpression()
+   {
+      return expression;
+   }
+
+   /**
+      This is called for each match. Parse this to turn it into a FileLink.
+
+      <!      Name        Description>
+    @param  fullSearchTest the full text that was searched
+    @param  matchedText the text that was matched
+    @param  start       the index into the entire searched text where the matchedText starts
+    @param  end         the index into the entire searched text where the matchedText ends
+    @param fileLinks
+      @return a FileLink or null if this is a false positive
+   */
+   public int parseFileLink( String fullSearchTest, String matchedText, int start, int end, boolean verifyFileExists, List<FileLink> fileLinks )
+   {
+      int extensionIndex = lastIndexOfCaseInsensitive( matchedText, extension );
+      if( extensionIndex == -1 ) //this shouldn't happen unless the extension is not included
+      {
+         return -1;
+      }
+
+      int prefixIndex = getStartOfFile( matchedText );   //we don't want to jst assume its the very first character. It probably is, but it might have spaces in front of it. This ensures the UI doesn't underline a space.
+      int realPathEnd = extensionIndex + extension.length();
+      String path = matchedText.substring( prefixIndex, realPathEnd ).trim();
+
+      File file = new File( path );
+      if( verifyFileExists && !file.exists() )  //so we can optionally disable this for testing.
+      {
+         return -1;
+      }
+
+      String remainder = matchedText.substring( realPathEnd );
+      int lineNumber = PrefixedFileLinkDefinition.getLineNumber( remainder, lineNumberDelimiter );
+
+      fileLinks.add( new FileLink( file, start + prefixIndex, end, lineNumber, this ) );
+      return end;
+   }
+
+   public static int lastIndexOfCaseInsensitive( String sourceText, String alreadyLowerCaseSoughtText )
+   {
+       sourceText = sourceText.toLowerCase();
+       return sourceText.lastIndexOf( alreadyLowerCaseSoughtText );
+   }
+
+   /**
+    This returns the index character that is the start of the file path. Basically, this skips over
+    whitespace that may be between the prefix and the path.
+    @param matchedText the text that was matched
+    @return the index of the start of the file
+    */
+   private int getStartOfFile( String matchedText )
+   {
+      int index = 0;
+      while( Character.isWhitespace( matchedText.charAt( index ) ) )
+      {
+         index++;
+      }
+
+      return index;
+   }
+
+   @Override
+   public String toString()
+   {
+      return "Name: '" + name + "'" +
+            " Expression ='" + expression + '\'' +
+            " LineNumberDelimter='" + lineNumberDelimiter + '\'' +
+            " Extension='" + extension + '\'';
+   }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/definitions/FileLinkDefinition.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/definitions/FileLinkDefinition.java
new file mode 100644
index 0000000..cd7fb09
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/definitions/FileLinkDefinition.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.foundation.output.definitions;
+
+import org.gradle.foundation.output.FileLink;
+
+import java.util.List;
+
+/**
+
+ .
+
+ @author mhunsicker
+ */
+public interface FileLinkDefinition
+{
+   /**
+    @return a name that really only useful for debugging
+    */
+   String getName();
+
+   /**
+      @return the regular expression used to find a potential FileLink
+   */
+   String getSearchExpression();
+
+   /**
+      This is called for each match. Parse this to turn it into a FileLink.
+
+      <!    Name        Description>
+    @param  fullSearchText the full text that was searched
+    @param  matchedText the text that was matched
+    @param  start       the index into the entire searched text where the matchedText starts
+    @param  end         the index into the entire searched text where the matchedText ends
+    @param fileLinks
+      @return a FileLink or null if this is a false positive
+   */
+   int parseFileLink( String fullSearchText, String matchedText, int start, int end, boolean verifyFileExists, List<FileLink> fileLinks );
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/definitions/OptionalLineNumberFileLinkDefinition.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/definitions/OptionalLineNumberFileLinkDefinition.java
new file mode 100644
index 0000000..d394c0a
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/definitions/OptionalLineNumberFileLinkDefinition.java
@@ -0,0 +1,39 @@
+/*
+ * 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.foundation.output.definitions;
+
+/**
+ This is just like BasicFileLinkDefinition except that the line number delimiter is optional and
+ it allows spaces between the file and the delimiter.
+
+ @author mhunsicker
+ */
+public class OptionalLineNumberFileLinkDefinition extends PrefixedFileLinkDefinition
+{
+   public OptionalLineNumberFileLinkDefinition( String name, String prefix, String extension, String lineNumberDelimiter )
+   {
+      super( name, prefix, extension, lineNumberDelimiter );
+   }
+
+   /**
+    This has been overridden to optionally look for a line number delimiter.
+    */
+   @Override
+   protected String generateLineNumberExpression( String lineNumberDelimiter )
+   {
+      return "(.*" + quoteLiteral( lineNumberDelimiter ) + ".*\\d*)?";
+   }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/definitions/PrefixedFileLinkDefinition.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/definitions/PrefixedFileLinkDefinition.java
new file mode 100644
index 0000000..fcbc7a6
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/definitions/PrefixedFileLinkDefinition.java
@@ -0,0 +1,241 @@
+/*
+ * 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.foundation.output.definitions;
+
+import org.gradle.foundation.output.FileLink;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ This is a basic FileLinkDefinition that uses a prefix, file extension to identify files. This also allows you to
+ specify an optional line number delimiter. This will handle files where the error always has a specific prefix
+ ([ant:javac]) and always has a known extension (.java). It also handles a delimiter after the path to specify a
+ line number (the delimiter cannot be before the path).
+
+ Here's a sample line output from an ant compile error:
+       [ant:javac] /home/someguy/path/etc/etc.java:186: cannot find symbol
+
+ Here's a sample line output from gradle when it encounters an exception:
+       Build file '/home/someguy/path/etc/etc/build.gradle'
+
+ @author mhunsicker
+ */
+public class PrefixedFileLinkDefinition implements FileLinkDefinition
+{
+   private String expression;
+   private String prefix;
+   private String lineNumberDelimiter;
+   private String extension;
+   private String name;
+
+   public PrefixedFileLinkDefinition( String name, String prefix, String extension )
+   {
+      this( name, prefix, extension, null );
+   }
+
+   /**
+    @param name  the name of this file link definition. Used by tests mostly.
+    @param prefix  the text that is before the file path. It should be enough to make it fairly unique
+    @param extension  the expected file extension. If we don't find this extension, we do not consider
+            the text a file's path. If there are multiple extensions, you'll have to add multiples of these.
+    @param lineNumberDelimiter optional delimiter text for line number. Whatever is after
+            this will be assumed to be a line number. We'll only parse the numbers after
+            this so there can be other stuff after the line number. Pass in null to ignore.
+    */
+   public PrefixedFileLinkDefinition( String name, String prefix, String extension, String lineNumberDelimiter )
+   {
+      this.name = name;
+      this.prefix = prefix;
+      this.lineNumberDelimiter = lineNumberDelimiter;
+      this.extension = extension;
+
+      String regExLiteralPrefix = quoteLiteral( prefix );
+      this.expression = regExLiteralPrefix + ".*\\" + extension;  //the ending slashes here are to escape the dot on the extension
+
+      if( lineNumberDelimiter != null )
+      {
+         this.expression += generateLineNumberExpression( lineNumberDelimiter );
+      }
+   }
+
+   public String getName() { return name; }
+
+   protected String generateLineNumberExpression( String lineNumberDelimiter )
+   {
+      return quoteLiteral( lineNumberDelimiter ) + "\\d*";
+   }
+
+   //This quotes the literal so it can be used in a regex without worrying about
+   //manually escaping any special characters. This does what Matcher.quoteReplacement()
+   //should do (but that seems to only handle $ and ").
+   public static String quoteLiteral( String literal )
+   {
+      StringBuilder builder = new StringBuilder();
+
+      for( int index = 0; index < literal.length(); index++ )
+      {
+         char c = literal.charAt( index );
+         if( isEscapedCharater( c ) )
+         {
+            builder.append( '\\' ).append( c );
+         }
+         else
+         {
+            builder.append( c );
+         }
+      }
+
+      return builder.toString();
+   }
+
+   private static boolean isEscapedCharater( char c )
+   {
+      return c == '[' ||
+             c == ']' ||
+             c == '(' ||
+             c == ')' ||
+             c == '{' ||
+             c == '}' ||
+             c == '\\' ||
+             c == '"' ||
+             c == '$' ||
+             c == '&' ||
+             c == '|' ||
+             c == '^' ||
+             c == '?' ||
+             c == '*' ||
+             c == '.';
+   }
+
+   public String getSearchExpression()
+   {
+      return expression;
+   }
+
+   /**
+      This is called for each match. Parse this to turn it into a FileLink.
+
+      <!      Name        Description>
+    @param  fullSearchTest the full text that was searched
+    @param  matchedText the text that was matched
+    @param  start       the index into the entire searched text where the matchedText starts
+    @param  end         the index into the entire searched text where the matchedText ends
+    @param fileLinks
+      @return a FileLink or null if this is a false positive
+   */
+   public int parseFileLink( String fullSearchTest, String matchedText, int start, int end, boolean verifyFileExists, List<FileLink> fileLinks )
+   {
+      int extensionIndex = matchedText.lastIndexOf( extension );
+      if( extensionIndex == -1 ) //this shouldn't happen unless the extension is not included
+      {
+         return -1;
+      }
+
+      int prefixIndex = getStartOfFile( matchedText );
+      int realPathEnd = extensionIndex + extension.length();
+      String path = matchedText.substring( prefixIndex, realPathEnd ).trim();
+
+      File file = new File( path );
+      if( verifyFileExists && !file.exists() )  //so we can optionally disable this for testing.
+      {
+         return -1;
+      }
+
+      String remainder = matchedText.substring( realPathEnd );
+      int lineNumber = getLineNumber( remainder, lineNumberDelimiter );
+
+      fileLinks.add( new FileLink( file, start + prefixIndex, end, lineNumber, this ) );
+      return end;
+   }
+
+   /**
+    This returns the index character that is the start of the file path. Basically, this skips over
+    whitespace that may be between the prefix and the path.
+    @param matchedText the text that was matched
+    @return the index of the start of the file
+    */
+   private int getStartOfFile( String matchedText )
+   {
+      int index = prefix.length();
+      while( Character.isWhitespace( matchedText.charAt( index ) ) )
+      {
+         index++;
+      }
+
+      return index;
+   }
+
+   public static int getLineNumber( String textAfterPath, String lineNumberDelimiter )
+   {
+      if( lineNumberDelimiter != null )
+      {
+         int lineDelimterIndex = textAfterPath.indexOf( lineNumberDelimiter );
+         if( lineDelimterIndex != -1 )
+         {
+            String lineNumberText = textAfterPath.substring( lineDelimterIndex + lineNumberDelimiter.length() );
+
+            lineNumberText = lineNumberText.trim();
+            lineNumberText = getConsecutiveNumbers( lineNumberText );
+            if( !"".equals( lineNumberText ) ) {
+               return Integer.parseInt( lineNumberText );
+            }
+         }
+      }
+
+      return -1;
+   }
+
+   /**
+    This returns the first grouping of consecutive numbers. This is used to extract line numbers from a line
+    that may have additional things immediately after the number. This assumes the first character is already
+    a number. If it is not, you'll get a blank string being returned.
+    @param text the text to search
+    @return a string consisting of only numbers.
+    */
+   public static String getConsecutiveNumbers( String text )
+   {
+      StringBuilder numbersOnly = new StringBuilder();
+
+      boolean keepLooking = true;
+      int index = 0;
+      while( keepLooking && index < text.length() )
+      {
+         char c = text.charAt( index );
+         if( Character.isDigit( c ) )
+         {
+            numbersOnly.append( c );
+         }
+         else
+         {
+            keepLooking = false;
+         }
+         index++;
+      }
+
+      return numbersOnly.toString();
+   }
+   
+   @Override
+   public String toString()
+   {
+      return "Name: '" + name + "'" +
+            " Expression ='" + expression + '\'' +
+            " Prefix='" + prefix + '\'' +
+            " LineNumberDelimter='" + lineNumberDelimiter + '\'' +
+            " Extension='" + extension + '\'';
+   }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/definitions/TestReportFileLinkDefinition.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/definitions/TestReportFileLinkDefinition.java
new file mode 100644
index 0000000..4bc48b7
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/output/definitions/TestReportFileLinkDefinition.java
@@ -0,0 +1,94 @@
+/*
+ * 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.foundation.output.definitions;
+
+import org.gradle.foundation.output.FileLink;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ This is a special FileLinkDefinition for handling test reports. At the time of this writing,
+ the test reports error message merely told you the directory to visit, not the actual file.
+ So this appends 'index.html' to the directory to generate the file.
+
+ @author mhunsicker
+*/
+public class TestReportFileLinkDefinition implements FileLinkDefinition
+{
+   private String expression;
+   private String prefix;
+
+   public TestReportFileLinkDefinition()
+   {
+      prefix = "There were failing tests. See the report at ";
+      expression = prefix + ".*";
+   }
+
+   public String getSearchExpression()
+   {
+      return expression;
+   }
+
+   /**
+      This is called for each match. Parse this to turn it into a FileLink.
+      We're actually looking for a sentence, so we find the period, then get
+      whatever's between it and our prefix, then we have our directory. 
+
+      <!      Name        Description>
+    @param  fullSearchTest the full text that was searched
+    @param  matchedText the text that was matched
+    @param  start       the index into the entire searched text where the matchedText starts
+    @param  end         the index into the entire searched text where the matchedText ends
+    @param fileLinks
+      @return a FileLink or null if this is a false positive
+   */
+   public int parseFileLink( String fullSearchTest, String matchedText, int start, int end, boolean verifyFileExists, List<FileLink> fileLinks )
+   {
+      int indexOfPeriod = matchedText.lastIndexOf( '.' );   //the path ends with a dot
+      if( indexOfPeriod == -1 )
+      {
+         return -1;
+      }
+
+      String path = matchedText.substring( prefix.length(), indexOfPeriod ).trim();
+      File directory = new File( path );
+      if( verifyFileExists && !directory.exists() )
+      {
+         return -1;
+      }
+
+      File file = new File( directory, "index.html" );
+      if( verifyFileExists && !file.exists() )
+      {
+         return -1;
+      }
+
+      fileLinks.add( new FileLink( file, start + prefix.length(), end, -1, this ) );
+      return end;
+   }
+
+   @Override
+   public String toString()
+   {
+      return getName();
+   }
+
+   public String getName()
+   {
+      return "TestReportFileLinkDefinition";
+   }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/queue/ExecutionQueue.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/queue/ExecutionQueue.java
new file mode 100644
index 0000000..85fbcdf
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/queue/ExecutionQueue.java
@@ -0,0 +1,131 @@
+/*
+ * 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.foundation.queue;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class abstracts running multiple tasks consecutively. This exists because I'm not certain that Gradle is
+ * thread-safe and on Windows, running tasks that require lots of disk I/O get considerably slower when run
+ * concurrently. This will allow requests to be made and they will run as soon as any previous requests have finished.
+ *
+ * @author mhunsicker
+ */
+public class ExecutionQueue<R extends ExecutionQueue.Request> {
+    private final Logger logger = Logging.getLogger(ExecutionQueue.class);
+    private volatile LinkedBlockingQueue<R> requests = new LinkedBlockingQueue<R>();
+
+    private Thread executionThread;
+
+    /**
+     * This removes the complexities of managing queued up requests across threads. Implement this to define what to do
+     * when a request is made.
+    */
+    public interface ExecutionInteraction<R> {
+        /**
+        * When this is called, execute the given request.
+        *
+        * @param  request    the request to execute.
+        */
+        void execute(R request);
+    }
+
+    /**
+    * Marker interface for a request. It contains the command line to execute and some other information.
+    */
+    public interface Request {
+
+
+      /**
+       Marker interface for types. This defines a high-level category of this request (Refresh and Execution are the only types at the moment).
+       */
+      public interface Type {}
+
+      /**
+       @return the type of request.
+       */
+      public Type getType();
+    }
+
+    public ExecutionQueue(ExecutionInteraction<R> executeInteraction) {
+        executionThread = new Thread(new ExecutionThread(executeInteraction));
+
+        //This seems to solve some classloader issues. Actually, I did this in the SwingWorker thread when refreshing the tasks
+        //and it cleared up SOME problems, so I'm doing it here as well.
+        executionThread.setContextClassLoader(getClass().getClassLoader());
+        executionThread.start();
+    }
+
+    /**
+     * Call this to add a task to the execution queue. It will be executed as soon as the current task has completed.
+    *
+    * @param  request      the requested task
+    */
+    public void addRequestToQueue(R request) {
+        requests.offer(request);
+    }
+
+    public boolean removeRequestFromQueue(R request) {
+        return requests.remove(request);
+    }
+
+    public boolean hasRequests() {
+       return !requests.isEmpty();
+    }
+
+    public List<R> getRequests() {
+       return new ArrayList<R>( requests );
+    }
+
+    /**
+    * This waits until the next request is available.
+    * @return the next request.
+    */
+    private R getNextAvailableRequest() {
+        try {
+            return requests.take();
+        }
+        catch (InterruptedException e) {
+            logger.error("Getting next available request", e);
+            return null;
+        }
+    }
+
+    /**
+     * This thread actually launches the gradle commands. It waits until a new request is added, then it executes it.
+    */
+    private class ExecutionThread implements Runnable {
+        private ExecutionInteraction<R> executeInteraction;
+
+        private ExecutionThread(ExecutionInteraction<R> executeInteraction) {
+            this.executeInteraction = executeInteraction;
+        }
+
+        public void run() {
+            while (true) {
+                R request = getNextAvailableRequest();
+                if (request != null) {
+                    executeInteraction.execute(request);
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/visitors/AllProjectsAndTasksVisitor.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/visitors/AllProjectsAndTasksVisitor.java
new file mode 100644
index 0000000..511150f
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/visitors/AllProjectsAndTasksVisitor.java
@@ -0,0 +1,142 @@
+/*
+ * 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.foundation.visitors;
+
+import org.gradle.foundation.ProjectView;
+import org.gradle.foundation.TaskView;
+import org.gradle.gradleplugin.foundation.filters.AllowAllProjectAndTaskFilter;
+import org.gradle.gradleplugin.foundation.filters.ProjectAndTaskFilter;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This visits all projects and their subprojects and tasks in a hierarchal manner. Useful if you need to do some
+ * processing with each one.
+ *
+ * @author mhunsicker
+ */
+public class AllProjectsAndTasksVisitor {
+    /*
+          Visitor allowing you to do whatever you like with a project or task.
+       @author mhunsicker
+    */
+
+    public interface Visitor<P, T> {
+        /*
+           This is called for each project.
+         @param  project    the project
+         @param  parentProjectObject whatever you handed back from a prior call to
+            visitProject if this is a sub project. Otherwise, it'll be whatever
+            was passed into the visitPojectsAndTasks function.
+           @return an object that will be handed back to you for each of this
+                   project's tasks.
+           @author mhunsicker
+        */
+
+        public P visitProject(ProjectView project, P parentProjectObject);
+
+        /*
+           This is called for each task.
+
+         @param  task               the task
+         @param  tasksProject       the project for this task
+         @param  userProjectObject  whatever you returned from the parent project's visitProject
+           @author mhunsicker
+        */
+
+        public T visitTask(TaskView task, ProjectView tasksProject, P userProjectObject);
+    }
+
+    /*
+       This visitor will visit each project, sub-project and task that was discovered
+       by the GradleHelper. This is useful for building a list or tree of
+       projects and tasks.
+
+       This is the same as the other version of visitProjectsAndTasks except this
+       one visits everything.
+
+       @author mhunsicker
+    */
+
+    public static <P, T> void visitProjectAndTasks(List<ProjectView> projects, Visitor<P, T> visitor,
+                                                   P rootProjectObject) {
+        visitProjectAndTasks(projects, visitor, new AllowAllProjectAndTaskFilter(), rootProjectObject);
+    }
+
+    /*
+       This visitor will visit each project, sub-project and task that was discovered
+       by the GradleHelper. This is useful for building a list or tree of
+       projects and tasks.
+
+       @param  visitor    this notified you of each project and task.
+       @param  filter     allows you to skip projects and tasks as specified by the filter.
+       @param  rootProjectObject whatever you pass here will be passed to the
+                root-level projects as parentProjectObject.
+       @author mhunsicker
+    */
+
+    public static <P, T> void visitProjectAndTasks(List<ProjectView> projects, Visitor<P, T> visitor,
+                                                   ProjectAndTaskFilter filter, P rootProjectObject) {
+        visitProjects(visitor, filter, projects, rootProjectObject);
+    }
+
+    public static <P, T> List<P> visitProjects(Visitor<P, T> visitor, ProjectAndTaskFilter filter,
+                                               List<ProjectView> projects, P parentProjectObject) {
+        List<P> projectObjects = new ArrayList<P>();
+
+        Iterator<ProjectView> iterator = projects.iterator();
+        while (iterator.hasNext()) {
+            ProjectView project = iterator.next();
+
+            if (filter.doesAllowProject(project)) {
+                P userProjectObject = visitor.visitProject(project, parentProjectObject);
+                projectObjects.add(userProjectObject);
+
+                //visit sub projects
+                visitProjects(visitor, filter, project.getSubProjects(), userProjectObject);
+
+                //visit tasks
+                visitTasks(visitor, filter, project, userProjectObject);
+            }
+        }
+
+        return projectObjects;
+    }
+
+    /*
+       Add the list of tasks to the parent tree node.
+
+       @author mhunsicker
+    */
+
+    private static <P, T> List<T> visitTasks(Visitor<P, T> visitor, ProjectAndTaskFilter filter, ProjectView project,
+                                             P userProjectObject) {
+        List<T> taskObjects = new ArrayList<T>();
+        Iterator<TaskView> iterator = project.getTasks().iterator();
+        while (iterator.hasNext()) {
+            TaskView task = iterator.next();
+
+            if (filter.doesAllowTask(task)) {
+                T taskObject = visitor.visitTask(task, project, userProjectObject);
+                taskObjects.add(taskObject);
+            }
+        }
+
+        return taskObjects;
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/visitors/TaskTreePopulationVisitor.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/visitors/TaskTreePopulationVisitor.java
new file mode 100644
index 0000000..9854b56
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/visitors/TaskTreePopulationVisitor.java
@@ -0,0 +1,165 @@
+/*
+ * 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.foundation.visitors;
+
+import org.gradle.foundation.ProjectView;
+import org.gradle.foundation.TaskView;
+import org.gradle.gradleplugin.foundation.filters.AllowAllProjectAndTaskFilter;
+import org.gradle.gradleplugin.foundation.filters.ProjectAndTaskFilter;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This visits each project and task in a hierarchical manner. This visitor is specifically meant to walk the projects
+ * first and tasks second for the purpose of populating a tree where the projects/subprojects are first and the tasks
+ * are second.
+ *
+ * @author mhunsicker
+ */
+public class TaskTreePopulationVisitor {
+    public interface Visitor<P, T> {
+        /*
+           This is called for each project.
+         @param  project    the project
+         @param indexOfProject
+         @param  parentProjectObject whatever you handed back from a prior call to
+                 visitProject if this is a sub project. Otherwise, it'll be whatever
+                 was passed into the visitPojectsAndTasks function.
+           @return an object that will be handed back to you for each of this
+                   project's tasks.
+           @author mhunsicker
+        */
+
+        public P visitProject(ProjectView project, int indexOfProject, P parentProjectObject);
+
+        /*
+           This is called for each task.
+
+         @param  task               the task
+         @param indexOfTask
+         @param  tasksProject       the project for this task
+         @param  userProjectObject  whatever you returned from the parent project's visitProject
+           @author mhunsicker
+        */
+
+        public T visitTask(TaskView task, int indexOfTask, ProjectView tasksProject, P userProjectObject);
+
+        /*
+           This is called when a project has been visited completely and is just a
+           notification giving you an opportunity to do whatever you like.
+           This is possibly where you want to delete any nodes that we didn't
+           visit.
+
+           @param  parentProjectObject the object that represents the parent of
+                                       the project and task objects below
+           @param  projectObjects      a list of whatever you returned from visitProject
+           @param  taskObjects         a list of whatever you returned from visitTask
+           @author mhunsicker
+        */
+
+        public void completedVisitingProject(P parentProjectObject, List<P> projectObjects, List<T> taskObjects);
+    }
+
+    /*
+       This visitor will visit each project, sub-project and task that was discovered
+       by the GradleHelper. This is useful for building a list or tree of
+       projects and tasks.
+
+       This is the same as the other version of visitProjectsAndTasks except this
+       one visits everything.
+
+       @author mhunsicker
+    */
+
+    public static <P, T> void visitProjectAndTasks(List<ProjectView> projects, Visitor<P, T> visitor,
+                                                   P rootProjectObject) {
+        visitProjectAndTasks(projects, visitor, new AllowAllProjectAndTaskFilter(), rootProjectObject);
+    }
+
+    /*
+       This visitor will visit each project, sub-project and task that was discovered
+       by the GradleHelper. This is useful for building a list or tree of
+       projects and tasks.
+
+       @param  visitor    this notified you of each project and task.
+       @param  filter     allows you to skip projects and tasks as specified by the filter.
+       @param  rootProjectObject whatever you pass here will be passed to the
+                root-level projects as parentProjectObject.
+       @author mhunsicker
+    */
+
+    public static <P, T> void visitProjectAndTasks(List<ProjectView> projects, Visitor<P, T> visitor,
+                                                   ProjectAndTaskFilter filter, P rootProjectObject) {
+        List<P> userProjectObjects = visitProjects(visitor, filter, projects, rootProjectObject);
+
+        //notify the visitation of the root projects. There are no tasks for this one, but there are projects.
+        visitor.completedVisitingProject(rootProjectObject, userProjectObjects, Collections.EMPTY_LIST);
+    }
+
+    private static <P, T> List<P> visitProjects(Visitor<P, T> visitor, ProjectAndTaskFilter filter,
+                                                List<ProjectView> projects, P parentProjectObject) {
+        List<P> projectObjects = new ArrayList<P>();
+
+        Iterator<ProjectView> iterator = projects.iterator();
+        int index = 0;
+        while (iterator.hasNext()) {
+            ProjectView project = iterator.next();
+
+            if (filter.doesAllowProject(project)) {
+                P userProjectObject = visitor.visitProject(project, index, parentProjectObject);
+                projectObjects.add(userProjectObject);
+
+                //visit sub projects
+                List<P> subProjectObjects = visitProjects(visitor, filter, project.getSubProjects(), userProjectObject);
+
+                //visit tasks. Notice that we pass in the number of subprojects as a starting index. This is so they'll come afterwards.
+                List<T> taskObjects = visitTasks(visitor, filter, project, subProjectObjects.size(), userProjectObject);
+
+                visitor.completedVisitingProject(userProjectObject, subProjectObjects, taskObjects);
+            }
+            index++;
+        }
+
+        return projectObjects;
+    }
+
+    /*
+       Add the list of tasks to the parent tree node.
+
+       @author mhunsicker
+    */
+
+    private static <P, T> List<T> visitTasks(Visitor<P, T> visitor, ProjectAndTaskFilter filter, ProjectView project,
+                                             int startingIndex, P userProjectObject) {
+        List<T> taskObjects = new ArrayList<T>();
+        Iterator<TaskView> iterator = project.getTasks().iterator();
+        int index = startingIndex;
+        while (iterator.hasNext()) {
+            TaskView task = iterator.next();
+
+            if (filter.doesAllowTask(task)) {
+                T taskObject = visitor.visitTask(task, index, project, userProjectObject);
+                taskObjects.add(taskObject);
+            }
+            index++;
+        }
+
+        return taskObjects;
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/foundation/visitors/UniqueNameProjectAndTaskVisitor.java b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/visitors/UniqueNameProjectAndTaskVisitor.java
new file mode 100644
index 0000000..44270b0
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/foundation/visitors/UniqueNameProjectAndTaskVisitor.java
@@ -0,0 +1,91 @@
+/*
+ * 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.foundation.visitors;
+
+import org.gradle.foundation.ProjectView;
+import org.gradle.foundation.TaskView;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * This visitor builds up a list of unqiuely named projects and tasks. The projects will be their full path, so they're
+ * all unique.
+ *
+ * @author mhunsicker
+ */
+public class UniqueNameProjectAndTaskVisitor implements AllProjectsAndTasksVisitor.Visitor<Object, Object> {
+    private List<String> taskNames = new ArrayList<String>();
+    private List<String> projectNames = new ArrayList<String>();
+
+    public List<String> getTaskNames() {
+        return taskNames;
+    }
+
+    public List<String> getProjectNames() {
+        return projectNames;
+    }
+
+    public List<String> getSortedTaskNames() {
+        ArrayList<String> tasks = new ArrayList<String>(taskNames);
+        Collections.sort(tasks);
+        return tasks;
+    }
+
+    public List<String> getSortedProjectNames() {
+        ArrayList<String> projects = new ArrayList<String>(projectNames);
+        Collections.sort(projects);
+        return projects;
+    }
+
+    /*
+    This is called for each project.
+    @param project             the project
+    @param parentProjectObject whatever you handed back from a prior call to
+                               visitProject if this is a sub project. Otherwise,
+                               it'll be whatever was passed into the
+                               visitPojectsAndTasks function.
+    @return always null
+    @author mhunsicker
+    */
+
+    public Object visitProject(ProjectView project, Object parentProjectObject) {
+        String name = project.getFullProjectName();
+        if (!projectNames.contains(name)) {
+            projectNames.add(name);
+        }
+
+        return null;
+    }
+
+    /*
+    This is called for each task.
+    @param task              the task
+    @param tasksProject      the project for this task
+    @param userProjectObject always null.
+    @author mhunsicker
+    */
+
+    public Object visitTask(TaskView task, ProjectView tasksProject, Object userProjectObject) {
+        String name = task.getName();
+        if (!taskNames.contains(name)) {
+            taskNames.add(name);
+        }
+
+        return null;
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/CommandLineArgumentAlteringListener.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/CommandLineArgumentAlteringListener.java
new file mode 100644
index 0000000..1b070c8
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/CommandLineArgumentAlteringListener.java
@@ -0,0 +1,33 @@
+/*
+ * 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.gradleplugin.foundation;
+
+/**
+ * This allows you to add a listener that can add additional command line arguments whenever gradle is executed. This is
+ * useful if you've customized your gradle build and need to specify, for example, an init script.
+ *
+ * @author mhunsicker
+ */
+public interface CommandLineArgumentAlteringListener {
+    /**
+     * This is called when you can add additional command line arguments. Return any additional arguments to add. This
+     * doesn't modify the existing commands.
+     *
+     * @return any command lines to add or null to leave it alone
+     */
+    public String getAdditionalCommandLineArguments(String commandLineArguments);
+}
+
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/DOM4JSerializer.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/DOM4JSerializer.java
new file mode 100644
index 0000000..1da21c7
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/DOM4JSerializer.java
@@ -0,0 +1,274 @@
+/*
+ * 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.gradleplugin.foundation;
+
+import org.dom4j.Document;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+import org.dom4j.io.OutputFormat;
+import org.dom4j.io.SAXReader;
+import org.dom4j.io.XMLWriter;
+import org.gradle.gradleplugin.foundation.settings.DOM4JSettingsNode;
+import org.gradle.gradleplugin.foundation.settings.SettingsNode;
+import org.gradle.gradleplugin.foundation.settings.SettingsSerializable;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+
+import javax.swing.filechooser.FileFilter;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * This saves and reads thing to and from DOM structures.
+ *
+ * @author mhunsicker
+ */
+public class DOM4JSerializer {
+    private static final Logger LOGGER = Logging.getLogger(DOM4JSerializer.class);
+
+    /**
+     * Implement this when you export a file. This allows to interactively ask the user (or automated test) questions as
+     * we need answers.
+     */
+    public interface ExportInteraction {
+        /**
+         * This is called when you should ask the user for a destination file of a save.
+         *
+         * @param fileFilter describes the allowed file type. Suitable for passing to JFileChooser.
+         * @return a file to save to or null to cancel.
+         */
+        public File promptForFile(FileFilter fileFilter);
+
+        /**
+         * Report an error that occurred. The save failed.
+         *
+         * @param error the error message.
+         */
+        public void reportError(String error);
+
+        /**
+         * The file already exists. Confirm whether or not you want to overwrite it.
+         *
+         * @param file the file in question
+         * @return true to overwrite it, false not to.
+         */
+        boolean confirmOverwritingExisingFile(File file);
+    }
+
+    /**
+     * Call this to save the JDOMSerializable to a file. This handles confirming overwriting an existing file as well as
+     * ensuring the extension is correct based on the passed in fileFilter.
+     */
+    public static void exportToFile(String rootElementTag, ExportInteraction exportInteraction,
+                                    ExtensionFileFilter fileFilter, SettingsSerializable... serializables) {
+        File file = promptForFile(exportInteraction, fileFilter);
+        if (file == null)   //the user canceled.
+        {
+            return;
+        }
+
+        FileOutputStream fileOutputStream = null;
+        try {
+            fileOutputStream = new FileOutputStream(file);
+        } catch (FileNotFoundException e) {
+            LOGGER.error("Could not write to file: " + file.getAbsolutePath(), e);
+            exportInteraction.reportError("Could not write to file: " + file.getAbsolutePath());
+            return;
+        }
+
+        try {
+            XMLWriter xmlWriter = new XMLWriter(fileOutputStream, OutputFormat.createPrettyPrint());
+
+            Document document = DocumentHelper.createDocument();
+            Element rootElement = document.addElement(rootElementTag);
+            DOM4JSettingsNode settingsNode = new DOM4JSettingsNode(rootElement);
+            for (int index = 0; index < serializables.length; index++) {
+                SettingsSerializable serializable = serializables[index];
+                try {  //don't let a single serializer stop the entire thing from being written in.
+                    serializable.serializeOut(settingsNode);
+                } catch (Exception e) {
+                    LOGGER.error("serializing", e);
+                }
+            }
+            xmlWriter.write(document);
+        } catch (Throwable t) {
+            LOGGER.error("Failed to save", t);
+            exportInteraction.reportError("Internal error. Failed to save.");
+        } finally {
+            closeQuietly(fileOutputStream);
+        }
+    }
+
+    public static void exportToFile(ExportInteraction exportInteraction, ExtensionFileFilter fileFilter,
+                                    DOM4JSettingsNode settingsNode) {
+        File file = promptForFile(exportInteraction, fileFilter);
+        if (file == null)   //the user canceled.
+        {
+            return;
+        }
+
+        FileOutputStream fileOutputStream = null;
+        try {
+            fileOutputStream = new FileOutputStream(file);
+        } catch (FileNotFoundException e) {
+            LOGGER.error("Could not write to file: " + file.getAbsolutePath(), e);
+            exportInteraction.reportError("Could not write to file: " + file.getAbsolutePath());
+            return;
+        }
+
+        try {
+            XMLWriter xmlWriter = new XMLWriter(fileOutputStream, OutputFormat.createPrettyPrint());
+            Element rootElement = settingsNode.getElement();
+            rootElement.detach();
+
+            Document document = DocumentHelper.createDocument(rootElement);
+
+            xmlWriter.write(document);
+        } catch (Throwable t) {
+            LOGGER.error("Internal error. Failed to save.", t);
+            exportInteraction.reportError("Internal error. Failed to save.");
+        } finally {
+            closeQuietly(fileOutputStream);
+        }
+    }
+
+    /**
+     * This prompts the user for a file. It may exist, so we have to confirm overwriting it. This will sit in a loop
+     * until the user cancels or gives us a valid file. This also makes sure the extension is correct.
+     */
+    private static File promptForFile(ExportInteraction exportInteraction, ExtensionFileFilter fileFilter) {
+        boolean promptAgain = false;
+        File file = null;
+        int counter = 0;
+        do {
+            promptAgain = false;
+            file = exportInteraction.promptForFile(fileFilter);
+            if (file != null) {
+                file = ensureFileHasCorrectExtensionAndCase(file, fileFilter.getExtension());
+
+                if (file.exists()) {
+                    promptAgain = !exportInteraction.confirmOverwritingExisingFile(file);
+                }
+            }
+
+            counter++;
+        } while (promptAgain && counter
+                < 1000);   //the counter is just to make sure any tests that use this don't get stuck in an infinite loop (they may return the same thing from promptForFile).
+
+        return file;
+    }
+
+    //
+
+    /**
+     * Implement this when you import a file. This allows to interactively ask the user (or automated test) questions as
+     * we need answers.
+     */
+    public interface ImportInteraction {
+        /**
+         * This is called when you should ask the user for a source file to read.
+         *
+         * @return a file to read or null to cancel.
+         */
+        public File promptForFile(FileFilter fileFilters);
+
+        /**
+         * Report an error that occurred. The read failed.
+         *
+         * @param error the error message.
+         */
+        public void reportError(String error);
+    }
+
+    /**
+     * Call this to read the JDOMSerializable from a file.
+     */
+    public static boolean importFromFile(ImportInteraction importInteraction, FileFilter fileFilter,
+                                         SettingsSerializable... serializables) {
+        SettingsNode settings = readSettingsFile(importInteraction, fileFilter);
+        if (settings == null) {
+            return false;
+        }
+
+        for (int index = 0; index < serializables.length; index++) {
+            SettingsSerializable serializable = serializables[index];
+
+            try {  //don't let a single serializer stop the entire thing from being read in.
+                serializable.serializeIn(settings);
+            } catch (Exception e) {
+                LOGGER.error("importing file", e);
+            }
+        }
+
+        return true;
+    }
+
+    public static DOM4JSettingsNode readSettingsFile(ImportInteraction importInteraction, FileFilter fileFilter) {
+        File file = importInteraction.promptForFile(fileFilter);
+        if (file == null) {
+            return null;
+        }
+
+        if (!file.exists()) {
+            importInteraction.reportError("File does not exist: " + file.getAbsolutePath());
+            return null;  //we should really sit in a loop until they cancel or give us a valid file.
+        }
+
+        try {
+            SAXReader reader = new SAXReader();
+            Document document = reader.read(file);
+
+            return new DOM4JSettingsNode(document.getRootElement());
+        } catch (Throwable t) {
+            LOGGER.error("Unable to read file: " + file.getAbsolutePath(), t);
+            importInteraction.reportError("Unable to read file: " + file.getAbsolutePath());
+            return null;
+        }
+    }
+
+    private static void closeQuietly(Closeable closeable) {
+        try {
+            if (closeable != null) {
+                closeable.close();
+            }
+        } catch (IOException e) {
+            LOGGER.error("Closing", e);
+        }
+    }
+
+    /**
+     * A convenience function that ensures that the specified file does have a specific extension. You have to tell us
+     * that extension.
+     */
+    private static File ensureFileHasCorrectExtensionAndCase(File file, String requiredExtension) {
+        String name = file.getName();
+        if (!name.toLowerCase().endsWith(requiredExtension.toLowerCase())) {
+            return new File(file.getParentFile(), name + requiredExtension);
+        }
+
+        return file;   //it already ends with the correct extension.
+    }
+
+    public static DOM4JSettingsNode createBlankSettings() {
+        Document document = DocumentHelper.createDocument();
+        Element rootElement = document.addElement("root");
+        DOM4JSettingsNode settings = new DOM4JSettingsNode(rootElement);
+        return settings;
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/Dom4JUtility.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/Dom4JUtility.java
new file mode 100644
index 0000000..851e8f6
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/Dom4JUtility.java
@@ -0,0 +1,116 @@
+/*
+ * 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.gradleplugin.foundation;
+
+import org.dom4j.Attribute;
+import org.dom4j.Element;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.ArrayList;
+
+/*
+ Just some utility functions that really should be in Dom4J already.
+
+ @author mhunsicker
+  */
+
+public class Dom4JUtility {
+    /**
+     * This returns the node that is a child of the specified parent that has the specified 'name' and an attribute with
+     * the specified value. This is similar to the below getChild, but this requires a specific tag name.
+     */
+    public static Element getChild(Element parent, String tagName, String attribute, String attributeValue) {
+        Element childElement = null;
+        Iterator iterator = parent.elements(tagName).iterator();
+        while (iterator.hasNext() && childElement == null) {
+            childElement = (Element) iterator.next();
+            String actualValue = childElement.attributeValue(attribute);
+            if (!attributeValue.equals(actualValue)) {
+                childElement = null;
+            }
+        }
+        return childElement;
+    }
+
+    public static List<Element> getChildren(Element parent, String tagName, String attribute, String attributeValue) {
+        List<Element> children = new ArrayList<Element>();
+
+        Iterator iterator = parent.elements(tagName).iterator();
+        while (iterator.hasNext()) {
+            Element childElement = (Element) iterator.next();
+            String actualValue = childElement.attributeValue(attribute);
+            if (attributeValue.equals(actualValue)) {
+                children.add(childElement);
+            }
+        }
+
+        return children;
+    }
+
+    /**
+     * Thie returns the node that is a child of hte specified parent that has the specified attribute with the specified
+     * value. This is similar to the above getChild, but no tag name is required.
+     */
+    public static Element getChild(Element parent, String attribute, String attributeValue) {
+        Element childElement = null;
+        Iterator iterator = parent.elements().iterator();
+        while (iterator.hasNext() && childElement == null) {
+            childElement = (Element) iterator.next();
+            String actualValue = childElement.attributeValue(attribute);
+            if (!attributeValue.equals(actualValue)) {
+                childElement = null;
+            }
+        }
+        return childElement;
+    }
+
+    /**
+     * Thie returns the node that is a child of hte specified parent that has the specified attribute with the specified
+     * value. This is similar to the above getChild, but no tag name is required.
+     */
+    public static List<Element> getChildren(Element parent, String attribute, String attributeValue) {
+        List<Element> children = new ArrayList<Element>();
+
+        Iterator iterator = parent.elements().iterator();
+        while (iterator.hasNext()) {
+            Element childElement = (Element) iterator.next();
+            String actualValue = childElement.attributeValue(attribute);
+            if (attributeValue.equals(actualValue)) {
+                children.add(childElement);
+            }
+        }
+
+        return children;
+    }
+
+    public static void setAttributeAsBoolean(Element element, String attribute, boolean value) {
+        if (value) {
+            element.addAttribute(attribute, "true");
+        } else {
+            element.addAttribute(attribute, "false");
+        }
+    }
+
+    public static boolean getAttributeAsBoolean(Element element, String attributeName, boolean defaultValue) {
+        Attribute attribute = element.attribute(attributeName);
+        if (attribute == null) {
+            return defaultValue;
+        }
+
+        return "true".equals(attribute.getValue());
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/ExtensionFileFilter.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/ExtensionFileFilter.java
new file mode 100644
index 0000000..16afd5c
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/ExtensionFileFilter.java
@@ -0,0 +1,56 @@
+/*
+ * 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.gradleplugin.foundation;
+
+import javax.swing.filechooser.FileFilter;
+import java.io.File;
+
+/**
+ * Man! Why doesn't this get put into java's standard library?! Well, they did, but it doesn't hide hidden files. By
+ * definition of them being hidden, you probably don't want to see them. <p/> While FileFilter is technically a Swing
+ * class, it shouldn't be. The foundation needs to drive what is allowed in the UI.
+ *
+ * @author mhunsicker
+ */
+public class ExtensionFileFilter extends FileFilter {
+    private String extension;
+    private String description;
+
+    public ExtensionFileFilter(String extension, String description) {
+        this.extension = extension.toLowerCase();
+        this.description = description;
+    }
+
+    public boolean accept(File file) {
+        if (file.isHidden()) {
+            return false;
+        }
+
+        if (file.isDirectory()) {
+            return true;
+        }
+
+        return file.getName().toLowerCase().endsWith(extension);
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public String getExtension() {
+        return extension;
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/GradlePluginLord.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/GradlePluginLord.java
new file mode 100644
index 0000000..76231c9
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/GradlePluginLord.java
@@ -0,0 +1,748 @@
+/*
+ * 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.gradleplugin.foundation;
+
+import org.codehaus.groovy.runtime.StackTraceUtils;
+import org.gradle.api.LocationAwareException;
+import org.gradle.api.internal.DefaultClassPathProvider;
+import org.gradle.gradleplugin.foundation.favorites.FavoriteTask;
+import org.gradle.initialization.DefaultCommandLine2StartParameterConverter;
+import org.gradle.StartParameter;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.foundation.CommandLineAssistant;
+import org.gradle.foundation.ProjectView;
+import org.gradle.foundation.TaskView;
+import org.gradle.foundation.common.ObserverLord;
+import org.gradle.foundation.ipc.basic.ProcessLauncherServer;
+import org.gradle.foundation.queue.ExecutionQueue;
+import org.gradle.gradleplugin.foundation.favorites.FavoritesEditor;
+import org.gradle.gradleplugin.foundation.request.ExecutionRequest;
+import org.gradle.gradleplugin.foundation.request.RefreshTaskListRequest;
+import org.gradle.gradleplugin.foundation.request.Request;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Formatter;
+import java.util.List;
+import java.util.Iterator;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * This class has nothing to do with plugins inside of gradle, but are related to making a plugin that uses gradle, such
+ * as for an IDE. It is also used by the standalone IDE (that way the standalone UI and plugin UIs are kept in synch).
+ * <p/> This is the class that stores most of the information that the Gradle plugin works directly with. It is meant to
+ * simplify creating a plugin that uses gradle. It maintains a queue of commands to execute and executes them in a
+ * separate process due to some complexities with gradle and its dependencies classpaths and potential memory issues.
+ *
+ * @author mhunsicker
+ */
+public class GradlePluginLord {
+    private final Logger logger = Logging.getLogger(GradlePluginLord.class);
+
+    private File gradleHomeDirectory;   //the directory where gradle is installed
+    private File currentDirectory;      //the directory of your gradle-based project
+    private File customGradleExecutor;  //probably will be null. This allows a user to specify a different batch file or shell script to initiate gradle.
+
+    private List<ProjectView> projects = new ArrayList<ProjectView>();
+
+    private FavoritesEditor favoritesEditor;  //an editor for the current favorites. The user can edit this at any time, hence we're using an editor.
+
+    private ExecutionQueue<Request> executionQueue;
+    private LinkedBlockingQueue<Request> currentlyExecutingRequests = new LinkedBlockingQueue<Request>();
+
+    private boolean isStarted;  //this flag is mostly to prevent initialization from firing off repeated refresh requests.
+
+    private StartParameter.ShowStacktrace stackTraceLevel = StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS;
+    private LogLevel logLevel = LogLevel.LIFECYCLE;
+
+    private ObserverLord<GeneralPluginObserver> generalObserverLord = new ObserverLord<GeneralPluginObserver>();
+    private ObserverLord<RequestObserver> requestObserverLord = new ObserverLord<RequestObserver>();
+    private ObserverLord<SettingsObserver> settingsObserverLord = new ObserverLord<SettingsObserver>();
+
+    private ObserverLord<CommandLineArgumentAlteringListener> commandLineArgumentObserverLord = new ObserverLord<CommandLineArgumentAlteringListener>();
+
+    private long nextRequestID = 1;  //a unique number assigned to requests
+
+    public List<ProjectView> getProjects() {
+        return Collections.unmodifiableList(projects);
+    }
+
+   /**
+    * Sets the current projects. This is only supposed to be called by internal gradle classes.
+    * @param newProjects
+    */
+    public void setProjects(final List<ProjectView> newProjects) {
+        projects.clear();
+        if (newProjects != null) {
+           projects.addAll(newProjects);
+        }
+
+      generalObserverLord.notifyObservers(new ObserverLord.ObserverNotification<GeneralPluginObserver>() {
+            public void notify(GeneralPluginObserver observer) {
+                observer.projectsAndTasksReloaded(newProjects != null);
+            }
+        });
+    }
+
+   public interface GeneralPluginObserver {
+        /**
+        * Notification that we're about to reload the projects and tasks.
+        */
+        public void startingProjectsAndTasksReload();
+
+        /**
+         * Notification that the projects and tasks have been reloaded. You may want
+         * to repopulate or update your views.
+         * @param wasSuccessful true if they were successfully reloaded. False if an error occurred so we no longer can show the projects and tasks (probably an error in a .gradle file).
+        */
+        public void projectsAndTasksReloaded(boolean wasSuccessful);
+    }
+
+   public interface RequestObserver
+   {
+      public void executionRequestAdded( ExecutionRequest request );
+
+      public void refreshRequestAdded( RefreshTaskListRequest request );
+
+        /**
+         * Notification that a command is about to be executed. This is mostly useful
+         * for IDE's that may need to save their files.
+        */
+        public void aboutToExecuteRequest( Request request );
+
+      /**
+       * Notification that the command has completed execution.
+       * @param request the original request containing the command that was executed
+       * @param result the result of the command
+       * @param output the output from gradle executing the command
+       */
+      public void requestExecutionComplete( Request request, int result, String output );
+   }
+
+   public interface SettingsObserver {
+
+       /**
+        * Notification that some settings have changed for the plugin. Settings such as current directory, gradle home
+        * directory, etc. This is useful for UIs that need to update their UIs when this is changed by other means.
+        */
+       public void settingsChanged();
+   }
+
+
+
+    public GradlePluginLord() {
+        favoritesEditor = new FavoritesEditor();
+
+        //create the queue that executes the commands. The contents of this interaction are where we actually launch gradle.
+        executionQueue = new ExecutionQueue<Request>(new ExecutionQueueInteraction());
+
+        currentDirectory = new File(System.getProperty("user.dir"));
+
+        String gradleHomeProperty = System.getProperty("gradle.home");
+        if (gradleHomeProperty != null) {
+           gradleHomeDirectory = new File(gradleHomeProperty);
+        }
+        else {
+           gradleHomeDirectory = new DefaultClassPathProvider().getGradleHome();
+        }
+    }
+
+
+    public File getGradleHomeDirectory() {
+        return gradleHomeDirectory;
+    }
+
+   /**
+    * sets the gradle home directory. You can't just set this here. You must also set the "gradle.home" system property.
+    * This code could do this for you, but at this time, I didn't want this to have side effects and setting "gradle.home"
+    * can affect other things and there may be some timing issues.
+    * @param gradleHomeDirectory the new home directory
+    */
+    public void setGradleHomeDirectory(File gradleHomeDirectory) {
+        if( areEqual( this.gradleHomeDirectory, gradleHomeDirectory ) )    //already set to this. This prevents recursive notifications.
+        {
+            return;
+        }
+       this.gradleHomeDirectory = gradleHomeDirectory;
+        notifySettingsChanged();
+    }
+
+    /**
+    * @return the root directory of your gradle project.
+    */
+    public File getCurrentDirectory() {
+        return currentDirectory;
+    }
+
+    /**
+    * @param  currentDirectory the new root directory of your gradle project.
+    * @returns true if we changed the current directory, false if not (it was already set to this)
+    */
+    public boolean setCurrentDirectory(File currentDirectory) {
+        if( areEqual( this.currentDirectory, currentDirectory ) )    //already set to this. This prevents recursive notifications.
+        {
+            return false;
+        }
+        this.currentDirectory = currentDirectory;
+        notifySettingsChanged();
+        return true;
+    }
+
+    public File getCustomGradleExecutor() {
+        return customGradleExecutor;
+    }
+
+    public boolean setCustomGradleExecutor(File customGradleExecutor) {
+        if( areEqual( this.customGradleExecutor, customGradleExecutor ) )    //already set to this. This prevents recursive notifications.
+        {
+            return false;
+        }
+        this.customGradleExecutor = customGradleExecutor;
+        notifySettingsChanged();
+        return true;
+    }
+
+
+    public FavoritesEditor getFavoritesEditor() {
+        return favoritesEditor;
+    }
+
+   /**
+    * this allows you to change how much information is given when an error occurs.
+    */
+    public void setStackTraceLevel(StartParameter.ShowStacktrace stackTraceLevel) {
+        if( areEqual( this.stackTraceLevel, stackTraceLevel ) )    //already set to this. This prevents recursive notifications.
+        {
+            return;
+        }
+       this.stackTraceLevel = stackTraceLevel;
+        notifySettingsChanged();
+    }
+
+    public StartParameter.ShowStacktrace getStackTraceLevel() {
+        return stackTraceLevel;
+    }
+
+    public LogLevel getLogLevel() {
+        return logLevel;
+    }
+
+    public void setLogLevel(LogLevel logLevel) {
+        if (logLevel == null) {
+           return;
+        }
+
+       if( areEqual( this.logLevel, logLevel ) )    //already set to this. This prevents recursive notifications.
+       {
+           return;
+       }
+       this.logLevel = logLevel;
+       notifySettingsChanged();
+    }
+
+    /**
+     * Call this to start execution. This is done after you've initialized everything.
+    */
+    public void startExecutionQueue() {
+        isStarted = true;
+    }
+
+
+    /**
+      * This gives requests of the queue and then executes them by kicking off gradle
+      * in a separate process. Most of the code here is tedious setup code needed to
+      * start the server. The server is what starts gradle and opens a socket for
+      * interprocess communication so we can receive messages back from gradle.
+    */
+    private class ExecutionQueueInteraction implements ExecutionQueue.ExecutionInteraction<Request> {
+        /**
+         * When this is called, execute the given request.
+         *
+         * @param  request    the request to execute.
+        */
+        public void execute( final Request request) {
+
+            //mark this request as being currently executed
+            currentlyExecutingRequests.add( request );
+
+            notifyAboutToExecuteRequest( request );
+
+           //I'm just putting these in temp variables for easier debugging
+            File currentDirectory = getCurrentDirectory();
+            File gradleHomeDirectory = getGradleHomeDirectory();
+            File customGradleExecutor = getCustomGradleExecutor();
+
+            //the protocol handles the command line to launch gradle and messaging between us and said externally launched gradle.
+            ProcessLauncherServer.Protocol serverProtocol = request.createServerProtocol(logLevel, stackTraceLevel, currentDirectory, gradleHomeDirectory, customGradleExecutor);
+
+            //the server kicks off gradle as an external process and manages the communication with said process
+            ProcessLauncherServer server = new ProcessLauncherServer(serverProtocol);
+            request.setProcessLauncherServer(server);
+
+            //we need to know when this command is finished executing so we can mark it as complete and notify any observers.
+            server.addServerObserver( new ProcessLauncherServer.ServerObserver()
+            {
+               public void clientExited( int result, String output )
+               {
+                  currentlyExecutingRequests.remove( request );
+                  notifyRequestExecutionComplete( request, result, output );
+               }
+
+               public void serverExited() { }
+            }, false );
+
+            server.start();
+        }
+    }
+
+    private void notifyAboutToExecuteRequest( final Request request )
+    {
+      requestObserverLord.notifyObservers( new ObserverLord.ObserverNotification<RequestObserver>()
+      {
+          public void notify( RequestObserver observer )
+          {
+             try { //wrap this in a try/catch block so execeptions in the observer doesn't stop everything
+                observer.aboutToExecuteRequest( request );
+             }
+             catch( Exception e )
+             {
+                logger.error( "notifying aboutToExecuteCommand() " + e.getMessage() );
+             }
+          }
+       } );
+    }
+
+   private void notifyRequestExecutionComplete( final Request request, final int result, final String output )
+   {
+      requestObserverLord.notifyObservers( new ObserverLord.ObserverNotification<RequestObserver>()
+      {
+          public void notify( RequestObserver observer )
+          {
+             try { //wrap this in a try/catch block so execeptions in the observer doesn't stop everything
+                observer.requestExecutionComplete( request, result, output );
+             }
+             catch( Exception e )
+             {
+                logger.error( "notifying requestExecutionComplete() " + e.getMessage() );
+             }
+          }
+       } );
+   }
+
+    /**
+     * Adds an observer for various events. See PluginObserver.
+     *
+     * @param  observer     your observer
+     * @param  inEventQueue true if you want to be notified in the Event Dispatch Thread.
+    */
+    public void addGeneralPluginObserver(GeneralPluginObserver observer, boolean inEventQueue) {
+        generalObserverLord.addObserver(observer, inEventQueue);
+    }
+
+    public void removeGeneralPluginObserver(GeneralPluginObserver observer) {
+        generalObserverLord.removeObserver(observer);
+    }
+
+    public void addRequestObserver( RequestObserver observer, boolean inEventQueue )
+    {
+       requestObserverLord.addObserver( observer, inEventQueue );
+    }
+
+    public void removeRequestObserver( RequestObserver observer )
+    {
+       requestObserverLord.removeObserver( observer );
+    }
+
+    public void addSettingsObserver( SettingsObserver observer, boolean inEventQueue )
+    {
+       settingsObserverLord.addObserver( observer, inEventQueue );
+    }
+
+    public void removeSettingsObserver( SettingsObserver observer )
+    {
+       settingsObserverLord.removeObserver( observer );
+    }
+
+    private void notifySettingsChanged() {
+        settingsObserverLord.notifyObservers(new ObserverLord.ObserverNotification<SettingsObserver>() {
+            public void notify(SettingsObserver observer) {
+                observer.settingsChanged();
+            }
+        });
+    }
+
+    /**
+     * Determines if two are objects are equal and considers them both being null as equal
+     * @param object1 the first object
+     * @param object2 the second object
+     * @return true if they're both null or both equal.
+     */
+   private boolean areEqual( Object object1, Object object2)
+   {
+      if ( object1 == null || object2 == null )
+      {
+         return object2 == object1; //yes, we're not using '.equals', we're making sure they both equal null because one of them is null!
+      }
+
+      return object1.equals(object2);
+   }
+
+    /**
+     * Determines if all required setup is complete based on the current settings.
+     *
+     * @return true if a setup is complete, false if not.
+    */
+    public boolean isSetupComplete() {
+        //return gradleWrapper.getGradleHomeDirectory() != null &&
+        //       gradleWrapper.getGradleHomeDirectory().exists() &&
+        return getCurrentDirectory() != null &&
+                getCurrentDirectory().exists();
+    }
+
+   public Request addExecutionRequestToQueue( String fullCommandLine, String displayName )
+   {
+      return addExecutionRequestToQueue( fullCommandLine, displayName, false );
+   }
+
+   /**
+     * This executes a task in a background thread. This creates or uses
+     * an existing OutputPanel to display the results.
+     *
+     * @param  task       the task to execute.
+     * @param forceOutputToBeShown overrides the user setting onlyShowOutputOnErrors so that the output is shown regardless
+    */
+    public Request addExecutionRequestToQueue(final TaskView task, boolean forceOutputToBeShown, String... additionCommandLineOptions)
+    {
+        if( task == null ) {
+           return null;
+        }
+
+       String fullCommandLine = CommandLineAssistant.appendAdditionalCommandLineOptions(task, additionCommandLineOptions);
+        return addExecutionRequestToQueue(fullCommandLine, task.getFullTaskName(), forceOutputToBeShown );
+    }
+
+   /**
+    * This executes all the tasks together in a background thread. That is, all tasks
+    * are passed to a single gradle call at once. This creates or uses an existing
+    * OutputPanel to display the results.
+    *
+    * @param tasks the tasks to execute
+    * @param forceOutputToBeShown overrides the user setting onlyShowOutputOnErrors so that the output is shown regardless
+    * @param additionCommandLineOptions additional command line options to exeucte.
+    */
+    public Request addExecutionRequestToQueue(final List<TaskView> tasks, boolean forceOutputToBeShown, String... additionCommandLineOptions) {
+
+      if( tasks == null || tasks.isEmpty() ) {
+         return null;
+      }
+
+      if( tasks.size() == 1 ) { //if there's only 1, just treat it as one
+         return addExecutionRequestToQueue( tasks.get( 0 ), forceOutputToBeShown, additionCommandLineOptions );
+      }
+
+      String singleCommandLine = CommandLineAssistant.combineTasks( tasks, additionCommandLineOptions );
+      return addExecutionRequestToQueue(singleCommandLine, tasks.get( 0 ).getName() + "...", forceOutputToBeShown );
+    }
+
+    /**
+     * Executes several favorites commands at once as a single command. This has the affect
+     * of simply concatenating all the favorite command lines into a single line.
+     * @param favorites a list of favorites. If just one favorite, it executes it normally.
+     *                  If multiple favorites, it executes them all at once as a single command.
+     */
+    public Request addExecutionRequestToQueue(List<FavoriteTask> favorites) {
+        if( favorites.isEmpty() ) {
+            return null;
+        }
+
+        FavoriteTask firstFavoriteTask = favorites.get( 0 );
+        String displayName;
+        String fullCommandLine;
+        boolean alwaysShowOutput = firstFavoriteTask.alwaysShowOutput();
+
+        if( favorites.size() == 1 )
+        {
+            displayName = firstFavoriteTask.getDisplayName();
+            fullCommandLine = firstFavoriteTask.getFullCommandLine();
+        }
+        else
+        {
+            displayName = "Multiple (" + firstFavoriteTask.getDisplayName() + ", ... )";
+            fullCommandLine = FavoritesEditor.combineFavoriteCommandLines(favorites);
+        }
+
+        return addExecutionRequestToQueue( fullCommandLine, displayName, alwaysShowOutput );
+    }
+
+   /**
+    * Call this to execute a task in a background thread. This creates or uses
+    * an existing OutputPanel to display the results. This version takes text
+    * instead of a task object.
+    *
+    * @param  fullCommandLine the full command line to pass to gradle.
+    * @param  displayName     what we show on the tab.
+    * @param forceOutputToBeShown overrides the user setting onlyShowOutputOnErrors so that the output is shown regardless
+    */
+    public Request addExecutionRequestToQueue(String fullCommandLine, String displayName, boolean forceOutputToBeShown) {
+        if (!isStarted) {
+           return null;
+        }
+
+      if (fullCommandLine == null){
+           return null;
+        }
+
+      //here we'll give the UI a chance to add things to the command line.
+        fullCommandLine = alterCommandLine(fullCommandLine);
+
+        final ExecutionRequest request = new ExecutionRequest( getNextRequestID(), fullCommandLine, displayName, forceOutputToBeShown, executionQueue );
+        executionQueue.addRequestToQueue(request);
+        requestObserverLord.notifyObservers( new ObserverLord.ObserverNotification<RequestObserver>()
+        {
+           public void notify( RequestObserver observer )
+           {
+              observer.executionRequestAdded( request );
+           }
+        } );
+        return request;
+    }
+
+    private synchronized long getNextRequestID()
+    {
+       return nextRequestID++;
+    }
+
+    /**
+     * This will refresh the project/task tree.
+     * @return the Request that was created. Null if no request created.
+     */
+    public Request addRefreshRequestToQueue() {
+        return addRefreshRequestToQueue( null );
+    }
+
+    /**
+     * This will refresh the project/task tree. This version allows you to specify additional
+     * arguments to be passed to gradle during the refresh (such as -b to specify a build file)
+     * @param additionalCommandLineArguments the arguments to add, or null if none.
+     * @return the Request that was created. Null if no request created.
+     */
+    public Request addRefreshRequestToQueue( String additionalCommandLineArguments ) {
+        if (!isStarted){
+           return null;
+        }
+
+       if( hasRequestOfType( RefreshTaskListRequest.TYPE ) )
+       {
+          return null; //we're already doing a refresh.
+       }
+
+       //we'll request a task list since there is no way to do a no op. We're not really interested
+        //in what's being executed, just the ability to get the task list (which must be populated as
+        //part of executing anything).
+        String fullCommandLine = '-' + DefaultCommandLine2StartParameterConverter.TASKS;
+
+        if( additionalCommandLineArguments != null )
+        {
+            fullCommandLine += ' ' + additionalCommandLineArguments;
+        }
+
+        //here we'll give the UI a chance to add things to the command line.
+        fullCommandLine = alterCommandLine(fullCommandLine);
+
+        final RefreshTaskListRequest request = new RefreshTaskListRequest( getNextRequestID(), fullCommandLine, executionQueue, this);
+        executionQueue.addRequestToQueue(request);
+        requestObserverLord.notifyObservers( new ObserverLord.ObserverNotification<RequestObserver>()
+        {
+           public void notify( RequestObserver observer )
+           {
+              observer.refreshRequestAdded( request );
+           }
+        } );
+        return request;
+    }
+
+    /**
+     * This is where we notify listeners and give them a chance to add things to the command line.
+     *
+     * @param  fullCommandLine the full command line
+     *
+     * @return the new command line.
+    */
+    private String alterCommandLine(String fullCommandLine) {
+        CommandLineArgumentAlteringNotification notification = new CommandLineArgumentAlteringNotification(fullCommandLine);
+        commandLineArgumentObserverLord.notifyObservers(notification);
+
+        return notification.getFullCommandLine();
+    }
+
+
+    //
+    /**
+     * This class notifies the listeners and modifies the command line by adding
+     * additional commands to it. Each listener will be given the 'new' full command
+     * line, so the order you add things becomes important.
+    */
+    private class CommandLineArgumentAlteringNotification implements ObserverLord.ObserverNotification<CommandLineArgumentAlteringListener> {
+        private StringBuilder fullCommandLineBuilder;
+
+        private CommandLineArgumentAlteringNotification(String fullCommandLine) {
+            this.fullCommandLineBuilder = new StringBuilder(fullCommandLine);
+        }
+
+        public void notify(CommandLineArgumentAlteringListener observer) {
+            String additions = observer.getAdditionalCommandLineArguments(fullCommandLineBuilder.toString());
+            if (additions != null) {
+               fullCommandLineBuilder.append(' ').append(additions);
+            }
+        }
+
+        public String getFullCommandLine() {
+            return fullCommandLineBuilder.toString();
+        }
+    }
+
+
+    /**
+     * This allows you to add a listener that can add additional command line
+     * arguments whenever gradle is executed. This is useful if you've customized
+     * your gradle build and need to specify, for example, an init script.
+     *
+     * param  listener   the listener that modifies the command line arguments.
+    */
+    public void addCommandLineArgumentAlteringListener(CommandLineArgumentAlteringListener listener) {
+        commandLineArgumentObserverLord.addObserver(listener, false);
+    }
+
+    public void removeCommandLineArgumentAlteringListener(CommandLineArgumentAlteringListener listener) {
+        commandLineArgumentObserverLord.removeObserver(listener);
+    }
+
+   /**
+    * This code was copied from BuildExceptionReporter.reportBuildFailure in gradle's source, then modified slightly
+    * to compensate for the fact that we're not driven by options or logging things to a logger object.
+    */
+    public static String getGradleExceptionMessage(Throwable failure, StartParameter.ShowStacktrace stackTraceLevel) {
+        if (failure == null) {
+           return "";
+        }
+
+      Formatter formatter = new Formatter();
+
+        formatter.format("%nBuild failed.%n");
+
+        if (stackTraceLevel == StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS) {
+           formatter.format("Use the stack trace options to get more details.");
+        }
+
+      if (failure != null) {
+            formatter.format("%n");
+
+            if (failure instanceof LocationAwareException) {
+                LocationAwareException scriptException = (LocationAwareException) failure;
+                formatter.format("%s%n%n", scriptException.getLocation());
+                formatter.format("%s", scriptException.getOriginalMessage());
+
+                for (Throwable cause : scriptException.getReportableCauses()) {
+                    formatter.format("%nCause: %s", getMessage(cause));
+                }
+            } else {
+                formatter.format("%s", getMessage(failure));
+            }
+
+            if (stackTraceLevel != StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS) {
+                formatter.format("%n%nException is:\n");
+                if (stackTraceLevel == StartParameter.ShowStacktrace.ALWAYS_FULL) {
+                   return formatter.toString() + getStackTraceAsText(failure);
+                }
+
+               return formatter.toString() + getStackTraceAsText(StackTraceUtils.deepSanitize(failure));
+            }
+        }
+
+        return formatter.toString();
+    }
+
+    private static String getStackTraceAsText(Throwable t) {
+        StringBuilder builder = new StringBuilder();
+        StackTraceElement[] stackTraceElements = t.getStackTrace();
+
+        for (int index = 0; index < stackTraceElements.length; index++) {
+            StackTraceElement stackTraceElement = stackTraceElements[index];
+            builder.append("   ").append(stackTraceElement.toString()).append('\n');
+        }
+
+        return builder.toString();
+    }
+
+    //tries to get a message from a Throwable. Something there's a message and sometimes there's not.
+    private static String getMessage(Throwable throwable) {
+        String message = throwable.getMessage();
+        if (!GUtil.isTrue(message)){
+           message = String.format("%s (no error message)", throwable.getClass().getName());
+        }
+
+       if (throwable.getCause() != null){
+           message += "\nCaused by: " + getMessage(throwable.getCause());
+        }
+
+       return message;
+    }
+
+   /**
+    * Determines if there are tasks executing or waiting to execute.
+    * We only care about execution requests, not refresh requests.
+    * @return true if this is busy, false if not.
+    */
+   public boolean isBusy()
+   {
+      return hasRequestOfType( ExecutionRequest.TYPE );
+   }
+
+   /**
+    Determines if we have an request of the specified type
+    @param type the sought type of request.
+    @return true if it has the request, false if not.
+    */
+   private boolean hasRequestOfType( Request.Type type )
+   {
+      Iterator<Request> iterator = currentlyExecutingRequests.iterator();
+      while( iterator.hasNext() )
+      {
+         ExecutionQueue.Request request = iterator.next();
+         if( request.getType() == type )
+         {
+            return true;
+         }
+      }
+
+      List<Request> pendingRequests = executionQueue.getRequests();
+      iterator = pendingRequests.iterator();
+      while( iterator.hasNext() )
+      {
+         ExecutionQueue.Request request = iterator.next();
+         if( request.getType() == type )
+         {
+            return true;
+         }
+      }
+
+      return false;
+   }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoriteTask.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoriteTask.java
new file mode 100644
index 0000000..a76065d
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoriteTask.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.gradleplugin.foundation.favorites;
+
+/**
+ * This holds onto a favorite task. Note: a user may define a favorite task, but it may or may be not present (or may be
+ * hidden because of a temporary error in gradle files). So we hold onto the full name of the task so we can get to the
+ * task should it become available.
+ *
+ * @author mhunsicker
+ */
+public class FavoriteTask {
+    private String fullCommandLine;
+    private String displayName;
+    private boolean alwaysShowOutput;
+
+    public FavoriteTask(String fullCommandLine, String displayName, boolean alwaysShowOutput) {
+        this.fullCommandLine = fullCommandLine;
+        this.displayName = displayName;
+        this.alwaysShowOutput = alwaysShowOutput;
+    }
+
+    public String toString() {
+        return displayName;
+    }
+
+    public String getFullCommandLine() {
+        return fullCommandLine;
+    }
+
+    //if you're wanting to set this, go through the FavoritesEditor.
+    /*package*/ void setFullCommandLine(String fullCommandLine) {
+        this.fullCommandLine = fullCommandLine;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    //if you're wanting to set this, go through the FavoritesEditor.
+    /*package*/ void setDisplayName(String displayName) {
+        this.displayName = displayName;
+    }
+
+    public boolean alwaysShowOutput() {
+        return alwaysShowOutput;
+    }
+
+    //if you're wanting to set this, go through the FavoritesEditor.
+    /*package*/ void setAlwaysShowOutput(boolean alwaysShowOutput) {
+        this.alwaysShowOutput = alwaysShowOutput;
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoritesEditor.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoritesEditor.java
new file mode 100644
index 0000000..f44c396
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoritesEditor.java
@@ -0,0 +1,504 @@
+/*
+ * 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.gradleplugin.foundation.favorites;
+
+import org.gradle.foundation.CommandLineAssistant;
+import org.gradle.foundation.TaskView;
+import org.gradle.foundation.common.ObserverLord;
+import org.gradle.foundation.common.ReorderableList;
+import org.gradle.gradleplugin.foundation.DOM4JSerializer;
+import org.gradle.gradleplugin.foundation.ExtensionFileFilter;
+import org.gradle.gradleplugin.foundation.settings.SettingsNode;
+import org.gradle.gradleplugin.foundation.settings.SettingsSerializable;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This holds onto and edits favorite tasks. 'Favorite tasks' provides a quick way to run frequently used tasks.
+ *
+ * @author mhunsicker
+ */
+public class FavoritesEditor implements SettingsSerializable {
+    private ReorderableList<FavoriteTask> favoriteTasks = new ReorderableList<FavoriteTask>();
+
+    private ObserverLord<FavoriteTasksObserver> favoriteTasksObserverLord = new ObserverLord<FavoriteTasksObserver>();
+
+    public interface FavoriteTasksObserver {
+        /**
+         * Notification that the favorites list has changed.
+         */
+        public void favoritesChanged();
+
+        /**
+         * Notification that the favorites were re-ordered
+         *
+         * @param favoritesReordered the favorites that were reordered
+         */
+        public void favoritesReordered(List<FavoriteTask> favoritesReordered);
+    }
+
+    public FavoritesEditor() {
+    }
+
+    public List<FavoriteTask> getFavoriteTasks() {
+        return Collections.unmodifiableList(favoriteTasks);
+    }
+
+    public void addFavoriteTasksObserver(FavoritesEditor.FavoriteTasksObserver observer, boolean inEventQueue) {
+        favoriteTasksObserverLord.addObserver(observer, inEventQueue);
+    }
+
+    public void removeFavoriteTasksObserver(FavoritesEditor.FavoriteTasksObserver observer) {
+        favoriteTasksObserverLord.removeObserver(observer);
+    }
+
+    public FavoriteTask getFavorite(String fullCommandLine) {
+        Iterator<FavoriteTask> taskIterator = favoriteTasks.iterator();
+        while (taskIterator.hasNext()) {
+            FavoriteTask favoriteTask = taskIterator.next();
+
+            if (fullCommandLine.equals(favoriteTask.getFullCommandLine())) {
+                return favoriteTask;
+            }
+        }
+
+        return null;
+    }
+
+    public FavoriteTask getFavoriteByDisplayName(String displayName) {
+        Iterator<FavoriteTask> taskIterator = favoriteTasks.iterator();
+        while (taskIterator.hasNext()) {
+            FavoriteTask favoriteTask = taskIterator.next();
+
+            if (displayName.equals(favoriteTask.getDisplayName())) {
+                return favoriteTask;
+            }
+        }
+
+        return null;
+    }
+
+    public FavoriteTask getFavorite(TaskView task) {
+        return getFavorite(task.getFullTaskName());
+    }
+
+    /**
+     * Fires off a notification that the favorite tasks have changed.
+     */
+    private void notifyFavoritesChanged() {
+        favoriteTasksObserverLord.notifyObservers(new ObserverLord.ObserverNotification<FavoriteTasksObserver>() {
+            public void notify(FavoriteTasksObserver observer) {
+                observer.favoritesChanged();
+            }
+        });
+    }
+
+    //
+
+    public enum AddMultipleResult {
+        AddSeparately, AddAsSingleCommand, Cancel
+    }
+
+    ;
+
+    /**
+
+     */
+    public interface AddMultipleFavoritesInteraction {
+        /**
+         * This is called when you try to add multiple tasks at once. You should prompt the user whether or not they
+         * want to add each task separately or together as one task with multiple commands.
+         *
+         * @param tasksSample the individual tasks we will add
+         * @param singleCommandSample a sample of what the single command would look like
+         * @return where or not to combine tasks into a single command line, false to add them as separate commands.
+         *         See AddMultiple.
+         */
+        public AddMultipleResult promptUserToCombineTasks(List<TaskView> tasksSample, String singleCommandSample);
+    }
+
+    /**
+     * Call this to add tasks as favorites. If you pass in multiple tasks, we'll prompt the user whether or not the tasks
+     * should be added as one favorite with multiple tasks or separate tasks.
+     *
+     * @param tasks the tasks to add. Their order in the list becomes the order in the single task if the user chooses
+     * this.
+     * @param interaction how we interact with the user or other UI.
+     */
+    public void addMutlipleFavorites(List<TaskView> tasks, boolean alwaysShowOutput,
+                                     AddMultipleFavoritesInteraction interaction) {
+        if (tasks.isEmpty()) {
+            return;
+        }  //no tasks, bail
+
+        if (tasks.size() == 1) {  //only 1 task. just add it
+            addFavorite(tasks.get(0), alwaysShowOutput);
+            return;
+        }
+
+        //multiple tasks. Ask the user what to do
+        String singleCommandLine = CommandLineAssistant.combineTasks(tasks);
+
+        //prompt the user what to do
+        AddMultipleResult addMultipleResult = interaction.promptUserToCombineTasks(tasks, singleCommandLine);
+        switch (addMultipleResult) {
+            case Cancel:
+                return;
+
+            case AddSeparately:
+                Iterator<TaskView> iterator = tasks.iterator();
+                while (iterator.hasNext()) {
+                    TaskView task = iterator.next();
+                    addFavorite(task, alwaysShowOutput);
+                }
+                break;
+
+            case AddAsSingleCommand:
+                addFavorite(singleCommandLine, alwaysShowOutput);
+                break;
+        }
+    }
+
+    public FavoriteTask addFavorite(TaskView task, boolean alwaysShowOutput) {
+        return addFavorite(task.getFullTaskName(), alwaysShowOutput);
+    }
+
+    public FavoriteTask addFavorite(String fullCommandLine, boolean alwaysShowOutput) {
+        FavoriteTask favorite = addFavorite(fullCommandLine, fullCommandLine, alwaysShowOutput);
+        if (favorite != null) {
+            notifyFavoritesChanged();
+        }
+
+        return favorite;
+    }
+
+    public FavoriteTask addFavorite(String fullCommandLine, String displayName, boolean alwaysShowOutput) {
+        if( ( fullCommandLine == null || fullCommandLine.trim().equals( "" ) ) &&
+            ( displayName == null || displayName.trim().equals( "" ) ) )    //don't allow someone to add a blank favorite.
+        {
+            return null;
+        }
+
+        FavoriteTask favoriteTask = new FavoriteTask(fullCommandLine, displayName, alwaysShowOutput);
+        favoriteTasks.add(favoriteTask);
+        return favoriteTask;
+    }
+
+    /**
+     * Call this to add the specified tasks as favorite tasks
+     *
+     * @param tasks the task to make a favorite.
+     */
+    public void addFavorites(List<TaskView> tasks, boolean alwaysShowOutput) {
+        boolean addedFavorite = false;
+
+        Iterator<TaskView> iterator = tasks.iterator();
+        while (iterator.hasNext()) {
+            TaskView task = iterator.next();
+            String fullTaskName = task.getFullTaskName();
+            if (this.addFavorite(fullTaskName, alwaysShowOutput) != null) {
+                addedFavorite = true;
+            }
+        }
+
+        if (addedFavorite)  //don't notify anyone unless we actually did something.
+        {
+            favoriteTasksObserverLord.notifyObservers(
+                    new ObserverLord.ObserverNotification<FavoritesEditor.FavoriteTasksObserver>() {
+                        public void notify(FavoritesEditor.FavoriteTasksObserver observer) {
+                            observer.favoritesChanged();
+                        }
+                    });
+        }
+    }
+
+    /**
+     * Call this to add a favorite that isn't in the task list. This exists because you can add functionality to gradle
+     * that isn't really a task.
+     *
+     * @param addFavoriteInteraction allows us to interact with the user
+     * @return the favorite that was added, or null if the user canceled
+     */
+    public FavoriteTask addFavorite(EditFavoriteInteraction addFavoriteInteraction) {
+        FavoriteTask newFavorite = new FavoriteTask("", "", false);
+        if (!editInternal(newFavorite, addFavoriteInteraction)) {
+            return null;
+        }
+
+        favoriteTasks.add(newFavorite);
+
+        notifyFavoritesChanged();
+        return newFavorite;
+    }
+
+    /**
+     * This is what you actually edit when you want to edit a favorite. I wanted the FavoriteTask object to be immutable
+     * so the only way to edit it is via this editor. This way any necessary validation or notification will always be
+     * performed
+     */
+    public class EditibleFavoriteTask {
+        public String fullCommandLine;
+        public String displayName;
+        public boolean alwaysShowOutput;
+
+        public EditibleFavoriteTask(FavoriteTask favoriteTask) {
+            this(favoriteTask.getFullCommandLine(), favoriteTask.getDisplayName(), favoriteTask.alwaysShowOutput());
+        }
+
+        public EditibleFavoriteTask(String fullCommandLine, String displayName, boolean alwaysShowOutput) {
+            this.fullCommandLine = fullCommandLine;
+            this.displayName = displayName;
+            this.alwaysShowOutput = alwaysShowOutput;
+        }
+    }
+
+    public interface EditFavoriteInteraction extends ValidationInteraction {
+        public boolean editFavorite(EditibleFavoriteTask favoriteTask);
+    }
+
+    /**
+     * Edits the specified favorite task.
+     *
+     * @param favoriteTask the task to edit.
+     * @param editFavoriteInteraction how we interact with the user
+     * @return true if we made changes, false if not.
+     */
+    public boolean editFavorite(FavoriteTask favoriteTask, EditFavoriteInteraction editFavoriteInteraction) {
+        if (favoriteTask == null) {
+            return false;
+        }
+
+        if (favoriteTasks.indexOf(favoriteTask) == -1) {
+            return false;
+        }  //not our favorite
+
+        if (!editInternal(favoriteTask, editFavoriteInteraction)) {
+            return false;
+        }
+
+        notifyFavoritesChanged();
+        return true;
+    }
+
+    /**
+     * This edits the specified favorite task. We create a EditableFavoriteTask so the user can trash it and cancel and
+     * it won't affect the original. We'll sit in a loop prompting the user to edit it until no errors exist then we'll
+     * set the values on the original task.
+     *
+     * @param favoriteTask the task to edit.
+     * @param editFavoriteInteraction how we interact with the user.
+     * @return true if we edited it, false if not (the user canceled).
+     */
+    private boolean editInternal(FavoriteTask favoriteTask, EditFavoriteInteraction editFavoriteInteraction) {
+        EditibleFavoriteTask workingCopy = new EditibleFavoriteTask(favoriteTask);
+        boolean isValid = true;
+        do {
+            if (!editFavoriteInteraction.editFavorite(workingCopy)) {
+                return false;
+            }
+
+            isValid = validateEditableFavoriteTask(workingCopy, favoriteTask, editFavoriteInteraction);
+        } while (!isValid);
+
+        favoriteTask.setFullCommandLine(workingCopy.fullCommandLine);
+
+        favoriteTask.setDisplayName(workingCopy.displayName);
+        favoriteTask.setAlwaysShowOutput(workingCopy.alwaysShowOutput);
+
+        return true;
+    }
+
+    public interface ValidationInteraction {
+        public void reportError(String error);
+    }
+
+    /**
+     * This validates the editable favorite task. It makes sure the task name is specified and that it's not a
+     * duplicate.
+     *
+     * @param editableFavoriteTask the task your editing.
+     * @param originalFavoriteTaskObject the original object. This is used to test for duplication. If its new and not
+     * in the favorites, that's OK.
+     * @param validationInteraction how we report errors to the user.
+     * @return true if the task is valid, false if not.
+     */
+    private boolean validateEditableFavoriteTask(EditibleFavoriteTask editibleFavoriteTask,
+                                                 FavoriteTask originalFavoriteTaskObject,
+                                                 ValidationInteraction validationInteraction) {
+        if (editibleFavoriteTask.fullCommandLine == null || editibleFavoriteTask.fullCommandLine.trim().equals("")) {
+            validationInteraction.reportError("Full task name must be specified");
+            return false;
+        }
+
+        if (editibleFavoriteTask.displayName == null || editibleFavoriteTask.displayName.trim().equals("")) {
+            validationInteraction.reportError("Display name must be specified");
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Call this to remove the specified favorites from the favorite tasks.
+     *
+     * @param favoritesToRemove the favorite tasks to remove
+     */
+    public void removeFavorites(List<FavoriteTask> favoritesToRemove) {
+        if (favoriteTasks.removeAll(favoritesToRemove)) {
+            notifyFavoritesChanged();
+        }
+    }
+
+    /**
+     * This moves the specified favorites up.
+     *
+     * @param favoritesToMove .
+     */
+    public void moveFavoritesBefore(List<FavoriteTask> favoritesToMove) {
+        moveFavorites(favoritesToMove, true);
+    }
+
+    public void moveFavoritesAfter(List<FavoriteTask> favoritesToMove) {
+        moveFavorites(favoritesToMove, false);
+    }
+
+    private void moveFavorites(final List<FavoriteTask> favoritesToMove, boolean moveBefore) {
+        if (moveBefore) {
+            favoriteTasks.moveBefore(favoritesToMove);
+        } else {
+            favoriteTasks.moveAfter(favoritesToMove);
+        }
+
+        favoriteTasksObserverLord.notifyObservers(new ObserverLord.ObserverNotification<FavoriteTasksObserver>() {
+            public void notify(FavoriteTasksObserver observer) {
+                observer.favoritesReordered(favoriteTasks);
+            }
+        });
+    }
+
+    /**
+     * Call this to save favorites to a file.
+     */
+    public void exportToFile(DOM4JSerializer.ExportInteraction exportInteraction) {
+        DOM4JSerializer.exportToFile("favorites", exportInteraction, createFileFilter(), this);
+    }
+
+    /**
+     * Call this to read favorites from a file. I'm going to use FavoritesSerializable rather than ourselves (even
+     * though we're a JDOMSerializable) so if something goes wrong, we won't wipe out our current settings.
+     */
+    public boolean importFromFile(DOM4JSerializer.ImportInteraction importInteraction) {
+        FavoritesSerializable serializable = new FavoritesSerializable();
+        if (!DOM4JSerializer.importFromFile(importInteraction, createFileFilter(), serializable)) {
+            return false;
+        }
+
+        //only if we succeed should we clear out the existing favorites
+        favoriteTasks.clear();
+        favoriteTasks.addAll(serializable.getFavorites());
+
+        notifyFavoritesChanged();
+
+        return true;
+    }
+
+    private ExtensionFileFilter createFileFilter() {
+        return new ExtensionFileFilter(".favorite-tasks", "Favorite Tasks");
+    }
+
+    /**
+     * Call this to saves the current settings.
+     *
+     * @param settings where you save the settings.
+     */
+    public void serializeOut(SettingsNode settings) {
+        FavoritesSerializable.serializeOut(settings, favoriteTasks);
+    }
+
+    /**
+     * Call this to read in this object's settings. The reverse of serializeOut.
+     *
+     * @param settings where you read your settings.
+     */
+    public void serializeIn(SettingsNode settings) {
+        FavoritesSerializable.serializeIn(settings, favoriteTasks);
+    }
+
+    /**
+     * This makes a copy of all the selected tasks.
+     * @param tasksToCopy the tasks to copy
+     */
+    public void duplicateFavorites(List<FavoriteTask> tasksToCopy) {
+        if( tasksToCopy == null || tasksToCopy.isEmpty() ) {
+            return;
+        }
+
+        Iterator<FavoriteTask> iterator = tasksToCopy.iterator();
+        while( iterator.hasNext() )
+        {
+           FavoriteTask taskToCopy = iterator.next();
+           FavoriteTask newFavoriteTask = new FavoriteTask( taskToCopy.getFullCommandLine(), taskToCopy.getDisplayName(), taskToCopy.alwaysShowOutput() );
+           favoriteTasks.add( newFavoriteTask );
+        }
+
+        notifyFavoritesChanged();
+    }
+
+    /**
+     * This makes a copy of the selected task.
+     * @param taskToCopy the task to copy
+     */
+    public FavoriteTask duplicateFavorite(FavoriteTask taskToCopy) {
+
+        if( taskToCopy == null ) {
+            return null;
+        }
+
+        FavoriteTask newFavoriteTask = new FavoriteTask( taskToCopy.getFullCommandLine(), taskToCopy.getDisplayName(), taskToCopy.alwaysShowOutput() );
+        favoriteTasks.add( newFavoriteTask );
+        notifyFavoritesChanged();
+        return newFavoriteTask;
+    }
+
+    /**
+     * This combines all the command lines of the favorites list into a single command line.
+     * This is useful for allowing a user to execute multiple favorites at once.
+     * Note: this doesn't do anything intelligent by trying to weed out duplicates.
+     * Gradle handles that nicely already.
+     * @param favoriteTasks the favorite tasks to combine
+     * @return a single String of all the command combined.
+     */
+    public static String combineFavoriteCommandLines( List<FavoriteTask> favoriteTasks )
+    {
+        StringBuilder builder = new StringBuilder();
+
+        Iterator<FavoriteTask> iterator = favoriteTasks.iterator();
+        while( iterator.hasNext() )
+        {
+           FavoriteTask favoriteTask = iterator.next();
+
+           builder.append( favoriteTask.getFullCommandLine() );
+           if( iterator.hasNext() )
+           {
+               builder.append( ' ' );
+           }
+        }
+
+        return builder.toString();
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoritesSerializable.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoritesSerializable.java
new file mode 100644
index 0000000..b69420e
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/favorites/FavoritesSerializable.java
@@ -0,0 +1,123 @@
+/*
+ * 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.gradleplugin.foundation.favorites;
+
+import org.gradle.gradleplugin.foundation.settings.SettingsNode;
+import org.gradle.gradleplugin.foundation.settings.SettingsSerializable;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Inner class that handles serializing favorites. You can either pass it a favorites list and serialize them out or use
+ * the default constructor and serialize it. This allows you to serialize a favorites list with or without an editor.
+ *
+ * @author mhunsicker
+ */
+class FavoritesSerializable implements SettingsSerializable {
+    private List<FavoriteTask> favorites;
+
+    private static final String FAVORITE_ELEMENT_TAG = "favorite";
+    private static final String FULL_COMMAND_LINE = "full-command-line";
+    private static final String DISPLAY_NAME = "display-name";
+    private static final String SHOW_OUTPUT = "show-output";
+    private static final String ROOT_TAG = "favorites";
+    private static final String FAVORITES_SIZE = "favorites-size";
+    private static final String FAVORITE_PREFIX = "favorite_";
+
+    //use this constructor if you're serializing OUT
+
+    public FavoritesSerializable(List<FavoriteTask> favorites) {
+        this.favorites = favorites;
+    }
+
+    //use this constructor if you're serialzing IN
+
+    public FavoritesSerializable() {
+        favorites = new ArrayList<FavoriteTask>();
+    }
+
+    //call this to get the favorites that were serilized in.
+
+    public List<FavoriteTask> getFavorites() {
+        return favorites;
+    }
+
+    /**
+     * Call this to saves the current settings.
+     *
+     * @param settings where you save the settings.
+     */
+    public void serializeOut(SettingsNode settings) {
+        serializeOut(settings, favorites);
+    }
+
+    public static void serializeOut(SettingsNode settings, List<FavoriteTask> favorites) {
+        SettingsNode rootNode = settings.addChildIfNotPresent(ROOT_TAG);
+        rootNode.removeAllChildren(); //clear out whatever may have already been there
+
+        Iterator<FavoriteTask> iterator = favorites.iterator();
+        while (iterator.hasNext()) {
+            FavoriteTask favoriteTask = iterator.next();
+
+            SettingsNode taskNode = rootNode.addChild(FAVORITE_ELEMENT_TAG);
+            taskNode.setValueOfChild(FULL_COMMAND_LINE, favoriteTask.getFullCommandLine());
+            taskNode.setValueOfChild(DISPLAY_NAME, favoriteTask.getDisplayName());
+            taskNode.setValueOfChildAsBoolean(SHOW_OUTPUT, favoriteTask.alwaysShowOutput());
+        }
+    }
+
+    /**
+     * Call this to read in this object's settings. The reverse of serializeOut.
+     *
+     * @param settings where you read your settings.
+     */
+    public void serializeIn(SettingsNode settings) {
+        serializeIn(settings, favorites);
+    }
+
+    public static void serializeIn(SettingsNode settings, List<FavoriteTask> favorites) {
+        favorites.clear();  //remove everything already there
+
+        SettingsNode rootElement = settings.getChildNode(ROOT_TAG);
+        if (rootElement == null) {
+            return;
+        }
+
+        Iterator<SettingsNode> iterator = rootElement.getChildNodes(FAVORITE_ELEMENT_TAG).iterator();
+        while (iterator.hasNext()) {
+            SettingsNode taskNode = iterator.next();
+
+            String fullCommandLine = taskNode.getValueOfChild(FULL_COMMAND_LINE, null);
+            if (fullCommandLine != null) {
+                String displayName = taskNode.getValueOfChild(DISPLAY_NAME, fullCommandLine);
+                boolean showOutput = taskNode.getValueOfChildAsBoolean(SHOW_OUTPUT, false);
+
+                addFavoriteTask(favorites, fullCommandLine, displayName, showOutput);
+            }
+        }
+    }
+
+    private static void addFavoriteTask(List<FavoriteTask> favorites, String fullCommandLine, String displayName,
+                                        boolean alwaysShowOutput) {
+        if (displayName == null) {
+            displayName = fullCommandLine;
+        }
+
+        favorites.add(new FavoriteTask(fullCommandLine, displayName, alwaysShowOutput));
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/filters/AllowAllProjectAndTaskFilter.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/filters/AllowAllProjectAndTaskFilter.java
new file mode 100644
index 0000000..4da8f88
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/filters/AllowAllProjectAndTaskFilter.java
@@ -0,0 +1,46 @@
+/*
+ * 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.gradleplugin.foundation.filters;
+
+import org.gradle.foundation.ProjectView;
+import org.gradle.foundation.TaskView;
+
+/**
+ * This filter actually doesn't filter. It allows everything through.
+ *
+ * @author mhunsicker
+ */
+public class AllowAllProjectAndTaskFilter implements ProjectAndTaskFilter {
+    /**
+     * Determines if the specified project should be allowed or not.
+     *
+     * @param project the project in question
+     * @return true to allow it, false not to.
+     */
+    public boolean doesAllowProject(ProjectView project) {
+        return true;
+    }
+
+    /**
+     * Determines if the specified task should be allowed or not.
+     *
+     * @param task the task in question
+     * @return true to allow it, false not to.
+     */
+    public boolean doesAllowTask(TaskView task) {
+        return true;
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/filters/BasicFilterEditor.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/filters/BasicFilterEditor.java
new file mode 100644
index 0000000..a714bfa
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/filters/BasicFilterEditor.java
@@ -0,0 +1,274 @@
+/*
+ * 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.gradleplugin.foundation.filters;
+
+import org.gradle.foundation.ProjectView;
+import org.gradle.foundation.TaskView;
+import org.gradle.foundation.common.ObserverLord;
+import org.gradle.gradleplugin.foundation.DOM4JSerializer;
+import org.gradle.gradleplugin.foundation.ExtensionFileFilter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+/*
+ This allows editing a BasicProjectAndTaskFilter. You don't work directly
+ with the BasicProjectAndTaskFilter to alter it. Go through this.
+ During the editing process, you often need to fire off notifications that
+ normally aren't required. This is where those notifications would come from.
+ This also has some extra validation that isn't present in the filter itself.
+
+ @author mhunsicker
+  */
+
+public class BasicFilterEditor implements ProjectAndTaskFilter {
+    private List<String> filteredOutProjectNames = new ArrayList<String>();
+    private List<String> filteredOutTaskNames = new ArrayList<String>();
+    private boolean filterOutTasksWithNoDescription;
+    private ObserverLord<FilterEditorObserver> observerLord = new ObserverLord<FilterEditorObserver>();
+
+    public interface FilterEditorObserver {
+        /**
+         * A generic notification that the filter has changed.
+         */
+        public void filterChanged();
+    }
+
+    public BasicFilterEditor() {
+
+    }
+
+    public BasicFilterEditor(BasicProjectAndTaskFilter filter) {
+        initializeFromFilter(filter);
+    }
+
+    public void initializeFromFilter(BasicProjectAndTaskFilter filter) {
+        filteredOutProjectNames.clear();
+        hideProjectsByName(filter.getFilteredOutProjectNames());
+
+        filteredOutTaskNames.clear();
+        hideTasksByName(filter.getFilteredOutTaskNames());
+
+        filterOutTasksWithNoDescription = filter.filterOutTasksWithNoDescription();
+        notifyChanges();
+    }
+
+    public void addFilterEditorObserver(FilterEditorObserver observer, boolean inEventQueue) {
+        observerLord.addObserver(observer, inEventQueue);
+    }
+
+    public void removeFilterEditorObserver(FilterEditorObserver observer) {
+        observerLord.removeObserver(observer);
+    }
+
+    public boolean filterOutTasksWithNoDescription() {
+        return filterOutTasksWithNoDescription;
+    }
+
+    public void setFilterOutTasksWithNoDescription(boolean filterOutTasksWithNoDescription) {
+        this.filterOutTasksWithNoDescription = filterOutTasksWithNoDescription;
+    }
+
+    public void hideProjects(ProjectView... filteredProjects) {
+        hideProjects(Arrays.asList(filteredProjects));
+    }
+
+    public void hideProjects(List<ProjectView> filteredProjects) {
+        Iterator<ProjectView> iterator = filteredProjects.iterator();
+        while (iterator.hasNext()) {
+            ProjectView projectView = iterator.next();
+            if (!filteredOutProjectNames.contains(projectView.getName())) {
+                filteredOutProjectNames.add(projectView.getName());
+            }
+        }
+
+        notifyChanges();
+    }
+
+    public void hideProjectsByName(String... projectNames) {
+        hideProjectsByName(Arrays.asList(projectNames));
+    }
+
+    public void hideProjectsByName(List<String> projectNames) {
+        Iterator<String> iterator = projectNames.iterator();
+        while (iterator.hasNext()) {
+            String gradleProject = iterator.next();
+            if (!filteredOutProjectNames.contains(gradleProject)) {
+                filteredOutProjectNames.add(gradleProject);
+            }
+        }
+        notifyChanges();
+    }
+
+    public void showProjects(ProjectView... filteredProjects) {
+        showProjects(Arrays.asList(filteredProjects));
+    }
+
+    public void showProjects(List<ProjectView> filteredProjects) {
+        Iterator<ProjectView> iterator = filteredProjects.iterator();
+        while (iterator.hasNext()) {
+            ProjectView projectView = iterator.next();
+            filteredOutProjectNames.remove(projectView.getName());
+        }
+        notifyChanges();
+    }
+
+    public void showProjectsByName(String... filteredProjects) {
+        showProjectsByName(Arrays.asList(filteredProjects));
+    }
+
+    public void showProjectsByName(List<String> filteredProjects) {
+        Iterator<String> iterator = filteredProjects.iterator();
+        while (iterator.hasNext()) {
+            String gradleProjectName = iterator.next();
+            filteredOutProjectNames.remove(gradleProjectName);
+        }
+        notifyChanges();
+    }
+
+    /**
+     * Determines if the specified project should be allowed or not.
+     *
+     * @param project the project in question
+     * @return true to allow it, false not to.
+     */
+    public boolean doesAllowProject(ProjectView project) {
+        return !filteredOutProjectNames.contains(project.getName());
+    }
+
+    public boolean doesAllowProject(String projectName) {
+        return !filteredOutProjectNames.contains(projectName);
+    }
+
+    public void hideTasks(TaskView... filteredTasks) {
+        hideTasks(Arrays.asList(filteredTasks));
+    }
+
+    public void hideTasks(List<TaskView> filteredTasks) {
+        Iterator<TaskView> iterator = filteredTasks.iterator();
+        while (iterator.hasNext()) {
+            TaskView taskView = iterator.next();
+            if (!filteredOutTaskNames.contains(taskView.getName())) {
+                filteredOutTaskNames.add(taskView.getName());
+            }
+        }
+        notifyChanges();
+    }
+
+    public void hideTasksByName(String... taskNames) {
+        hideTasksByName(Arrays.asList(taskNames));
+    }
+
+    public void hideTasksByName(List<String> taskNames) {
+        Iterator<String> iterator = taskNames.iterator();
+        while (iterator.hasNext()) {
+            String gradleTask = iterator.next();
+            if (!filteredOutTaskNames.contains(gradleTask)) {
+                filteredOutTaskNames.add(gradleTask);
+            }
+        }
+        notifyChanges();
+    }
+
+    public void showTasks(TaskView... filteredTasks) {
+        showTasks(Arrays.asList(filteredTasks));
+    }
+
+    public void showTasks(List<TaskView> filteredTasks) {
+        Iterator<TaskView> iterator = filteredTasks.iterator();
+        while (iterator.hasNext()) {
+            TaskView taskView = iterator.next();
+            filteredOutTaskNames.remove(taskView.getName());
+        }
+        notifyChanges();
+    }
+
+    public void showTasksByName(String... filteredTasks) {
+        showTasksByName(Arrays.asList(filteredTasks));
+    }
+
+    public void showTasksByName(List<String> filteredTasks) {
+        Iterator<String> iterator = filteredTasks.iterator();
+        while (iterator.hasNext()) {
+            String gradleTaskName = iterator.next();
+            filteredOutTaskNames.remove(gradleTaskName);
+        }
+        notifyChanges();
+    }
+
+    /**
+     * Determines if the specified task should be allowed or not.
+     *
+     * @param task the task in question
+     * @return true to allow it, false not to.
+     */
+    public boolean doesAllowTask(TaskView task) {
+        //since we've got the task here, we can more than just filter it by name. We can
+        //filter it out if it has no description.
+        return BasicProjectAndTaskFilter.doesAllowTask(task, filteredOutTaskNames, filterOutTasksWithNoDescription);
+    }
+
+    public boolean doesAllowTask(String taskName) {
+        return !filteredOutTaskNames.contains(taskName);
+    }
+
+    public BasicProjectAndTaskFilter createFilter() {
+        return new BasicProjectAndTaskFilter(filteredOutProjectNames, filteredOutTaskNames,
+                filterOutTasksWithNoDescription);
+    }
+
+    /**
+     * Call this to save this filter to a file.
+     */
+    public void exportToFile(DOM4JSerializer.ExportInteraction exportInteraction) {
+        BasicProjectAndTaskFilter basicProjectAndTaskFilter = createFilter();
+        DOM4JSerializer.exportToFile("basic-filter", exportInteraction, createFileFilter(), basicProjectAndTaskFilter);
+    }
+
+    /**
+     * Call this to read a filter from a file.
+     */
+    public boolean importFromFile(DOM4JSerializer.ImportInteraction importInteraction) {
+        BasicProjectAndTaskFilter basicProjectAndTaskFilter = new BasicProjectAndTaskFilter();
+        if (!DOM4JSerializer.importFromFile(importInteraction, createFileFilter(), basicProjectAndTaskFilter)) {
+            return false;
+        }
+
+        initializeFromFilter(basicProjectAndTaskFilter);
+        notifyChanges();
+        return true;
+    }
+
+    /**
+     * This creates a file filter suitable for storing/reading this filter.
+     */
+    private ExtensionFileFilter createFileFilter() {
+        return new ExtensionFileFilter(".task-filter", "Task Filter");
+    }
+
+    /**
+     * Call this whenever you make changes so we can notify any observers.
+     */
+    private void notifyChanges() {
+        observerLord.notifyObservers(new ObserverLord.ObserverNotification<FilterEditorObserver>() {
+            public void notify(FilterEditorObserver observer) {
+                observer.filterChanged();
+            }
+        });
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/filters/BasicProjectAndTaskFilter.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/filters/BasicProjectAndTaskFilter.java
new file mode 100644
index 0000000..24707cd
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/filters/BasicProjectAndTaskFilter.java
@@ -0,0 +1,196 @@
+/*
+ * 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.gradleplugin.foundation.filters;
+
+import org.gradle.foundation.ProjectView;
+import org.gradle.foundation.TaskView;
+import org.gradle.gradleplugin.foundation.settings.SettingsSerializable;
+import org.gradle.gradleplugin.foundation.settings.SettingsNode;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This is a basic filter where you can specify specific projects and tasks that will be filtered out.
+ *
+ * @author mhunsicker
+ */
+public class BasicProjectAndTaskFilter implements ProjectAndTaskFilter, SettingsSerializable {
+    private static final String BASIC_PROJECT_AND_TASK_FILTER = "basic-project-and-task-filter";
+    private static final String FILTERED_OUT_PROJECTS = "filtered-out-projects";
+    private static final String FILTERED_OUT_TASKS = "filtered-out-tasks";
+    private static final String FILTER_OUT_TASKS_WITH_NO_DESCRIPTION = "filter-out-tasks-with-no-description";
+    private static final String ITEM = "item";
+    private static final String VALUE = "value";
+
+    private List<String> filteredOutProjectNames = new ArrayList<String>();
+    private List<String> filteredOutTaskNames = new ArrayList<String>();
+    private boolean filterOutTasksWithNoDescription;
+
+    public BasicProjectAndTaskFilter(List<String> filteredOutProjectNames, List<String> filteredOutTaskNames,
+                                     boolean filterOutTasksWithNoDescription) {
+        this.filterOutTasksWithNoDescription = filterOutTasksWithNoDescription;
+        this.filteredOutProjectNames.addAll(filteredOutProjectNames);
+        this.filteredOutTaskNames.addAll(filteredOutTaskNames);
+    }
+
+    public BasicProjectAndTaskFilter() {
+    }
+
+    public List<String> getFilteredOutProjectNames() {
+        return filteredOutProjectNames;
+    }
+
+    /*package*/
+
+    void setFilteredOutProjectNames(List<String> filteredOutProjectNames) {
+        this.filteredOutProjectNames = filteredOutProjectNames;
+    }
+
+    public List<String> getFilteredOutTaskNames() {
+        return filteredOutTaskNames;
+    }
+
+    /*package*/
+
+    void setFilteredOutTaskNames(List<String> filteredOutTaskNames) {
+        this.filteredOutTaskNames = filteredOutTaskNames;
+    }
+
+    public boolean filterOutTasksWithNoDescription() {
+        return filterOutTasksWithNoDescription;
+    }
+
+    /*package*/
+
+    void setFilterOutTasksWithNoDescription(boolean filterOutTasksWithNoDescription) {
+        this.filterOutTasksWithNoDescription = filterOutTasksWithNoDescription;
+    }
+
+    /**
+     * Determines if the specified project should be allowed or not.
+     *
+     * @param project the project in question
+     * @return true to allow it, false not to.
+     */
+    public boolean doesAllowProject(ProjectView project) {
+        return !filteredOutProjectNames.contains(project.getName());
+    }
+
+    /**
+     * Determines if the specified task should be allowed or not.
+     *
+     * @param task the task in question
+     * @return true to allow it, false not to.
+     */
+    public boolean doesAllowTask(TaskView task) {
+        return doesAllowTask(task, filteredOutTaskNames, filterOutTasksWithNoDescription);
+    }
+
+    /**
+     * Determines if the specified task should be allowed or not.
+     *
+     * This version is static and is shared by this filter and its editor.
+     *
+     * @param task the task in question
+     * @param filteredOutTasks a list of filtered out task names
+     * @param filterOutTasksWithNoDescription whether or not to hide it if it as no description
+     * @return true if the task is allowed, false if not.
+     */
+    public static boolean doesAllowTask(TaskView task, List<String> filteredOutTasks,
+                                        boolean filterOutTasksWithNoDescription) {
+        if (filterOutTasksWithNoDescription) {
+            if (!task.hasDescription()) {
+                return false;
+            }
+        }
+
+        return !filteredOutTasks.contains(task.getName());
+    }
+
+    /**
+     * Call this to saves the current settings.
+     *
+     * @param settings where you save the settings.
+     */
+    public void serializeOut(SettingsNode settings) {
+        SettingsNode rootNode = settings.addChildIfNotPresent(BASIC_PROJECT_AND_TASK_FILTER);
+        rootNode.removeAllChildren(); //clear out whatever may have already been there
+
+        rootNode.setValueOfChildAsBoolean(FILTER_OUT_TASKS_WITH_NO_DESCRIPTION, filterOutTasksWithNoDescription);
+
+        SettingsNode filteredOutProjectsNode = rootNode.addChild(FILTERED_OUT_PROJECTS);
+        serializeOutStringList(filteredOutProjectsNode, filteredOutProjectNames);
+
+        SettingsNode filteredOutTasksNode = rootNode.addChild(FILTERED_OUT_TASKS);
+        serializeOutStringList(filteredOutTasksNode, filteredOutTaskNames);
+    }
+
+    /**
+     * Writes out a list of strings as 'item' element children of parentElement.
+     */
+    private void serializeOutStringList(SettingsNode parentNode, List<String> strings) {
+        Iterator<String> iterator = strings.iterator();
+        while (iterator.hasNext()) {
+            String item = iterator.next();
+            SettingsNode itemNode = parentNode.addChild(ITEM);
+            itemNode.setValue(item);
+        }
+    }
+
+    /**
+     * Call this to read in this object's settings. The reverse of serializeOut.
+     *
+     * @param settings where you read your settings.
+     */
+    public void serializeIn(SettingsNode settings) {
+        filteredOutProjectNames.clear();
+        filteredOutTaskNames.clear();
+
+        SettingsNode rootNode = settings.getChildNode(BASIC_PROJECT_AND_TASK_FILTER);
+        if (rootNode == null) {
+            return;
+        }
+
+        filterOutTasksWithNoDescription = rootNode.getValueOfChildAsBoolean(FILTER_OUT_TASKS_WITH_NO_DESCRIPTION,
+                filterOutTasksWithNoDescription);
+
+        SettingsNode filteredOutProjectsNode = rootNode.getChildNode(FILTERED_OUT_PROJECTS);
+        if (filteredOutProjectsNode != null) {
+            serializeInStringList(filteredOutProjectsNode, filteredOutProjectNames);
+        }
+
+        SettingsNode filteredOutTasksNode = rootNode.getChildNode(FILTERED_OUT_TASKS);
+        if (filteredOutTasksNode != null) {
+            serializeInStringList(filteredOutTasksNode, filteredOutTaskNames);
+        }
+    }
+
+    /**
+     * Reads in a list of strings as 'item' element children of parentElement.
+     */
+    private void serializeInStringList(SettingsNode parentNode, List<String> strings) {
+        Iterator<SettingsNode> iterator = parentNode.getChildNodes(ITEM).iterator();
+        while (iterator.hasNext()) {
+            SettingsNode itemNode = iterator.next();
+            String item = itemNode.getValue();
+            if (item != null) {
+                strings.add(item);
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/filters/ProjectAndTaskFilter.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/filters/ProjectAndTaskFilter.java
new file mode 100644
index 0000000..44e806b
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/filters/ProjectAndTaskFilter.java
@@ -0,0 +1,43 @@
+/*
+ * 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.gradleplugin.foundation.filters;
+
+import org.gradle.foundation.ProjectView;
+import org.gradle.foundation.TaskView;
+
+/**
+ * Interface for a filter that weeds out certain projects and tasks. Useful when trying to walk the projects and tasks.
+ *
+ * @author mhunsicker
+ */
+public interface ProjectAndTaskFilter {
+    /**
+     * Determines if the specified project should be allowed or not.
+     *
+     * @param project the project in question
+     * @return true to allow it, false not to.
+     */
+    public boolean doesAllowProject(ProjectView project);
+
+    /**
+     * Determines if the specified task should be allowed or not.
+     *
+     * @param task the task in question
+     * @return true to allow it, false not to.
+     * @author mhunsicker
+     */
+    public boolean doesAllowTask(TaskView task);
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/request/AbstractRequest.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/request/AbstractRequest.java
new file mode 100644
index 0000000..cda1a6a
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/request/AbstractRequest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.gradleplugin.foundation.request;
+
+import org.gradle.foundation.ipc.basic.ProcessLauncherServer;
+import org.gradle.foundation.ipc.gradle.ExecuteGradleCommandServerProtocol;
+import org.gradle.foundation.queue.ExecutionQueue;
+
+/**
+ * This represents a basic reques to gradle that is executed in a separate process using the ProcessLauncherServer. This
+ * stores the command line to execute and has the ability to cancel itself by either removing it from the queue if it
+ * hasn't started yet, or killing the external process.
+ *
+ * @author mhunsicker
+*/
+public abstract class AbstractRequest implements Request {
+   private long requestID;
+   private String fullCommandLine;
+    private String displayName;
+    private boolean forceOutputToBeShown;
+    private ExecutionQueue executionQueue;
+    private ProcessLauncherServer server;
+    protected ExecuteGradleCommandServerProtocol.ExecutionInteraction executionInteraction = new DummyExecutionInteraction();
+
+    public AbstractRequest(long requestID, String fullCommandLine, String displayName, boolean forceOutputToBeShown, ExecutionQueue executionQueue) {
+       this.requestID = requestID;
+       this.fullCommandLine = fullCommandLine;
+        this.displayName = displayName;
+        this.forceOutputToBeShown = forceOutputToBeShown;
+        this.executionQueue = executionQueue;
+    }
+
+    public long getRequestID() {
+      return requestID;
+    }
+
+    public String getFullCommandLine() {
+        return fullCommandLine;
+    }
+
+    public String getDisplayName() {
+      return displayName;
+    }
+
+    public boolean forceOutputToBeShown() {
+      return forceOutputToBeShown;
+    }
+
+   /**
+     * Cancels this request.
+     */
+    public synchronized boolean cancel() {
+        if (this.server != null) {
+           server.killProcess();
+        }
+
+      executionQueue.removeRequestFromQueue(this);
+        return true;
+    }
+
+    public synchronized void setProcessLauncherServer(ProcessLauncherServer server) {
+        this.server = server;
+    }
+
+    public void setExecutionInteraction( ExecuteGradleCommandServerProtocol.ExecutionInteraction executionInteraction ) {
+      this.executionInteraction = executionInteraction;
+    }
+   
+   /**
+    * This is a dummy ExecutionInteraction. It does nothing. It exists because the requests require one,
+    * but there's a timing issue about when the Request and ExecutionInteraction are paired. Actually,
+    * this mechanism needs to allow for multiple listeners instead of just a single interaction.
+    * I was in the middle of refactoring other things and didn't want to get into that, so I'm doing
+    * this instead. Its only meant to be temporary, but we'll see.
+    */
+   public class DummyExecutionInteraction implements ExecuteGradleCommandServerProtocol.ExecutionInteraction
+   {
+      /**
+       Notification that gradle has started execution. This may not get called
+       if some error occurs that prevents gradle from running.
+       */
+      public void reportExecutionStarted()
+      {
+
+      }
+
+      /**
+       * Notification of the total number of tasks that will be executed. This is
+       * called after reportExecutionStarted and before any tasks are executed.
+       *
+       * @param size the total number of tasks.
+       */
+      public void reportNumberOfTasksToExecute( int size )
+      {
+
+      }
+
+      /**
+       * Notification that execution has finished. Note: if the client fails
+       * to launch at all, this should still be called.
+       *
+       * @param wasSuccessful true if gradle was successful (returned 0)
+       * @param message       the output of gradle if it ran. If it didn't, an error message.
+       * @param throwable     an exception if one occurred
+       */
+      public void reportExecutionFinished( boolean wasSuccessful, String message, Throwable throwable )
+      {
+
+      }
+
+      public void reportTaskStarted( String message, float percentComplete )
+      {
+
+      }
+
+      public void reportTaskComplete( String message, float percentComplete )
+      {
+
+      }
+
+      public void reportLiveOutput( String message )
+      {
+
+      }
+   }
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/request/ExecutionRequest.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/request/ExecutionRequest.java
new file mode 100644
index 0000000..9140dc8
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/request/ExecutionRequest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.gradleplugin.foundation.request;
+
+import org.gradle.StartParameter;
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.foundation.ipc.basic.ProcessLauncherServer;
+import org.gradle.foundation.ipc.gradle.ExecuteGradleCommandServerProtocol;
+import org.gradle.foundation.queue.ExecutionQueue;
+
+import java.io.File;
+
+/**
+ * This represents a reques to gradle that is executed in a separate process using the ProcessLauncherServer. This
+ * version is for directly executing commands in gradle (the most common type of request).
+ *
+ * @author mhunsicker
+*/
+public class ExecutionRequest extends AbstractRequest {
+
+   public static final Type TYPE = new Type() {};
+
+    public ExecutionRequest(long requestID, String fullCommandLine, String displayName, boolean forceOutputToBeShown, ExecutionQueue executionQueue) {
+        super(requestID, fullCommandLine, displayName, forceOutputToBeShown, executionQueue);
+    }
+
+    /**
+     * This is called right before this command is executed (because the settings such as log level and stack trace
+     * level can be changed between the time someone initiates a command and it executes). The execution takes place in
+     * another process so this should create the appropriate Protocol suitable for passing the results of the execution
+     * back to us.
+     *
+     * @param  logLevel             the user's log level.
+     * @param  stackTraceLevel      the user's stack trace level
+     * @param  currentDirectory     the current working directory of your gradle project
+     * @param  gradleHomeDirectory  the gradle home directory
+     * @param  customGradleExecutor the path to a custom gradle executable. May be null.
+     * @return a protocol that our server will use to communicate with the launched gradle process.
+    */
+    public ProcessLauncherServer.Protocol createServerProtocol(LogLevel logLevel, StartParameter.ShowStacktrace stackTraceLevel, File currentDirectory, File gradleHomeDirectory, File customGradleExecutor) {
+        executionInteraction.reportExecutionStarted();  //go ahead and fire off that the execution has started. It has from the user's standpoint.
+
+        return new ExecuteGradleCommandServerProtocol(currentDirectory, gradleHomeDirectory, customGradleExecutor, getFullCommandLine(), logLevel, stackTraceLevel, executionInteraction);
+    }
+
+   public void executeAgain( GradlePluginLord gradlePluginLord )
+   {
+      gradlePluginLord.addExecutionRequestToQueue( getFullCommandLine(), getDisplayName(), forceOutputToBeShown() );
+   }
+
+   public Type getType()
+   {
+      return TYPE;
+   }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/request/RefreshTaskListRequest.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/request/RefreshTaskListRequest.java
new file mode 100644
index 0000000..1275881
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/request/RefreshTaskListRequest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.gradleplugin.foundation.request;
+
+import org.gradle.StartParameter;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.foundation.ProjectView;
+import org.gradle.foundation.ipc.basic.ProcessLauncherServer;
+import org.gradle.foundation.ipc.gradle.ExecuteGradleCommandServerProtocol;
+import org.gradle.foundation.ipc.gradle.TaskListServerProtocol;
+import org.gradle.foundation.queue.ExecutionQueue;
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * This represents a reques to gradle that is executed in a separate process using the ProcessLauncherServer. This is a
+ * special request where the results are to build up a project/task tree.
+ *
+ * @author mhunsicker
+ */
+public class RefreshTaskListRequest extends AbstractRequest {
+
+    public static final Type TYPE = new Type() {};
+
+    private GradlePluginLord gradlePluginLord;
+
+    public RefreshTaskListRequest(long requestID, String fullCommandLine, ExecutionQueue executionQueue, GradlePluginLord gradlePluginLord) {
+        super(requestID, fullCommandLine, "Refresh", false, executionQueue);
+        this.gradlePluginLord = gradlePluginLord;
+    }
+
+   /**
+    * This is called right before this command is executed (because the settings such as log level and stack trace
+    * level can be changed between the time someone initiates a command and it executes). The execution takes place in
+    * another process so this should create the appropriate Protocol suitable for passing the results of the execution
+    * back to us.
+    *
+    * @param  logLevel             the user's log level.
+    * @param  stackTraceLevel      the user's stack trace level
+    * @param  currentDirectory     the current working directory of your gradle project
+    * @param  gradleHomeDirectory  the gradle home directory
+    * @param  customGradleExecutor the path to a custom gradle executable. May be null.
+    * @return a protocol that our server will use to communicate with the launched gradle process.
+    */
+    public ProcessLauncherServer.Protocol createServerProtocol(LogLevel logLevel, StartParameter.ShowStacktrace stackTraceLevel, File currentDirectory, File gradleHomeDirectory, File customGradleExecutor) {
+        executionInteraction.reportExecutionStarted();  //go ahead and fire off that the execution has started. It has from the user's standpoint.
+
+        ExecutionInteractionWrapper wrapper = new ExecutionInteractionWrapper(executionInteraction);
+
+        return new TaskListServerProtocol(currentDirectory, gradleHomeDirectory, customGradleExecutor, getFullCommandLine(), logLevel, stackTraceLevel, wrapper);
+    }
+
+    private class ExecutionInteractionWrapper implements TaskListServerProtocol.ExecutionInteraction {
+        private ExecuteGradleCommandServerProtocol.ExecutionInteraction executionInteraction;
+
+        private ExecutionInteractionWrapper(ExecuteGradleCommandServerProtocol.ExecutionInteraction executionInteraction) {
+            this.executionInteraction = executionInteraction;
+        }
+
+        /**
+         * Notification that gradle has started execution. This may not get called if some error occurs that prevents
+         * gradle from running.
+        */
+        public void reportExecutionStarted() {
+            executionInteraction.reportExecutionStarted();
+        }
+
+        /**
+         * Notification that execution has finished. Note: if the client fails to launch at all, this should still be
+         * called.
+         *
+         * @param wasSuccessful true if gradle was successful (returned 0)
+         * @param message       the output of gradle if it ran. If it didn't, an error message.
+         * @param throwable     an exception if one occurred
+         * @param projects      a hierachical list of projects. This is the final result.
+         */
+        public void reportExecutionFinished(boolean wasSuccessful, String message, Throwable throwable) {
+            executionInteraction.reportExecutionFinished(wasSuccessful, message, throwable);
+        }
+
+       public void projectsPopulated( List<ProjectView> projects )
+       {
+          gradlePluginLord.setProjects(projects);
+       }
+
+       public void reportLiveOutput(String message) {
+            executionInteraction.reportLiveOutput(message);
+        }
+    }
+
+   public void executeAgain( GradlePluginLord gradlePluginLord )
+   {
+      gradlePluginLord.addRefreshRequestToQueue();
+   }
+
+   public Type getType()
+   {
+      return TYPE;
+   }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/request/Request.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/request/Request.java
new file mode 100644
index 0000000..eb55d3a
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/request/Request.java
@@ -0,0 +1,75 @@
+/*
+ * 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.gradleplugin.foundation.request;
+
+import org.gradle.foundation.ipc.basic.ProcessLauncherServer;
+import org.gradle.foundation.ipc.gradle.ExecuteGradleCommandServerProtocol;
+import org.gradle.foundation.queue.ExecutionQueue;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.StartParameter;
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+
+import java.io.File;
+
+/**
+ * This represents a reques to gradle that is executed in a separate process using the ProcessLauncherServer.
+ *
+ * @author mhunsicker
+  */
+public interface Request extends ExecutionQueue.Request {
+
+    public long getRequestID();
+
+    public String getFullCommandLine();
+
+    public String getDisplayName();
+
+    public boolean forceOutputToBeShown();
+
+    /**
+    * This is called internally to link the request with the server that is
+    * running the gradle process.
+    *
+    * @param  server     the server.
+    */
+    public void setProcessLauncherServer(ProcessLauncherServer server);
+
+    /**
+    * Cancels this request.
+    *
+    * @return true if you can cancel or it or if it has already ran. This return code is mainly meant to prevent you from
+    */
+    public boolean cancel();
+
+    public void setExecutionInteraction( ExecuteGradleCommandServerProtocol.ExecutionInteraction executionInteraction );
+
+    /**
+    * This is called right before this command is executed (because the settings such as log level and stack trace
+    * level can be changed between the time someone initiates a command and it executes). The execution takes place in
+    * another process so this should create the appropriate Protocol suitable for passing the results of the execution
+    * back to us.
+    *
+    * @param  logLevel             the user's log level.
+    * @param  stackTraceLevel      the user's stack trace level
+    * @param  currentDirectory     the current working directory of your gradle project
+    * @param  gradleHomeDirectory  the gradle home directory
+    * @param  customGradleExecutor the path to a custom gradle executable. May be null.
+    * @return a protocol that our server will use to communicate with the launched gradle process.
+    */
+    public ProcessLauncherServer.Protocol createServerProtocol(LogLevel logLevel, StartParameter.ShowStacktrace stackTraceLevel, File currentDirectory, File gradleHomeDirectory, File customGradleExecutor);
+
+   public void executeAgain( GradlePluginLord gradlePluginLord );
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/runner/GradleRunner.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/runner/GradleRunner.java
new file mode 100644
index 0000000..2e2aece
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/runner/GradleRunner.java
@@ -0,0 +1,77 @@
+/*
+ * 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.gradleplugin.foundation.runner;
+
+import org.gradle.StartParameter;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.foundation.ipc.basic.ProcessLauncherServer;
+import org.gradle.foundation.ipc.gradle.ExecuteGradleCommandServerProtocol;
+
+import java.io.File;
+
+/**
+ * This executes a command line in an external process.
+ *
+ * @author mhunsicker
+ */
+public class GradleRunner {
+    private File currentDirectory;
+    private File gradleHomeDirectory;
+    private File customGradleExecutor;
+    private ProcessLauncherServer server;
+
+    public GradleRunner(File currentDirectory, File gradleHomeDirectory, File customGradleExecutor) {
+        this.currentDirectory = currentDirectory;
+        this.gradleHomeDirectory = gradleHomeDirectory;
+        this.customGradleExecutor = customGradleExecutor;
+    }
+
+    public synchronized void executeCommand(String commandLine, LogLevel logLevel,
+                                            StartParameter.ShowStacktrace stackTraceLevel,
+                                            ExecuteGradleCommandServerProtocol.ExecutionInteraction executionInteraction) {
+        //the protocol manages the command line and messaging observers
+        ExecuteGradleCommandServerProtocol serverProtocol = new ExecuteGradleCommandServerProtocol(currentDirectory,
+                gradleHomeDirectory, customGradleExecutor, commandLine, logLevel, stackTraceLevel,
+                executionInteraction);
+
+        //the server kicks off gradle as an external process and manages the communication with said process
+        server = new ProcessLauncherServer(serverProtocol);
+        server.addServerObserver(new ProcessLauncherServer.ServerObserver() {
+            public void clientExited(int result, String output) {
+            }
+
+            public void serverExited() {
+                clearServer();
+            }
+        }, false);
+
+        executionInteraction.reportExecutionStarted();  //go ahead and fire off that the execution has started. Normally, this is done by the request, but we don't have a request in this case.
+        server.start();
+    }
+
+    /**
+     * Call this to stop the gradle process.
+     */
+    public synchronized void killProcess() {
+        if (server != null) {
+            server.killProcess();
+        }
+    }
+
+    private synchronized void clearServer() {
+        server = null;
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/settings/DOM4JSettingsNode.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/settings/DOM4JSettingsNode.java
new file mode 100644
index 0000000..188f8b7
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/settings/DOM4JSettingsNode.java
@@ -0,0 +1,277 @@
+/*
+ * 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.gradleplugin.foundation.settings;
+
+import org.dom4j.Element;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * An implementation of SettingsNode that uses DOM4J nodes as its actual storage medium.
+ *
+ * @author mhunsicker
+ */
+public class DOM4JSettingsNode implements SettingsNode {
+    public static final String TAG_NAME = "setting";
+    public static final String NAME_ATTRIBUTE = "name";
+    public static final String VALUE_ATTRIBUTE = "value";
+    private Element element;
+
+    public DOM4JSettingsNode(Element element) {
+        this.element = element;
+    }
+
+    public Element getElement() {
+        return element;
+    }
+
+    public void setName(String name) {
+        element.addAttribute(NAME_ATTRIBUTE, name);
+    }
+
+    public String getName() {
+        return element.attributeValue(NAME_ATTRIBUTE);
+    }
+
+    public void setValue(String value) {
+        element.addAttribute(VALUE_ATTRIBUTE, value);
+    }
+
+    public String getValue() {
+        return element.attributeValue(VALUE_ATTRIBUTE);
+    }
+
+    public void setValueOfChild(String name, String value) {
+        SettingsNode settingsNode = addChildIfNotPresent(name);
+        settingsNode.setValue(value);
+    }
+
+    public String getValueOfChild(String name, String defaultValue) {
+        SettingsNode settingsNode = getChildNode(name);
+        if (settingsNode != null) {
+            String value = settingsNode.getValue();
+            if (value != null) {
+                return value;
+            }
+        }
+        return defaultValue;
+    }
+
+    public SettingsNode getChildNode(String name) {
+        Iterator iterator = element.elements().iterator();
+        while (iterator.hasNext()) {
+            Element childElement = (Element) iterator.next();
+            if (name.equals(childElement.attributeValue(NAME_ATTRIBUTE))) {
+                return new DOM4JSettingsNode(childElement);
+            }
+        }
+        return null;
+    }
+
+    public List<SettingsNode> getChildNodes() {
+        return convertNodes(element.elements());
+    }
+
+    private List<SettingsNode> convertNodes(List elements) {
+        List<SettingsNode> children = new ArrayList<SettingsNode>();
+
+        Iterator iterator = elements.iterator();
+        while (iterator.hasNext()) {
+            Element childElement = (Element) iterator.next();
+            children.add(new DOM4JSettingsNode(childElement));
+        }
+
+        return children;
+    }
+
+    public List<SettingsNode> getChildNodes(String name) {
+        List<SettingsNode> children = new ArrayList<SettingsNode>();
+
+        Iterator iterator = element.elements().iterator();
+        while (iterator.hasNext()) {
+            Element childElement = (Element) iterator.next();
+            if (name.equals(childElement.attributeValue(NAME_ATTRIBUTE))) {
+                children.add(new DOM4JSettingsNode(childElement));
+            }
+        }
+
+        return children;
+    }
+
+    public int getValueOfChildAsInt(String name, int defaultValue) {
+        SettingsNode settingsNode = getChildNode(name);
+        if (settingsNode != null) {
+            String value = settingsNode.getValue();
+
+            try {
+                if (value != null) {
+                    return Integer.parseInt(value);
+                }
+            } catch (NumberFormatException e) {
+                //we couldn't parse it. Just return the default.
+            }
+        }
+        return defaultValue;
+    }
+
+    public void setValueOfChildAsInt(String name, int value) {
+        setValueOfChild(name, Integer.toString(value));
+    }
+
+    public long getValueOfChildAsLong(String name, long defaultValue) {
+        SettingsNode settingsNode = getChildNode(name);
+        if (settingsNode != null) {
+            String value = settingsNode.getValue();
+
+            try {
+                if (value != null) {
+                    return Long.parseLong(value);
+                }
+            } catch (NumberFormatException e) {
+                //we couldn't parse it. Just return the default.
+            }
+        }
+        return defaultValue;
+    }
+
+    public void setValueOfChildAsLong(String name, long value) {
+        setValueOfChild(name, Long.toString(value));
+    }
+
+    public boolean getValueOfChildAsBoolean(String name, boolean defaultValue) {
+        SettingsNode settingsNode = getChildNode(name);
+        if (settingsNode != null) {
+            String value = settingsNode.getValue();
+
+            //I'm not calling 'Boolean.parseBoolean( value )' because it will return false if the value isn't true/false
+            //and we want it to return whatever the default is if its not a boolean.
+            if (value != null) {
+                if ("true".equalsIgnoreCase(value)) {
+                    return true;
+                }
+
+                if ("false".equalsIgnoreCase(value)) {
+                    return false;
+                }
+            }
+        }
+
+        return defaultValue;
+    }
+
+    public void setValueOfChildAsBoolean(String name, boolean value) {
+        setValueOfChild(name, Boolean.toString(value));
+    }
+
+    public SettingsNode addChild(String name) {
+        DOM4JSettingsNode childElement = new DOM4JSettingsNode(element.addElement(TAG_NAME));
+
+        childElement.setName(name);
+        return childElement;
+    }
+
+    public SettingsNode addChildIfNotPresent(String name) {
+        SettingsNode settingsNode = getChildNode(name);
+        if (settingsNode == null) {
+            settingsNode = addChild(name);
+        }
+
+        return settingsNode;
+    }
+
+    public SettingsNode getNodeAtPath(String... pathPortions) {
+        if (pathPortions == null || pathPortions.length == 0) {
+            return null;
+        }
+
+        String firstPathPortion = pathPortions[0];
+
+        SettingsNode currentNode = getChildNode(firstPathPortion);
+
+        int index = 1; //Skip the first one. we've already used that one.
+        while (index < pathPortions.length && currentNode != null) {
+            String pathPortion = pathPortions[index];
+            currentNode = currentNode.getChildNode(pathPortion);
+            index++;
+        }
+
+        return currentNode;
+    }
+
+    private SettingsNode getNodeAtPathCreateIfNotFound(String... pathPortions) {
+        if (pathPortions == null || pathPortions.length == 0) {
+            return null;
+        }
+
+        String firstPathPortion = pathPortions[0];
+
+        SettingsNode currentNode = getChildNode(firstPathPortion);
+        if (currentNode == null) {
+            currentNode = addChild(firstPathPortion);
+        }
+
+        int index = 1;
+        while (index < pathPortions.length) {
+            String pathPortion = pathPortions[index];
+            currentNode = currentNode.getChildNode(pathPortion);
+            if (currentNode == null) {
+                currentNode = addChild(firstPathPortion);
+            }
+
+            index++;
+        }
+
+        return currentNode;
+    }
+
+    public void removeFromParent() {
+        element.detach();
+    }
+
+    public void removeAllChildren() {
+        List list = element.elements();
+        Iterator iterator = list.iterator();
+
+        while (iterator.hasNext()) {
+            Element child = (Element) iterator.next();
+            child.detach();
+        }
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof DOM4JSettingsNode)) {
+            return false;
+        }
+
+        DOM4JSettingsNode otherNode = (DOM4JSettingsNode) obj;
+
+        //we're the same if our elements are the same.
+        return otherNode.element.equals(element);
+    }
+
+    @Override
+    public int hashCode() {
+        return element.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return getName() + "='" + getValue() + "' " + element.elements().size() + " children";
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/settings/SettingsNode.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/settings/SettingsNode.java
new file mode 100644
index 0000000..424fae7
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/settings/SettingsNode.java
@@ -0,0 +1,189 @@
+/*
+ * 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.gradleplugin.foundation.settings;
+
+import java.util.List;
+
+/**
+ * This provides a mechanism for storing settings. It is a hybrid of a DOM (like xml) and something much simpler like
+ * the java preferences. It is first meant to be easy to use. Second, it is supposed to abstract how the settings are
+ * actually stored. The point is to allow IDEs and such to store this is whatever manner they choose. Thus, this is
+ * vague. Third, it is meant to be relatively easy to implement.
+ *
+ * While this is a hiearchy, it is not meant to be as complex as XML with arbitrary attributes. Instead, it is only
+ * meant to be a key-value pairing. However, the hiearchy allows you to easily have lists and other complex structures
+ * without having to worry about name collisions as you would if this were a pure key-value pair.
+ *
+ * This is meant to be a single tree for the entire application. You shouldn't create these on your own. A node for your
+ * use should be given to you by your parent. Only the highest level object should directly create an instance of one of
+ * these. It should manage saving and restore these settings.
+ *
+ * Due to how some 'owners' of the settings work, you should save your settings immediately here (think of this as you
+ * would a database). Why? Well, for example: when Idea is the owner in the gradle Idea plugin, it attempts to store its
+ * settings very frequently and uses differences in the results to determine if changes have been made to a plugin. As a
+ * result, we store things in the setting immediately.
+ *
+ * This node consists to 3 things: - name: this is the 'key' of key-value pair. It is required. - value: this is the
+ * 'value' of key-value pair. It is NOT required. - child nodes: each node can have children nodes.
+ *
+ * Using these you can create a tree structure storing whatever you like. If you need multiple attributes like XML has,
+ * you should create children instead. So if, in xml, you wanted to do:
+ *
+ * <setting name="myname" value="myvalue" myotherattribute="attribute1" somethingelse="attribute2" >
+ *
+ * do this instead:
+ *
+ * node name="myName" value="myvalue" node name="myotherattribute" value="attribute1" node name="somethingelse"
+ * value="attribute2"
+ *
+ * This has several convenience functions for setting and getting values from child nodes. These are meant to be used in
+ * more of a java preferences replacement. You should create your own root node of your settings (by call
+ * addChildIfNotPresent from a node that is given to you) then you can use these functions and you only need to worry
+ * about uniqueness within your own node.
+ *
+ * @author mhunsicker
+ */
+public interface SettingsNode {
+    /**
+     * Sets the name of this node. This is used as its identifier.
+     *
+     * @param name the new name. Cannot be null!
+     */
+    public void setName(String name);
+
+    public String getName();
+
+    /**
+     * Sets the value of this node. This is whatever you like, but is always internally text.
+     *
+     * @param value the new value. Can be null.
+     */
+    public void setValue(String value);
+
+    public String getValue();
+
+    /**
+     * Sets the value of the child node, adding it if it is not already present. This is a convenience function
+     * providing more java preferences-like behavior.
+     *
+     * @param name the name of the child node.
+     * @param value the new value.
+     */
+    public void setValueOfChild(String name, String value);
+
+    /**
+     * Gets the value of the child node. If it is not present, the defaultValue is returned. This is a convenience
+     * function providing more java preferences-like behavior.
+     *
+     * @param name the name of the child node.
+     * @param defaultValue the value to return if the child node is not present
+     * @return the value.
+     */
+    public String getValueOfChild(String name, String defaultValue);
+
+    /**
+     * Sets the value of the child node as an integer, adding it if it is not already present. This is a convenience
+     * function providing more java preferences-like behavior.
+     *
+     * @param name the name of the child node.
+     * @param value the new value.
+     */
+    public void setValueOfChildAsInt(String name, int value);
+
+    /**
+     * Gets the value of the child node as an integer. If it is not present or the value is cannot be interpretted as an
+     * integer, the defaultValue is returned. This is a convenience function providing more java preferences-like
+     * behavior.
+     *
+     * @param name the name of the child node.
+     * @param defaultValue the value to return if the child node is not present or cannot be interpretted as an
+     * integer.
+     * @return the value.
+     */
+    public int getValueOfChildAsInt(String name, int defaultValue);
+
+    //same as setValueOfChildAsInt but with a boolean
+
+    public void setValueOfChildAsBoolean(String name, boolean value);
+
+    //same as getValueOfChildAsInt but with a boolean
+
+    public boolean getValueOfChildAsBoolean(String name, boolean defaultValue);
+
+    //same as setValueOfChildAsInt but with a long
+
+    public void setValueOfChildAsLong(String name, long value);
+
+    //same as getValueOfChildAsInt but with a long
+
+    public long getValueOfChildAsLong(String name, long defaultValue);
+
+    /**
+     * @return a list of all child nodes of this node.
+     */
+    public List<SettingsNode> getChildNodes();
+
+    /**
+     * @param name the names of the sought child nodes.
+     * @return a list of all child nodes of this node that have the specified name. If none are found, this should
+     *         return an empty list. Never null.
+     */
+    public List<SettingsNode> getChildNodes(String name);
+
+    /**
+     * Returns the child node with the specified name.
+     *
+     * @param name the name of the sought node
+     * @return the child settings node or null if no match found.
+     */
+    public SettingsNode getChildNode(String name);
+
+    /**
+     * Call this to add a child node to this node.
+     *
+     * @param name the name of the node
+     * @return the child settings node.
+     */
+    public SettingsNode addChild(String name);
+
+    /**
+     * This adds a child node with the specified name or returns the existing child node if one already exists with said
+     * name.
+     *
+     * @param name the name.
+     * @return the child settings node. Never null.
+     */
+    public SettingsNode addChildIfNotPresent(String name);
+
+    /**
+     * Returns a node at the specified path starting at 'this' node.
+     *
+     * @param pathPortions an array of 'names' of the nodes. The first item corresponds to a direct child of this node.
+     * The second item would be the grand child of this node. etc. etc.
+     * @return a node if it exists. Null if not.
+     */
+    public SettingsNode getNodeAtPath(String... pathPortions);
+
+    /**
+     * Removes this node from its parent.
+     */
+    public void removeFromParent();
+
+    /**
+     * Deletes all the children of this node.
+     */
+    public void removeAllChildren();
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/settings/SettingsSerializable.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/settings/SettingsSerializable.java
new file mode 100644
index 0000000..a1fab38
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/foundation/settings/SettingsSerializable.java
@@ -0,0 +1,39 @@
+/*
+ * 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.gradleplugin.foundation.settings;
+
+/**
+ * Something that can be serialized to an XML structure. This is meant to store any preferences or settings (lightweight
+ * and heavyweight).
+ *
+ * @author mhunsicker
+ */
+public interface SettingsSerializable {
+    /**
+     * Call this to saves the current settings.
+     *
+     * @param settings where you save the settings.
+     */
+    public void serializeOut(SettingsNode settings);
+
+    /**
+     * Call this to read in this object's settings. The reverse of serializeOut.
+     *
+     * @param settings where you read your settings.
+     * @author mhunsicker
+     */
+    public void serializeIn(SettingsNode settings);
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/AlternateUIInteraction.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/AlternateUIInteraction.java
new file mode 100644
index 0000000..5f9ac3a
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/AlternateUIInteraction.java
@@ -0,0 +1,57 @@
+/*
+ * 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.gradleplugin.userinterface;
+
+import java.io.File;
+
+/**
+ * This allows this plugin to interact with alternative UIs. Specifically, this has callbacks for IDE's so tell it to
+ * edit a project file or the like. This is the 'alternate' UI interaction because it interacts with other UIs (other
+ * than the built-in UI).
+ *
+ * @author mhunsicker
+ */
+public interface AlternateUIInteraction {
+
+   /**
+    Notification that you should open the specified file and go to the specified line. Its up to the
+    application to determine if this file should be opened for editing or simply displayed. The difference
+    comes into play for things like xml or html files where a user may want to open them in a browser vs
+    a source code file where they may want to open it directly in an IDE.
+
+    @param file the file to opened
+    @param line the line to go to. -1 if no line is specified.
+    */
+    public void openFile( File file, int line );
+
+    /*
+      This is called when we should open the specified file for editing. This version explicitly wants them
+      edited versus just opened.
+
+      @param  file      the file to open
+      @param line the line to go to. -1 if no line is specified.
+      @author mhunsicker
+   */
+    public void editFile( File file, int line );
+
+    /**
+     * Determines if we can call editFile or openFile. This is not a dynamic answer and should always return either true of false.
+     * If you want to change the answer, return true and then handle the files differently in editFiles.
+     *
+     * @return true if support editing files, false otherwise.
+     */
+    public boolean doesSupportEditingOpeningFiles();
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessImageButton.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessImageButton.java
new file mode 100644
index 0000000..63d39ff
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessImageButton.java
@@ -0,0 +1,117 @@
+/*
+ * 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.gradleplugin.userinterface.swing.common;
+
+import javax.swing.Action;
+import javax.swing.Icon;
+import javax.swing.InputMap;
+import javax.swing.JButton;
+import javax.swing.KeyStroke;
+import javax.swing.plaf.metal.MetalButtonUI;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.geom.Rectangle2D;
+
+/**
+ * This is button that has no border and only an image. It highlights when the user moves over it. This style was
+ * modeled after Idea. This was used because the borders on toolbars can get a little busy and this looks a little
+ * cleaner.
+ *
+ * @author mhunsicker
+ */
+public class BorderlessImageButton extends JButton {
+    private Color oldBackgroundColor;
+
+    public BorderlessImageButton(Action action, Icon icon) {
+        super(action);
+        setUI();
+
+        // If icon exist use icon otherwise let button use text ( if available ?).
+        if (action.getValue(Action.SMALL_ICON) != null) {
+            setText(null);
+        }
+
+        String name = (String) action.getValue(action.NAME);
+        InputMap inputMap = this.getInputMap(this.WHEN_IN_FOCUSED_WINDOW);
+        KeyStroke keyStroke = (KeyStroke) action.getValue(action.ACCELERATOR_KEY);
+        inputMap.put(keyStroke, name);
+
+        init(icon);
+    }
+
+    private void setUI() {
+        // This fixes an issue where the WindowsButtonUI wants to draw a border
+        // around a button that isn't in a toolbar.  This occurs even if you set
+        // an empty border because it ignores your border and draws its own.
+        setUI(MetalButtonUI.createUI(this));
+    }
+
+    private void init(Icon icon) {
+        setBorder(BorderlessUtility.DEFAULT_BORDER);
+        addMouseListener(new HighlightMouseListener());
+
+        setText(null);
+
+        if (icon != null) {
+            setIcon(icon);
+
+            int height = icon.getIconHeight();
+            int width = icon.getIconWidth();
+            Dimension preferredSize = new Dimension(width + 2, height + 2); //plus 2 for the border
+
+            setMinimumSize(preferredSize);
+            setMaximumSize(preferredSize);
+            setPreferredSize(preferredSize);
+            setFocusPainted(false);
+        }
+    }
+
+    private class HighlightMouseListener extends MouseAdapter {
+        private HighlightMouseListener() {
+        }
+
+        public void mouseEntered(MouseEvent event) {
+            if (getAction() != null ? getAction().isEnabled() : isEnabled()) {
+                oldBackgroundColor = BorderlessImageButton.this.getBackground();
+                BorderlessImageButton.this.setBackground(BorderlessUtility.ON_MOUSE_OVER_BACKGROUND);
+                BorderlessImageButton.this.setBorder(BorderlessUtility.ON_MOUSEOVER_BORDER);
+            }
+        }
+
+        public void mousePressed(MouseEvent event) {
+            if (getAction() != null ? getAction().isEnabled() : isEnabled()) {
+                BorderlessImageButton.this.setBackground(BorderlessUtility.ON_BUTTON_PRESSED_BACKGROUND);
+            }
+        }
+
+        public void mouseReleased(MouseEvent event) {
+            if (getAction() != null ? getAction().isEnabled() : isEnabled()) {
+                // do a hit test to make sure the mouse is being released inside the button
+                Rectangle2D buttonRect = BorderlessImageButton.this.getBounds();
+                if (buttonRect.contains(event.getPoint())) {
+                    BorderlessImageButton.this.setBackground(BorderlessUtility.ON_MOUSE_OVER_BACKGROUND);
+                }
+            }
+        }
+
+        public void mouseExited(MouseEvent event) {
+            BorderlessImageButton.this.setBackground(oldBackgroundColor);
+            BorderlessImageButton.this.setBorder(BorderlessUtility.DEFAULT_BORDER);
+        }
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessImageToggleButton.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessImageToggleButton.java
new file mode 100644
index 0000000..b4a968b
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessImageToggleButton.java
@@ -0,0 +1,132 @@
+/*
+ * 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.gradleplugin.userinterface.swing.common;
+
+import javax.swing.Action;
+import javax.swing.BorderFactory;
+import javax.swing.Icon;
+import javax.swing.JToggleButton;
+import javax.swing.border.Border;
+import javax.swing.plaf.metal.MetalButtonUI;
+import java.awt.Dimension;
+import java.awt.Color;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.geom.Rectangle2D;
+
+/**
+ * This is button that has no border and only an image. It highlights when the user moves over it. This version is a
+ * toggle button. This style was modeled after Idea. This was used because the borders on toolbars can get a little busy
+ * and this looks a little cleaner.
+ *
+ * @author mhunsicker
+ */
+public class BorderlessImageToggleButton extends JToggleButton {
+    public Border selectedBorder = BorderFactory.createLoweredBevelBorder();
+    private Color defaultBackground;
+
+    public BorderlessImageToggleButton(Action action, Icon icon) {
+        super(action);
+        setUI();
+
+        this.init(icon);
+    }
+
+    private void setUI() {
+        // This fixes an issue where the WindowsButtonUI wants to draw a border
+        // around a button that isn't in a toolbar.  This occurs even if you set
+        // an empty border because it ignores your border and draws its own.
+        setUI(MetalButtonUI.createUI(this));
+    }
+
+    private void init(Icon icon) {
+        this.setBorder(BorderlessUtility.DEFAULT_BORDER);
+        defaultBackground = this.getBackground();
+        this.addMouseListener(new HighlightMouseListener());
+
+        setText(null);
+
+        if (icon != null) {
+            setIcon(icon);
+
+            int height = icon.getIconHeight();
+            int width = icon.getIconWidth();
+            Dimension preferredSize = new Dimension(width + 2, height + 2); //plus 2 for the border
+
+            setMinimumSize(preferredSize);
+            setMaximumSize(preferredSize);
+            setPreferredSize(preferredSize);
+            setFocusPainted(false);
+        }
+    }
+
+    public void setSelected(boolean select) {
+        super.setSelected(select);
+        setBorder(null);
+    }
+
+    /**
+     * I added this to correct an architecture
+     * problem. Whenever this button was removed or added to a parent container the underlying swing architecture was
+     * resetting the border and it wasn't taking into account our need to change the border depending on the selection
+     * state of the button. This overrides negates that effect causing the button to behave as intended.
+     *
+     * @param border The new border to set for this button the we disregard and replace with our own.
+     * @author wwhitaker
+     */
+    public void setBorder(Border border) {
+        super.setBorder(
+                BorderlessImageToggleButton.this.isSelected() ? selectedBorder : BorderlessUtility.DEFAULT_BORDER);
+    }
+
+    private class HighlightMouseListener extends MouseAdapter {
+        public HighlightMouseListener() {
+        }
+
+        public void mouseEntered(MouseEvent event) {
+            if (getAction() != null ? getAction().isEnabled() : isEnabled()) {
+                BorderlessImageToggleButton.this.setBackground(BorderlessUtility.ON_MOUSE_OVER_BACKGROUND);
+                BorderlessImageToggleButton.this.setBorder(BorderlessUtility.ON_MOUSEOVER_BORDER);
+            }
+        }
+
+        public void mousePressed(MouseEvent event) {
+            if (getAction() != null ? getAction().isEnabled() : isEnabled()) {
+                BorderlessImageToggleButton.this.setBackground(BorderlessUtility.ON_BUTTON_PRESSED_BACKGROUND);
+            }
+        }
+
+        public void mouseReleased(MouseEvent event) {
+            if (getAction() != null ? getAction().isEnabled() : isEnabled()) {
+                // do a hit test to make sure the mouse is being released inside the button
+                Rectangle2D buttonRect = BorderlessImageToggleButton.this.getBounds();
+                if (buttonRect.contains(event.getPoint())) {
+                    BorderlessImageToggleButton.this.setBackground(BorderlessUtility.ON_MOUSE_OVER_BACKGROUND);
+                }
+            }
+        }
+
+        public void mouseExited(MouseEvent event) {
+            BorderlessImageToggleButton.this.setBackground(defaultBackground );
+
+            if (BorderlessImageToggleButton.this.isSelected()) {
+                BorderlessImageToggleButton.this.setBorder(selectedBorder);
+            } else {
+                BorderlessImageToggleButton.this.setBorder(BorderlessUtility.DEFAULT_BORDER);
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessUtility.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessUtility.java
new file mode 100644
index 0000000..77e6db5
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/BorderlessUtility.java
@@ -0,0 +1,35 @@
+/*
+ * 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.gradleplugin.userinterface.swing.common;
+
+import javax.swing.BorderFactory;
+import javax.swing.UIManager;
+import javax.swing.border.Border;
+import java.awt.Color;
+
+/**
+ * Utility functions/constants for borderless buttons
+ *
+ * @author mhunsicker
+ */
+public class BorderlessUtility {
+    public static final Color ON_MOUSE_OVER_BACKGROUND = new Color(181, 190, 214);
+    public static final Color ON_BUTTON_PRESSED_BACKGROUND = new Color(130, 146, 185);
+    public static final Border ON_MOUSEOVER_BORDER = BorderFactory.createLineBorder(new Color(8, 36, 107));
+
+    //make the default border NOT an EMPTY border (so things don't resize), but make it appear clear by using the panel background color
+    public static final Border DEFAULT_BORDER = BorderFactory.createLineBorder(UIManager.getColor("Panel.background"));
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/PreferencesAssistant.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/PreferencesAssistant.java
new file mode 100644
index 0000000..615b6ff
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/common/PreferencesAssistant.java
@@ -0,0 +1,177 @@
+/*
+ * 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.gradleplugin.userinterface.swing.common;
+
+import org.gradle.api.UncheckedIOException;
+import org.gradle.gradleplugin.foundation.settings.SettingsNode;
+
+import javax.swing.JFileChooser;
+import javax.swing.JFrame;
+import javax.swing.JSplitPane;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.Window;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * This class just helps do some of the mundane tasks of saving and restoring the location of something.
+ *
+ * @author mhunsicker
+ */
+public class PreferencesAssistant {
+    private static final String WINDOW_X = "window_x";
+    private static final String WINDOW_Y = "window_y";
+    private static final String WINDOW_WIDTH = "window_width";
+    private static final String WINDOW_HEIGHT = "window_height";
+    private static final String EXTENDED_STATE = "extended-state";
+    private static final String DIVIDER_LOCATION = "divider_location";
+    private static final String DIRECTORY_NAME = "directory_name";
+
+    public static SettingsNode saveSettings(SettingsNode settingsNode, Window window, String id, Class windowClass) {
+        Point p = window.getLocation();
+        Dimension size = window.getSize();
+
+        SettingsNode childNode = settingsNode.addChildIfNotPresent(getPrefix(windowClass, id));
+
+        childNode.setValueOfChildAsInt(WINDOW_X, p.x);
+        childNode.setValueOfChildAsInt(WINDOW_Y, p.y);
+        childNode.setValueOfChildAsInt(WINDOW_WIDTH, size.width);
+        childNode.setValueOfChildAsInt(WINDOW_HEIGHT, size.height);
+
+        return childNode;
+    }
+
+    /**
+     * This version works for frames. It makes sure it doesn't save the extended state (maximized, iconified, etc) if
+     * its iconified. Doing so, causes problems when its restored.
+     */
+    public static void saveSettings(SettingsNode settingsNode, JFrame frame, String id, Class windowClass) {
+        if (frame.getExtendedState() == JFrame.ICONIFIED) {
+            return;
+        }
+
+        SettingsNode childNode = saveSettings(settingsNode, (Window) frame, id, windowClass);
+
+        if (frame.getExtendedState() != JFrame.ICONIFIED) {
+            childNode.setValueOfChildAsInt(EXTENDED_STATE, frame.getExtendedState());
+        }
+    }
+
+    /**
+     * Call this to restore the preferences that were saved via a call to save settings. Note: if no preferences are
+     * found it doesn't do anything.
+     *
+     * @param window the window who's settings to save
+     * @param id a unique ID for these settings.
+     * @param windowClass Any class. Just used for the preferences mechanism to obtain an instance. Making this an
+     * argument gives you more flexibility.
+     */
+    public static SettingsNode restoreSettings(SettingsNode settingsNode, Window window, String id, Class windowClass) {
+        SettingsNode childNode = settingsNode.getChildNode(getPrefix(windowClass, id));
+        if (childNode == null) {
+            return null;
+        }
+
+        int x, y, width, height;
+
+        x = childNode.getValueOfChildAsInt(WINDOW_X, window.getLocation().x);
+        y = childNode.getValueOfChildAsInt(WINDOW_Y, window.getLocation().y);
+        width = childNode.getValueOfChildAsInt(WINDOW_WIDTH, window.getSize().width);
+        height = childNode.getValueOfChildAsInt(WINDOW_HEIGHT, window.getSize().height);
+
+        window.setLocation(x, y);
+        window.setSize(width, height);
+
+        return childNode;
+    }
+
+    /**
+     * This restores the position of a frame. We not only restore the size, but we'll maximize it if its was maximized
+     * when saved.
+     */
+    public static void restoreSettings(SettingsNode settingsNode, JFrame frame, String id, Class windowClass) {
+        SettingsNode childNode = restoreSettings(settingsNode, (Window) frame, id, windowClass);
+        if (childNode == null) {
+            return;
+        }
+
+        int extendedState = childNode.getValueOfChildAsInt(EXTENDED_STATE, frame.getExtendedState());
+
+        if (extendedState != JFrame.ICONIFIED) {
+            frame.setExtendedState(extendedState);
+        }
+    }
+
+    public static void saveSettings(SettingsNode settingsNode, JSplitPane splitter, String id, Class splitterClass) {
+        SettingsNode childNode = settingsNode.addChildIfNotPresent(getPrefix(splitterClass, id));
+
+        childNode.setValueOfChildAsInt(DIVIDER_LOCATION, splitter.getDividerLocation());
+    }
+
+    public static void restoreSettings(SettingsNode settingsNode, JSplitPane splitter, String id, Class splitterClass) {
+        SettingsNode childNode = settingsNode.getChildNode(getPrefix(splitterClass, id));
+        if (childNode == null) {
+            return;
+        }
+
+        int location = childNode.getValueOfChildAsInt(DIVIDER_LOCATION, splitter.getDividerLocation());
+        splitter.setDividerLocation(location);
+    }
+
+    private static String getPrefix(Class aClass, String id) {
+        return aClass.getSimpleName() + '_' + id;
+    }
+
+    /**
+     * Saves the settings of the file chooser; and by settings I mean the 'last visited directory'.
+     *
+     * @param saveCurrentDirectoryVsSelectedFilesParent this should be true true if you're selecting only directories,
+     * false if you're selecting only files. I don't know what if you allow both.
+     */
+    public static void saveSettings(SettingsNode settingsNode, JFileChooser fileChooser, String id,
+                                    Class fileChooserClass, boolean saveCurrentDirectoryVsSelectedFilesParent) {
+        SettingsNode childNode = settingsNode.addChildIfNotPresent(getPrefix(fileChooserClass, id));
+
+        String save;
+        try {
+            if (saveCurrentDirectoryVsSelectedFilesParent) {
+                save = fileChooser.getCurrentDirectory().getCanonicalPath();
+            } else {
+                save = fileChooser.getSelectedFile().getCanonicalPath();
+            }
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+        if (save != null) {
+            childNode.setValueOfChild(DIRECTORY_NAME, save);
+        }
+    }
+
+    public static void restoreSettings(SettingsNode settingsNode, JFileChooser fileChooser, String id,
+                                       Class fileChooserClass) {
+        SettingsNode childNode = settingsNode.getChildNode(getPrefix(fileChooserClass, id));
+        if (childNode == null) {
+            return;
+        }
+
+        String lastDirectory = childNode.getValueOfChild(DIRECTORY_NAME, null);
+
+        if (lastDirectory != null) {
+            fileChooser.setCurrentDirectory(new File(lastDirectory));
+        }
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/AbstractGradleUIInstance.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/AbstractGradleUIInstance.java
new file mode 100644
index 0000000..81b7538
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/AbstractGradleUIInstance.java
@@ -0,0 +1,232 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic;
+
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.gradleplugin.foundation.settings.SettingsNode;
+import org.gradle.gradleplugin.userinterface.AlternateUIInteraction;
+import org.gradle.gradleplugin.userinterface.swing.generic.tabs.GradleTab;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Font;
+import java.io.File;
+
+/**
+ A simple UI for gradle that is meant to be embedded into an IDE. This doesn't
+ have it own output since most IDEs have their own mechanism for that.
+ @author mhunsicker
+*/
+public abstract class AbstractGradleUIInstance implements BasicGradleUI {
+    protected MainGradlePanel gradlePanel;
+    protected GradlePluginLord gradlePluginLord;
+    protected SettingsNode settings;
+    protected AlternateUIInteraction alternateUIInteraction;
+
+    protected JPanel mainPanel;
+
+   public AbstractGradleUIInstance()
+    {
+      gradlePluginLord = new GradlePluginLord();
+    }
+
+   public void initialize( SettingsNode settings, AlternateUIInteraction alternateUIInteraction ) {
+        this.settings = settings;
+        this.alternateUIInteraction = alternateUIInteraction;
+
+        setupUI();
+    }
+
+    public JComponent getComponent() {
+        return mainPanel;
+    }
+
+    protected void setupUI() {
+        mainPanel = new JPanel(new BorderLayout());
+        mainPanel.add( createMainGradlePanel(), BorderLayout.CENTER);
+    }
+
+    protected Component createMainGradlePanel() {
+        gradlePanel = new MainGradlePanel(gradlePluginLord, getOutputUILord(), settings, alternateUIInteraction);
+        return gradlePanel;
+    }
+
+    public abstract OutputUILord getOutputUILord();
+
+    /**
+       Call this whenever you're about to show this panel. We'll do whatever
+       initialization is necessary.
+    */
+    public void aboutToShow() {
+        gradlePanel.aboutToShow();
+    }
+
+    /**
+       Call this to deteremine if you can close this pane. if we're busy, we'll
+       ask the user if they want to close.
+
+       @param  closeInteraction allows us to interact with the user
+       @return true if we can close, false if not.
+    */
+    public boolean canClose(CloseInteraction closeInteraction) {
+        if( !gradlePluginLord.isBusy() ) {
+           return true;
+        }
+
+       return closeInteraction.promptUserToConfirmClosingWhileBusy();
+    }
+
+    /**
+       Call this before you close the pane. This gives it an opportunity to do
+       cleanup. You probably should call canClose before this. It gives the
+       app a chance to cancel if its busy.
+    */
+    public void close() {
+        gradlePanel.aboutToClose();
+    }
+
+    public File getCurrentDirectory() {
+        return gradlePluginLord.getCurrentDirectory();
+    }
+
+    public void setCurrentDirectory(File currentDirectory) {
+        gradlePluginLord.setCurrentDirectory(currentDirectory);
+    }
+
+    /**
+       Call this to add one of your own tabs to this. You can call this at any
+       time.
+       @param  index      where to add the tab
+       @param  gradleTab  the tab to add
+    */
+    public void addGradleTab(int index, GradleTab gradleTab) {
+        gradlePanel.addGradleTab(index, gradleTab);
+    }
+
+    /**
+       Call this to remove one of your own tabs from this.
+       @param  gradleTab  the tab to remove
+    */
+    public void removeGradleTab(GradleTab gradleTab) {
+        gradlePanel.removeGradleTab(gradleTab);
+    }
+
+    /**
+       @return the total number of tabs.
+    */
+    public int getGradleTabCount() {
+        return gradlePanel.getGradleTabCount();
+    }
+
+    /**
+       @param  index      the index of the tab
+       @return the name of the tab at the specified index.
+    */
+    public String getGradleTabName(int index) {
+        return gradlePanel.getGradleTabName(index);
+    }
+
+    public GradlePluginLord getGradlePluginLord() {
+        return gradlePluginLord;
+    }
+
+    /**
+     * Returns the index of the gradle tab with the specified name.
+     *
+     * @param name the name of the tab
+     * @return the index of the tab or -1 if not found
+     */
+    public int getGradleTabIndex(String name) {
+        return gradlePanel.getGradleTabIndex(name);
+    }
+
+    /**
+     * @return the currently selected tab
+     */
+    public int getCurrentGradleTab() {
+        return gradlePanel.getCurrentGradleTab();
+    }
+
+    /**
+     * Makes the specified tab the current tab.
+     *
+     * @param index the index of the tab.
+     */
+    public void setCurrentGradleTab(int index) {
+        gradlePanel.setCurrentGradleTab( index );
+    }
+
+    /*
+      This executes the given gradle command.
+
+      @param  commandLineArguments the command line arguments to pass to gradle.
+      @param displayName           the name displayed in the UI for this command
+      @author mhunsicker
+   */
+   public void executeCommand( String commandLineArguments, String displayName )
+   {
+      gradlePluginLord.addExecutionRequestToQueue( commandLineArguments, displayName );
+   }
+
+   /**
+    This refreshes the task tree. Useful if you know you've changed something behind
+    gradle's back or when first displaying this UI.
+    */
+   public void refreshTaskTree()
+   {
+      gradlePluginLord.addRefreshRequestToQueue();
+   }
+
+    /**
+    This refreshes the task tree. Useful if you know you've changed something behind
+    gradle's back or when first displaying this UI.
+    @param additionalCommandLineArguments additional command line arguments to be passed to gradle when
+                                          refreshing the task tree.
+    */
+   public void refreshTaskTree( String additionalCommandLineArguments )
+   {
+      gradlePluginLord.addRefreshRequestToQueue( additionalCommandLineArguments );
+   }
+
+   /**
+    Determines if commands are currently being executed or not.
+
+    @return true if we're busy, false if not.
+    */
+   public boolean isBusy()
+   {
+      return gradlePluginLord.isBusy();
+   }
+
+   /**
+    This adds the specified component to the setup panel. It is added below the last
+    'default' item. You can only add 1 component here, so if you need to add multiple
+    things, you'll have to handle adding that to yourself to the one component.
+    @param component the component to add.
+    */
+   public void setCustomPanelToSetupTab( JComponent component ) {
+      gradlePanel.setCustomPanelToSetupTab( component );
+   }
+
+   /**
+    Sets the font for the output text
+    @param font the new font
+    */
+   public void setOutputTextFont( Font font ) {
+      getOutputUILord().setOutputTextFont( font );
+   }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/BasicGradleUI.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/BasicGradleUI.java
new file mode 100644
index 0000000..b1758b9
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/BasicGradleUI.java
@@ -0,0 +1,164 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic;
+
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.gradleplugin.userinterface.swing.generic.tabs.GradleTab;
+
+import javax.swing.JComponent;
+import java.awt.Font;
+
+/**
+ .
+
+ @author mhunsicker
+ */
+public interface BasicGradleUI
+{
+   public GradlePluginLord getGradlePluginLord();
+
+/*
+      @return the panel for this pane. This can be inserted directly into your UI.
+      @author mhunsicker
+   */
+   public JComponent getComponent();
+
+   /*
+      Call this whenever you're about to show this panel. We'll do whatever
+      initialization is necessary.
+      @author mhunsicker
+   */
+   public void aboutToShow();
+
+   //
+            public interface CloseInteraction
+            {
+               /*
+                  This is called if gradle tasks are being executed and you want to know if
+                  we can close. Ask the user.
+                  @return true if the user confirms cancelling the current tasks. False if not.
+                  @author mhunsicker
+               */
+               public boolean promptUserToConfirmClosingWhileBusy();
+            }
+
+   /*
+      Call this to deteremine if you can close this pane. if we're busy, we'll
+      ask the user if they want to close.
+
+      @param  closeInteraction allows us to interact with the user
+      @return true if we can close, false if not.
+      @author mhunsicker
+   */
+   public boolean canClose( CloseInteraction closeInteraction );
+
+   /*
+      Call this before you close the pane. This gives it an opportunity to do
+      cleanup. You probably should call canClose before this. It gives the
+      app a chance to cancel if its busy.
+      @author mhunsicker
+   */
+   public void close();
+
+   /*
+      @return the total number of tabs.
+      @author mhunsicker
+   */
+   public int getGradleTabCount();
+
+   /*
+      @param  index      the index of the tab
+      @return the name of the tab at the specified index.
+      @author mhunsicker
+   */
+   public String getGradleTabName( int index );
+
+    /**
+     * Returns the index of the gradle tab with the specified name.
+     * @param name the name of the tab
+     * @return the index of the tab or -1 if not found
+     */
+    public int getGradleTabIndex( String name );
+
+    /**
+     * @return the currently selected tab
+     */
+    public int getCurrentGradleTab();
+
+    /**
+     * Makes the specified tab the current tab.
+     * @param index the index of the tab.
+     */
+    public void setCurrentGradleTab( int index );
+
+   /*
+      Call this to execute the given gradle command.
+
+      @param  commandLineArguments the command line arguments to pass to gradle.
+      @param displayName           the name displayed in the UI for this command
+      @author mhunsicker
+   */
+   public void executeCommand( String commandLineArguments, String displayName );
+
+   /**
+    This refreshes the task tree. Useful if you know you've changed something behind
+    gradle's back or when first displaying this UI.
+    */
+   public void refreshTaskTree();
+
+    /**
+    This refreshes the task tree. Useful if you know you've changed something behind
+    gradle's back or when first displaying this UI.
+    @param additionalCommandLineArguments additional command line arguments to be passed to gradle when
+                                          refreshing the task tree.
+    */
+   public void refreshTaskTree( String additionalCommandLineArguments );
+
+   /**
+       Call this to add one of your own tabs to this. You can call this at any
+       time.
+       @param  index      where to add the tab
+       @param  gradleTab  the tab to add
+    */
+   public void addGradleTab(int index, GradleTab gradleTab);
+
+    /**
+       Call this to remove one of your own tabs from this.
+       @param  gradleTab  the tab to remove
+    */
+    public void removeGradleTab(GradleTab gradleTab);
+
+    public OutputUILord getOutputUILord();
+
+   /**
+      Determines if commands are currently being executed or not.
+      @return true if we're busy, false if not.
+   */
+   public boolean isBusy();
+
+   /**
+    This adds the specified component to the setup panel. It is added below the last
+    'default' item. You must call this after initialize
+    @param component the component to add.
+    */
+   public void setCustomPanelToSetupTab( JComponent component );
+
+   /**
+    Sets the font for the output text
+    @param font the new font
+    */
+   public void setOutputTextFont( Font font );
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/DualPaneUIInstance.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/DualPaneUIInstance.java
new file mode 100644
index 0000000..c981310
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/DualPaneUIInstance.java
@@ -0,0 +1,62 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic;
+
+import org.gradle.gradleplugin.foundation.settings.SettingsNode;
+import org.gradle.gradleplugin.userinterface.AlternateUIInteraction;
+
+import javax.swing.JPanel;
+import java.awt.BorderLayout;
+import java.awt.Component;
+
+/**
+ A simple UI for gradle. This has two panels that can be inserted into a
+ stand-alone application or an IDE. This is meant to hide most of the complexities
+ of gradle. The two panes are a tabbed pane for executing tasks and an output pane.
+
+ @author mhunsicker
+  */
+public class DualPaneUIInstance extends AbstractGradleUIInstance
+{
+   private OutputPanelLord outputPanelLord;
+
+   public DualPaneUIInstance() { }
+
+
+    public void initialize( SettingsNode settings, AlternateUIInteraction alternateUIInteraction) {
+
+        outputPanelLord = new OutputPanelLord( gradlePluginLord, alternateUIInteraction );
+
+        super.initialize( settings, alternateUIInteraction );
+    }
+
+   /**
+    We've overridden this to setup our splitter and our output window.
+    */
+   @Override
+    protected void setupUI() {
+        mainPanel = new JPanel(new BorderLayout());
+        mainPanel.add( createMainGradlePanel(), BorderLayout.CENTER);
+    }
+
+   public OutputUILord getOutputUILord() {
+      return outputPanelLord;
+   }
+
+   public Component getOutputPanel() {
+      return outputPanelLord.getMainPanel();
+   }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/MainGradlePanel.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/MainGradlePanel.java
new file mode 100644
index 0000000..216d95f
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/MainGradlePanel.java
@@ -0,0 +1,250 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic;
+
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.gradleplugin.foundation.settings.SettingsNode;
+import org.gradle.gradleplugin.userinterface.AlternateUIInteraction;
+import org.gradle.gradleplugin.userinterface.swing.generic.tabs.CommandLineTab;
+import org.gradle.gradleplugin.userinterface.swing.generic.tabs.FavoriteTasksTab;
+import org.gradle.gradleplugin.userinterface.swing.generic.tabs.GradleTab;
+import org.gradle.gradleplugin.userinterface.swing.generic.tabs.SetupTab;
+import org.gradle.gradleplugin.userinterface.swing.generic.tabs.TaskTreeTab;
+
+import javax.swing.*;
+import javax.swing.event.ChangeListener;
+import javax.swing.event.ChangeEvent;
+import java.awt.BorderLayout;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This is a tabbed pane meant to handle several tabs of gradle-related things. To use this, instantiate it, place it
+ * some Swing container (dialog, frame), then call aboutToShow() before you show the parent container. You can also add
+ * your own tabs to this (just call addGradleTab before calling aboutToShow()). When you shut down, call aboutToClose()
+ * before doing so.
+ *
+ * @author mhunsicker
+  */
+public class MainGradlePanel extends JPanel {
+    private static final String CURRENT_TAB = "current-tab";
+    private static final String MAIN_PANEL = "main_panel";
+
+    private GradlePluginLord gradlePluginLord;
+
+    private SettingsNode settings;
+    private AlternateUIInteraction alternateUIInteraction;
+
+    private List<GradleTab> gradleTabs = new ArrayList<GradleTab>();
+
+    private JTabbedPane tabbedPane;
+   private SetupTab setupTab;
+
+   public MainGradlePanel(GradlePluginLord gradlePluginLord, OutputUILord outputUILord, SettingsNode settings, AlternateUIInteraction alternateUIInteraction) {
+        this.alternateUIInteraction = alternateUIInteraction;
+        this.gradlePluginLord = gradlePluginLord;
+        this.settings = settings;
+        addDefaultTabs( outputUILord, alternateUIInteraction);
+    }
+
+    private void addDefaultTabs(OutputUILord outputUILord, AlternateUIInteraction alternateUIInteraction) {
+        //we'll give each tab their own settings node just so we don't have to worry about collisions.
+        gradleTabs.add(new TaskTreeTab(gradlePluginLord, settings.addChildIfNotPresent("task-tab"), alternateUIInteraction));
+        gradleTabs.add(new FavoriteTasksTab(gradlePluginLord, settings.addChildIfNotPresent("favorites-tab")));
+        gradleTabs.add(new CommandLineTab(gradlePluginLord, settings.addChildIfNotPresent("command_line-tab")));
+        setupTab = new SetupTab(gradlePluginLord, outputUILord, settings.addChildIfNotPresent("setup-tab"));
+        gradleTabs.add( setupTab );
+    }
+
+    private int getGradleTabIndex(Class soughtClass) {
+        for (int index = 0; index < gradleTabs.size(); index++) {
+            GradleTab gradleTab = gradleTabs.get(index);
+            if (gradleTab.getClass() == soughtClass) {
+               return index;
+            }
+        }
+        return -1;
+    }
+
+    public int getGradleTabIndex(String name) {
+        if (name != null) {
+            for (int index = 0; index < gradleTabs.size(); index++) {
+                GradleTab gradleTab = gradleTabs.get(index);
+                if (name.equals(gradleTab.getName())) {
+                   return index;
+                }
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * @return the currently selected tab
+     */
+    public int getCurrentGradleTab() {
+        return tabbedPane.getSelectedIndex();
+    }
+
+
+    public void setCurrentGradleTab(int index) {
+        if( index >= 0 && index < getGradleTabCount() ) {
+            tabbedPane.setSelectedIndex( index );
+        }
+    }
+
+
+    /**
+     * Call this to add one of your own tabs to this. You must call this before you call aboutToShow.
+    */
+    public void addGradleTab(int index, GradleTab gradleTab) {
+        //this can ultimately be called via external APIs so let's add a little extra error checking.
+        if (index < 0) {
+           index = 0;
+        }
+       if (index > gradleTabs.size()) {
+           index = gradleTabs.size();
+        }
+
+       gradleTabs.add(index, gradleTab);
+
+        if (tabbedPane != null) {   //if we've already displayed the tabs, we'll need to manually add it now to the tabbed pane.
+           addGradleTabToTabbedPane(index, gradleTab);
+        }
+    }
+
+    //this adds the tab. This is only to be used when adding a tab after the tabbed
+    //pane has already been displayed and populated with tabs.
+    private void addGradleTabToTabbedPane(int index, GradleTab gradleTab) {
+        tabbedPane.add(gradleTab.createComponent(), index);
+        tabbedPane.setTitleAt(index, gradleTab.getName());
+    }
+
+    public void removeGradleTab(GradleTab gradleTab) {
+        int existingIndex = gradleTabs.indexOf(gradleTab);
+        if (existingIndex == -1) {
+           return;
+        }
+
+       gradleTabs.remove(gradleTab);
+
+        tabbedPane.remove(existingIndex);
+
+        tabbedPane.invalidate();
+        tabbedPane.revalidate();
+        tabbedPane.repaint();
+    }
+
+    /**
+     * @return the total number of tabs.
+    */
+    public int getGradleTabCount() {
+        return gradleTabs.size();
+    }
+
+    /**
+     * @param index the index of the tab
+     * @return the name of the tab at the specified index.
+    */
+    public String getGradleTabName(int index) {
+        return gradleTabs.get(index).getName();
+    }
+
+    /**
+     * This is called when this about to displayed. Do any kind of initialization you need to do here.
+    */
+    public void aboutToShow() {
+        setupUI();
+
+        Iterator<GradleTab> iterator = gradleTabs.iterator();
+        while (iterator.hasNext()) {
+            GradleTab gradleTab = iterator.next();
+            gradleTab.aboutToShow();
+        }
+
+        //now start up the plugin now that everything has been initialized
+        gradlePluginLord.startExecutionQueue();
+    }
+
+    /**
+     * Notification that we're about to be closed. Here we're going to save our current settings.
+    */
+    public void aboutToClose() {
+    }
+
+    private void setupUI() {
+        setLayout(new BorderLayout());
+
+        tabbedPane = new JTabbedPane();
+        add(tabbedPane, BorderLayout.CENTER);
+
+        addTabs();
+
+        restoreLastTab();
+
+        //add a listener so we can store the current tab when it changes.
+        tabbedPane.addChangeListener(new ChangeListener() {
+            public void stateChanged(ChangeEvent e) {
+                int selection = tabbedPane.getSelectedIndex();
+                if (selection >= 0 && selection < gradleTabs.size()) {
+                    SettingsNode rootNode = settings.addChildIfNotPresent(MAIN_PANEL);
+                    rootNode.setValueOfChild(CURRENT_TAB, gradleTabs.get(selection).getName());
+                }
+            }
+        });
+    }
+
+    private void restoreLastTab() {
+        //if they're not setup, make the setup tab visible first.
+        if (!gradlePluginLord.isSetupComplete()) {
+            int tabToSelect = getGradleTabIndex(SetupTab.class);
+            if (tabToSelect != -1) {
+               tabbedPane.setSelectedIndex(tabToSelect);
+            }
+        } else {  //otherwise, try to get the last-used tab
+            int lastTabIndex = -1;
+
+            //all this is to just restore the last selected tab
+            SettingsNode rootNode = settings.getChildNode(MAIN_PANEL);
+            if (rootNode != null) {
+                String lastTabName = rootNode.getValueOfChild(CURRENT_TAB, "");
+                lastTabIndex = getGradleTabIndex(lastTabName);
+            }
+
+            if (lastTabIndex != -1) {
+               tabbedPane.setSelectedIndex(lastTabIndex);
+            }
+        }
+    }
+
+    private void addTabs() {
+        Iterator<GradleTab> iterator = gradleTabs.iterator();
+        while (iterator.hasNext()) {
+            GradleTab gradleTab = iterator.next();
+            tabbedPane.add(gradleTab.getName(), gradleTab.createComponent());
+        }
+    }
+
+   /**
+    This adds the specified component to the setup panel. It is added below the last
+    'default' item. You can only add 1 component here, so if you need to add multiple
+    things, you'll have to handle adding that to yourself to the one component.
+    @param component the component to add.
+    */
+   public void setCustomPanelToSetupTab( JComponent component ) {
+      setupTab.setCustomPanel( component );
+   }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanel.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanel.java
new file mode 100644
index 0000000..55429a4
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanel.java
@@ -0,0 +1,501 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic;
+
+import org.gradle.BuildResult;
+import org.gradle.StartParameter;
+import org.gradle.foundation.ipc.gradle.ExecuteGradleCommandServerProtocol;
+import org.gradle.foundation.output.FileLinkDefinitionLord;
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.gradleplugin.foundation.request.Request;
+import org.gradle.gradleplugin.userinterface.AlternateUIInteraction;
+
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.JButton;
+import javax.swing.AbstractAction;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.ActionEvent;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.io.File;
+
+/**
+ * This is a panel that displays the results of executing a gradle command. It shows gradle's output as well as
+ * progress.
+ *
+ * @author mhunsicker
+ */
+public class OutputPanel extends JPanel implements ExecuteGradleCommandServerProtocol.ExecutionInteraction {
+
+    private OutputPanelParent parent;
+    private AlternateUIInteraction alternateUIInteraction;
+
+    private JPanel gradleOutputTextPanel;
+    private OutputTextPane gradleOutputTextPane;
+
+    private JPanel progressPanel;
+    private JLabel progressLabel;
+    private JProgressBar progressBar;
+
+    private JPanel statusPanel;
+    private JLabel statusLabel;
+
+    private JButton executeAgainButton;
+
+    private JLabel forceShowOutputButtonLabel;   //a label that acts like a button
+
+    private boolean isBusy;     //is this actively showing output?
+    private boolean isPending;  //is this waitin got show output?
+    private boolean isPinned;   //keeps this panel open and disallows it from being re-used.
+    private boolean showProgress = true;
+    private boolean onlyShowOutputOnErrors;
+
+    private Request request;
+
+   public interface OutputPanelParent {
+
+       public void removeOutputPanel( OutputPanel outputPanel );
+
+       void reportExecuteFinished( Request request, boolean wasSuccessful );
+
+       void executeAgain( Request request, OutputPanel outputPanel );
+
+       public FileLinkDefinitionLord getFileLinkDefinitionLord();
+   }
+
+    public OutputPanel( OutputPanelParent parent, AlternateUIInteraction alternateUIInteraction ) {
+       this.parent = parent;
+       this.alternateUIInteraction = alternateUIInteraction;
+    }
+
+   /**
+    Call this after initializing this, but after setting any additional swing properties (actually, just the font for now).
+    I really only added this as an optimization. Since we'll always be setting the font, I didn't want the various style
+    objects created only to be thrown away and re-created. This way, you can set the font before we create the styles.
+
+    */
+   public void initialize() {
+      setupUI();
+   }
+
+   /**
+    * This is called whenever a new request is made. It associates this request with this output panel.
+    *
+    * @param request
+    * @param onlyShowOutputOnErrors
+    */
+   public void setRequest( Request request, boolean onlyShowOutputOnErrors )
+   {
+      this.request = request;
+      if( request.forceOutputToBeShown() ) {
+         setOnlyShowOutputOnErrors(false);
+      }
+      else {
+         setOnlyShowOutputOnErrors( onlyShowOutputOnErrors );
+      }
+
+      //set this to indeterminate until we figure out how many tasks to execute.
+      progressBar.setIndeterminate( true );
+      progressBar.setStringPainted( false ); //And don't show '0%' in the mean time.
+
+      setPending(true);
+      showProgress(true);   //make sure the progress is shown. It may have been turned off if we're reusing this component
+
+      appendGradleOutput( getPrefixText() );
+   }
+
+   /**
+    * Returns a string stating the command we're currently executing. This is placed at the beginning of
+    * the output text. This is called when we start and when the command is finished (where we replace all
+    * of our text with the total output)
+    */
+   private String getPrefixText() {return "Executing command: \"" + request.getFullCommandLine() + "\"\n";}
+
+   public boolean isPinned() {
+        return isPinned;
+    }
+
+    public void setPinned(boolean pinned) {
+        isPinned = pinned;
+    }
+
+    public boolean isBusy() {
+        return isBusy;
+    }
+
+    protected void setBusy(boolean busy) {
+        isBusy = busy;
+    }   //this should be the only way to isBusy.
+
+    public boolean isPending() {
+        return isPending;
+    }
+
+    private void setPending(boolean pending) {
+        isPending = pending;
+        if (isPending) {
+           statusLabel.setText("Waiting to execute");
+        }
+
+       progressBar.setVisible(!isPending);
+    }
+
+    public Request getRequest() {
+        return request;
+    }
+
+    private void setupUI() {
+        setLayout(new BorderLayout());
+
+        add(createInfoPanel(), BorderLayout.NORTH);
+        add(createGradleOutputPanel(), BorderLayout.CENTER);
+    }
+
+    private Component createGradleOutputPanel() {
+        gradleOutputTextPanel = new JPanel(new BorderLayout());
+
+        gradleOutputTextPane = new OutputTextPane( new OutputTextPane.Interaction()
+        {
+           public void fileClicked( File file, int line )
+           {
+              alternateUIInteraction.openFile( file, line );
+           }
+        }, alternateUIInteraction.doesSupportEditingOpeningFiles(), getFont(), parent.getFileLinkDefinitionLord() );
+
+        gradleOutputTextPanel.add( gradleOutputTextPane.asComponent(), BorderLayout.CENTER);
+
+        return gradleOutputTextPanel;
+    }
+
+    private Component createInfoPanel() {
+        JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+
+        panel.add(createStatusPanel());
+        panel.add(createProgressPanel());
+
+        return panel;
+    }
+
+    private Component createProgressPanel() {
+        progressPanel = new JPanel(new BorderLayout());
+        progressLabel = new JLabel("Progress");
+        progressBar = new JProgressBar();
+        progressBar.setStringPainted(true);
+
+        progressPanel.add(progressBar, BorderLayout.NORTH);
+        progressPanel.add(progressLabel, BorderLayout.SOUTH);
+
+        progressPanel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
+
+        progressPanel.setVisible(false);
+        return progressPanel;
+    }
+
+    private Component createStatusPanel() {
+        statusPanel = new JPanel();
+        statusPanel.setLayout(new BoxLayout(statusPanel, BoxLayout.X_AXIS));
+        statusLabel = new JLabel();
+       executeAgainButton = Utility.createButton( OutputPanel.class, "/org/gradle/gradleplugin/userinterface/swing/generic/tabs/execute.png", "Execute Again", new AbstractAction()
+        {
+           public void actionPerformed( ActionEvent e )
+           {
+              parent.executeAgain( request, OutputPanel.this );
+           }
+        });
+        executeAgainButton.setVisible( false );
+
+        //this button is only shown when the output is hidden
+        forceShowOutputButtonLabel = new JLabel("Show Output");
+
+        forceShowOutputButtonLabel.addMouseListener(new MouseAdapter() {
+            public void mouseClicked(MouseEvent e) {
+                forciblyShowOutput();
+            }
+
+            public void mouseEntered(MouseEvent e) {
+                forceShowOutputButtonLabel.setForeground(UIManager.getColor("textHighlightText"));
+            }
+
+            public void mouseExited(MouseEvent e) {
+                forceShowOutputButtonLabel.setForeground(UIManager.getColor("Label.foreground"));
+            }
+        });
+
+        statusPanel.add( executeAgainButton );
+        statusPanel.add( Box.createHorizontalStrut( 2 ) );
+        statusPanel.add(statusLabel);
+        statusPanel.add(Box.createHorizontalGlue());
+        statusPanel.add(forceShowOutputButtonLabel);
+
+        statusPanel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
+        return statusPanel;
+    }
+
+    /**
+    * Call this if you're going to reuse this. it resets its output.
+    */
+    public void reset() {
+        executeAgainButton.setVisible( false );
+        statusLabel.setText("");
+        statusLabel.setForeground(UIManager.getColor("Label.foreground"));
+        gradleOutputTextPane.setText("");
+        progressLabel.setText("");
+    }
+
+    /**
+       * Call this to append text to the gradle output field. We'll also move the caret to the end.
+       *
+       * @param  text       the text to add
+    */
+    private void appendGradleOutput(final String text) {
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                gradleOutputTextPane.appendText( text );
+            }
+        });
+    }
+
+    private void setProgress( final String text, final float percentComplete) {
+       SwingUtilities.invokeLater( new Runnable()
+       {
+          public void run()
+          {
+              progressBar.setValue((int) percentComplete);
+              progressLabel.setText(text);
+          }
+       } );
+    }
+
+    /**
+       Notification that execution of a task or tasks has been started.
+    */
+    public void reportExecutionStarted() {
+       SwingUtilities.invokeLater( new Runnable()
+       {
+          public void run()
+          {
+             setPending(false);
+             setBusy(true);
+             setProgress("Starting", 0);
+             if (showProgress) {
+                progressPanel.setVisible(true);
+             }
+
+             statusLabel.setText("Executing");
+
+             //give the user the option to override this.
+             forceShowOutputButtonLabel.setVisible(onlyShowOutputOnErrors);
+          }
+       } );
+    }
+
+   /**
+    * Notification of the total number of tasks that will be executed. This is called after reportExecutionStarted and before any tasks are executed.
+    *
+    * @param size the total number of tasks.
+    */
+   public void reportNumberOfTasksToExecute( final int size )
+   {  //if we only have a single task, then the intire process will be indeterminately long (it'll just from 0 to 100)
+      SwingUtilities.invokeLater( new Runnable()
+       {
+          public void run()
+          {
+            boolean isIndeterminate = size == 1;
+            progressBar.setIndeterminate( isIndeterminate );
+            progressBar.setStringPainted( !isIndeterminate );
+          }
+      } );
+   }
+
+   /**
+     * Notification that execution of all tasks has completed. This is only called once at the end.
+     *
+     * @param wasSuccessful whether or not gradle encountered errors.
+     * @param buildResult   contains more detailed information about the result of a build.
+     * @param output        the text that gradle produced. May contain error information, but is usually just status.
+     */
+    public void reportExecutionFinished(boolean wasSuccessful, BuildResult buildResult, String output) {
+        reportExecutionFinished(wasSuccessful, output, buildResult.getFailure());
+    }
+
+    /**
+     * Notification that execution of a task has completed. This is the task you initiated and not for each subtask or dependent task.
+     *
+     * @param  wasSuccessful whether or not gradle encountered errors.
+     * @param  output        the text that gradle produced. May contain error information, but is usually just status.
+     *
+     * @param throwable
+    */
+    public void reportExecutionFinished( final boolean wasSuccessful, final String output, final Throwable throwable) {
+       SwingUtilities.invokeLater( new Runnable()
+       {
+          public void run()
+          {
+              setPending(false); //this can be called before we actually get a start message if it fails early. This clears the pending flag so we know we can reuse it.
+              setBusy(false);
+              progressPanel.setVisible(false);
+
+              //Make the output equal to all of our output. There are some timing issues where we don't get the last live output from gradle.
+              //This 'output' is the entire text. This way we always get all output.
+              String newText = getPrefixText() + output;
+              gradleOutputTextPane.setText( newText );
+
+              //show the user the time we finished this.
+              SimpleDateFormat formatter = new SimpleDateFormat("h:mm:ss aa");
+              String formattedTime = formatter.format(Calendar.getInstance().getTime());
+
+              if (wasSuccessful) {
+                  statusLabel.setText("Completed successfully at " + formattedTime);
+                  appendGradleOutput("\nCompleted Successfully");
+              } else {
+                  statusLabel.setText("Completed with errors at " + formattedTime);
+                  statusLabel.setForeground(Color.red.darker());
+
+                  //since errors occurred, show the output. If onlyShowOutputOnErrors is false, this textPanel will already be visible.
+                  gradleOutputTextPanel.setVisible(true);
+              }
+
+              executeAgainButton.setVisible( true );
+
+              appendThrowable(throwable);
+
+              //lastly, if the text output is not visible, make the 'show output' button visible
+              forceShowOutputButtonLabel.setVisible(!gradleOutputTextPanel.isVisible());
+
+              parent.reportExecuteFinished( request, wasSuccessful );
+
+          }
+       } );
+    }
+
+    private void appendThrowable(Throwable throwable) {
+        if (throwable != null) {
+            String output = GradlePluginLord.getGradleExceptionMessage(throwable, StartParameter.ShowStacktrace.ALWAYS_FULL);
+            appendGradleOutput(output);
+        }
+    }
+
+    /**
+     * Notification that a single task has completed. Note: the task you kicked off probably executes other tasks.
+     *
+     * @param currentTaskName the task being executed
+     * @param percentComplete the percent complete of all the tasks that make up the task you requested.
+    */
+    public void reportTaskStarted(String currentTaskName, float percentComplete) {
+        setProgress(currentTaskName, percentComplete);
+    }
+
+    public void reportTaskComplete(String currentTaskName, float percentComplete) {
+        setProgress(currentTaskName, percentComplete);
+    }
+
+    public void reportFatalError(String message) {
+        appendGradleOutput('\n' + message + "\n\nFailed.\n");
+    }
+
+    /**
+     * Report real-time output from gradle and its subsystems (such as ant).
+     *
+     * @param output a single line of text to show.
+     * @author mhunsicker
+    */
+    public void reportLiveOutput(String output) {
+        appendGradleOutput(output);
+    }
+
+    /**
+     * Determines if this panel is ready to be reused. Currently, if its not busy or pinned, it can be reused.
+     *
+     * @author mhunsicker
+    */
+    public boolean canBeReusedNow() {
+        return !isPending && !isBusy && !isPinned;
+    }
+
+    /**
+     * Call this to show progress. Some tasks have no useful progress, so this allows you to disable it.
+     *
+     * @param showProgress true to show a progress bar, false not to.
+    */
+    private void showProgress(boolean showProgress) {
+        this.showProgress = showProgress;
+        progressPanel.setVisible(showProgress);
+    }
+
+    /**
+     * This overrides the onlyShowOutputOnErrors
+    */
+    private void forciblyShowOutput() {
+        gradleOutputTextPanel.setVisible(true);
+        forceShowOutputButtonLabel.setVisible(false);
+    }
+
+    public void setOnlyShowOutputOnErrors(boolean value) {
+        this.onlyShowOutputOnErrors = value;
+        gradleOutputTextPanel.setVisible(!value);
+    }
+
+    public boolean getOnlyShowOutputOnErrors() {
+        return onlyShowOutputOnErrors;
+    }
+
+    public boolean close() {
+        if (request != null)   //if we have a request, we can only close if it allows us to.
+        {
+            if (!request.cancel()) {
+               return false;
+            }
+        }
+
+        parent.removeOutputPanel( this );
+
+        setPinned(false);  //unpin it when it is removed
+        return true;
+    }
+
+   /**
+    Sets the font for this component.
+
+    @param font the desired <code>Font</code> for this component
+    @beaninfo preferred: true
+    bound: true
+    attribute: visualUpdate true
+    description: The font for the component.
+    @see Component#getFont
+    */
+   @Override
+   public void setFont( Font font )
+   {
+      super.setFont( font );
+      if( gradleOutputTextPane != null )  //this gets called by internal Swing APIs, so we may not have this yet.
+      {
+         gradleOutputTextPane.setFont( font );
+      }
+   }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanelLord.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanelLord.java
new file mode 100644
index 0000000..ee3db53
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputPanelLord.java
@@ -0,0 +1,474 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic;
+
+import org.gradle.foundation.output.FileLinkDefinitionLord;
+import org.gradle.foundation.queue.ExecutionQueue;
+import org.gradle.foundation.common.ObserverLord;
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.gradleplugin.foundation.request.ExecutionRequest;
+import org.gradle.gradleplugin.foundation.request.RefreshTaskListRequest;
+import org.gradle.gradleplugin.foundation.request.Request;
+import org.gradle.gradleplugin.userinterface.AlternateUIInteraction;
+
+import javax.swing.*;
+import java.awt.BorderLayout;
+import java.awt.Point;
+import java.awt.Font;
+import java.awt.event.ActionEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This class manages displaying the results of a gradle execution in a panel
+ * inside a JTabbedPane. It can reuse existing tabs but creates new ones if you
+ * run multiple things concurrently.
+ * @author mhunsicker
+ */
+public class OutputPanelLord implements OutputUILord, GradlePluginLord.RequestObserver, OutputPanel.OutputPanelParent {
+
+    private JPanel mainPanel;
+    private JTabbedPane tabbedPane;
+
+    private JPopupMenu popupMenu;
+
+    private boolean onlyShowOutputOnErrors;
+    private JMenuItem closeMenuItem;
+    private JMenuItem closeAllMenuItem;
+    private JMenuItem closeAllButThisMenuItem;
+    private JMenuItem togglePinStateMenuItem;
+
+   private ObserverLord<OutputObserver> observerLord = new ObserverLord<OutputObserver>();
+   private GradlePluginLord gradlePluginLord;
+   private AlternateUIInteraction alternateUIInteraction;
+   private Font font;
+
+    private FileLinkDefinitionLord fileLinkDefinitionLord;
+
+    private ExecutionRequest lastExecutionRequest;
+
+    public OutputPanelLord( GradlePluginLord gradlePluginLord, AlternateUIInteraction alternateUIInteraction ) {
+      this.gradlePluginLord = gradlePluginLord;
+      this.alternateUIInteraction = alternateUIInteraction;
+
+       fileLinkDefinitionLord = new FileLinkDefinitionLord();
+
+      //add the OutputPanelLord as a request observer so it can create new tabs when new requests are added.
+        gradlePluginLord.addRequestObserver( this, true );
+
+        setupUI();
+
+       //gradle formats some output in 'ascii art' fashion. This ensures things line up properly.
+       Font font = new Font("Monospaced", Font.PLAIN, UIManager.getDefaults().getFont("Label.font").getSize());
+
+       setOutputTextFont( font );
+    }
+
+   public JPanel getMainPanel() {
+        return mainPanel;
+    }
+
+    private void setupUI() {
+        mainPanel = new JPanel(new BorderLayout());
+
+        tabbedPane = new JTabbedPane();
+        mainPanel.add(tabbedPane, BorderLayout.CENTER);
+
+        setupPopupMenu();
+    }
+
+    private void setupPopupMenu() {
+        popupMenu = new JPopupMenu();
+
+        closeMenuItem = new JMenuItem(new AbstractAction("Close") {
+            public void actionPerformed(ActionEvent e) {
+                closeSelectedTab();
+            }
+        });
+        popupMenu.add(closeMenuItem);
+
+        closeAllMenuItem = new JMenuItem(new AbstractAction("Close All") {
+            public void actionPerformed(ActionEvent e) {
+                closeAllTabs();
+            }
+        });
+        popupMenu.add(closeAllMenuItem);
+
+        closeAllButThisMenuItem = new JMenuItem(new AbstractAction("Close All But This") {
+            public void actionPerformed(ActionEvent e) {
+                closeAllButSelectedTab();
+            }
+        });
+
+        popupMenu.add(closeAllButThisMenuItem);
+        popupMenu.addSeparator();
+
+        togglePinStateMenuItem = new JMenuItem(new AbstractAction("Pin") {
+            public void actionPerformed(ActionEvent e) {
+                togglePinSelectedTab();
+            }
+        });
+
+        popupMenu.add(togglePinStateMenuItem);
+
+        tabbedPane.addMouseListener(new MouseAdapter() {
+            public void mouseClicked(MouseEvent e) {
+                if (e.getClickCount() == 1 && e.getButton() == MouseEvent.BUTTON3) {
+                    enablePopupMenuAppropriately();
+                    Point point = e.getPoint();
+                    popupMenu.show(tabbedPane, point.x, e.getPoint().y);
+                }
+            }
+        });
+    }
+
+    private void enablePopupMenuAppropriately() {
+        OutputPanel panel = getSelectedOutputPanel();
+        if (panel == null) {
+            closeMenuItem.setEnabled(false);
+            togglePinStateMenuItem.setEnabled(false);
+        } else {
+            closeMenuItem.setEnabled(true);
+
+            //change the name of this to reflect what is actually happening.
+            if (panel.isPinned()) {
+               togglePinStateMenuItem.setText("Unpin");
+            }
+            else {
+               togglePinStateMenuItem.setText("Pin");
+            }
+        }
+    }
+
+    /**
+       This obtains an output panel for executing a task. It will try to reuse
+       an existing tab.
+
+       I don't like how this mechanism works. Its not obvious what you're going
+       to get and how the tabs will be reused (from a user's standpoint). IntelliJ
+       Idea doesn't allow multiple compiles/builds at a time, so they don't have
+       this issue there. They do have it on Find where they have an option to
+       explicitly display in a new tab. I don't think that quite works here
+       as you don't normally think about the output. This is only an issue if
+       you run multiple tasks at once or try to run new tasks while others are
+       still executing. Ultimately, I don't think tabs are the way to go because
+       closing a bunch of tabs is a pain.
+
+       @param  description          the title we'll give to the output.
+       @param selectOutputPanel true to select the output panel after we setup
+                                the tab, false if not. This is really only useful
+                                if you're calling this for multiple tasks one
+                                right after the other. Pass in false for all but
+                                the first (or last) one depending on what you want.
+       @param reuseSelectedOutputPanelFirst true to attempt to reuse the current
+                                output tab. Otherwise, we'll go from left to right
+                                looking for a tab to reuse. This is really only
+                                useful if you're calling this for multiple tasks
+                                one after the other. In that case, you probably
+                                want to pass in false.
+       @return an output panel.
+    */
+    private OutputPanel getOutputPanelForExecution(String description, boolean selectOutputPanel, boolean reuseSelectedOutputPanelFirst) {
+        OutputTab outputPanel = findExistingOutputPanelForExecution(reuseSelectedOutputPanelFirst);
+        if (outputPanel != null) {
+            outputPanel.setTabHeaderText(description);
+            outputPanel.reset();
+        } else {  //we don't have an existing tab. Create a new one.
+            outputPanel = new OutputTab( this, description, alternateUIInteraction );
+            outputPanel.setFont( font );
+            outputPanel.initialize();
+            tabbedPane.addTab(description, outputPanel);
+            if (selectOutputPanel) {
+               tabbedPane.setSelectedComponent(outputPanel);
+            }
+
+           Utility.setTabComponent15Compatible(tabbedPane, tabbedPane.getTabCount() - 1, outputPanel.getTabHeader());
+        }
+
+        return outputPanel;
+    }
+
+    /**
+       This locates an existing panel to reuse.
+    */
+    private OutputTab findExistingOutputPanelForExecution(boolean considerSelectedTabFirst) {
+        OutputTab outputPanel = null;
+        if (considerSelectedTabFirst) {
+            outputPanel = (OutputTab) tabbedPane.getSelectedComponent();
+            if (outputPanel != null && outputPanel.canBeReusedNow()) {
+               return outputPanel;
+            }
+        }
+
+        Iterator<OutputPanel> iterator = getOutputPanels().iterator();
+        while (iterator.hasNext()) {
+            outputPanel = (OutputTab) iterator.next();
+            if (outputPanel.canBeReusedNow()) {
+               return outputPanel;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+       @return a list of all the output panels currenly in the tabbed pane.
+    */
+    private List<OutputPanel> getOutputPanels() {
+        List<OutputPanel> panels = new ArrayList<OutputPanel>();
+        for (int index = 0; index < tabbedPane.getTabCount(); index++) {
+            OutputPanel outputPanel = (OutputPanel) tabbedPane.getComponentAt(index);
+            panels.add(outputPanel);
+        }
+
+        return panels;
+    }
+
+   /**
+    This formats a display name so it isn't too long. The actual size is purely arbitrary.
+    @param displayName the current display name
+    @return a display name that isn't too long to display on tabs.
+    */
+   private String reformatDisplayName( String displayName )
+   {
+      if( displayName.length() <= 20 ) {
+         return displayName;   //its fine
+      }
+
+       //I'm going 6 characters less because it looks stupid to replace 3 characters with 3 characters.
+       //There's no absolute amount here, this just seems to look better.
+      return displayName.substring( 0, 14 ) + "...";
+   }
+
+   /**
+       Determines if any tasks are currently being run. We check all of our
+       OutputPanels.
+       @return true if we're busy, false if not.
+    */
+    public boolean isBusy() {
+        Iterator<OutputPanel> iterator = getOutputPanels().iterator();
+        while (iterator.hasNext()) {
+            OutputPanel outputPanel = iterator.next();
+            if (outputPanel.isBusy()) {
+               return true;
+            }
+        }
+        return false;
+    }
+
+    public void setOnlyShowOutputOnErrors(boolean value) {
+        this.onlyShowOutputOnErrors = value;
+    }
+
+    public boolean getOnlyShowOutputOnErrors() {
+        return onlyShowOutputOnErrors;
+    }
+
+    private void closeSelectedTab() {
+        OutputTab component = getSelectedOutputPanel();
+        if (component != null) {
+           component.close();
+        }
+    }
+
+    private void closeAllTabs() {
+        Iterator<OutputPanel> iterator = getOutputPanels().iterator();
+        while (iterator.hasNext()) {
+            OutputPanel outputPanel = iterator.next();
+            outputPanel.close();
+        }
+    }
+
+    private void closeAllButSelectedTab() {
+        OutputTab component = getSelectedOutputPanel();
+        Iterator<OutputPanel> iterator = getOutputPanels().iterator();
+        while (iterator.hasNext()) {
+            OutputPanel outputPanel = iterator.next();
+            if (outputPanel != component) {
+               outputPanel.close();
+            }
+        }
+    }
+
+    /**
+       Changes the current pinned status of the selected tab.
+    */
+    private void togglePinSelectedTab() {
+        OutputTab component = getSelectedOutputPanel();
+        if (component != null) {
+           component.setPinned(!component.isPinned());
+        }
+    }
+
+    private OutputTab getSelectedOutputPanel() {
+        return (OutputTab) tabbedPane.getSelectedComponent();
+    }
+
+    //return the output panel for the specified request.
+    private OutputPanel getOutputPanel(ExecutionQueue.Request request) {
+        Iterator<OutputPanel> iterator = getOutputPanels().iterator();
+        while (iterator.hasNext()) {
+            OutputPanel outputPanel = iterator.next();
+            if (outputPanel.getRequest() == request) {
+               return outputPanel;
+            }
+        }
+        return null;
+    }
+
+   public void executeAgain( Request request, OutputPanel outputPanel )
+   {
+      //this needs to work better. It needs to do the execute again in the same
+      //OutputPanel. However, because this generically listens for requests and
+      //adds them to this panel, things are more complicated.
+      request.executeAgain( gradlePluginLord );
+   }
+
+   public void reportExecuteFinished( final Request request, final boolean wasSuccessful )
+   {
+      observerLord.notifyObservers( new ObserverLord.ObserverNotification<OutputObserver>()
+      {
+         public void notify( OutputObserver observer )
+         {
+            observer.reportExecuteFinished( request, wasSuccessful );
+         }
+      } );
+   }
+
+   public void removeOutputPanel( final OutputPanel outputPanel )
+   {
+      tabbedPane.remove( outputPanel );
+
+      observerLord.notifyObservers( new ObserverLord.ObserverNotification<OutputObserver>()
+      {
+         public void notify( OutputObserver observer )
+         {
+            observer.outputTabClosed( outputPanel.getRequest() );
+         }
+      } );
+   }
+
+   public void executionRequestAdded( final ExecutionRequest request )
+   {
+       lastExecutionRequest = request;
+
+      String displayName = reformatDisplayName( request.getDisplayName() );
+      requestAdded( request, "Execute '" + displayName + "'" );
+      observerLord.notifyObservers( new ObserverLord.ObserverNotification<OutputObserver>()
+      {
+         public void notify( OutputObserver observer )
+         {
+            observer.executionRequestAdded( request );
+         }
+      } );
+   }
+
+   public void refreshRequestAdded( final RefreshTaskListRequest request )
+   {
+      requestAdded( request, "Refresh" );
+      observerLord.notifyObservers( new ObserverLord.ObserverNotification<OutputObserver>()
+      {
+         public void notify( OutputObserver observer )
+         {
+            observer.refreshRequestAdded( request );
+         }
+      } );
+   }
+
+   private void requestAdded( Request request, String name )
+   {
+      OutputPanel outputPanel = getOutputPanelForExecution(name, false, true );
+
+      outputPanel.setRequest( request, onlyShowOutputOnErrors );
+      request.setExecutionInteraction( outputPanel );
+   }
+
+   /**
+    Notification that a command is about to be executed. This is mostly useful
+    for IDE's that may need to save their files.
+
+    @param request the request to be executed
+    @author mhunsicker
+    */
+   public void aboutToExecuteRequest( Request request )
+   {
+   }
+
+   /**
+    Notification that the command has completed execution.
+
+    @param request the original request containing the command that was executed
+    @param result  the result of the command
+    @param output  the output from gradle executing the command
+    */
+   public void requestExecutionComplete( Request request, int result, String output )
+   {
+
+   }
+
+   public void addOutputObserver( OutputObserver observer, boolean inEventQueue )
+   {
+      observerLord.addObserver( observer, inEventQueue );
+   }
+
+   public void removeOutputObserver( OutputObserver observer )
+   {
+      observerLord.removeObserver( observer );
+   }
+
+   public int getTabCount()
+   {
+      return tabbedPane.getTabCount();
+   }
+
+   /**
+    Sets the font for the output text
+    @param font the new font
+    */
+   public void setOutputTextFont( Font font ) {
+
+      this.font = font;
+      Iterator<OutputPanel> iterator = getOutputPanels().iterator();
+      while( iterator.hasNext() )
+      {
+         OutputPanel outputPanel = iterator.next();
+         outputPanel.setFont( font );
+      }
+   }
+
+    public Font getOutputTextFont() {
+        return font;
+    }
+
+    public FileLinkDefinitionLord getFileLinkDefinitionLord() {
+        return fileLinkDefinitionLord;
+    }
+
+    /*
+    This re-executes the last execution command (ignores refresh commands).
+    This is potentially useful for IDEs to hook into (hotkey to execute last command).
+     */
+    public void reExecuteLastCommand()
+    {
+        ExecutionRequest executionRequest = lastExecutionRequest;
+        if( executionRequest != null ) {
+            gradlePluginLord.addExecutionRequestToQueue( executionRequest.getFullCommandLine(), executionRequest.getDisplayName(), executionRequest.forceOutputToBeShown() );
+        }
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputTab.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputTab.java
new file mode 100644
index 0000000..95a64a2
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputTab.java
@@ -0,0 +1,163 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.gradleplugin.userinterface.AlternateUIInteraction;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.ImageIcon;
+import javax.imageio.ImageIO;
+import java.awt.Component;
+import java.awt.image.BufferedImage;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.InputStream;
+import java.io.IOException;
+
+/**
+ * This just wraps up an OutputPanel so it has a tab header that can be dynamic. The current (rather awkward) JTabbedPane
+ * implementation is to separate the tab contents from its component. This only works with java 1.6 or later.
+ *
+ * @author mhunsicker
+ */
+public class OutputTab extends OutputPanel {
+
+    private static final Logger LOGGER = Logging.getLogger(OutputTab.class);
+
+    private JPanel mainPanel;
+    private JLabel mainTextLabel;
+    private JLabel pinnedLabel;
+    private JLabel closeLabel;
+
+   private static ImageIcon closeIcon;
+   private static ImageIcon closeHighlightIcon;
+
+   public OutputTab(OutputPanelParent parent, String header, AlternateUIInteraction alternateUIInteraction) {
+        super( parent, alternateUIInteraction );
+        mainPanel = new JPanel();
+        mainPanel.setOpaque(false);
+        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.X_AXIS));
+
+        mainTextLabel = new JLabel(header);
+        pinnedLabel = new JLabel("(Pinned) ");
+        pinnedLabel.setVisible(isPinned());
+
+        setupCloseLabel();
+
+        mainPanel.add(mainTextLabel);
+        mainPanel.add(Box.createHorizontalStrut(5));
+        mainPanel.add(pinnedLabel);
+        mainPanel.add(closeLabel);
+    }
+
+   private void setupCloseLabel()
+   {
+      if( closeIcon == null )
+      {
+          BufferedImage closeImage = getImageResource( "close.png" );
+          BufferedImage closeHighlightImage = getImageResource( "close-highlight.png" );
+
+          if( closeImage != null ) {
+             closeIcon = new ImageIcon( closeImage );
+          }
+
+         if( closeHighlightImage != null ) {
+            closeHighlightIcon = new ImageIcon( closeHighlightImage );
+         }
+      }
+
+      closeLabel = new JLabel( closeIcon );
+      closeLabel.addMouseListener( new MouseAdapter() {
+         @Override
+         public void mouseEntered( MouseEvent e ) {
+            closeLabel.setIcon( closeHighlightIcon );
+         }
+
+         @Override
+         public void mouseExited( MouseEvent e ) {
+            closeLabel.setIcon( closeIcon );
+         }
+
+         public void mouseClicked(MouseEvent e) {
+             close();
+         }
+      } );
+   }
+
+   private BufferedImage getImageResource( String imageResourceName )
+    {
+       InputStream inputStream = getClass().getResourceAsStream(imageResourceName);
+       if (inputStream != null) {
+          try {
+              BufferedImage image = ImageIO.read(inputStream);
+             return image;
+          }
+          catch ( IOException e) {
+              LOGGER.error("Reading image " + imageResourceName, e);
+          }
+       }
+
+       return null;
+    }
+
+    /**
+     * Call this if you're going to reuse this. it resets its output.
+     *
+     * @author mhunsicker
+     */
+    @Override
+    public void reset() {
+        super.reset();
+        closeLabel.setEnabled(true);
+    }
+
+    public Component getTabHeader() {
+        return mainPanel;
+    }
+
+    public void setTabHeaderText(String newText) {
+        mainTextLabel.setText(newText);
+    }
+
+    public boolean close() {
+        closeLabel.setEnabled(false); // provide feedback to the user that we received their click
+
+        boolean result = super.close();
+        if( result ) {
+           closeLabel.setEnabled(true);
+        }
+
+       return result;
+    }
+
+    /**
+     * Overridden so we can indicate the pinned state.
+     *
+     * @param pinned whether or not we're pinned
+     * @author mhunsicker
+    */
+    @Override
+    public void setPinned(boolean pinned) {
+        pinnedLabel.setVisible(pinned);
+
+        super.setPinned(pinned);
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputTextPane.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputTextPane.java
new file mode 100644
index 0000000..236f81c
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputTextPane.java
@@ -0,0 +1,340 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic;
+
+import org.gradle.foundation.output.FileLinkDefinitionLord;
+import org.gradle.foundation.output.LiveOutputParser;
+import org.gradle.foundation.output.FileLink;
+
+import javax.swing.*;
+import javax.swing.text.*;
+
+import java.awt.*;
+import java.awt.datatransfer.StringSelection;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.ActionEvent;
+import java.util.List;
+import java.util.Iterator;
+import java.io.File;
+
+/**
+  Rich text pane meant to simplify adding text, scrolling, prevent line wrapping, and highlighting FileLinks.
+ */
+public class OutputTextPane
+{
+   private JScrollPane scroll;
+   private final TextPane textPane;
+   private DefaultStyledDocument document;
+
+   private Font font;
+
+   private AttributeSet defaultStyle;  //the style of most text
+   private AttributeSet fileStyle;     //the style of file links
+
+   private LiveOutputParser liveOutputParser;
+
+   private Interaction interaction;
+   private boolean hasClickableFiles;  //determines whether or not we allow the user to click on files. We'll highlight them if we allow this.
+
+   private JPopupMenu popupMenu;
+
+   /**
+    This allows us to interact with our parent control.
+    */
+   public interface Interaction {
+      /**
+       Notification that the user clicked a file link
+       @param file the file that was clicked
+       @param line the line number the file link points to. Will be -1 if no line was specified
+       */
+      public void fileClicked( File file, int line );
+   }
+
+   public OutputTextPane( Interaction interaction, boolean hasClickableFiles, Font font, FileLinkDefinitionLord fileLinkDefinitionLord ) {
+      this.interaction = interaction;
+      this.hasClickableFiles = hasClickableFiles;
+      this.font = font;
+
+      document = new DefaultStyledDocument();
+      textPane = new TextPane( document );
+      textPane.setEditable( false );
+      textPane.setAutoscrolls( false );
+
+      scroll = new JScrollPane( textPane );
+      scroll.setAutoscrolls( false );
+      scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+      scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+
+      //we have to set a new caret so we can force it not to scroll. We want to control the scrolling.
+      //this is so a user can scroll up to look at the constantly updating output and not have it continue
+      //scrolling. It also allows them to select something while the output is being updated. Without
+      //this, their selection would be removed with each update.
+      DefaultCaret caret = new DefaultCaret();
+      caret.setUpdatePolicy( DefaultCaret.NEVER_UPDATE );
+      textPane.setCaret( caret );
+
+      Color background = Color.white;
+      textPane.setBackground( background );  //its not editable, but it looks better with a white background. (I tried using UI.Manager.getColor( "TextArea.background" ) (and others) but it was showing up as gray when using inside Idea. I think the L&F remapped some things and we want it white.
+      scroll.setBackground( background );    //the scroll pane was showing up as grey in the Idea plugin. Not sure why. This should fix it.
+      scroll.getViewport().setBackground( background );    //this makes the non-text area of the scroll pane appear white on Windows (if you have short text).
+      resetFontStyles();
+
+      textPane.addMouseListener( new MouseAdapter()
+      {
+         @Override
+         public void mouseReleased( MouseEvent e )
+         {
+            handleClick( e.getButton() == MouseEvent.BUTTON3, e.getPoint() );
+         }
+      } );
+       liveOutputParser = new LiveOutputParser( fileLinkDefinitionLord, true );
+   }
+
+   private void resetFontStyles()
+   {
+      //setup the fileStyle
+      StyleContext styleContent = StyleContext.getDefaultStyleContext();
+
+      defaultStyle = createDefaultAttributeSet();
+
+      //modify the default to have a blue color and an underline
+      fileStyle = createDefaultAttributeSet();
+      fileStyle = styleContent.addAttribute( fileStyle, StyleConstants.Foreground, Color.blue);
+      fileStyle = styleContent.addAttribute( fileStyle, StyleConstants.Underline, true );
+   }
+
+   /**
+    This creates a standard attribute set for the current text pane's font.
+    @return an attribute set
+    */
+   private AttributeSet createDefaultAttributeSet()
+   {
+      StyleContext styleContent = StyleContext.getDefaultStyleContext();
+      AttributeSet attributeSet = styleContent.addAttribute( SimpleAttributeSet.EMPTY, StyleConstants.FontFamily, font.getName());
+      attributeSet = styleContent.addAttribute( attributeSet, StyleConstants.FontSize, font.getSize() );
+      return attributeSet;
+   }
+
+   public JComponent asComponent() { return scroll; }
+
+   public String getText() { return textPane.getText(); }
+
+   /**
+    When a user clicks, we determine if a FileLink was clicked on and if so, notify our interaction.
+    @param point
+    */
+   private void handleClick( boolean isRightButton, Point point )
+   {
+      if( isRightButton )
+      {
+         showPopup( point );
+      }
+      else
+      {
+         if( hasClickableFiles )
+         {
+            FileLink fileLink = getFileLinkAt( point );
+            if( fileLink != null )
+            {
+               interaction.fileClicked( fileLink.getFile(), fileLink.getLineNumber() );
+            }
+         }
+      }
+   }
+
+   private void showPopup( Point point )
+   {
+      buildPopup();
+
+      popupMenu.show( textPane, point.x, point.y );
+   }
+
+   private void buildPopup()
+   {
+      if( popupMenu != null )
+      {
+         return;
+      }
+
+      popupMenu = new JPopupMenu( );
+
+      popupMenu.add( new AbstractAction( "Copy")
+      {
+         public void actionPerformed( ActionEvent e )
+         {
+            String text = textPane.getSelectedText();
+            if( text != null )
+            {
+               Toolkit.getDefaultToolkit().getSystemClipboard().setContents( new StringSelection( text ), null );
+            }
+         }
+      } );
+
+      popupMenu.add( new AbstractAction( "Select All")
+      {
+         public void actionPerformed( ActionEvent e )
+         {
+            textPane.selectAll();
+         }
+      } );
+   }
+
+   /**
+    This appends the text to gradle's output window.
+    */
+   public void appendText( String text ) {
+      appendText( text, false );
+   }
+
+
+   /**
+   This sets the full text of this control, removing existing text.
+    @param text the new text of this control
+    */
+   public void setText( String text ) {
+      liveOutputParser.reset();
+      appendText( text, true );
+   }
+
+   /**
+    This appends or replaces the text to gradle's output window. This should be simple, but we've got a
+    complication: We want to scroll nicely. By default, adding text doesn't scroll at all. We want to
+    scroll so the user can see the latest output. However, if the user scrolls manually to see older
+    output, we don't want to keep scrolling to the end on them. This behavior is surprisingly complicated
+    to achieve. We have to determine if we're at the end of the viewport. If we are, we can scroll.
+    However, we have to perform the actual scroll in an invokeLater because the text control's size
+    hasn't been updated yet. Also, this is where we parse the text looking for FileLinks
+
+    @param text the text to add
+    @param replaceExisting true to replace the existing text completely, false to just append to the end.
+    */
+   private void appendText( String text, boolean replaceExisting ) {
+
+      Rectangle viewBounds = scroll.getViewport().getViewRect();  //the bounds of what we can see
+      Dimension viewSize = scroll.getViewport().getViewSize();    //the total bounds of the text
+
+      int maxViewBoundsY = viewBounds.y + viewBounds.height;
+
+      //if they're close to the end, we should scroll. I could have said if viewSize.height == maxViewBoundsY
+      //but this allows the user to scroll close to the end and get the same results
+      boolean shouldScroll = viewSize.height - maxViewBoundsY < 20;
+
+      try
+      {
+         if( replaceExisting )   //if we're supposed to be replacing, then do so.
+         {
+            document.remove( 0, document.getLength() );
+         }
+
+         document.insertString( document.getLength(), text, defaultStyle );
+      }
+      catch( BadLocationException e )
+      {
+         e.printStackTrace();
+      }
+
+      //parse this text and apply the styles accordingly. Note: the LiveOutputParser only returns FileLinks for full lines. The text
+      //we add may contain a FileLink, but it won't parse it until it reaches a new line.
+      if( hasClickableFiles ) {
+         List<FileLink> fileLinks = liveOutputParser.appendText( text );
+         highlightFileLinks( fileLinks );
+      }
+
+      if( shouldScroll ) {
+         SwingUtilities.invokeLater( new Runnable() {
+            public void run() {
+               scrollToBottom();
+            }
+         } );
+      }
+   }
+
+   /**
+    This applies a text style to the text where the FileLinks are. This makes them appear to be
+    clickable hotspots.
+    @param fileLinks the FileLinks to apply
+    */
+   private void highlightFileLinks( List<FileLink> fileLinks ) {
+      Iterator<FileLink> iterator = fileLinks.iterator();
+      while( iterator.hasNext() ) {
+         FileLink fileLink = iterator.next();
+
+         document.setCharacterAttributes( fileLink.getStartingIndex(), fileLink.getLength(), fileStyle, true );
+      }
+   }
+
+   /**
+   This scrolls to the bottom of the output text. To do this, we can't just
+   set the scroll bar to the maximum position. That would be too easy. We have
+   to consider the height of the scrollbar 'thumb'. This way also preserves
+   whatever horizontal scrolling the user has done.
+    */
+   private void scrollToBottom() {
+      int height = scroll.getVerticalScrollBar().getHeight();
+      int maximum = scroll.getVerticalScrollBar().getMaximum();
+      int newValue = maximum - height;
+      scroll.getVerticalScrollBar().setValue( newValue );
+   }
+
+   /**
+    Returns the FileLink at the specified point.
+    @param point the point where a FileLink may or may not be
+    @return a FileLink object if one exists at the specified point, null otherwise.
+    */
+   public FileLink getFileLinkAt( Point point ) {
+      int index = textPane.viewToModel( point );
+      return liveOutputParser.getFileLink( index );
+   }
+
+   /**
+    This is only overridden to prevent line wrapping
+    */
+   private class TextPane extends JTextPane {
+
+      private TextPane( DefaultStyledDocument doc )
+      {
+         super( doc );
+      }
+
+      /**
+       Overridden to prevent line wrapping
+       @return always false
+       */
+      @Override
+      public boolean getScrollableTracksViewportWidth() {
+         return false;
+      }
+   }
+
+   public void setFont( Font font )
+   {
+      this.font = font;
+      resetFontStyles();
+      resetText();
+   }
+
+   /**
+    This resets the text using the current font styles. This is useful if you change fonts.
+    The simplest way to do this is just re-add our text.
+    */
+   private void resetText()
+   {
+      String text = liveOutputParser.getText();
+      liveOutputParser.reset();
+      appendText( text, true );
+   }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputUILord.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputUILord.java
new file mode 100644
index 0000000..8adafdf
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/OutputUILord.java
@@ -0,0 +1,95 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic;
+
+import org.gradle.foundation.output.FileLinkDefinitionLord;
+import org.gradle.gradleplugin.foundation.request.ExecutionRequest;
+import org.gradle.gradleplugin.foundation.request.RefreshTaskListRequest;
+import org.gradle.gradleplugin.foundation.request.Request;
+
+import java.awt.Font;
+
+/**
+ This interface manages the output of executing gradle tasks.
+ */
+public interface OutputUILord
+{
+   void setOnlyShowOutputOnErrors( boolean show );
+
+   boolean getOnlyShowOutputOnErrors();
+
+
+         public interface OutputObserver
+         {
+            /**
+               Notification that a request was added to the output. This means we've got some output
+               that is useful to display.
+
+               Note: this is slightly different from the GradlePluginLord.RequestObserver. While
+               these are directly related, this one really means that it has been added to the UI.
+               <!      Name            Description>
+               @param  request         the request that was added.
+            */
+            void executionRequestAdded( ExecutionRequest request );
+
+            /**
+              Notification that a refresh task list request was added to the output. This means
+              we've got some output that is useful to display.
+
+              Note: this is slightly different from the GradlePluginLord.RequestObserver. While
+              these are directly related, this one really means that it has been added to the UI.
+               <!      Name            Description>
+               @param  request         the request that was added.
+             */
+            void refreshRequestAdded( RefreshTaskListRequest request );
+
+            /**
+             Notification that an output tab was closed. You might want to know this if you want to close your
+             IDE output window when all tabs are closed
+             */
+            public void outputTabClosed( Request request );
+
+            /**
+             Notification that execution of a request is complete
+             @param request the original request
+             @param wasSuccessful
+             */
+            public void reportExecuteFinished( Request request, boolean wasSuccessful );
+         }
+
+   public void addOutputObserver( OutputObserver observer, boolean inEventQueue );
+   public void removeOutputObserver( OutputObserver observer );
+
+   public int getTabCount();
+
+   /**
+    Sets the font for the output text
+    @param font the new font
+    */
+   public void setOutputTextFont( Font font );
+   public Font getOutputTextFont();
+
+    /**
+     * @return the object this is used to handle parsing of files in the output.
+     */
+   public FileLinkDefinitionLord getFileLinkDefinitionLord();
+
+    /*
+    This re-executes the last execution command (ignores refresh commands).
+    This is potentially useful for IDEs to hook into (hotkey to execute last command).
+     */
+    public void reExecuteLastCommand();
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SinglePaneUIInstance.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SinglePaneUIInstance.java
new file mode 100644
index 0000000..899616f
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SinglePaneUIInstance.java
@@ -0,0 +1,113 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic;
+
+
+import org.gradle.gradleplugin.foundation.settings.SettingsNode;
+import org.gradle.gradleplugin.userinterface.AlternateUIInteraction;
+import org.gradle.gradleplugin.userinterface.swing.common.PreferencesAssistant;
+
+import javax.swing.JPanel;
+import javax.swing.JSplitPane;
+import javax.swing.SwingUtilities;
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Window;
+import java.awt.event.HierarchyEvent;
+import java.awt.event.HierarchyListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+/**
+ * A simple UI for gradle. This is a single panel that can be inserted into a stand-alone application or an IDE. This is
+ * meant to hide most of the complexities of gradle. 'single pane' means that both the tabbed pane and the output pane
+ * are contained within a single pane that this maintains. Meaning, you add this to a UI and its a self-contained gradle
+ * UI. This is opposed to a multi-pane concept where the output would be separated from the tabbed pane.
+ *
+ * @author mhunsicker
+  */
+public class SinglePaneUIInstance extends AbstractGradleUIInstance
+{
+    private static final String SPLITTER_PREFERENCES_ID = "splitter-id";
+
+    private JSplitPane splitter;
+   private OutputPanelLord outputPanelLord;
+
+   public SinglePaneUIInstance() { }
+
+
+    public void initialize(SettingsNode settings, AlternateUIInteraction alternateUIInteraction) {
+
+        outputPanelLord = new OutputPanelLord( gradlePluginLord, alternateUIInteraction );
+
+        super.initialize( settings, alternateUIInteraction );
+    }
+
+   /**
+    We've overridden this to setup our splitter and our output window.
+    */
+   @Override
+    protected void setupUI() {
+        mainPanel = new JPanel(new BorderLayout());
+        mainPanel.add(createCenterPanel(), BorderLayout.CENTER);
+    }
+
+    public OutputUILord getOutputUILord() {
+      return outputPanelLord;
+   }
+
+   private Component createCenterPanel() {
+        splitter = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
+
+        splitter.setTopComponent( createMainGradlePanel());
+        splitter.setBottomComponent( outputPanelLord.getMainPanel());
+
+        splitter.setContinuousLayout(true);
+
+        //This little bit of tedium is so we can set our size based on window's size. This listens
+        //for when the window is actually shown. It then adds a listen to store the location.
+        splitter.addHierarchyListener(new HierarchyListener() {
+            public void hierarchyChanged(HierarchyEvent e) {
+                if (HierarchyEvent.SHOWING_CHANGED == (e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED)) {
+                    splitter.removeHierarchyListener(this); //we only want the first one of these, so remove ourselves as a listener.
+                    Window window = SwingUtilities.getWindowAncestor(splitter);
+                    if (window != null) {
+                        Dimension dimension = window.getSize();
+                        int halfHeight = dimension.height / 2; //we'll just make ourselves half the height of the window
+                        splitter.setDividerLocation(halfHeight);
+                    }
+                    PreferencesAssistant.restoreSettings(settings, splitter, SPLITTER_PREFERENCES_ID, SinglePaneUIInstance.class);
+
+
+                    //Now that we're visible, this is so we save the location when the splitter is moved.
+                    splitter.addPropertyChangeListener(new PropertyChangeListener() {
+                        public void propertyChange(PropertyChangeEvent evt) {
+                            if (JSplitPane.DIVIDER_LOCATION_PROPERTY.equals(evt.getPropertyName())) {
+                               PreferencesAssistant.saveSettings(settings, splitter, SPLITTER_PREFERENCES_ID, SinglePaneUIInstance.class);
+                            }
+                        }
+                    });
+                }
+            }
+        });
+
+
+        splitter.setResizeWeight(1);   //this keeps the bottom the same size when resizing the window. Extra space is added/removed from the top.
+
+        return splitter;
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingAddMultipleFavoritesInteraction.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingAddMultipleFavoritesInteraction.java
new file mode 100644
index 0000000..16c09e9
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingAddMultipleFavoritesInteraction.java
@@ -0,0 +1,219 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic;
+
+import org.gradle.gradleplugin.foundation.favorites.FavoritesEditor;
+import org.gradle.foundation.TaskView;
+
+import javax.swing.*;
+import java.awt.Window;
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.event.*;
+import java.util.List;
+
+/**
+ * This handles prompting the user how to handle adding multiple tasks as favorites.
+ *
+ * @author mhunsicker
+ */
+public class SwingAddMultipleFavoritesInteraction implements FavoritesEditor.AddMultipleFavoritesInteraction {
+    private Window parent;
+
+    public SwingAddMultipleFavoritesInteraction(Window parent) {
+        this.parent = parent;
+    }
+
+    public FavoritesEditor.AddMultipleResult promptUserToCombineTasks(List<TaskView> tasksSample,
+                                                                      String singleCommandSample) {
+        PromptToCombineTasksDialog dialog = new PromptToCombineTasksDialog();
+        return dialog.show(parent, tasksSample, singleCommandSample);
+    }
+
+    public class PromptToCombineTasksDialog {
+        private JDialog dialog;
+        private FavoritesEditor.AddMultipleResult addMultipleResult;
+        private JRadioButton separatelyRadioButton;
+        private JRadioButton combinedRadioButton;
+        private ButtonGroup buttonGroup;
+
+        private JLabel separateLine1;
+        private JLabel separateLine2;
+        private JLabel separateLine3;
+
+        private JLabel combinedLine1;
+
+        public FavoritesEditor.AddMultipleResult show(Window parent, List<TaskView> tasksSample,
+                                                      String singleCommandSample) {
+            setupUI(parent);
+            populateValues(tasksSample, singleCommandSample);
+            dialog.setVisible(true);
+            return this.addMultipleResult;
+        }
+
+        /**
+         * this populates the dialog's sample values. Most of this function is trying to be very explicit about
+         * showing precisely what we're going to do (but for space reasons, we'll only show 3 commands in the
+         * list.
+         */
+        private void populateValues(List<TaskView> tasksSample, String singleCommandSample) {
+            separatelyRadioButton.setText("Add as separate " + tasksSample.size() + " commands:");
+
+            separateLine1.setText('\"' + tasksSample.get(0).getFullTaskName() + "\",");
+            String secondTask = '\"' + tasksSample.get(1).getFullTaskName() + "\"";
+            String thirdTask = "";
+
+            if (tasksSample.size() > 2) {
+                secondTask += ",";   //add a comma
+
+                thirdTask = '\"' + tasksSample.get(2).getFullTaskName() + "\"";
+                if (tasksSample.size() > 3)  //if there are more, show a comma and ellipses
+                {
+                    thirdTask += ", ... ";
+                }
+            }
+
+            separateLine2.setText(secondTask);
+            separateLine3.setText(thirdTask);
+            separateLine3.setVisible(tasksSample.size() > 2);   //only show this if there are more than 2 samples
+
+            combinedLine1.setText('\"' + singleCommandSample + '\"');
+        }
+
+        private void setupUI(Window parent) {
+            dialog = Utility.createDialog(parent, "Add Multiple Tasks", true);
+            dialog.setSize(400, 350);
+
+            dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
+            dialog.addWindowListener(new WindowAdapter() {
+                public void windowClosing(WindowEvent e) {
+                    close(FavoritesEditor.AddMultipleResult.Cancel);
+                }
+            });
+
+            JPanel panel = new JPanel(new BorderLayout());
+            dialog.getContentPane().add(panel);
+
+            panel.add(createMainPanel(), BorderLayout.CENTER);
+            panel.add(createButtonPanel(), BorderLayout.SOUTH);
+
+            panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+
+            dialog.setLocationRelativeTo(dialog.getParent());
+        }
+
+        private Component createMainPanel() {
+            JPanel panel = new JPanel();
+            panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+
+            separatelyRadioButton = new JRadioButton();
+            combinedRadioButton = new JRadioButton("Add as a single command:");
+            buttonGroup = new ButtonGroup();
+
+            buttonGroup.add(separatelyRadioButton);
+            buttonGroup.add(combinedRadioButton);
+            separatelyRadioButton.setSelected(true);
+
+            panel.add(Utility.addLeftJustifiedComponent(new JLabel("How you do want to add multiple tasks?")));
+            panel.add(Box.createVerticalStrut(20));
+            panel.add(Utility.addLeftJustifiedComponent(separatelyRadioButton));
+            panel.add(Box.createVerticalStrut(5));
+            panel.add(createSeparateSamplePanel());
+            panel.add(Box.createVerticalStrut(20));
+            panel.add(Utility.addLeftJustifiedComponent(combinedRadioButton));
+            panel.add(Box.createVerticalStrut(5));
+            panel.add(createCombinedSamplePanel());
+            panel.add(Box.createVerticalGlue());
+
+            return panel;
+        }
+
+        private JPanel createSeparateSamplePanel() {
+            separateLine1 = new JLabel();   //we'll use at most three samples (actually 2 with ellipses)
+            separateLine2 = new JLabel();
+            separateLine3 = new JLabel();
+
+            JPanel separateSamplePanel = new JPanel();
+            separateSamplePanel.setLayout(new BoxLayout(separateSamplePanel, BoxLayout.Y_AXIS));
+            separateSamplePanel.setBorder(BorderFactory.createEmptyBorder(0, 30, 0, 0)); //indent it
+            separateSamplePanel.add(Utility.addLeftJustifiedComponent(separateLine1));
+            separateSamplePanel.add(Utility.addLeftJustifiedComponent(separateLine2));
+            separateSamplePanel.add(Utility.addLeftJustifiedComponent(separateLine3));
+
+            return separateSamplePanel;
+        }
+
+        private JPanel createCombinedSamplePanel() {
+            combinedLine1 = new JLabel();
+
+            JPanel combinedSamplePanel = new JPanel();
+            combinedSamplePanel.setLayout(new BoxLayout(combinedSamplePanel, BoxLayout.Y_AXIS));
+            combinedSamplePanel.setBorder(BorderFactory.createEmptyBorder(0, 30, 0, 0)); //indent it
+            combinedSamplePanel.add(Utility.addLeftJustifiedComponent(combinedLine1));
+
+            return combinedSamplePanel;
+        }
+
+        private FavoritesEditor.AddMultipleResult getCurrentSelection() {
+            if (separatelyRadioButton.isSelected()) {
+                return FavoritesEditor.AddMultipleResult.AddSeparately;
+            }
+            return FavoritesEditor.AddMultipleResult.AddAsSingleCommand;
+        }
+
+        private Component createButtonPanel() {
+            JPanel panel = new JPanel();
+            panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+
+            JButton okButton = new JButton(new AbstractAction("OK") {
+                public void actionPerformed(ActionEvent e) {
+                    close(getCurrentSelection());
+                }
+            });
+
+            //make OK the default button
+            dialog.getRootPane().setDefaultButton(okButton);
+
+            JButton cancelButton = new JButton(new AbstractAction("Cancel") {
+                public void actionPerformed(ActionEvent e) {
+                    close(FavoritesEditor.AddMultipleResult.Cancel);
+                }
+            });
+
+            //equate escape with cancle
+            dialog.getRootPane().registerKeyboardAction(new ActionListener() {
+                public void actionPerformed(ActionEvent actionEvent) {
+                    close(FavoritesEditor.AddMultipleResult.Cancel);
+                }
+            }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
+
+            panel.add(Box.createHorizontalGlue());
+            panel.add(okButton);
+            panel.add(Box.createHorizontalStrut(10));
+            panel.add(cancelButton);
+            panel.add(Box.createHorizontalGlue());
+
+            panel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
+
+            return panel;
+        }
+
+        private void close(FavoritesEditor.AddMultipleResult addMultipleResult) {
+            this.addMultipleResult = addMultipleResult;
+            dialog.setVisible(false);
+        }
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingEditFavoriteInteraction.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingEditFavoriteInteraction.java
new file mode 100644
index 0000000..41fe46b
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingEditFavoriteInteraction.java
@@ -0,0 +1,203 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic;
+
+import org.gradle.gradleplugin.foundation.favorites.FavoritesEditor;
+
+import javax.swing.*;
+import javax.swing.text.BadLocationException;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.DocumentEvent;
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Window;
+import java.awt.event.*;
+
+/**
+ * This edits the properties of a single favorite task.
+ *
+ * @author mhunsicker
+ */
+public class SwingEditFavoriteInteraction implements FavoritesEditor.EditFavoriteInteraction {
+    private JDialog dialog;
+    private JTextField fullCommandLineTextField;
+    private JTextField displayNameTextField;
+    private JCheckBox alwaysShowOutputCheckBox;
+    private boolean saveResults;
+    private boolean synchronizeDisplayNameWithCommand;
+
+    //pass in true to synchronizeDisplayNameWithCommand for new favorites.
+
+    public SwingEditFavoriteInteraction(Window parent, String title, boolean synchronizeDisplayNameWithCommand) {
+        this.synchronizeDisplayNameWithCommand = synchronizeDisplayNameWithCommand;
+        setupUI(parent, title);
+    }
+
+    private void setupUI(Window parent, String title) {
+        dialog = Utility.createDialog(parent, title, true);
+
+        dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
+        dialog.addWindowListener(new WindowAdapter() {
+            public void windowClosing(WindowEvent e) {
+                close(false);
+            }
+        });
+
+        JPanel panel = new JPanel(new BorderLayout());
+        dialog.getContentPane().add(panel);
+
+        panel.add(createMainPanel(), BorderLayout.CENTER);
+        panel.add(createButtonPanel(), BorderLayout.SOUTH);
+
+        panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+
+        dialog.pack();
+    }
+
+    private Component createMainPanel() {
+        JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+
+        fullCommandLineTextField = new JTextField();
+        displayNameTextField = new JTextField();
+        alwaysShowOutputCheckBox = new JCheckBox("Always Show Live Output");
+
+        panel.add(Utility.addLeftJustifiedComponent(new JLabel("Command Line")));
+        panel.add(Utility.addLeftJustifiedComponent(fullCommandLineTextField));
+        panel.add(Box.createVerticalStrut(10));
+        panel.add(Utility.addLeftJustifiedComponent(new JLabel("Display Name")));
+        panel.add(Utility.addLeftJustifiedComponent(displayNameTextField));
+        panel.add(Box.createVerticalStrut(10));
+        panel.add(Utility.addLeftJustifiedComponent(alwaysShowOutputCheckBox));
+        panel.add(Box.createVerticalGlue());
+
+        synchronizeDisplayNameWithCommand();
+
+        return panel;
+    }
+
+    /**
+     * This synchronizes the display name with the command line. This is so when you're adding a new favorite, the
+     * display name is automatic. If you type anything in the display name, we'll cancel synchronization.
+     */
+    private void synchronizeDisplayNameWithCommand() {
+        if (!synchronizeDisplayNameWithCommand) {
+            return;
+        }
+
+        final DocumentListener documentListener = new DocumentListener() {
+            public void insertUpdate(DocumentEvent documentEvent) {
+                setDisplayNameTextToCommandLineText();
+            }
+
+            public void removeUpdate(DocumentEvent documentEvent) {
+                setDisplayNameTextToCommandLineText();
+            }
+
+            public void changedUpdate(DocumentEvent documentEvent) {
+                setDisplayNameTextToCommandLineText();
+            }
+        };
+
+        fullCommandLineTextField.getDocument().addDocumentListener(documentListener);
+        displayNameTextField.addKeyListener(new KeyAdapter() {
+            @Override
+            public void keyPressed(KeyEvent keyEvent) {  //the user typed someting. Remove the document listener
+                fullCommandLineTextField.getDocument().removeDocumentListener(documentListener);
+            }
+        });
+    }
+
+    private void setDisplayNameTextToCommandLineText() {
+        try {
+            String text = fullCommandLineTextField.getDocument().getText(0,
+                    fullCommandLineTextField.getDocument().getLength());
+            displayNameTextField.setText(text);
+        } catch (BadLocationException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private Component createButtonPanel() {
+        JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+
+        JButton okButton = new JButton(new AbstractAction("OK") {
+            public void actionPerformed(ActionEvent e) {
+                close(true);
+            }
+        });
+
+        //make OK the default button
+        dialog.getRootPane().setDefaultButton(okButton);
+
+        JButton cancelButton = new JButton(new AbstractAction("Cancel") {
+            public void actionPerformed(ActionEvent e) {
+                close(false);
+            }
+        });
+
+        //equate escape with cancle
+        dialog.getRootPane().registerKeyboardAction(new ActionListener() {
+            public void actionPerformed(ActionEvent actionEvent) {
+                close(false);
+            }
+        }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
+
+        panel.add(Box.createHorizontalGlue());
+        panel.add(okButton);
+        panel.add(Box.createHorizontalStrut(10));
+        panel.add(cancelButton);
+        panel.add(Box.createHorizontalGlue());
+
+        panel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
+
+        return panel;
+    }
+
+    private void close(boolean saveResults) {
+        this.saveResults = saveResults;
+        dialog.setVisible(false);
+    }
+
+    public boolean editFavorite(FavoritesEditor.EditibleFavoriteTask favoriteTask) {
+        saveResults = false;
+
+        fullCommandLineTextField.setText(favoriteTask.fullCommandLine);
+        displayNameTextField.setText(favoriteTask.displayName);
+        alwaysShowOutputCheckBox.setSelected(favoriteTask.alwaysShowOutput);
+
+        dialog.pack();
+        dialog.setLocationRelativeTo(dialog.getParent());
+        dialog.setVisible(true);
+
+        if (saveResults) {
+            favoriteTask.fullCommandLine = fullCommandLineTextField.getText();
+            favoriteTask.displayName = displayNameTextField.getText();
+            favoriteTask.alwaysShowOutput = alwaysShowOutputCheckBox.isSelected();
+        }
+
+        return saveResults;
+    }
+
+    public void reportError(String error) {
+        if (dialog.isVisible()) {
+            JOptionPane.showMessageDialog(dialog, error);
+        } else {
+            JOptionPane.showMessageDialog(dialog.getParent(), error);
+        }
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingExportInteraction.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingExportInteraction.java
new file mode 100644
index 0000000..cedd57d
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingExportInteraction.java
@@ -0,0 +1,77 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic;
+
+import org.gradle.gradleplugin.foundation.DOM4JSerializer;
+
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+import javax.swing.filechooser.FileFilter;
+import java.awt.Window;
+import java.io.File;
+
+/**
+ * Swing implementation of ExportInteraction. This prompts the user for a file via the JFileChooser and handles
+ * reporting errors.
+ *
+ * @author mhunsicker
+ */
+public class SwingExportInteraction implements DOM4JSerializer.ExportInteraction {
+    private Window parent;
+
+    public SwingExportInteraction(Window parent) {
+        this.parent = parent;
+    }
+
+    /**
+     * This is called when you should ask the user for a source file to read.
+     *
+     * @return a file to read or null to cancel.
+     */
+    public File promptForFile(FileFilter fileFilter) {
+        JFileChooser chooser = new JFileChooser();
+        chooser.addChoosableFileFilter(fileFilter);
+
+        if (chooser.showSaveDialog(parent) != JFileChooser.APPROVE_OPTION) {
+            return null;
+        }
+
+        return chooser.getSelectedFile();
+    }
+
+    /**
+     * Report an error that occurred. The read failed.
+     *
+     * @param error the error message.
+     */
+    public void reportError(String error) {
+        JOptionPane.showMessageDialog(parent, error, "Error", JOptionPane.ERROR_MESSAGE);
+    }
+
+    /**
+     * The file already exists. Confirm whether or not you want to overwrite it.
+     *
+     * @param file the file in question
+     * @return true to overwrite it, false not to.
+     */
+    public boolean confirmOverwritingExisingFile(File file) {
+        int result = JOptionPane.showConfirmDialog(SwingUtilities.getWindowAncestor(parent),
+                "The file '" + file.getAbsolutePath() + "' already exists. Overwrite?", "Confirm Overwriting File",
+                JOptionPane.YES_NO_OPTION);
+        return result == JOptionPane.YES_OPTION;
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingImportInteraction.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingImportInteraction.java
new file mode 100644
index 0000000..9b92746
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/SwingImportInteraction.java
@@ -0,0 +1,63 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic;
+
+import org.gradle.gradleplugin.foundation.DOM4JSerializer;
+
+import javax.swing.JOptionPane;
+import javax.swing.JFileChooser;
+import javax.swing.filechooser.FileFilter;
+import java.awt.Window;
+import java.io.File;
+
+/**
+ * Swing implementation of ImportInteraction. This prompts the user for a file via the JFileChooser and handles
+ * reporting errors.
+ *
+ * @author mhunsicker
+ */
+public class SwingImportInteraction implements DOM4JSerializer.ImportInteraction {
+    private Window parent;
+
+    public SwingImportInteraction(Window parent) {
+        this.parent = parent;
+    }
+
+    /**
+     * This is called when you should ask the user for a source file to read.
+     *
+     * @return a file to read or null to cancel.
+     */
+    public File promptForFile(FileFilter fileFilter) {
+        JFileChooser chooser = new JFileChooser();
+        chooser.addChoosableFileFilter(fileFilter);
+
+        if (chooser.showOpenDialog(parent) != JFileChooser.APPROVE_OPTION) {
+            return null;
+        }
+
+        return chooser.getSelectedFile();
+    }
+
+    /**
+     * Report an error that occurred. The read failed.
+     *
+     * @param error the error message.
+     */
+    public void reportError(String error) {
+        JOptionPane.showMessageDialog(parent, error, "Error", JOptionPane.ERROR_MESSAGE);
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/TaskTreeComponent.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/TaskTreeComponent.java
new file mode 100644
index 0000000..147ecce
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/TaskTreeComponent.java
@@ -0,0 +1,703 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic;
+
+import org.gradle.foundation.ProjectView;
+import org.gradle.foundation.TaskView;
+import org.gradle.foundation.visitors.TaskTreePopulationVisitor;
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.gradleplugin.foundation.filters.ProjectAndTaskFilter;
+
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.JTree;
+import javax.swing.KeyStroke;
+import javax.swing.SwingUtilities;
+import javax.swing.ToolTipManager;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeCellRenderer;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreeCellRenderer;
+import javax.swing.tree.TreeNode;
+import javax.swing.tree.TreePath;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Enumeration;
+
+/**
+ * This displays a tree of projects, subprojects, and tasks. You implement the Interaction to detemine how to handle
+ * right clicks and double clicking tasks. To use this, call populate and pass it a filter (allows you to change exactly
+ * what is displayed). There are several functions to obtaining the selected items, plus you can get the tree directly
+ * for any advanced functionality.
+ *
+ * @author mhunsicker
+ */
+public class TaskTreeComponent {
+    private GradlePluginLord gradlePluginLord;
+    private Interaction interaction;
+
+    private JTree tree;
+    private DefaultTreeModel model;
+    private TaskTreeBaseNode rootNode;
+
+    private boolean isPopulated;
+
+    private TaskTreeComponent.Renderer renderer;
+
+    public interface Interaction {
+        void rightClick(JTree tree, int x, int y);
+
+        /**
+         * Notification that a project was invoked (double-clicked). Do whatever you like, such as execute its default
+         * task.
+         *
+         * @param project the project that was invoked.
+         */
+        void projectInvoked(ProjectView project);
+
+        /**
+         * Notification that a task was invoked (double-clicked). Do whatever you like, such as execute it.
+         *
+         * @param task the task that was invoked.
+         * @param isCtrlKeyDown true if the CTRL key was pressed at the time
+         */
+        void taskInvoked(TaskView task, boolean isCtrlKeyDown);
+    }
+
+    public TaskTreeComponent(GradlePluginLord gradlePluginLord, Interaction interaction) {
+        this.gradlePluginLord = gradlePluginLord;
+        this.interaction = interaction;
+
+        createTreePanel();
+    }
+
+    private void createTreePanel() {
+        rootNode = new TaskTreeBaseNode();
+
+        model = new DefaultTreeModel(rootNode);
+        tree = new JTree(model);
+
+        tree.setRootVisible(false);
+        tree.setShowsRootHandles(true);
+
+        renderer = new Renderer();
+        tree.setCellRenderer(renderer);
+
+        ToolTipManager.sharedInstance().registerComponent(tree);
+
+        tree.setToggleClickCount(
+                99);  //prevents double clicks from expanding/collapsing the tree. We want to treat them as double-clicks
+
+        tree.addMouseListener(new MyMouseListener());
+
+        //make hitting Enter and CTRL Enter on a node equal executing it (the first node at least)
+        tree.registerKeyboardAction(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                executeFirstSelectedNode(false);
+            }
+        }, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
+
+        tree.registerKeyboardAction(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                executeFirstSelectedNode(true);
+            }
+        }, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.CTRL_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW);
+    }
+
+    public JTree getTree() {
+        return tree;
+    }
+
+    public void setTreeCellRenderer(TreeCellRenderer renderer) {
+        tree.setCellRenderer(renderer);
+    }
+
+    /**
+     * This renders our projects and tasks. This removes the icon and optionally shows the description in a different
+     * color. Since there's quite a bit of code for handling rendering tree cells, I'm just going to mooch off of the
+     * DefaultTreeCellRenderer. I'll just modify it's behavior a little (I probably don't need that or the description
+     * since it's not going to draw a selection or highlight).
+     */
+    private class Renderer implements TreeCellRenderer {
+        private JPanel panel;
+        private DefaultTreeCellRenderer nameRenderer;
+        private DefaultTreeCellRenderer descriptionRenderer;
+        private Color descriptionColor;
+        private boolean showDescription = true;
+        private Component seperator;
+        private Font normalFont;
+        private Font boldFont;
+
+        private Renderer() {
+            setupRendererUI();
+            setShowDescription(true);
+
+            descriptionColor = Color.blue;
+        }
+
+        private void setupRendererUI() {
+            panel = new JPanel();
+            panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+
+            nameRenderer = new DefaultTreeCellRenderer();
+            descriptionRenderer = new DefaultTreeCellRenderer();
+
+            panel.add(nameRenderer);
+            seperator = Box.createHorizontalStrut(10);
+            panel.add(seperator);
+            panel.add(descriptionRenderer);
+
+            panel.setOpaque(false);
+
+            setupFonts();
+        }
+
+        /**
+         * Setup the fonts. On some platforms, bold is the typical version. We explicitly don't want that. So we'll make
+         * the fonts plain and use the bold for our own purposes (indicating default tasks).
+         */
+        private void setupFonts() {
+            normalFont = nameRenderer.getFont().deriveFont(Font.PLAIN);
+            boldFont = normalFont.deriveFont(Font.BOLD);
+
+            nameRenderer.setFont(normalFont);
+            descriptionRenderer.setFont(normalFont);
+        }
+
+        public boolean showDescription() {
+            return showDescription;
+        }
+
+        //the easiest thing to do is just hide the description and its separator
+
+        public void setShowDescription(boolean showDescription) {
+            this.showDescription = showDescription;
+            seperator.setVisible(showDescription);
+            descriptionRenderer.setVisible(showDescription);
+            seperator.invalidate();
+            nameRenderer.invalidate();
+            descriptionRenderer.invalidate();
+            panel.invalidate();
+
+            //have to tell the tree each node changed. This is so it will recalculate its size. Without this, if the description is
+            //initially disabled, the tree is populated and expanded, then description is enabled, nothing shows up because the tree
+            //caches the node's size for some dumb reason.
+            Enumeration enumeration = rootNode.breadthFirstEnumeration();
+            while (enumeration.hasMoreElements()) {
+                TaskTreeBaseNode treeNode = (TaskTreeBaseNode) enumeration.nextElement();
+                model.nodeChanged(treeNode);
+            }
+
+            tree.repaint();
+        }
+
+        public Component getTreeCellRendererComponent(JTree tree, Object value, boolean isSelected, boolean expanded,
+                                                      boolean leaf, int row, boolean hasFocus) {
+            TaskTreeBaseNode node = (TaskTreeBaseNode) value;
+
+            String description = node.getDescription();
+
+            //we've already added these components to our panel. We know they're just labels. Calling getTreeCell... just sets their text and colors correctly.
+            this.nameRenderer.getTreeCellRendererComponent(tree, node.toString(), isSelected, expanded, leaf, row,
+                    hasFocus);
+            this.descriptionRenderer.getTreeCellRendererComponent(tree, description, isSelected, expanded, leaf, row,
+                    false);
+
+            //set the tooltip. This must be on the component we return not our sub renderers
+            panel.setToolTipText(description);
+
+            //just remove the icon entirely
+            nameRenderer.setIcon(null);
+            this.descriptionRenderer.setIcon(null);
+
+            if (node.isBold()) {
+                nameRenderer.setFont(boldFont);
+            } else {
+                nameRenderer.setFont(normalFont);
+            }
+
+            //set the description color. If its selected, make it the name renderer's color
+            //so we know the colors won't conflict (they do on Windows XP).
+            if (!isSelected) {
+                this.descriptionRenderer.setForeground(descriptionColor);
+            } else {
+                this.descriptionRenderer.setForeground(nameRenderer.getForeground());
+            }
+
+            nameRenderer.invalidate();
+            descriptionRenderer.invalidate();
+            seperator.invalidate();
+            panel.invalidate();
+            panel.validate();
+
+            return panel;
+        }
+    }
+
+    private class MyMouseListener extends MouseAdapter {
+        public void mousePressed(MouseEvent e) {
+            if (e.getButton() == MouseEvent.BUTTON3) {  //This is here just to select a node that we right click on.
+                //The tree really needs to handle this for us.
+                Point point = e.getPoint();
+                int row = tree.getRowForLocation(point.x, point.y);
+                if (row != -1) {
+                    if (tree.isRowSelected(row)) {
+                        return;
+                    }  //if its already selected, just leave it alone. This prevents us from changing selecting when a user right-clicks on one of many selected items.
+
+                    //we need to determine if we move the selection, or just add it to the existing selection.
+                    if (isAddToSelectionKey(e)) {
+                        tree.addSelectionRow(row);
+                    } else {
+                        tree.setSelectionRow(row);
+                    }
+                }
+            }
+        }
+
+        private boolean isAddToSelectionKey(
+                MouseEvent e) {  //this is actually OS-specific, but for now, I'll just use CTRL.
+            return (e.getModifiers() & MouseEvent.CTRL_MASK) != 0;
+        }
+
+        public void mouseClicked(MouseEvent e) {
+            if (e.getClickCount() == 1) {
+                if (e.getButton() == MouseEvent.BUTTON3) {
+                    Point point = e.getPoint();
+                    interaction.rightClick(tree, point.x, point.y);
+                }
+            } else if (e.getClickCount() == 2) {
+                TaskTreeBaseNode node = getNodeAtPoint(e.getPoint());
+                if (node != null) {
+                    boolean isCtrlKeyDown = (e.getModifiers() & MouseEvent.CTRL_MASK) != 0;
+                    node.executeTask(isCtrlKeyDown);
+                }
+            }
+        }
+    }
+
+    /**
+     * This populates (and repopulates) the tree. This is surprisingly tedious in an effort to make the tree collapse as
+     * little as possible.
+     */
+    public void populate(ProjectAndTaskFilter filter) {
+        TaskTreePopulationVisitor.visitProjectAndTasks(gradlePluginLord.getProjects(), new PopulateTreeVisitor(),
+                filter, rootNode);
+
+        model.reload();
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {  //this expands the 'root' project
+                tree.expandRow(0);
+            }
+        });
+
+        isPopulated = true;
+    }
+
+    public boolean isPopulated() {
+        return isPopulated;
+    }
+
+    /**
+     * This visitor populates the tree as we walk projects and tasks. This jumpts through quite a bit of hoops in an
+     * effort to keep the tree from collapsing. It still does, but not completely. In order to keep it from collapsing,
+     * you must track some additional information that frankly the tree could and should do for you.
+     */
+    private class PopulateTreeVisitor implements TaskTreePopulationVisitor.Visitor<TaskTreeBaseNode, TaskTreeNode> {
+        /**
+         * This is called for each project.
+         *
+         * @param project the project
+         * @param parentProjectObject whatever you handed back from a prior call to visitProject if this is a sub
+         * project. Otherwise, it'll be whatever was passed into the visitPojectsAndTasks function.
+         * @return an object that will be handed back to you for each of this project's tasks.
+         */
+        public TaskTreeBaseNode visitProject(ProjectView project, int indexOfProject,
+                                             TaskTreeBaseNode parentProjectObject) {
+            ProjectTreeNode projectTreeNode = findProjectChild(parentProjectObject, project.getName());
+            if (projectTreeNode == null) {
+                projectTreeNode = new ProjectTreeNode(project);
+            }
+
+            int actualIndex = parentProjectObject.getIndex(projectTreeNode);
+            if (actualIndex != indexOfProject) //this will be -1 for a new node
+            {
+                if (actualIndex != -1) //only try to remove it if its already there. Swing doesn't like this otherwise.
+                {
+                    model.removeNodeFromParent(projectTreeNode);
+                }
+
+                insertChildNode(parentProjectObject, projectTreeNode, indexOfProject);
+            }
+
+            return projectTreeNode;
+        }
+
+        private ProjectTreeNode findProjectChild(TaskTreeBaseNode parentNode, String projectName) {
+            for (int index = 0; index < parentNode.getChildCount(); index++) {
+                TreeNode child = parentNode.getChildAt(index);
+                if (child instanceof ProjectTreeNode) {
+                    if (((ProjectTreeNode) child).getProject().getName().equals(projectName)) {
+                        return (ProjectTreeNode) child;
+                    }
+                }
+            }
+            return null;
+        }
+
+        /**
+         * This is called for each task.
+         *
+         * @param task the task
+         * @param indexOfTask index
+         * @param tasksProject the project for this task
+         */
+        public TaskTreeNode visitTask(TaskView task, int indexOfTask, ProjectView tasksProject,
+                                      TaskTreeBaseNode parentTreeNode) {
+            TaskTreeNode taskTreeNode = findTaskChild((ProjectTreeNode) parentTreeNode, task.getName());
+
+            if (taskTreeNode == null) {
+                taskTreeNode = new TaskTreeNode(task);
+            }
+
+            int actualIndex = parentTreeNode.getIndex(taskTreeNode);
+            if (actualIndex != indexOfTask) //this will be -1 for a new node
+            {
+                if (actualIndex != -1) //only try to remove it if its already there. Swing doesn't like this otherwise.
+                {
+                    model.removeNodeFromParent(taskTreeNode);
+                }
+
+                insertChildNode(parentTreeNode, taskTreeNode, indexOfTask);
+            }
+
+            return taskTreeNode;
+        }
+
+        //This only exists so we can call insert or add appropriately.
+        //The stupid tree isn't smart enough to do this for you.
+
+        private void insertChildNode(DefaultMutableTreeNode parent, DefaultMutableTreeNode child, int index) {
+            if (parent.getChildCount() < index) {
+                parent.add(child);
+                model.nodesWereInserted(parent, new int[]{parent.getChildCount() - 1});
+            } else {
+                parent.insert(child, index);
+                model.nodesWereInserted(parent, new int[]{index});
+            }
+        }
+
+        private TaskTreeNode findTaskChild(ProjectTreeNode parentNode, String taskName) {
+            for (int index = 0; index < parentNode.getChildCount(); index++) {
+                TreeNode child = parentNode.getChildAt(index);
+                if (child instanceof TaskTreeNode) {
+                    if (((TaskTreeNode) child).getTask().getName().equals(taskName)) {
+                        return (TaskTreeNode) child;
+                    }
+                }
+            }
+            return null;
+        }
+
+        /**
+         * This is called when a project has been visited completely and is just a notification giving you an
+         * opportunity to do whatever you like.
+         *
+         * Here, we're going to remove any nodes that aren't in either of the lists. This is when a task or project is
+         * hidden or when things simply change.
+         *
+         * @param parentProjectObject the object that represents the parent of the project and task objects below
+         * @param projectObjects a list of whatever you returned from visitProject
+         * @param taskObjects a list of whatever you returned from visitTask
+         */
+        public void completedVisitingProject(TaskTreeBaseNode parentProjectObject,
+                                             List<TaskTreeBaseNode> projectObjects, List<TaskTreeNode> taskObjects) {
+            int index = 0;
+            while (index < parentProjectObject.getChildCount()) {
+                TaskTreeBaseNode child = (TaskTreeBaseNode) parentProjectObject.getChildAt(index);
+                if (!projectObjects.contains(child) && !taskObjects.contains(child)) {
+                    model.removeNodeFromParent(child);
+                } else {
+                    index++;
+                }
+            }
+        }
+    }
+
+    private void expandNode(TreeNode node) {
+        tree.expandPath(new TreePath(node));
+    }
+
+    /**
+     * This is a basic tree node. All nodes in this tree must extend this. This is so we don't have to deal with all the
+     * differing types of things that may be in this tree.
+     */
+    public class TaskTreeBaseNode extends DefaultMutableTreeNode {
+        public void executeTask(boolean isCtrlKeyDown) {
+        }  //do nothing by default.
+
+        public String toString() {
+            return "hidden-root";
+        }  //by default, its the root.
+
+        public String getDescription() {
+            return null;
+        }
+
+        public boolean isBold() {
+            return false;
+        }
+    }
+
+    /**
+     * This represents a project.
+     */
+    public class ProjectTreeNode extends TaskTreeBaseNode {
+        private ProjectView project;
+
+        private ProjectTreeNode(ProjectView project) {
+            this.project = project;
+        }
+
+        public String toString() {
+            return project.getName();
+        }
+
+        @Override
+        public void executeTask(boolean isCtrlKeyDown) {
+            interaction.projectInvoked(project);
+        }
+
+        public ProjectView getProject() {
+            return project;
+        }
+    }
+
+    /**
+     * This represents a single task.
+     */
+    public class TaskTreeNode extends TaskTreeBaseNode {
+        private TaskView task;
+
+        private TaskTreeNode(TaskView task) {
+            this.task = task;
+        }
+
+        public String toString() {
+            return task.getName();
+        }
+
+        @Override
+        public void executeTask(boolean isCtrlKeyDown) {
+            interaction.taskInvoked(task, isCtrlKeyDown);
+        }
+
+        public TaskView getTask() {
+            return task;
+        }
+
+        @Override
+        public String getDescription() {
+            return task.getDescription();
+        }
+
+        @Override
+        public boolean isBold() {
+            return task.isDefault();
+        }
+    }
+
+    /**
+     * Returns the node at the specified point.
+     */
+    public TaskTreeBaseNode getNodeAtPoint(Point point) {
+        int row = tree.getRowForLocation(point.x, point.y);
+        if (row == -1) {
+            return null;
+        }
+
+        TreePath path = tree.getPathForLocation(point.x, point.y);
+        if (path == null) {
+            return null;
+        }
+        return (TaskTreeBaseNode) path.getLastPathComponent();
+    }
+
+    /**
+     * @return the first selected node or null if nothing is selected.
+     */
+    public TaskTreeBaseNode getFirstSelectedNode() {
+        TreePath path = tree.getSelectionPath();
+        if (path == null) {
+            return null;
+        }
+
+        return (TaskTreeBaseNode) path.getLastPathComponent();
+    }
+
+    /**
+     * @return a list of TaskTabTreeNode based on what is selected in the tree or an empty list if nothing is selected.
+     */
+    public List<TaskTreeBaseNode> getSelectedNodes() {
+        TreePath[] treePaths = tree.getSelectionPaths();
+        if (treePaths == null) {
+            return Collections.emptyList();
+        }
+
+        List<TaskTreeBaseNode> nodes = new ArrayList<TaskTreeBaseNode>();
+
+        for (int index = 0; index < treePaths.length; index++) {
+            TreePath treePath = treePaths[index];
+            nodes.add((TaskTreeBaseNode) treePath.getLastPathComponent());
+        }
+
+        return nodes;
+    }
+
+    /**
+     * Determines if we have any projects selected. This ignores selected tasks.
+     *
+     * @return true if we have projects selected, false otherwise.
+     */
+    public boolean hasProjectsSelected() {
+        TreePath[] treePaths = tree.getSelectionPaths();
+        if (treePaths == null) {
+            return false;
+        }
+
+        for (int index = 0; index < treePaths.length; index++) {
+            TreePath treePath = treePaths[index];
+
+            Object o = treePath.getLastPathComponent();
+            if (o instanceof ProjectTreeNode) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Determines if we have any tasks selected. This ignores selected projects.
+     *
+     * @return true if we have tasks selected, false otherwise.
+     */
+    public boolean hasTasksSelected() {
+        TreePath[] treePaths = tree.getSelectionPaths();
+        if (treePaths == null) {
+            return false;
+        }
+
+        for (int index = 0; index < treePaths.length; index++) {
+            TreePath treePath = treePaths[index];
+
+            Object o = treePath.getLastPathComponent();
+            if (o instanceof TaskTreeNode) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * This returns a list of selected tasks. This ignores selected projects.
+     *
+     * @return the selected tasks. Will return an empty list if no tasks are selected.
+     */
+    public List<TaskView> getSelectedTasks() {
+        TreePath[] treePaths = tree.getSelectionPaths();
+        if (treePaths == null) {
+            return Collections.emptyList();
+        }
+
+        List<TaskView> tasks = new ArrayList<TaskView>();
+
+        for (int index = 0; index < treePaths.length; index++) {
+            TreePath treePath = treePaths[index];
+
+            Object o = treePath.getLastPathComponent();
+            if (o instanceof TaskTreeNode) {
+                tasks.add(((TaskTreeNode) o).task);
+            }
+        }
+
+        return tasks;
+    }
+
+    /**
+     * Object to hold onto mutliple selections, but not just multiples of the same type of node. This separates the
+     * selected nodes by type. You can have multiple projects and tasks selected.
+     */
+    public class MultipleSelection {
+        public List<ProjectView> projects = new ArrayList<ProjectView>();
+        public List<TaskView> tasks = new ArrayList<TaskView>();
+    }
+
+    /**
+     * This returns the current selection broken up into projects and tasks.
+     *
+     * @return the selected projects and tasks. This never returns null and the contained lists are never null.
+     */
+    public MultipleSelection getSelectedProjectsAndTasks() {
+        MultipleSelection multipleSelection = new MultipleSelection();
+
+        TreePath[] treePaths = tree.getSelectionPaths();
+        if (treePaths == null) {
+            return multipleSelection;
+        }
+
+        for (int index = 0; index < treePaths.length; index++) {
+            TreePath treePath = treePaths[index];
+
+            Object o = treePath.getLastPathComponent();
+            if (o instanceof TaskTreeNode) {
+                multipleSelection.tasks.add(((TaskTreeNode) o).getTask());
+            } else if (o instanceof ProjectTreeNode) {
+                multipleSelection.projects.add(((ProjectTreeNode) o).getProject());
+            }
+        }
+
+        return multipleSelection;
+    }
+
+    public boolean showDescription() {
+        return renderer.showDescription();
+    }
+
+    public void setShowDescription(boolean showDescription) {
+        this.renderer.setShowDescription(showDescription);
+    }
+
+    private void executeFirstSelectedNode(boolean isCtrlKeyDown) {
+        TaskTreeComponent.TaskTreeBaseNode node = getFirstSelectedNode();
+        if (node != null) {
+            node.executeTask(isCtrlKeyDown);
+        }
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/Utility.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/Utility.java
new file mode 100644
index 0000000..0f1644f
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/Utility.java
@@ -0,0 +1,210 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.gradleplugin.userinterface.swing.common.BorderlessImageButton;
+import org.gradle.gradleplugin.userinterface.swing.common.BorderlessImageToggleButton;
+
+import javax.imageio.ImageIO;
+import javax.swing.Action;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+import javax.swing.JToggleButton;
+import javax.swing.JMenuItem;
+import java.awt.Component;
+import java.awt.Window;
+import java.awt.event.InputEvent;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+
+/**
+ * Just some utility functions.
+ *
+ * @author mhunsicker
+ */
+public class Utility {
+    private static final Logger LOGGER = Logging.getLogger(Utility.class);
+
+   public static Component addLeftJustifiedComponent(Component component) {
+        JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+
+        panel.add(component);
+        panel.add(Box.createHorizontalGlue());
+
+        return panel;
+    }
+
+    public static Component addRightJustifiedComponent(Component component) {
+        JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+
+        panel.add(Box.createHorizontalGlue());
+        panel.add(component);
+
+        return panel;
+    }
+
+    /**
+     * This creates a dialog. I only created this because I was using JDK 1.6, then realized I needed to use 1.5 and one
+     * of the most useful features of 1.6 is dialogs taking Windows as parents. This abstracts that so I don't have to
+     * make major changes to the code by passing around JFrames and JDialogs explicitly.
+       *
+       * @param  parent     the parent window
+       * @param  isModal    true if its modal, false if not.
+       * @return a dialog
+    */
+    public static JDialog createDialog(Window parent, String title, boolean isModal) {
+        if (parent instanceof JDialog) {
+           return new JDialog((JDialog) parent, title, isModal);
+        }
+        else if (parent instanceof JFrame) {
+           return new JDialog((JFrame) parent, title, isModal);
+        }
+
+       throw new RuntimeException("Unknown window type!");
+    }
+
+    /**
+     * This uses reflection to set the tab component if we're running under 1.6. It does nothing if you're running under
+     * 1.5. This is so you can run this on java 1.6 and get this benefit, but its not required to compile.
+       *
+       * This is the same as calling JTabbedPane.setTabComponentAt(). It just does so using reflection.
+    */
+    public static void setTabComponent15Compatible(JTabbedPane tabbedPane, int index, Component component) {
+        try {
+            Method method = tabbedPane.getClass().getMethod("setTabComponentAt", new Class[]{Integer.TYPE, Component.class});
+            method.invoke(tabbedPane, index, component);
+        }
+        catch (NoSuchMethodException e) {
+            //e.printStackTrace();
+            //we're not requiring 1.6, so its not a problem if we don't find the method. We just don't get this feature.
+        }
+        catch (Exception e) {
+            LOGGER.error("Setting tab component", e);
+        }
+    }
+
+    /**
+     * This creates a button with the specified action, image, and tooltip text. The main issue here is that it doesn't
+     * crash if the image is missing (which is just something that happens in real life from time to time). You probably
+     * should specify a name on the action just in case.
+    *
+    * @param  imageResourceName the image resource
+    * @param  tooltip           the tooltip to display
+    * @param  action            the action to perform
+    * @return the button that was created.
+    */
+    public static JButton createButton(Class resourceClass, String imageResourceName, String tooltip, Action action) {
+
+       JButton button = null;
+       if (imageResourceName != null) {
+            InputStream inputStream = resourceClass.getResourceAsStream(imageResourceName);
+            if (inputStream != null) {
+                try {
+                    BufferedImage image = ImageIO.read(inputStream);
+
+                    button = new BorderlessImageButton( action, new ImageIcon(image) );
+                }
+                catch (IOException e) {
+                    LOGGER.error("Reading image " + imageResourceName, e);
+                }
+            }
+        }
+
+       if( button == null ) {
+          button = new JButton( action );
+       }
+
+       if (tooltip != null) {
+          button.setToolTipText(tooltip);
+       }
+
+       return button;
+    }
+
+   public static JToggleButton createToggleButton( Class resourceClass, String imageResourceName, String tooltip, Action action ) {
+
+      JToggleButton button = null;
+
+       if (imageResourceName != null) {
+          ImageIcon icon = getImageIcon( resourceClass, imageResourceName );
+          if( icon != null ) {
+             button = new BorderlessImageToggleButton( action, icon );
+          }
+       }
+
+       if( button == null ) {
+          button = new JToggleButton( action );
+       }
+
+      if (tooltip != null) {
+         button.setToolTipText(tooltip);
+      }
+
+      return button;
+   }
+
+   public static JMenuItem createMenuItem( Class resourceClass, String name, String imageResourceName, Action action )
+    {
+       JMenuItem item = new JMenuItem( action );
+       item.setText( name );
+
+       if( imageResourceName != null )
+       {
+          ImageIcon icon = getImageIcon( resourceClass, imageResourceName );
+          item.setIcon( icon );
+       }
+
+       return item;
+    }
+
+   /**
+   * this determines if the CTRL key is down based on the modifiers from an event.
+   * This actualy needs to be checking for different things. CTRL doesn't meant the
+   * same thing on each platform.
+   */
+   public static boolean isCTRLDown( int eventModifiersEx )
+   {
+      return ( eventModifiersEx & InputEvent.CTRL_DOWN_MASK ) == InputEvent.CTRL_DOWN_MASK;
+   }
+
+   public static ImageIcon getImageIcon( Class resourceClass, String imageResourceName )
+   {
+      InputStream inputStream = resourceClass.getResourceAsStream(imageResourceName);
+      if (inputStream != null) {
+          try {
+              BufferedImage image = ImageIO.read(inputStream);
+              return new ImageIcon( image );
+             }
+          catch (IOException e) {
+              LOGGER.error("Reading image " + imageResourceName, e);
+          }
+      }
+
+      return null;
+   }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/filter/AbstractFilterEditorPanel.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/filter/AbstractFilterEditorPanel.java
new file mode 100644
index 0000000..55d8bf7
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/filter/AbstractFilterEditorPanel.java
@@ -0,0 +1,246 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic.filter;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.DefaultListModel;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This panel displays something that is filtered. Its really just a list with show/hide buttons. You populate it with
+ * whatever you like (in String form).
+ *
+ * @author mhunsicker
+ */
+public abstract class AbstractFilterEditorPanel {
+    private JPanel mainPanel;
+    private DefaultListModel model;
+    private JList list;
+
+    private JButton hideButton;
+    private JButton showButton;
+
+    public AbstractFilterEditorPanel() {
+        setupUI();
+    }
+
+    public JComponent getComponent() {
+        return mainPanel;
+    }
+
+    private void setupUI() {
+        mainPanel = new JPanel(new BorderLayout());
+
+        mainPanel.add(createOptionsPanel(), BorderLayout.NORTH);
+        mainPanel.add(createListPanel(), BorderLayout.CENTER);
+    }
+
+    private Component createOptionsPanel() {
+        JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+
+        hideButton = new JButton(new AbstractAction("Hide") {
+            public void actionPerformed(ActionEvent e) {
+                hideSelected();
+            }
+        });
+
+        showButton = new JButton(new AbstractAction("Show") {
+            public void actionPerformed(ActionEvent e) {
+                showSelected();
+            }
+        });
+
+        panel.add(showButton);
+        panel.add(Box.createHorizontalStrut(10));
+        panel.add(hideButton);
+        panel.add(Box.createHorizontalGlue());
+        panel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
+
+        return panel;
+    }
+
+    private Component createListPanel() {
+        model = new DefaultListModel();
+        list = new JList(model);
+
+        list.setCellRenderer(new FilterRenderer());
+
+        list.addListSelectionListener(new ListSelectionListener() {
+            public void valueChanged(ListSelectionEvent e) {
+                if (!e.getValueIsAdjusting()) {
+                    enableAppropriately();
+                }
+            }
+        });
+
+        return new JScrollPane(list);
+    }
+
+    private class FilterRenderer extends DefaultListCellRenderer {
+        private Color defaultForegroundColor;
+
+        private FilterRenderer() {
+            defaultForegroundColor = getForeground();
+        }
+
+        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
+                                                      boolean cellHasFocus) {
+            Component component = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+
+            setIcon(null);  //just remove the icon entirely
+
+            boolean isAllowed = isAllowed((String) value);
+
+            if (isAllowed) {
+                setForeground(defaultForegroundColor);
+            } else {
+                setForeground(Color.red);
+            }
+
+            return component;
+        }
+    }
+
+    /**
+     * Implement this to determine if this item is filtered or not. This is used by the list to indicate if this item is
+     * filtered or not.
+     *
+     * @param item the item in question
+     * @return true if its filtered, false if not.
+     */
+    protected abstract boolean isAllowed(String item);
+
+    public void enableAppropriately() {
+        boolean isShowEnabled = false;
+        boolean isHideEnabled = false;
+
+        List<String> selectedObjects = getSelectedValues();
+        if (selectedObjects.isEmpty()) {
+            isShowEnabled = false;
+            isHideEnabled = false;
+        } else {  //we've got something selected, figure out how things should be enabled based on the state of what is selected.
+            StateHolder stateHolder = new StateHolder();
+            determineShowHideEnabledState(stateHolder, selectedObjects);
+
+            //now reverse them. show is enabled, if hidden things are present.
+            isShowEnabled = stateHolder.containsHiddenObjects;
+            isHideEnabled = stateHolder.containsShownObjects;
+        }
+
+        showButton.setEnabled(isShowEnabled);
+        hideButton.setEnabled(isHideEnabled);
+    }
+
+    /**
+     * This determines the state of the show hide buttons. We only want to enable one if its appropriate. Because we
+     * have to handle multiple selection, we look for both hidden and shown items. We stop as soon as we find both or
+     * we're done with the list.
+     *
+     * @param stateHolder where we store our state.
+     * @param selectedObjects the objects to search.
+     */
+    protected void determineShowHideEnabledState(StateHolder stateHolder, List<String> selectedObjects) {
+        Iterator<String> iterator = selectedObjects.iterator();
+
+        //iterate through them all or until we've found both a hidden and shown object.
+        while (iterator.hasNext() && (!stateHolder.containsHiddenObjects || !stateHolder.containsShownObjects)) {
+            String object = iterator.next();
+            if (isAllowed(object)) {
+                stateHolder.containsShownObjects = true;
+            } else {
+                stateHolder.containsHiddenObjects = true;
+            }
+        }
+    }
+
+    /**
+     * Just a holder for 2 variables.
+     */
+    protected class StateHolder {
+        boolean containsHiddenObjects;
+        boolean containsShownObjects;
+    }
+
+    protected List<String> getSelectedValues() {
+        Object[] objects = list.getSelectedValues();
+        if (objects == null || objects.length == 0) {
+            return Collections.emptyList();
+        }
+
+        List<String> nodes = new ArrayList<String>();
+
+        for (int index = 0; index < objects.length; index++) {
+            Object object = objects[index];
+            nodes.add((String) object);
+        }
+
+        return nodes;
+    }
+
+    private void hideSelected() {
+        List<String> selection = getSelectedValues();
+
+        hideSelected(selection);
+
+        enableAppropriately();  //now update the buttons to reflect the change
+
+        list.repaint();
+    }
+
+    protected abstract void hideSelected(List<String> selection);
+
+    private void showSelected() {
+        List<String> selection = getSelectedValues();
+
+        showSelected(selection);
+
+        enableAppropriately();  //now update the buttons to reflect the change
+
+        list.repaint();
+    }
+
+    protected abstract void showSelected(List<String> selection);
+
+    public void populate(List<String> items) {
+        model.clear();
+
+        Iterator<String> iterator = items.iterator();
+
+        while (iterator.hasNext()) {
+            String item = iterator.next();
+            model.addElement(item);
+        }
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/filter/ProjectAndTaskFilterDialog.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/filter/ProjectAndTaskFilterDialog.java
new file mode 100644
index 0000000..8313fe2
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/filter/ProjectAndTaskFilterDialog.java
@@ -0,0 +1,288 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic.filter;
+
+import org.gradle.foundation.visitors.AllProjectsAndTasksVisitor;
+import org.gradle.foundation.visitors.UniqueNameProjectAndTaskVisitor;
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.gradleplugin.foundation.filters.BasicFilterEditor;
+import org.gradle.gradleplugin.foundation.filters.BasicProjectAndTaskFilter;
+import org.gradle.gradleplugin.userinterface.swing.generic.SwingExportInteraction;
+import org.gradle.gradleplugin.userinterface.swing.generic.SwingImportInteraction;
+import org.gradle.gradleplugin.userinterface.swing.generic.Utility;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.List;
+
+/**
+ * This dialog allows you to edit what tasks and projects are visible when a filter is enabled. Filters are used to weed
+ * out rarely-used things to make finding things easier.
+ *
+ * @author mhunsicker
+ */
+public class ProjectAndTaskFilterDialog {
+    private JDialog dialog;
+    private JPanel mainPanel;
+    private TaskFilterEditorPanel taskFilterEditorPanel;
+    private ProjectFilterEditorPanel projectFilterEditorPanel;
+
+    private JCheckBox filterOutTasksWithNoDescriptionCheckBox;
+
+    private BasicFilterEditor editor;
+    private GradlePluginLord gradlePluginLord;
+
+    private boolean saveResults;
+
+    public ProjectAndTaskFilterDialog(Window parent, GradlePluginLord gradlePluginLord) {
+        this.gradlePluginLord = gradlePluginLord;
+        this.dialog = Utility.createDialog(parent, "Filter", true);
+
+        setupUI();
+    }
+
+    /**
+     * Call this to start editing the given filter.
+     *
+     * @param filter the filter to edit
+     * @return a filter if the user OKs the changes, null if they cancel
+     */
+    public BasicProjectAndTaskFilter show(BasicProjectAndTaskFilter filter) {
+        this.editor = new BasicFilterEditor(filter);
+
+        if (mainPanel == null) {
+            setupUI();
+        }
+
+        populate();
+
+        taskFilterEditorPanel.enableAppropriately();
+        projectFilterEditorPanel.enableAppropriately();
+
+        dialog.setVisible(true);
+
+        if (this.saveResults) {
+            return editor.createFilter();
+        }
+
+        return null;
+    }
+
+    private void setupUI() {
+        mainPanel = new JPanel(new BorderLayout());
+        dialog.getContentPane().add(mainPanel);
+
+        mainPanel.add(createOptionsPanel(), BorderLayout.NORTH);
+        mainPanel.add(createCenterPanel(), BorderLayout.CENTER);
+        mainPanel.add(createOkCancelPanel(), BorderLayout.SOUTH);
+
+        mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+
+        dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
+        dialog.addWindowListener(new WindowAdapter() {
+            public void windowClosing(WindowEvent e) {
+                close(false);
+            }
+        });
+
+        dialog.setSize(600, 750);
+        dialog.setLocationRelativeTo(dialog.getParent());
+    }
+
+    private Component createOptionsPanel() {
+        JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+
+        JButton importButton = new JButton(new AbstractAction("Import...") {
+            public void actionPerformed(ActionEvent e) {
+                importFilter();
+            }
+        });
+
+        JButton exportButton = new JButton(new AbstractAction("Export...") {
+            public void actionPerformed(ActionEvent e) {
+                exportFilter();
+            }
+        });
+
+        filterOutTasksWithNoDescriptionCheckBox = new JCheckBox(new AbstractAction("Hide Tasks With No Description") {
+            public void actionPerformed(ActionEvent e) {
+                filterOutTasksWithNoDescription();
+            }
+        });
+
+        panel.add(filterOutTasksWithNoDescriptionCheckBox);
+        panel.add(Box.createHorizontalStrut(10));
+        panel.add(importButton);
+        panel.add(Box.createHorizontalStrut(10));
+        panel.add(exportButton);
+        panel.add(Box.createHorizontalGlue());
+
+        panel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
+
+        return panel;
+    }
+
+    private Component createOkCancelPanel() {
+        JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+
+        JButton okButton = new JButton(new AbstractAction("OK") {
+            public void actionPerformed(ActionEvent e) {
+                close(true);
+            }
+        });
+
+        JButton cancelButton = new JButton(new AbstractAction("Cancel") {
+            public void actionPerformed(ActionEvent e) {
+                close(false);
+            }
+        });
+
+        panel.add(Box.createHorizontalGlue());
+        panel.add(okButton);
+        panel.add(Box.createHorizontalStrut(10));
+        panel.add(cancelButton);
+        panel.add(Box.createHorizontalGlue());
+
+        panel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
+
+        return panel;
+    }
+
+    /**
+     * This creates the two list panels. This may seem odd, but I'm putting each of them into a BoxLayout then inside
+     * another BorderLayout. This is to make each of them as large as they can be and divide the space evenly between
+     * them.
+     */
+    private Component createCenterPanel() {
+        JPanel outterPanel = new JPanel();
+        outterPanel.setLayout(new BoxLayout(outterPanel, BoxLayout.Y_AXIS));
+
+        JPanel projectPanel = new JPanel(new BorderLayout());
+        JPanel taskPanel = new JPanel(new BorderLayout());
+
+        projectPanel.add(createProjectPanel(), BorderLayout.CENTER);
+        taskPanel.add(createTasksPanel(), BorderLayout.CENTER);
+
+        projectPanel.setBorder(BorderFactory.createTitledBorder("Projects"));
+        taskPanel.setBorder(BorderFactory.createTitledBorder("Tasks"));
+
+        outterPanel.add(projectPanel);
+        outterPanel.add(Box.createVerticalStrut(10));
+        outterPanel.add(taskPanel);
+
+        return outterPanel;
+    }
+
+    private Component createTasksPanel() {
+        taskFilterEditorPanel = new TaskFilterEditorPanel();
+
+        return taskFilterEditorPanel.getComponent();
+    }
+
+    private Component createProjectPanel() {
+        projectFilterEditorPanel = new ProjectFilterEditorPanel();
+
+        return projectFilterEditorPanel.getComponent();
+    }
+
+    private void close(boolean saveResults) {
+        this.saveResults = saveResults;
+        dialog.setVisible(false);
+    }
+
+    /**
+     * This imports a filter from a file.
+     */
+    private void importFilter() {
+        if (editor.importFromFile(new SwingImportInteraction(dialog))) {
+            taskFilterEditorPanel.getComponent().repaint();
+            projectFilterEditorPanel.getComponent().repaint();
+        }
+    }
+
+    /**
+     * This exports a filter to a file.
+     */
+    private void exportFilter() {
+        editor.exportToFile(new SwingExportInteraction(dialog));
+    }
+
+    /**
+     * Populates our lists. We'll use a visitor to build up a list of unique names of projects and tasks. Then we'll
+     * sort them and add them to each filter editor panel.
+     */
+    private void populate() {
+        UniqueNameProjectAndTaskVisitor visitor = new UniqueNameProjectAndTaskVisitor();
+
+        AllProjectsAndTasksVisitor.visitProjectAndTasks(gradlePluginLord.getProjects(), visitor, null);
+
+        List<String> taskNames = visitor.getSortedTaskNames();
+        List<String> projectNames = visitor.getSortedProjectNames();
+
+        taskFilterEditorPanel.populate(taskNames);
+        projectFilterEditorPanel.populate(projectNames);
+
+        filterOutTasksWithNoDescriptionCheckBox.setSelected(editor.filterOutTasksWithNoDescription());
+    }
+
+    private class TaskFilterEditorPanel extends AbstractFilterEditorPanel {
+        protected boolean isAllowed(String item) {
+            return editor.doesAllowTask(item);
+        }
+
+        protected void hideSelected(List<String> selection) {
+            editor.hideTasksByName(selection);
+        }
+
+        protected void showSelected(List<String> selection) {
+            editor.showTasksByName(selection);
+        }
+    }
+
+    private class ProjectFilterEditorPanel extends AbstractFilterEditorPanel {
+        protected boolean isAllowed(String item) {
+            return editor.doesAllowProject(item);
+        }
+
+        protected void hideSelected(List<String> selection) {
+            editor.hideProjectsByName(selection);
+        }
+
+        protected void showSelected(List<String> selection) {
+            editor.showProjectsByName(selection);
+        }
+    }
+
+    private void filterOutTasksWithNoDescription() {
+        editor.setFilterOutTasksWithNoDescription(filterOutTasksWithNoDescriptionCheckBox.isSelected());
+        taskFilterEditorPanel.getComponent().repaint();
+        projectFilterEditorPanel.getComponent().repaint();
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/CommandLineTab.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/CommandLineTab.java
new file mode 100644
index 0000000..b9b357c
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/CommandLineTab.java
@@ -0,0 +1,153 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic.tabs;
+
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.gradleplugin.foundation.favorites.FavoritesEditor;
+import org.gradle.gradleplugin.foundation.settings.SettingsNode;
+import org.gradle.gradleplugin.userinterface.swing.generic.Utility;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+
+/**
+ * A tab that allows you to just type a straight command line that is sent to Gradle.
+ *
+ * @author mhunsicker
+  */
+public class CommandLineTab implements GradleTab {
+    private GradlePluginLord gradlePluginLord;
+    private FavoritesEditor favoritesEditor;
+
+    private JPanel mainPanel;
+    private JTextField commandLineField;
+
+    private JButton executeButton;
+    private JButton addToFavoritesButton;
+
+   public CommandLineTab(GradlePluginLord gradlePluginLord, SettingsNode settingsNode) {
+        this.gradlePluginLord = gradlePluginLord;
+
+        this.favoritesEditor = gradlePluginLord.getFavoritesEditor();
+    }
+
+    /**
+    * @return the name of this tab
+    */
+    public String getName() {
+        return "Command Line";
+    }
+
+    /**
+     * Notification that this component is about to be shown. Do whatever initialization you choose.
+    */
+    public void aboutToShow() {
+
+    }
+
+    /**
+    * This is where we should create your component.
+    *
+    * @return the component
+    */
+    public Component createComponent() {
+        JPanel mainPanel = new JPanel(new BorderLayout());
+
+        mainPanel.add(createCommandLinePanel(), BorderLayout.NORTH);
+        mainPanel.add(Box.createVerticalGlue(), BorderLayout.CENTER);
+
+        mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+
+        return mainPanel;
+    }
+
+    private Component createCommandLinePanel() {
+        JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+
+        commandLineField = new JTextField();
+
+        //make Enter execute the command line.
+        commandLineField.registerKeyboardAction(new ExecuteAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
+
+        //we'll put 'gradle' in from the command line to make it more obvious that its not needed.
+        JPanel commandLinePanel = new JPanel();
+        commandLinePanel.setLayout(new BoxLayout(commandLinePanel, BoxLayout.X_AXIS));
+        commandLinePanel.add(new JLabel("gradle "));
+        commandLinePanel.add(commandLineField);
+
+        panel.add(Utility.addLeftJustifiedComponent(new JLabel("Command Line:")));
+        panel.add(Box.createVerticalStrut(5));
+        panel.add(commandLinePanel);
+        panel.add(Box.createVerticalStrut(5));
+        panel.add(createButtonPanel());
+
+        return panel;
+    }
+
+    private class ExecuteAction extends AbstractAction {
+        private ExecuteAction() {
+            super("Execute");
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            executeCommandLine();
+        }
+    }
+
+
+    private Component createButtonPanel() {
+        JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+
+        executeButton = new JButton(new ExecuteAction());
+
+        addToFavoritesButton = new JButton(new AbstractAction("Add To Favorites") {
+            public void actionPerformed(ActionEvent e) {
+                addToFavorites();
+            }
+        });
+
+        panel.add(Box.createHorizontalGlue());
+        panel.add(executeButton);
+        panel.add(Box.createHorizontalStrut(10));
+        panel.add(addToFavoritesButton);
+
+        return panel;
+    }
+
+    private void addToFavorites() {
+        String commandLineText = commandLineField.getText();
+        favoritesEditor.addFavorite(commandLineText, false);
+    }
+
+    private void executeCommandLine() {
+        String commandLineText = commandLineField.getText();
+        gradlePluginLord.addExecutionRequestToQueue(commandLineText, "Command Line" );
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/FavoriteTasksTab.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/FavoriteTasksTab.java
new file mode 100644
index 0000000..2ad49e6
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/FavoriteTasksTab.java
@@ -0,0 +1,423 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic.tabs;
+
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.gradleplugin.foundation.favorites.FavoriteTask;
+import org.gradle.gradleplugin.foundation.favorites.FavoritesEditor;
+import org.gradle.gradleplugin.foundation.settings.SettingsNode;
+import org.gradle.gradleplugin.userinterface.swing.generic.SwingEditFavoriteInteraction;
+import org.gradle.gradleplugin.userinterface.swing.generic.SwingExportInteraction;
+import org.gradle.gradleplugin.userinterface.swing.generic.SwingImportInteraction;
+import org.gradle.gradleplugin.userinterface.swing.generic.Utility;
+
+import javax.swing.*;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This displays a list of favorites and allows the user to add/remove items as well as change their order.
+ *
+ * @author mhunsicker
+  */
+public class FavoriteTasksTab implements GradleTab, GradlePluginLord.GeneralPluginObserver, FavoritesEditor.FavoriteTasksObserver {
+    private GradlePluginLord gradlePluginLord;
+    private FavoritesEditor favoritesEditor;
+
+    private SettingsNode settingsNode;
+
+    private JPanel mainPanel;
+    private DefaultListModel model;
+    private JList list;
+
+    private JPopupMenu popupMenu;
+    private JMenuItem executeMenuItem;
+    private JMenuItem editMenuItem;
+    private JMenuItem removeFavoritesMenuItem;
+    private JMenuItem copyFavoritesMenuItem;
+
+    private JButton executeButton;
+    private JButton addButton;
+    private JButton editButton;
+    private JButton removeButton;
+    private JButton moveUpButton;
+    private JButton moveDownButton;
+    private JButton importButton;
+    private JButton exportButton;
+
+   public FavoriteTasksTab(GradlePluginLord gradlePluginLord, SettingsNode settingsNode) {
+        this.gradlePluginLord = gradlePluginLord;
+        this.settingsNode = settingsNode;
+
+        this.favoritesEditor = gradlePluginLord.getFavoritesEditor();
+
+        gradlePluginLord.addGeneralPluginObserver(this, true);
+        favoritesEditor.addFavoriteTasksObserver(this, true);
+
+        //read in our settings before we populate things
+        favoritesEditor.serializeIn(settingsNode);
+    }
+
+    public String getName() {
+        return "Favorites";
+    }
+
+    public Component createComponent() {
+        setupUI();
+        enableThingsAppropriately();
+        return mainPanel;
+    }
+
+    /**
+    * Notification that this component is about to be shown. Do whatever initialization you choose.
+    */
+    public void aboutToShow() {
+        populate();
+    }
+
+    private void setupUI() {
+        mainPanel = new JPanel(new BorderLayout());
+
+        mainPanel.add(createButtonPanel(), BorderLayout.NORTH);
+        mainPanel.add(createListPanel(), BorderLayout.CENTER);
+
+        setupPopupMenu();
+    }
+
+    private Component createButtonPanel() {
+        JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+
+        executeButton = Utility.createButton(getClass(), "execute.png", "Execute the selected command", new AbstractAction("Execute") {
+            public void actionPerformed(ActionEvent e) {
+                executeSelectedTasks();
+            }
+        });
+
+        addButton = Utility.createButton(getClass(), "add.png", "Adds a new favorite gradle command", new AbstractAction("Add...") {
+            public void actionPerformed(ActionEvent e) {
+                addTask();
+            }
+        });
+
+        editButton = Utility.createButton(getClass(), "edit.png", "Edit the selected favorite", new AbstractAction("Edit...") {
+            public void actionPerformed(ActionEvent e) {
+                editTask();
+            }
+        });
+
+        removeButton = Utility.createButton(getClass(), "remove.png", "Delete the selected favorite", new AbstractAction("Remove") {
+            public void actionPerformed(ActionEvent e) {
+                removeSelectedFavorites();
+            }
+        });
+
+        moveUpButton = Utility.createButton(getClass(), "move-up.png", "Moves the selected favorites up", new AbstractAction("Move Up") {
+            public void actionPerformed(ActionEvent e) {
+                favoritesEditor.moveFavoritesBefore(getSelectedFavoriteTasks());
+            }
+        });
+
+        moveDownButton = Utility.createButton(getClass(), "move-down.png", "Moves the selected favorites down", new AbstractAction("Move Down") {
+            public void actionPerformed(ActionEvent e) {
+                favoritesEditor.moveFavoritesAfter(getSelectedFavoriteTasks());
+            }
+        });
+
+        importButton = Utility.createButton(getClass(), "import.png", "Imports current favorite settings", new AbstractAction("Import...") {
+            public void actionPerformed(ActionEvent e) {
+                importFavorites();
+            }
+        });
+
+        exportButton = Utility.createButton(getClass(), "export.png", "Exports current favorite settings", new AbstractAction("Export...") {
+            public void actionPerformed(ActionEvent e) {
+                exportFavorites();
+            }
+        });
+
+        panel.add(executeButton);
+        panel.add(Box.createHorizontalStrut(10));
+        panel.add(addButton);
+        panel.add(Box.createHorizontalStrut(5));
+        panel.add(editButton);
+        panel.add(Box.createHorizontalStrut(10));
+        panel.add(removeButton);
+        panel.add(Box.createHorizontalStrut(10));
+        panel.add(moveUpButton);
+        panel.add(Box.createHorizontalStrut(5));
+        panel.add(moveDownButton);
+        panel.add(Box.createHorizontalGlue());
+        panel.add(importButton);
+        panel.add(Box.createHorizontalStrut(10));
+        panel.add(exportButton);
+
+        panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+
+        return panel;
+    }
+
+    private Component createListPanel() {
+        model = new DefaultListModel();
+        list = new JList(model);
+
+        list.addMouseListener(new MouseAdapter() {
+            public void mouseClicked(MouseEvent e) {
+                if (e.getClickCount() == 2) {
+                   executeSelectedTasks();
+                }
+                else
+                   if( e.getButton() == MouseEvent.BUTTON3 ) {
+                      handleRightClick( e );
+                   }
+            }
+        });
+
+        list.addListSelectionListener(new ListSelectionListener() {
+            public void valueChanged(ListSelectionEvent e) {
+                if (!e.getValueIsAdjusting()) {
+                   enableThingsAppropriately();
+                }
+            }
+        });
+
+       //hook 'enter' so it runs the selected tasks.
+       list.registerKeyboardAction(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                executeSelectedTasks();
+            }
+        }, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
+
+        return new JScrollPane(list);
+    }
+
+    private void populate() {
+        model.clear();
+
+        Iterator<FavoriteTask> taskIterator = favoritesEditor.getFavoriteTasks().iterator();
+        while (taskIterator.hasNext()) {
+            FavoriteTask favoriteTask = taskIterator.next();
+            model.addElement(favoriteTask);
+        }
+    }
+
+
+  private void handleRightClick( MouseEvent e )
+  {
+     Point point = e.getPoint();
+     int index = list.locationToIndex( point );
+     if( index != -1 )  //all of this is because the JList won't select things on right-click. Which means you won't be acting upon what you think you're acting upon.
+     {
+        if( !list.isSelectedIndex( index ) )
+        {
+           if( Utility.isCTRLDown( e.getModifiersEx() ) ) {
+              list.addSelectionInterval( index, index ); //the CTRL key is down, just add this to our selection
+           }
+           else {
+              list.setSelectedIndex( index );            //the CTRL key is not down, just replace the selection
+           }
+           //we're not handling SHIFT! Nor are we handling OS X.
+        }
+     }
+     enableThingsAppropriately();
+     popupMenu.show( list, point.x, point.y );
+  }
+
+    /**
+    * Notification that we're about to reload the projects and tasks.
+    */
+    public void startingProjectsAndTasksReload() {
+        //we don't really care.
+    }
+
+    /**
+     * Notification that the projects and tasks have been reloaded. You may want to repopulate or update your views.
+     *
+     * @param wasSuccessful true if they were successfully reloaded. False if an error occurred so we no longer can show
+     * the projects and tasks (probably an error in a .gradle file).
+    */
+    public void projectsAndTasksReloaded(boolean wasSuccessful) {
+        //We need to repaint in case any are in error now, or no longer in error.
+        list.repaint();
+
+        //and possible change what is enabled
+        enableThingsAppropriately();
+    }
+
+   /**
+     * Notification that the favorites list has changed. We'll repopulate and then save our changes immediately. The
+     * save is useful for IDE integration where we don't control the settings.
+    */
+    public void favoritesChanged() {
+        populate();
+        favoritesEditor.serializeOut(settingsNode);
+    }
+
+    /**
+     * Notification that the favorites were re-ordered. We'll update our list and save our changes immediately. The save
+     * is useful for IDE integration where we don't control the settings.
+    *
+    * @param favoritesReordered the favorites that were reordered
+    */
+    public void favoritesReordered(List<FavoriteTask> favoritesReordered) {
+        Object[] previouslySelectedObjects = list.getSelectedValues();
+
+        populate();
+
+        list.clearSelection();
+        //now go re-select the things that were moved
+        if (previouslySelectedObjects != null) {
+           for (int index = 0; index < previouslySelectedObjects.length; index++) {
+                           Object previouslySelectedObject = previouslySelectedObjects[index];
+                           int listIndex = model.indexOf(previouslySelectedObject);
+                           if (listIndex != -1) {
+                              list.addSelectionInterval(listIndex, listIndex);
+                           }
+           }
+        }
+
+       favoritesEditor.serializeOut(settingsNode);
+    }
+
+    private void setupPopupMenu() {
+        popupMenu = new JPopupMenu();
+
+        executeMenuItem = Utility.createMenuItem( this.getClass(),"Execute", "execute.png", new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                executeSelectedTasks();
+            }
+        });
+
+        popupMenu.add(executeMenuItem);
+
+        editMenuItem = Utility.createMenuItem( this.getClass(),"Edit...", "edit.png", new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                editTask();
+            }
+        });
+
+        popupMenu.add(editMenuItem);
+
+        copyFavoritesMenuItem = Utility.createMenuItem( this.getClass(),"Duplicate ", "blank.png", new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                duplicateTasks();
+            }
+        });
+
+        popupMenu.add(copyFavoritesMenuItem);
+
+        removeFavoritesMenuItem = Utility.createMenuItem( this.getClass(), "Remove", "remove.png", new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                removeSelectedFavorites();
+            }
+        });
+
+        popupMenu.add(removeFavoritesMenuItem);
+    }
+
+    /**
+     * Executes the selected tasks. If only one is selected, we execute it as normal. If
+     * however, multiples are selected, we'll execute that all at once.
+     */
+    private void executeSelectedTasks() {
+        List<FavoriteTask> favorites = getSelectedFavoriteTasks();
+        gradlePluginLord.addExecutionRequestToQueue( favorites );
+    }
+
+    private void removeSelectedFavorites() {
+        List<FavoriteTask> favorites = getSelectedFavoriteTasks();
+        favoritesEditor.removeFavorites(favorites);
+    }
+
+    private List<FavoriteTask> getSelectedFavoriteTasks() {
+        Object[] objects = list.getSelectedValues();
+        if (objects == null) {
+           return Collections.emptyList();
+        }
+
+       List<FavoriteTask> favorites = new ArrayList<FavoriteTask>();
+        for (int index = 0; index < objects.length; index++) {
+           favorites.add((FavoriteTask) objects[index]);
+        }
+
+       return favorites;
+    }
+
+    private FavoriteTask getFirstSelectedFavoriteTask() {
+        return (FavoriteTask) list.getSelectedValue();
+    }
+
+    /**
+     * Enables buttons and menu items based on what is selected.
+    */
+    private void enableThingsAppropriately() {
+        Object[] objects = list.getSelectedValues();
+        boolean hasSelection = objects != null && objects.length != 0;
+        boolean hasSingleSelection = objects != null && objects.length == 1;
+
+        executeMenuItem.setEnabled(hasSelection);
+        removeFavoritesMenuItem.setEnabled(hasSelection);
+
+        executeButton.setEnabled(hasSelection);
+        removeButton.setEnabled(hasSelection);
+        moveUpButton.setEnabled(hasSelection);
+        moveDownButton.setEnabled(hasSelection);
+        copyFavoritesMenuItem.setEnabled(hasSelection);
+
+        editButton.setEnabled(hasSingleSelection);  //only can edit if a single task is selected
+    }
+
+    /**
+    * This imports favorites from a file.
+    */
+    private void importFavorites() {
+        favoritesEditor.importFromFile(new SwingImportInteraction(SwingUtilities.getWindowAncestor(mainPanel)));
+    }
+
+    /**
+    * This exports the favorites to a file.
+    */
+    private void exportFavorites() {
+        favoritesEditor.exportToFile(new SwingExportInteraction(SwingUtilities.getWindowAncestor(mainPanel)));
+    }
+
+    /**
+    * Call this to prompt the user for a task to add.
+    */
+    private void addTask() {
+        favoritesEditor.addFavorite(new SwingEditFavoriteInteraction(SwingUtilities.getWindowAncestor(mainPanel), "Add Favorite", true ));
+    }
+
+    private void editTask() {
+        FavoriteTask selectedFavoriteTask = getFirstSelectedFavoriteTask();
+       //if the user has kept these two in synch, we'll continue to keep them in synch.
+        boolean synchronizeDisplayNameWithCommand = selectedFavoriteTask.getDisplayName().equals( selectedFavoriteTask.getFullCommandLine() );
+        favoritesEditor.editFavorite(selectedFavoriteTask, new SwingEditFavoriteInteraction(SwingUtilities.getWindowAncestor(mainPanel), "Edit Favorite", synchronizeDisplayNameWithCommand ));
+    }
+
+    /**
+     * This duplicates all the selected tasks
+     */
+    private void duplicateTasks() {
+        favoritesEditor.duplicateFavorites( getSelectedFavoriteTasks() );
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/GradleTab.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/GradleTab.java
new file mode 100644
index 0000000..b5746e3
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/GradleTab.java
@@ -0,0 +1,43 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic.tabs;
+
+import java.awt.Component;
+
+/**
+ * Interface for a tab in the gradle UI.
+ *
+ * @author mhunsicker
+ */
+public interface GradleTab {
+    /**
+     * @return the name of this tab
+     * @author mhunsicker
+     */
+    public String getName();
+
+    /**
+     * This is where we should create the component.
+     *
+     * @return the component
+     */
+    public Component createComponent();
+
+    /**
+     * Notification that this component is about to be shown. Do whatever initialization you choose.
+     */
+    public void aboutToShow();
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/SetupTab.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/SetupTab.java
new file mode 100644
index 0000000..b4084bd
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/SetupTab.java
@@ -0,0 +1,626 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic.tabs;
+
+import org.gradle.initialization.DefaultCommandLine2StartParameterConverter;
+import org.gradle.StartParameter;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.gradleplugin.foundation.settings.SettingsNode;
+import org.gradle.gradleplugin.userinterface.swing.generic.OutputUILord;
+import org.gradle.gradleplugin.userinterface.swing.generic.Utility;
+
+import javax.swing.AbstractAction;
+import javax.swing.AbstractButton;
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.ButtonModel;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JFileChooser;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JTextField;
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.util.*;
+
+/**
+ * This tab contains general settings for the plugin.
+ *
+ * @author mhunsicker
+  */
+public class SetupTab implements GradleTab, GradlePluginLord.SettingsObserver {
+    private final Logger logger = Logging.getLogger(SetupTab.class);
+
+    private static final String STACK_TRACE_LEVEL_CLIENT_PROPERTY = "stack-trace-level-client-property";
+    private static final String SETUP = "setup";
+    private static final String STACK_TRACE_LEVEL = "stack-trace-level";
+    private static final String SHOW_OUTPUT_ON_ERROR = "show-output-on-error";
+    private static final String LOG_LEVEL = "log-level";
+    private static final String CURRENT_DIRECTORY = "current-directory";
+    private static final String CUSTOM_GRADLE_EXECUTOR = "custom-gradle-executor";
+
+    private GradlePluginLord gradlePluginLord;
+    private OutputUILord outputUILord;
+    private SettingsNode settingsNode;
+
+    private JPanel mainPanel;
+
+    private JRadioButton showNoStackTraceRadioButton;
+    private JRadioButton showStackTrackRadioButton;
+    private JRadioButton showFullStackTrackRadioButton;
+
+    private JComboBox logLevelComboBox;
+
+    private JCheckBox onlyShowOutputOnErrorCheckBox;
+
+    private ButtonGroup stackTraceButtonGroup;
+
+    private JTextField currentDirectoryTextField;
+
+    private JCheckBox useCustomGradleExecutorCheckBox;
+    private JTextField customGradleExecutorField;
+    private JButton browseForCustomGradleExecutorButton;
+
+   private JPanel customPanelPlaceHolder;
+
+   public SetupTab(GradlePluginLord gradlePluginLord, OutputUILord outputUILord, SettingsNode settingsNode) {
+        this.gradlePluginLord = gradlePluginLord;
+      this.outputUILord = outputUILord;
+      this.settingsNode = settingsNode.addChildIfNotPresent(SETUP);
+    }
+
+    public String getName() {
+        return "Setup";
+    }
+
+    public Component createComponent() {
+        setupUI();
+
+        return mainPanel;
+    }
+
+    /**
+    * Notification that this component is about to be shown. Do whatever initialization you choose.
+    */
+    public void aboutToShow() {
+        updatePluginLordSettings();
+        gradlePluginLord.addSettingsObserver( this, true );
+    }
+
+    private void setupUI() {
+        mainPanel = new JPanel();
+        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
+
+        mainPanel.add(createCurrentDirectoryPanel());
+        mainPanel.add(Box.createVerticalStrut(10));
+        mainPanel.add(createLogLevelPanel());
+        mainPanel.add(Box.createVerticalStrut(10));
+        mainPanel.add(createStackTracePanel());
+        mainPanel.add(Box.createVerticalStrut(10));
+        mainPanel.add(createOptionsPanel());
+        mainPanel.add(Box.createVerticalStrut(10));
+        mainPanel.add(createCustomExecutorPanel());
+        mainPanel.add(Box.createVerticalStrut(10));
+
+        //add a panel that can be used to add custom things to the setup tab
+        customPanelPlaceHolder = new JPanel( new BorderLayout() );
+        mainPanel.add( customPanelPlaceHolder );
+
+        //Glue alone doesn't work in this situation. This forces everything to the top.
+        JPanel expandingPanel = new JPanel(new BorderLayout());
+        expandingPanel.add(Box.createVerticalGlue(), BorderLayout.CENTER);
+        mainPanel.add(expandingPanel);
+
+        mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+    }
+
+    private Component createCurrentDirectoryPanel() {
+        currentDirectoryTextField = new JTextField();
+        currentDirectoryTextField.setEditable(false);
+
+        String currentDirectory = settingsNode.getValueOfChild(CURRENT_DIRECTORY, null);
+        if (currentDirectory == null || "".equals(currentDirectory.trim())) {
+           currentDirectory = gradlePluginLord.getCurrentDirectory().getAbsolutePath();
+        }
+
+       currentDirectoryTextField.setText(currentDirectory);
+        gradlePluginLord.setCurrentDirectory(new File(currentDirectory));
+
+        JButton browseButton = new JButton(new AbstractAction("Browse...") {
+            public void actionPerformed(ActionEvent e) {
+                File file = browseForDirectory( gradlePluginLord.getCurrentDirectory() );
+                if (file != null) {
+                    setCurrentDirectory( file );
+                }
+            }
+        });
+
+        JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+
+        panel.add(Utility.addLeftJustifiedComponent(new JLabel("Current Directory")));
+        panel.add(createSideBySideComponent(currentDirectoryTextField, browseButton));
+
+        return panel;
+    }
+
+    private void setCurrentDirectory(File file) {
+
+        if( file == null ) {
+            currentDirectoryTextField.setText("");
+            settingsNode.setValueOfChild(CURRENT_DIRECTORY, "" );
+        } else {
+            currentDirectoryTextField.setText( file.getAbsolutePath() );
+            settingsNode.setValueOfChild(CURRENT_DIRECTORY, file.getAbsolutePath());
+        }
+
+        if( gradlePluginLord.setCurrentDirectory(file) )
+        {
+            //refresh the tasks only if we actually changed the current directory
+            gradlePluginLord.addRefreshRequestToQueue();
+        }
+    }
+
+    /**
+    * this creates a panel where the right component is its preferred size. This is useful for putting on
+    * a button on the right and a text field on the left.
+    */
+    public static JComponent createSideBySideComponent(Component leftComponent, Component rightComponent) {
+        JPanel xLayoutPanel = new JPanel();
+        xLayoutPanel.setLayout(new BoxLayout(xLayoutPanel, BoxLayout.X_AXIS));
+
+        Dimension preferredSize = leftComponent.getPreferredSize();
+        leftComponent.setMaximumSize(new Dimension(Integer.MAX_VALUE, preferredSize.height));
+
+        xLayoutPanel.add(leftComponent);
+        xLayoutPanel.add(Box.createHorizontalStrut(5));
+        xLayoutPanel.add(rightComponent);
+
+        return xLayoutPanel;
+    }
+
+    /**
+     * Browses for a file using the text value from the text field as the current value.
+     * @param fileTextField where we get the current value
+     * @return
+     */
+    private File browseForDirectory(File initialFile ) {
+
+        if( initialFile == null ) {
+            initialFile = new File( System.getProperty("user.dir") );
+        }
+
+        JFileChooser chooser = new JFileChooser(initialFile);
+        chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+        chooser.setMultiSelectionEnabled(false);
+
+        File file = null;
+        if (chooser.showOpenDialog(mainPanel) == JFileChooser.APPROVE_OPTION) {
+            file = chooser.getSelectedFile();
+        }
+
+        return file;
+    }
+
+    /**
+     * Creates a panel that has a combo box to select a log level
+    */
+    private Component createLogLevelPanel() {
+        JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+
+        logLevelComboBox = new JComboBox(getLogLevelWrappers());
+
+        panel.add(Utility.addLeftJustifiedComponent(new JLabel("Log Level")));
+        panel.add(Utility.addLeftJustifiedComponent(logLevelComboBox));
+
+        //initialize our value
+        String logLevelName = settingsNode.getValueOfChild(LOG_LEVEL, null);
+        LogLevel logLevel = gradlePluginLord.getLogLevel();
+        if (logLevelName != null) {
+            try {
+                logLevel = LogLevel.valueOf(logLevelName);
+            }
+            catch (IllegalArgumentException e) //this may happen if the enum changes. We don't want this to stop the whole UI
+            {
+                logger.error("Converting log level text to log level enum '" + logLevelName + "'", e);
+            }
+        }
+
+        gradlePluginLord.setLogLevel(logLevel);
+        setLogLevelComboBoxSetting(logLevel);
+
+        logLevelComboBox.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                LogLevelWrapper wrapper = (LogLevelWrapper) logLevelComboBox.getSelectedItem();
+                if (wrapper != null) {
+                    gradlePluginLord.setLogLevel(wrapper.logLevel);
+                    settingsNode.setValueOfChild(LOG_LEVEL, wrapper.logLevel.name());
+                }
+            }
+        });
+
+        return panel;
+    }
+
+    /**
+    * This creates an array of wrapper objects suitable for passing to the constructor of the log level combo box.
+    */
+    private Vector<LogLevelWrapper> getLogLevelWrappers() {
+        Collection<LogLevel> collection = new DefaultCommandLine2StartParameterConverter().getLogLevels();
+
+        Vector<LogLevelWrapper> wrappers = new Vector<LogLevelWrapper>();
+
+        Iterator<LogLevel> iterator = collection.iterator();
+
+        while (iterator.hasNext()) {
+            LogLevel level = iterator.next();
+            wrappers.add(new LogLevelWrapper(level));
+        }
+
+        Collections.sort(wrappers, new Comparator<LogLevelWrapper>() {
+            public int compare(LogLevelWrapper o1, LogLevelWrapper o2) {
+                return o1.toString().compareToIgnoreCase(o2.toString());
+            }
+        });
+
+        return wrappers;
+    }
+
+    /**
+     * This exists solely for overriding toString to something nicer. We'll captilize the first letter. The rest become
+     * lower case. Ultimately, this should probably move into LogLevel. We'll also put the log level shortcut in parenthesis
+    */
+    private class LogLevelWrapper {
+        private LogLevel logLevel;
+        private String toString;
+
+        private LogLevelWrapper(LogLevel logLevel) {
+            this.logLevel = logLevel;
+
+            String temp = logLevel.toString().toLowerCase().replace('_', ' '); //replace underscores in the name with spaces
+            this.toString = Character.toUpperCase(temp.charAt(0)) + temp.substring(1);
+
+            //add the command line character to the end (so if an error message says use a log level, you can easily translate)
+            String commandLineCharacter = new DefaultCommandLine2StartParameterConverter().getLogLevelCommandLine( logLevel );
+            if( commandLineCharacter != null && !commandLineCharacter.equals( "" ))
+            {
+               this.toString += " (-" + commandLineCharacter + ")";
+            }
+        }
+
+        public String toString() {
+            return toString;
+        }
+    }
+
+    /**
+    * Sets the log level combo box to the specified log level.
+    *
+    * @param  logLevel   the log level in question.
+    */
+    private void setLogLevelComboBoxSetting(LogLevel logLevel) {
+        DefaultComboBoxModel model = (DefaultComboBoxModel) logLevelComboBox.getModel();
+        for (int index = 0; index < model.getSize(); index++) {
+            LogLevelWrapper wrapper = (LogLevelWrapper) model.getElementAt(index);
+            if (wrapper.logLevel == logLevel) {
+                logLevelComboBox.setSelectedIndex(index);
+                return;
+            }
+        }
+    }
+
+    /**
+     * Creates a panel with stack trace level radio buttons that allow you to specify how much info is given when an
+     * error occurs.
+    */
+    private Component createStackTracePanel() {
+        JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+
+        panel.setBorder(BorderFactory.createTitledBorder("Stack Trace Output"));
+
+        showNoStackTraceRadioButton = new JRadioButton("Exceptions Only");
+        showStackTrackRadioButton = new JRadioButton("Standard Stack Trace (-" + DefaultCommandLine2StartParameterConverter.STACKTRACE + ")");  //add the command line character to the end (so if an error message says use a stack trace level, you can easily translate)
+        showFullStackTrackRadioButton = new JRadioButton("Full Stack Trace (-" + DefaultCommandLine2StartParameterConverter.FULL_STACKTRACE + ")" );
+
+        showNoStackTraceRadioButton.putClientProperty(STACK_TRACE_LEVEL_CLIENT_PROPERTY, StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS);
+        showStackTrackRadioButton.putClientProperty(STACK_TRACE_LEVEL_CLIENT_PROPERTY, StartParameter.ShowStacktrace.ALWAYS);
+        showFullStackTrackRadioButton.putClientProperty(STACK_TRACE_LEVEL_CLIENT_PROPERTY, StartParameter.ShowStacktrace.ALWAYS_FULL);
+
+        stackTraceButtonGroup = new ButtonGroup();
+        stackTraceButtonGroup.add(showNoStackTraceRadioButton);
+        stackTraceButtonGroup.add(showStackTrackRadioButton);
+        stackTraceButtonGroup.add(showFullStackTrackRadioButton);
+
+        showNoStackTraceRadioButton.setSelected(true);
+
+        ActionListener radioButtonListener = new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                updateStackTraceSetting(true);
+            }
+        };
+
+        showNoStackTraceRadioButton.addActionListener(radioButtonListener);
+        showStackTrackRadioButton.addActionListener(radioButtonListener);
+        showFullStackTrackRadioButton.addActionListener(radioButtonListener);
+
+        panel.add(Utility.addLeftJustifiedComponent(showNoStackTraceRadioButton));
+        panel.add(Utility.addLeftJustifiedComponent(showStackTrackRadioButton));
+        panel.add(Utility.addLeftJustifiedComponent(showFullStackTrackRadioButton));
+
+        String stackTraceLevel = settingsNode.getValueOfChild(STACK_TRACE_LEVEL, getSelectedStackTraceLevel().name());
+        if (stackTraceLevel != null) {
+            try {
+                setSelectedStackTraceLevel(StartParameter.ShowStacktrace.valueOf(stackTraceLevel));
+                updateStackTraceSetting(false);   //false because we're serializing this in
+            }
+            catch (Exception e) {  //this can happen if the stack trace levels change because you're moving between versions.
+                logger.error("Converting stack trace level text to stack trace level enum '" + stackTraceLevel + "'", e);
+            }
+        }
+
+
+        return panel;
+    }
+
+    /**
+    * This stores the current stack trace setting (based on the UI controls) in the plugin.
+    */
+    private void updateStackTraceSetting(boolean saveSetting) {
+        StartParameter.ShowStacktrace stackTraceLevel = getSelectedStackTraceLevel();
+        gradlePluginLord.setStackTraceLevel(stackTraceLevel);
+
+        if (saveSetting) {
+           settingsNode.setValueOfChild(STACK_TRACE_LEVEL, stackTraceLevel.name());
+        }
+    }
+
+    /**
+     * Sets the selected strack trace level on the radio buttons. The radio buttons store their stack trace level as a
+     * client property and I'll look for a match using that. This way, we don't have to edit this if new levels are
+     * created.
+    *
+    *  @param  newStackTraceLevel   the new stack trace level.
+    */
+    private void setSelectedStackTraceLevel(StartParameter.ShowStacktrace newStackTraceLevel) {
+        Enumeration<AbstractButton> buttonEnumeration = stackTraceButtonGroup.getElements();
+        while (buttonEnumeration.hasMoreElements()) {
+            JRadioButton radioButton = (JRadioButton) buttonEnumeration.nextElement();
+            StartParameter.ShowStacktrace level = (StartParameter.ShowStacktrace) radioButton.getClientProperty(STACK_TRACE_LEVEL_CLIENT_PROPERTY);
+            if (newStackTraceLevel == level) {
+                radioButton.setSelected(true);
+                return;
+            }
+        }
+    }
+
+    /**
+     * Returns the currently selected stack trace level.  The radio buttons store their stack trace level as a client
+     * property so once we get the selected button, we know the level. This way, we don't have to edit this if new
+     * levels are created. Unfortunately, Swing doesn't have an easy way to get the actual button from the group.
+     *
+    *  @return the selected stack trace level
+    */
+    private StartParameter.ShowStacktrace getSelectedStackTraceLevel() {
+        ButtonModel selectedButtonModel = stackTraceButtonGroup.getSelection();
+        if (selectedButtonModel != null) {
+            Enumeration<AbstractButton> buttonEnumeration = stackTraceButtonGroup.getElements();
+            while (buttonEnumeration.hasMoreElements()) {
+                JRadioButton radioButton = (JRadioButton) buttonEnumeration.nextElement();
+                if (radioButton.getModel() == selectedButtonModel) {
+                    StartParameter.ShowStacktrace level = (StartParameter.ShowStacktrace) radioButton.getClientProperty(STACK_TRACE_LEVEL_CLIENT_PROPERTY);
+                    return level;
+                }
+            }
+        }
+
+        return StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS;
+    }
+
+    private Component createOptionsPanel() {
+        JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+
+        onlyShowOutputOnErrorCheckBox = new JCheckBox("Only Show Output When Errors Occur");
+
+        onlyShowOutputOnErrorCheckBox.addActionListener(new ActionListener() {
+         public void actionPerformed(ActionEvent e) {
+             updateShowOutputOnErrorsSetting();
+             settingsNode.setValueOfChildAsBoolean(SHOW_OUTPUT_ON_ERROR, onlyShowOutputOnErrorCheckBox.isSelected());
+         }
+        });
+
+        //initialize its default value
+        boolean valueAsBoolean = settingsNode.getValueOfChildAsBoolean(SHOW_OUTPUT_ON_ERROR, onlyShowOutputOnErrorCheckBox.isSelected());
+        onlyShowOutputOnErrorCheckBox.setSelected(valueAsBoolean);
+
+        updateShowOutputOnErrorsSetting();
+
+        panel.add(Utility.addLeftJustifiedComponent(onlyShowOutputOnErrorCheckBox));
+
+        return panel;
+    }
+
+   private void updateShowOutputOnErrorsSetting()
+   {
+      boolean value = onlyShowOutputOnErrorCheckBox.isSelected();
+
+       outputUILord.setOnlyShowOutputOnErrors(value);
+   }
+
+    private Component createCustomExecutorPanel() {
+        useCustomGradleExecutorCheckBox = new JCheckBox("Use Custom Gradle Executor");
+
+        customGradleExecutorField = new JTextField();
+        customGradleExecutorField.setEditable(false);
+
+        browseForCustomGradleExecutorButton = new JButton(new AbstractAction("Browse...") {
+            public void actionPerformed(ActionEvent e) {
+                browseForCustomGradleExecutor();
+            }
+        });
+
+        String customExecutorPath = settingsNode.getValueOfChild(CUSTOM_GRADLE_EXECUTOR, null);
+        if (customExecutorPath == null) {
+           setCustomGradleExecutor(null);
+        }
+        else {
+           setCustomGradleExecutor(new File(customExecutorPath));
+        }
+
+       JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
+
+        panel.add(Utility.addLeftJustifiedComponent(useCustomGradleExecutorCheckBox));
+        JComponent sideBySideComponent = createSideBySideComponent(customGradleExecutorField, browseForCustomGradleExecutorButton);
+        sideBySideComponent.setBorder(BorderFactory.createEmptyBorder(0, 30, 0, 0)); //indent it
+        panel.add(sideBySideComponent);
+
+        useCustomGradleExecutorCheckBox.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                if (useCustomGradleExecutorCheckBox.isSelected()) { //if they checked it, browse for a custom executor immediately
+                   browseForCustomGradleExecutor();
+                }
+                else {
+                   setCustomGradleExecutor(null);
+                }
+            }
+        });
+
+        return panel;
+    }
+
+    /**
+    * Call this to browse for a custom gradle executor.
+    */
+    private void browseForCustomGradleExecutor() {
+        File startingDirectory = new File(System.getProperty("user.home"));
+        File currentFile = gradlePluginLord.getCustomGradleExecutor();
+        if (currentFile != null) {
+           startingDirectory = currentFile.getAbsoluteFile();
+        }
+        else {
+            if (gradlePluginLord.getCurrentDirectory() != null) {
+               startingDirectory = gradlePluginLord.getCurrentDirectory();
+            }
+        }
+
+        JFileChooser chooser = new JFileChooser(startingDirectory);
+        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
+        chooser.setMultiSelectionEnabled(false);
+
+        File file = null;
+        if (chooser.showOpenDialog(mainPanel) == JFileChooser.APPROVE_OPTION) {
+           file = chooser.getSelectedFile();
+        }
+
+       if (file != null) {
+          setCustomGradleExecutor(file);
+       }
+       else {  //if they canceled, and they have no custom gradle executor specified, then we must clear things
+            //This will reset the UI back to 'not using a custom executor'. We can't have them check the
+            //field and not have a value here.
+            if (gradlePluginLord.getCustomGradleExecutor() == null) {
+               setCustomGradleExecutor(null);
+            }
+       }
+    }
+
+    /**
+    * Call this to set a custom gradle executor. We'll enable all fields appropriately and setup the foundation settings. We'll also fire off a refresh.
+    *
+    * @param  file       the file to use as a custom executor. Null not to use one.
+    */
+    private void setCustomGradleExecutor(File file) {
+        String storagePath;
+        boolean isUsingCustom = false;
+        if (file == null) {
+            isUsingCustom = false;
+            storagePath = null;
+        } else {
+            isUsingCustom = true;
+            storagePath = file.getAbsolutePath();
+        }
+
+        //set the executor in the foundation
+        if( gradlePluginLord.setCustomGradleExecutor(file) )
+        {
+            //refresh the tasks only if we actually changed the executor
+            gradlePluginLord.addRefreshRequestToQueue();
+        }
+
+        //set the UI values
+        useCustomGradleExecutorCheckBox.setSelected(isUsingCustom);
+        customGradleExecutorField.setText(storagePath);
+
+        //enable the UI appropriately.
+        browseForCustomGradleExecutorButton.setEnabled(isUsingCustom);
+        customGradleExecutorField.setEnabled(isUsingCustom);
+
+        //store the settings
+        settingsNode.setValueOfChild(CUSTOM_GRADLE_EXECUTOR, storagePath);
+    }
+
+   /**
+    This adds the specified component to the setup panel. It is added below the last
+    'default' item. You can only add 1 component here, so if you need to add multiple
+    things, you'll have to handle adding that to yourself to the one component.
+    @param component the component to add.
+    */
+    public void setCustomPanel( JComponent component ) {
+       customPanelPlaceHolder.add( component, BorderLayout.CENTER );
+       customPanelPlaceHolder.invalidate();
+       mainPanel.validate();
+    }
+
+    /**
+    * Notification that some settings have changed for the plugin. Settings such as current directory, gradle home
+    * directory, etc. This is useful for UIs that need to update their UIs when this is changed by other means.
+    */
+    public void settingsChanged() {
+        updatePluginLordSettings();
+    }
+
+    /**
+     * Called upon start up and whenever GradlePluginLord settings are changed. We'll update our values.
+     * Note: this actually gets called several times in a row for each settings during initialization. Its
+     * not optimal, but functional and I didn't want to deal with numerous, specific-field notifications.
+     */
+    private void updatePluginLordSettings() {
+        setCustomGradleExecutor( gradlePluginLord.getCustomGradleExecutor() );
+
+        setCurrentDirectory( gradlePluginLord.getCurrentDirectory() );
+
+        setSelectedStackTraceLevel(gradlePluginLord.getStackTraceLevel());
+
+        setLogLevelComboBoxSetting(gradlePluginLord.getLogLevel());
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/TaskTreeTab.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/TaskTreeTab.java
new file mode 100644
index 0000000..a0dcdc5
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/generic/tabs/TaskTreeTab.java
@@ -0,0 +1,598 @@
+/*
+ * 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.gradleplugin.userinterface.swing.generic.tabs;
+
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.foundation.CommandLineAssistant;
+import org.gradle.foundation.ProjectView;
+import org.gradle.foundation.TaskView;
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.gradleplugin.foundation.request.ExecutionRequest;
+import org.gradle.gradleplugin.foundation.request.RefreshTaskListRequest;
+import org.gradle.gradleplugin.foundation.request.Request;
+import org.gradle.gradleplugin.foundation.filters.AllowAllProjectAndTaskFilter;
+import org.gradle.gradleplugin.foundation.filters.BasicFilterEditor;
+import org.gradle.gradleplugin.foundation.filters.BasicProjectAndTaskFilter;
+import org.gradle.gradleplugin.foundation.settings.SettingsNode;
+import org.gradle.gradleplugin.userinterface.AlternateUIInteraction;
+import org.gradle.gradleplugin.userinterface.swing.generic.SwingAddMultipleFavoritesInteraction;
+import org.gradle.gradleplugin.userinterface.swing.generic.TaskTreeComponent;
+import org.gradle.gradleplugin.userinterface.swing.generic.Utility;
+import org.gradle.gradleplugin.userinterface.swing.generic.filter.ProjectAndTaskFilterDialog;
+
+import javax.swing.AbstractAction;
+import javax.swing.BorderFactory;
+import javax.swing.Box;
+import javax.swing.BoxLayout;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JPopupMenu;
+import javax.swing.JScrollPane;
+import javax.swing.JToggleButton;
+import javax.swing.JTree;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import java.awt.*;
+import java.awt.datatransfer.StringSelection;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This displays a tree of projects and tasks.
+ *
+ * @author mhunsicker
+  */
+public class TaskTreeTab implements GradleTab, GradlePluginLord.GeneralPluginObserver, GradlePluginLord.RequestObserver {
+    private final Logger logger = Logging.getLogger(TaskTreeTab.class);
+
+    private static final String SHOW_DESCRIPTION = "show-description";
+    private static final String BLANK_PNG = "blank.png"; //a blank image used as a spacer on the context menu.
+    private static final String EXECUTE_PNG = "execute.png";
+
+    private JPanel mainPanel;
+    private GradlePluginLord gradlePluginLord;
+    private AlternateUIInteraction alternateUIInteraction;
+
+    private TaskTreeComponent treeComponent;
+
+    private JPopupMenu popupMenu;
+    private JMenuItem addToFavoritesMenuItem;
+    private JMenuItem executeMenuItem;
+    private JMenuItem executeOnlyThisMenuItem;
+    private JMenuItem filterOutMenuItem;
+    private JMenuItem editFileMenuItem;
+    private JMenuItem copyTaskNameMenuItem;
+
+    private JButton refreshButton;
+    private JButton executeButton;
+    private JToggleButton toggleFilterButton;
+    private JButton editFilterButton;
+
+    private JCheckBox showDescriptionCheckBox;
+
+    private BasicFilterEditor editor;
+
+    private boolean isRefreshing;
+
+    private Color defaultTreeBackground;
+    private Color workingBackgroundColor = UIManager.getDefaults().getColor("Panel.background"); //just something to provide better feedback that we're working.
+    private JScrollPane treeScrollPane;
+
+    private SettingsNode settingsNode;
+
+   public TaskTreeTab(GradlePluginLord gradlePluginLord, SettingsNode settingsNode, AlternateUIInteraction alternateUIInteraction) {
+        this.gradlePluginLord = gradlePluginLord;
+        this.settingsNode = settingsNode;
+        this.alternateUIInteraction = alternateUIInteraction;
+
+        gradlePluginLord.addGeneralPluginObserver( this, true );
+        gradlePluginLord.addRequestObserver( this, true );
+
+        initializeFilterEditor();
+    }
+
+    /**
+     * This initializes our filter editor. We create a filter, serialize in our settings and then use that to create the
+     * editor. Lastly, we add an observer to the editor so we can save our changes immediately (useful for IDE
+     * integration where we don't control the settings).
+    */
+    private void initializeFilterEditor() {
+        BasicProjectAndTaskFilter filter = new BasicProjectAndTaskFilter();
+        filter.serializeIn(settingsNode);
+        editor = new BasicFilterEditor(filter);
+
+        editor.addFilterEditorObserver(new BasicFilterEditor.FilterEditorObserver() {
+            public void filterChanged() {  //whenever changes are made, save them.
+                editor.createFilter().serializeOut(settingsNode);
+            }
+        }, false);
+    }
+
+    public String getName() {
+        return "Task Tree";
+    }
+
+    public Component createComponent() {
+        setupUI();
+
+        enableThingsAppropriately();
+
+        return mainPanel;
+    }
+
+    /**
+    * Notification that this component is about to be shown. Do whatever initialization you choose.
+    */
+    public void aboutToShow() {
+        resetShowDescription(); //make sure that our setting is pushed to the tree's setting.
+
+        //when we start up, refresh our list.
+        SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+                if (gradlePluginLord.isSetupComplete()) {
+                   refresh();
+                }
+                else {
+                   showTextInViewport("Cannot show tasks until configuration is complete. See Setup tab.");
+                }
+            }
+        });
+    }
+
+    public void setupUI() {
+        mainPanel = new JPanel(new BorderLayout());
+
+        mainPanel.add(createTopPanel(), BorderLayout.NORTH);
+        mainPanel.add(createTreePanel(), BorderLayout.CENTER);
+
+        setupPopupMenu();
+    }
+
+    private Component createTopPanel() {
+        JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+
+        refreshButton = Utility.createButton(getClass(), "refresh.png", "Refreshes the task tree", new AbstractAction("Refresh") {
+            public void actionPerformed(ActionEvent e) {
+                refresh();
+            }
+        });
+
+        executeButton = Utility.createButton(getClass(), EXECUTE_PNG, "Execute the selected tasks", new AbstractAction("Execute") {
+            public void actionPerformed(ActionEvent e) {
+                executeSelectedTasks();
+            }
+        });
+
+        toggleFilterButton = Utility.createToggleButton( getClass(), "filter.png", "Toggles the view to show either everything or only the filtered items", new AbstractAction("Filter") {
+            public void actionPerformed(ActionEvent e) {
+                populate();
+            }
+        });
+
+        toggleFilterButton.setSelected(true);
+
+        editFilterButton = Utility.createButton(getClass(), "edit-filter.png", "Edits the filter to control what is visible", new AbstractAction("Edit Filter...") {
+            public void actionPerformed(ActionEvent e) {
+                configureFilter();
+            }
+        });
+
+        showDescriptionCheckBox = new JCheckBox("Description", true);
+        showDescriptionCheckBox.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                resetShowDescription();
+            }
+        });
+
+        showDescriptionCheckBox.setSelected(settingsNode.getValueOfChildAsBoolean(SHOW_DESCRIPTION, showDescriptionCheckBox.isSelected()));
+
+        panel.add(refreshButton);
+        panel.add(Box.createHorizontalStrut(10));
+        panel.add(executeButton);
+        panel.add(Box.createHorizontalStrut(10));
+        panel.add(toggleFilterButton);
+        panel.add(Box.createHorizontalStrut(10));
+        panel.add(showDescriptionCheckBox);
+        panel.add(Box.createHorizontalGlue());
+        panel.add(editFilterButton);
+
+        panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+
+        return panel;
+    }
+
+    private Component createTreePanel() {
+        treeComponent = new TaskTreeComponent(gradlePluginLord, new TaskTreeComponent.Interaction() {
+            public void rightClick(JTree tree, int x, int y) {
+                enableThingsAppropriately();
+                popupMenu.show(tree, x, y);
+            }
+
+            public void taskInvoked(TaskView task, boolean isCtrlKeyDown) {
+                if (isCtrlKeyDown) {
+                   gradlePluginLord.addExecutionRequestToQueue(task, false, "-a");
+                }
+                else {
+                   gradlePluginLord.addExecutionRequestToQueue(task, false);
+                }
+            }
+
+            public void projectInvoked(ProjectView project) {
+                executeDefaultTasksInProject(project);
+            }
+        });
+
+        treeComponent.getTree().addTreeSelectionListener(new TreeSelectionListener() {
+            public void valueChanged(TreeSelectionEvent e) {
+                enableThingsAppropriately();
+            }
+        });
+
+        defaultTreeBackground = treeComponent.getTree().getBackground();
+
+        treeScrollPane = new JScrollPane();
+
+        treeComponent.getTree().setBackground(workingBackgroundColor);  //change the color to better indicate that
+        showTextInViewport("Has not built projects/tasks yet.");
+
+        return treeScrollPane;
+    }
+
+    /**
+     * Replaces the tree with a label of text. This is used when there's nothing in the tree, but perhaps a 'working' or
+     * error message.
+    *
+    * @param  text       the text to display
+    */
+    private void showTextInViewport(String text) {
+        treeScrollPane.getViewport().removeAll();
+
+        JPanel panel = new JPanel();
+        panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
+        panel.add(Box.createHorizontalGlue());
+        panel.add(new JLabel(text));
+        panel.add(Box.createHorizontalGlue());
+
+        treeScrollPane.getViewport().add(panel);
+        treeScrollPane.revalidate();
+    }
+
+    /**
+     * Puts the tree in the main view. This is used once we've gathered the projects and tasks and want to display them
+     * in the tree.
+    */
+    private void showTreeInViewport() {
+        treeScrollPane.getViewport().removeAll();
+        treeScrollPane.getViewport().add(treeComponent.getTree());
+        treeScrollPane.revalidate();
+    }
+
+   public void executionRequestAdded( ExecutionRequest request )
+   {
+      //we don't really care
+   }
+
+   public void refreshRequestAdded( RefreshTaskListRequest request )
+   {
+      //when someone adds a refresh request, update the UI to reflect this.
+      isRefreshing = true;
+
+      enableThingsAppropriately();
+
+      treeComponent.getTree().setBackground(workingBackgroundColor);
+      showTextInViewport("Refreshing projects and tasks.");
+   }
+
+   /**
+   * Notification that a command is about to be executed. This is mostly useful for IDE's that may need to save their files.
+   *
+   * @param request the request that's about to be executed.
+   * @author mhunsicker
+    */
+   public void aboutToExecuteRequest( Request request )
+   {
+      //we don't really care
+   }
+
+   /**
+    * Notification that the command has completed execution.
+    *
+    * @param request the original request containing the command that was executed
+    * @param result  the result of the command
+    * @param output  the output from gradle executing the command
+    */
+   public void requestExecutionComplete( Request request, int result, String output )
+   {
+      if( request instanceof RefreshTaskListRequest )
+      {
+         isRefreshing = false;
+         enableThingsAppropriately();
+         if( result != 0 ) { //if something went wrong, let the user know
+            showTextInViewport("Error");
+         }
+      }
+   }
+
+   /**
+     * Call this to repopulate the tree. Useful if new tasks have been created.
+    */
+    private void refresh() {
+        gradlePluginLord.addRefreshRequestToQueue();
+    }
+
+    /**
+     * This populates (and repopulates) the tree.
+    */
+    private void populate() {
+        if (toggleFilterButton.isSelected()) {
+           treeComponent.populate(editor.createFilter());
+        }
+        else {
+           treeComponent.populate(new AllowAllProjectAndTaskFilter());
+        }
+
+       //reset the background to indicate that we're populated
+        treeComponent.getTree().setBackground(defaultTreeBackground);
+
+        showTreeInViewport();
+    }
+
+    private void executeSelectedTasks(String... additionCommandLineOptions) {
+        List<TaskView> taskViews = treeComponent.getSelectedTasks();
+        String singleCommandLine = CommandLineAssistant.combineTasks( taskViews, additionCommandLineOptions  );
+        if( singleCommandLine == null ) {
+           return;
+        }
+
+       gradlePluginLord.addExecutionRequestToQueue( singleCommandLine, singleCommandLine, false );
+    }
+
+    /**
+    * Notification that we're about to reload the projects and tasks.
+    */
+    public void startingProjectsAndTasksReload() {
+        treeComponent.getTree().setBackground(workingBackgroundColor);
+        showTextInViewport("Building projects/tasks.");
+    }
+
+    /**
+     * Notification that the projects and tasks have been reloaded. You may want to repopulate or update your views.
+     *
+     * @param wasSuccessful true if they were successfully reloaded. False if an error occurred so we no longer can show
+     * the projects and tasks (probably an error in a .gradle file).
+    */
+    public void projectsAndTasksReloaded(boolean wasSuccessful) {
+        isRefreshing = false;
+        enableThingsAppropriately();
+
+        if (!wasSuccessful) {
+           showTextInViewport("Error");
+        }
+        else {
+           populate();
+        }
+    }
+
+    /**
+     * Builds the popup menu
+    */
+    private void setupPopupMenu() {
+        popupMenu = new JPopupMenu();
+
+        executeMenuItem = Utility.createMenuItem( this.getClass(), "Execute", EXECUTE_PNG, new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                executeSelectedTasks();
+            }
+        });
+        popupMenu.add(executeMenuItem);
+
+        executeOnlyThisMenuItem = Utility.createMenuItem( this.getClass(), "Execute Ignoring Dependencies (-a)", BLANK_PNG, new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                executeSelectedTasks("-a");
+            }
+        });
+        popupMenu.add(executeOnlyThisMenuItem);
+
+        popupMenu.addSeparator();
+
+        addToFavoritesMenuItem = Utility.createMenuItem( this.getClass(), "Add To Favorites", BLANK_PNG, new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                addSelectedToFavorites();
+            }
+        });
+        popupMenu.add(addToFavoritesMenuItem);
+
+        filterOutMenuItem = Utility.createMenuItem( this.getClass(), "Hide", BLANK_PNG, new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                hideSelection();
+            }
+        });
+        popupMenu.add(filterOutMenuItem);
+
+        editFileMenuItem = Utility.createMenuItem( this.getClass(), "Edit File", BLANK_PNG, new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                editSelectedFiles();
+            }
+        });
+        popupMenu.add(editFileMenuItem);
+
+        copyTaskNameMenuItem = Utility.createMenuItem( this.getClass(), "Copy Task Name", BLANK_PNG, new AbstractAction() {
+            public void actionPerformed(ActionEvent e) {
+                copySelectedTaskNames();
+            }
+        });
+
+        popupMenu.addSeparator();
+        popupMenu.add(copyTaskNameMenuItem);
+    }
+
+    /**
+     * Enables buttons and menu items based on what is selected.
+    */
+    private void enableThingsAppropriately() {
+        boolean hasSelection = treeComponent.getTree().getSelectionPath() != null;
+        boolean hasTaskSelection = treeComponent.hasTasksSelected();
+        boolean canDoThings = !isRefreshing && treeComponent.isPopulated() && hasSelection; //can't be refreshing, is populated, and  hasSelections
+
+        refreshButton.setEnabled(!isRefreshing);
+
+        addToFavoritesMenuItem.setEnabled(canDoThings);
+        executeMenuItem.setEnabled(canDoThings);
+        executeOnlyThisMenuItem.setEnabled(canDoThings);
+
+        executeButton.setEnabled(canDoThings);
+
+        if (alternateUIInteraction.doesSupportEditingOpeningFiles())   //I'll allow this to be dynamic. If we start supporting editing while running (say a user configured a setting to use a specific external tool), then we'll allow it.
+        {
+            editFileMenuItem.setVisible(true);
+            boolean hasProjectsSelected = treeComponent.hasProjectsSelected();
+            editFileMenuItem.setEnabled(hasProjectsSelected && canDoThings);
+        } else {
+           editFileMenuItem.setVisible(false);  //just hide it if we don't support this
+        }
+
+        copyTaskNameMenuItem.setVisible( !isRefreshing && hasTaskSelection );
+    }
+
+    /**
+     * Adds whatever is selected to the favorites.
+    */
+    private void addSelectedToFavorites() {
+       List<TaskView> tasks = treeComponent.getSelectedTasks();
+
+       gradlePluginLord.getFavoritesEditor().addMutlipleFavorites( tasks, false, new SwingAddMultipleFavoritesInteraction( SwingUtilities.getWindowAncestor(mainPanel) ) );
+    }
+
+    /**
+     * This displays a dialog that allows the user to determine what shows up in the tree. We give the filter dialog a
+     * filter rather than handing it out editor so teh user can cancel. That is, the dialog uses its own editor which it
+     * modifies freely and throws away. This way, if the user cancels, we dodon't have to deal with restoring the
+     * previous values in our local editor.
+    */
+    private void configureFilter() {
+        ProjectAndTaskFilterDialog dialog = new ProjectAndTaskFilterDialog(SwingUtilities.getWindowAncestor(mainPanel), gradlePluginLord);
+
+        BasicProjectAndTaskFilter newFilter = dialog.show(editor.createFilter());
+        if (newFilter != null) //if the user didn't cancel...
+        {
+            editor.initializeFromFilter(newFilter);
+            populate();
+        }
+    }
+
+    /**
+     * Call this to filter out the currently selected items.
+    */
+    private void hideSelection() {
+        TaskTreeComponent.MultipleSelection multipleSelection = treeComponent.getSelectedProjectsAndTasks();
+        if (!multipleSelection.projects.isEmpty() || !multipleSelection.tasks.isEmpty()) {
+            editor.hideProjects(multipleSelection.projects);
+            editor.hideTasks(multipleSelection.tasks);
+
+            populate(); //unfortunately, we have to repopulate now.
+        }
+    }
+
+    /**
+     * This resets whether the description is shown or not based on the check box. The tree component does the real
+     * work.
+    */
+    private void resetShowDescription() {
+        settingsNode.setValueOfChildAsBoolean(SHOW_DESCRIPTION, showDescriptionCheckBox.isSelected());   //save it immediately
+        treeComponent.setShowDescription(showDescriptionCheckBox.isSelected());
+    }
+
+    /**
+     * This opens the selected files. This gets the 'parent' of this to do it for us. This facilitates using this inside
+     * an IDE (you get the IDE to open it).
+    */
+    private void editSelectedFiles() {
+        TaskTreeComponent.MultipleSelection tasks = treeComponent.getSelectedProjectsAndTasks();
+
+        Iterator<ProjectView> iterator = tasks.projects.iterator();
+        while (iterator.hasNext()) {
+            ProjectView projectView = iterator.next();
+            File file = projectView.getBuildFile();
+            if( file != null ) {
+               alternateUIInteraction.editFile(file, -1 );
+            }
+        }
+    }
+
+
+    /**
+     * This executes all default tasks in the specified project.
+     *
+     * @param  project    the project to execute.
+    */
+    private void executeDefaultTasksInProject(ProjectView project) {
+        Iterator<TaskView> iterator = project.getDefaultTasks().iterator();
+        while (iterator.hasNext()) {
+            TaskView task = iterator.next();
+            gradlePluginLord.addExecutionRequestToQueue(task, false);
+        }
+    }
+
+
+    /**
+     * Copies the selected tasks names to the clipboard
+     */
+    private void copySelectedTaskNames() {
+
+        String names = getSelectedTaskNames();
+        if (names.length() == 0) {
+            return;
+        }
+
+        Toolkit.getDefaultToolkit().getSystemClipboard().setContents( new StringSelection( names ), null );
+    }
+
+    /**
+     * This puts all the selected task names in a space-delimited String
+     * @return a string of all the tasks
+     */
+    private String getSelectedTaskNames() {
+        List<TaskView> tasks = treeComponent.getSelectedTasks();
+        if( tasks.isEmpty() ) {
+            return null;
+        }
+
+        StringBuilder taskString = new StringBuilder();
+        Iterator<TaskView> iterator = tasks.iterator();
+        while( iterator.hasNext() )
+        {
+           TaskView taskView = iterator.next();
+
+            taskString.append( taskView.getFullTaskName() );
+            if( iterator.hasNext() ) {
+                taskString.append( ' ' );
+            }
+        }
+
+        return taskString.toString();
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/Application.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/Application.java
new file mode 100644
index 0000000..1255e43
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/Application.java
@@ -0,0 +1,396 @@
+/*
+ * 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.gradleplugin.userinterface.swing.standalone;
+
+import org.gradle.gradleplugin.foundation.DOM4JSerializer;
+import org.gradle.gradleplugin.foundation.ExtensionFileFilter;
+import org.gradle.gradleplugin.foundation.settings.DOM4JSettingsNode;
+import org.gradle.gradleplugin.userinterface.AlternateUIInteraction;
+import org.gradle.gradleplugin.userinterface.swing.common.PreferencesAssistant;
+import org.gradle.gradleplugin.userinterface.swing.generic.SinglePaneUIInstance;
+import org.gradle.util.UncheckedException;
+
+import javax.swing.*;
+import javax.swing.filechooser.FileFilter;
+import java.awt.*;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.File;
+import java.lang.reflect.Method;
+import java.net.URI;
+
+/**
+ * The main entry point for a stand-alone application for Gradle. The real work is not done here. This is just a UI
+ * containing components that are meant to be reuseable in other UIs (say an IDE plugin). Those other components do the
+ * real work. Most of the work is wrapped inside SinglePaneUIInstance.
+ *
+ * @author mhunsicker
+ */
+public class Application implements AlternateUIInteraction {
+    private static final int DEFAULT_WIDTH = 800;
+    private static final int DEFAULT_HEIGHT = 800;
+
+    private static final String WINDOW_PREFERENCES_ID = "window-id";
+    private static final String SETTINGS_EXTENSION = ".setting";
+
+    private JFrame frame;
+    private SinglePaneUIInstance singlePaneUIInstance;
+
+    private boolean doesSupportEditingFiles;
+
+    private LifecycleListener lifecycleListener;
+    private DOM4JSettingsNode rootSettingsNode;
+
+   /**
+     * Interface that allows the caller to do post shutdown processing. For example, you may want to exit the VM. You
+     * may not.
+     */
+    public interface LifecycleListener {
+        /**
+         * Notification that the application has started successfully. This is fired within the same thread that
+         * instantiates us.
+         */
+        public void hasStarted();
+
+        /**
+         * Notification that the application has shut down. This is fired from the Event Dispatch Thread.
+         */
+        public void hasShutDown();
+    }
+
+    public static void main(String[] args) {
+        new Application(new LifecycleListener() {
+            public void hasStarted() {
+                //we don't care
+            }
+
+            public void hasShutDown() {
+                System.exit(0);
+            }
+        });
+    }
+
+    public Application(LifecycleListener lifecycleListener) {
+        this.lifecycleListener = lifecycleListener;
+
+        try {   //try and make it look like a native app
+            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+        } catch (Exception e) {
+            throw UncheckedException.asUncheckedException(e);
+        }
+
+        this.doesSupportEditingFiles = determineIfSupportsEditingFiles();
+
+        //read in the settings
+        rootSettingsNode = DOM4JSerializer.readSettingsFile(new SettingsImportInteraction(), createFileFilter());
+        if (rootSettingsNode == null) {
+            rootSettingsNode = DOM4JSerializer.createBlankSettings();
+        }
+
+        singlePaneUIInstance = new SinglePaneUIInstance();
+        singlePaneUIInstance.initialize( rootSettingsNode, this);
+
+        setupUI();
+
+        restoreSettings();
+
+        frame.setVisible(true);
+
+        lifecycleListener.hasStarted();  //notify listeners that we have successfully started
+    }
+
+    private void setupUI() {
+        frame = new JFrame("Gradle");
+
+        JPanel mainPanel = new JPanel(new BorderLayout());
+        frame.getContentPane().add(mainPanel);
+
+        mainPanel.add(singlePaneUIInstance.getComponent());
+        mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+
+        singlePaneUIInstance.aboutToShow();
+
+        frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
+        frame.addWindowListener(new WindowAdapter() {
+            public void windowClosing(WindowEvent e) {
+                close();
+            }
+        });
+
+        frame.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
+        frame.setLocationByPlatform(true);
+    }
+
+    private void close() {
+        boolean canClose = singlePaneUIInstance.canClose(new SinglePaneUIInstance.CloseInteraction() {
+            public boolean promptUserToConfirmClosingWhileBusy() {
+                int result = JOptionPane.showConfirmDialog(frame,
+                        "Gradle tasks are being currently being executed. Exit anyway?", "Exit While Busy?",
+                        JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
+                return result == JOptionPane.YES_OPTION;
+            }
+        });
+
+        if (!canClose) {
+            return;
+        }
+
+        singlePaneUIInstance.close();
+
+        saveSettings();
+        frame.setVisible(false);
+
+        if (lifecycleListener != null) {
+            lifecycleListener.hasShutDown();
+        } else {
+            System.exit(0);
+        }
+    }
+
+    private void saveSettings() {
+        PreferencesAssistant.saveSettings(rootSettingsNode, frame, WINDOW_PREFERENCES_ID, Application.class);
+
+        DOM4JSerializer.exportToFile(new SettingsExportInteraction(), createFileFilter(), rootSettingsNode);
+    }
+
+    private void restoreSettings() {
+        PreferencesAssistant.restoreSettings(rootSettingsNode, frame, WINDOW_PREFERENCES_ID, Application.class);
+    }
+
+   /**
+    Notification that you should open the specified file and go to the specified line. Its up to the
+    application to determine if this file should be opened for editing or simply displayed. The difference
+    comes into play for things like xml or html files where a user may want to open them in a browser vs
+    a source code file where they may want to open it directly in an IDE.
+
+    @param file the file to edit
+    @param line the line to go to. -1 if no line is specified.
+    */
+   public void openFile( File file, int line ) {
+      String name = file.getName().toLowerCase();
+      if( name.endsWith( ".html" ) || name.endsWith( ".htm" ) )
+      {
+         browseFile( file );
+      }
+      else
+      {
+         editFile( file, line );
+      }
+   }
+
+   public void browseFile( File file ) {
+      if( !file.exists())  //the file might not exist. This happens if its just using the default settings (no file is required).
+         {
+            JOptionPane.showMessageDialog(frame, "File does not exist '" + file.getAbsolutePath() + "'");
+         } else {
+
+         if( !invokeDesktopFunction( "browse", URI.class, file.toURI() ) )
+         {
+            String extension = getFileNameExtension( file.getName() );
+            JOptionPane.showMessageDialog(frame, "Cannot browse file. Do you have an application assocated with '" + extension + "' files?");
+         }
+       }
+   }
+
+
+   /**
+     * This is called when we should edit the specified file. Open it in the current IDE or some external editor.
+     *
+     * @param file
+    @param line
+     */
+    public void editFile( File file, int line )  {
+      editFileInExternalApplication( file, true );
+    }
+
+   /**
+    This edits the application using java.awt.Desktop. Since we're compiling with 1.5 and this is a 1.6 feature,
+    this is done using reflection making this much uglier than it needs to be.
+    @param file the file to edit
+    @param attemptToOpen true if we should attempt to just open the file is editing it fails. Often, file associations
+    don't distinguish edit from open and open is the default.
+    */
+    public void editFileInExternalApplication( File file, boolean attemptToOpen )  {
+        if( !file.exists())  //the file might not exist. This happens if its just using the default settings (no file is required).
+         {
+            JOptionPane.showMessageDialog(frame, "File does not exist '" + file.getAbsolutePath() + "'");
+         } else {
+         if( !invokeDesktopFunction( "edit", File.class, file ) )
+         {
+            openFileInExternalApplication( file );
+         }
+       }
+   }
+
+   public void openFileInExternalApplication( File file )  {
+
+        if( !file.exists())  //the file might not exist. This happens if its just using the default settings (no file is required).
+         {
+            JOptionPane.showMessageDialog(frame, "File does not exist '" + file.getAbsolutePath() + "'");
+         } else {
+         if( !invokeDesktopFunction( "open", File.class, file ) )
+         {
+            String extension = getFileNameExtension( file.getName() );
+            JOptionPane.showMessageDialog(frame, "Cannot open file. Do you have an application assocated with '" + extension + "' files?");
+         }
+       }
+   }
+
+   /**
+    This invokes one of the java.awt.Desktop functions. Since we're compiling with 1.5 and this is a 1.6 feature,
+    this is done using reflection making this much uglier than it needs to be. This is for calling one of the
+    'edit', 'browse', 'open' or even 'mail' functions that always take a single argument.
+
+    @param name  the function to invoke
+    @param argumentClass  the class of the argument of the above function. 
+    @param argument the argument itself.
+    @return true if it worked, false if not. It might fail if the platform doesn't support editing/opening
+    the file passed in, for example.
+    */
+   public boolean invokeDesktopFunction( String name, Class argumentClass, Object argument )  {
+      try {
+            Class<?> desktopClass = Class.forName("java.awt.Desktop");
+            Method getDesktopMethod = desktopClass.getDeclaredMethod("getDesktop", (Class<?>[]) null);
+            Object desktopObject = getDesktopMethod.invoke(null, (Object[]) null);
+            if (desktopObject != null)   //may be null if this plaform doesn't support this.
+            {
+                Method method = desktopClass.getMethod( name, new Class[]{ argumentClass });
+                method.invoke(desktopObject, argument );
+               return true;
+            }
+       } catch (Exception e) {
+            //ignore this. Just return false. This is relatively normal to get these and if you look at where this is called with 'edit', if it fails, we'll try again with open.
+       }
+      return false;
+   }
+
+
+   /**<!===== getFileNameExtension ===========================================>
+      Returns the file extension preserving its case.
+
+      <!      Name       Description>
+      @param  fileName   the file name
+      @return its extension.
+      @author mhunsicker
+   <!=======================================================================>*/
+   public static String getFileNameExtension( String fileName )
+   {
+      String result = fileName;
+      int indexOfDot = fileName.lastIndexOf('.');
+      if ( indexOfDot > 0 ) {
+          result = fileName.substring( indexOfDot + 1, result.length() );
+      }
+
+       return result;
+   }
+
+    /**
+     * Determines if we can call editFiles. This is not a dynamic answer and should always return either true of false.
+     * If you want to change the answer, return true and then handle the files differently in editFiles.
+     *
+     * @return true if support editing files, false otherwise.
+     */
+    public boolean doesSupportEditingOpeningFiles() {
+        return doesSupportEditingFiles;
+    }
+
+    /**
+     * Determines if we support editing files. At the time of this writing, we were mooching off of java 1.6's ability
+     * to get the OS to do this. If we're running on 1.5, this will fail.
+     *
+     * @return true if we support it, false if not.
+     */
+    public boolean determineIfSupportsEditingFiles() {
+        try {
+            Class<?> desktopClass = Class.forName("java.awt.Desktop");
+            Method getDesktopMethod = desktopClass.getDeclaredMethod("isDesktopSupported", (Class<?>[]) null);
+            Object desktopObject = getDesktopMethod.invoke(null, (Object[]) null);
+            return (Boolean) desktopObject;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    private ExtensionFileFilter createFileFilter() {
+        return new ExtensionFileFilter(SETTINGS_EXTENSION, "Setting");
+    }
+
+    /**
+     * @return the file that we save our settings to.
+     */
+    private File getSettingsFile() {
+        return new File(System.getProperty("user.dir"), "gradle-app" + SETTINGS_EXTENSION);
+    }
+
+    private class SettingsImportInteraction implements DOM4JSerializer.ImportInteraction {
+        /**
+         * This is called when you should ask the user for a source file to read.
+         *
+         * @return a file to read or null to cancel.
+         */
+        public File promptForFile(FileFilter fileFilters) {
+            File settingsFile = getSettingsFile();
+            if (!settingsFile
+                    .exists())  //if its not present (first time we've run on this machine), just cancel the read.
+            {
+                return null;
+            }
+            return settingsFile;
+        }
+
+        /**
+         * Report an error that occurred. The read failed.
+         *
+         * @param error the error message.
+         */
+        public void reportError(String error) {
+            JOptionPane.showMessageDialog(frame, "Failed to read settings: " + error);
+        }
+    }
+
+    /**
+     * This interaction is for saving our settings. As such, its not all that interactive unless errors occur.
+     */
+    private class SettingsExportInteraction implements DOM4JSerializer.ExportInteraction {
+        /**
+         * This is called when you should ask the user for a destination file of a save.
+         *
+         * @return a file to save to or null to cancel.
+         */
+        public File promptForFile(FileFilter fileFilters) {
+            return getSettingsFile();
+        }
+
+        /**
+         * The file already exists. Confirm whether or not you want to overwrite it.
+         *
+         * @param file the file in question
+         * @return true to overwrite it, false not to.
+         */
+        public boolean confirmOverwritingExisingFile(File file) {
+            return true;   //It's most likely going to exist. Always overwrite it.
+        }
+
+        /**
+         * Report an error that occurred. The save failed.
+         *
+         * @param error the error message.
+         */
+        public void reportError(String error) {
+            JOptionPane.showMessageDialog(frame, "Failed to save settings: " + error);
+        }
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/BlockingApplication.java b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/BlockingApplication.java
new file mode 100644
index 0000000..9b2ebff
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/gradleplugin/userinterface/swing/standalone/BlockingApplication.java
@@ -0,0 +1,106 @@
+/*
+ * 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.gradleplugin.userinterface.swing.standalone;
+
+import org.gradle.util.UncheckedException;
+
+import javax.swing.*;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * This is the same as Application, but this version blocks the calling thread until the Application shuts down.
+ *
+ * @author mhunsicker
+ */
+public class BlockingApplication {
+
+    /**
+     * This launches this application and blocks until it closes. Useful for being called from the gradle command line.
+     * We launch this in the Event Dispatch Thread and block the calling thread.
+     */
+    public static void launchAndBlock() {
+        if (SwingUtilities.isEventDispatchThread()) {
+            throw new RuntimeException("Cannot launch and block from the Event Dispatch Thread!");
+        }
+
+        //create a lock to wait on
+        final WaitingLock waitingLock = new WaitingLock();
+
+        //instantiate the app in the Event Dispatch Thread
+        try {
+            SwingUtilities.invokeAndWait(new Runnable() {
+                public void run() {
+                    new Application(new Application.LifecycleListener() {
+                        /**
+                         Notification that the application has started successfully. This is
+                         fired within the same thread that instantiates us.
+                         */
+                        public void hasStarted() {  //only lock if we start
+                            waitingLock.lock();
+                        }
+
+                        /**
+                         Notification that the application has shut down. This is fired from the
+                         Event Dispatch Thread.
+                         */
+                        public void hasShutDown() {  //when we shutdown we'll unlock
+                            waitingLock.unlock();
+                        }
+                    });
+                }
+            });
+        } catch (InterruptedException e) {
+            throw UncheckedException.asUncheckedException(e);
+        } catch (InvocationTargetException e) {
+            throw UncheckedException.asUncheckedException(e.getCause());
+        }
+
+        //the calling thread will now block until the caller is complete.
+        waitingLock.waitOnLock();
+    }
+
+    /**
+     * Lock so the calling thread can wait on the Application to exit.
+     */
+    private static class WaitingLock {
+        private boolean isLocked;
+
+        public synchronized void lock() {
+            isLocked = true;
+
+            //Notify status has changed.
+            notifyAll();
+        }
+
+        public synchronized void unlock() {
+            isLocked = false;
+
+            //Notify status has changed.
+            notifyAll();
+        }
+
+        public synchronized void waitOnLock() {
+            //Wait only if we're locked
+            while (isLocked) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                    throw new UncheckedException(e);
+                }
+            }
+        }
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/RunnerWrapperFactory.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/RunnerWrapperFactory.java
new file mode 100644
index 0000000..32a4a66
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/RunnerWrapperFactory.java
@@ -0,0 +1,68 @@
+/*
+ * 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.openapi.wrappers;
+
+import org.gradle.openapi.external.runner.GradleRunnerInteractionVersion1;
+import org.gradle.openapi.external.runner.GradleRunnerVersion1;
+import org.gradle.openapi.wrappers.runner.GradleRunnerWrapper;
+
+import java.io.File;
+
+/**
+* This factory instantiates GradleRunnerWrappers by an external process. It is meant to be
+* called via the Open API GradleRunnerFactory class using reflection. This is because it is
+* called dynamically. It is also meant to help shield a Gradle user from changes to different
+* versions of the GradleRunner. It does so by using wrappers that can dynamically choose
+* what/how to implement. The wrappers usually use the latest, however, some of the
+* functionality requires a matching Open API jar (which will be included with the external
+* process trying to use this). If the matching functionality is not found (a NoClassDefFoundError
+* is thrown), it will fall back to earlier versions.
+*
+* This class should not be moved or renamed, nor should its functions be renamed or have arguments
+* added to/removed from them. This is to ensure forward/backward compatibility with multiple
+* versions of IDE plugins. Instead, consider changing the interaction that is passed to the functions
+* as a means of having the caller provide different functionality.
+*
+* @author mhunsicker
+*/
+public class RunnerWrapperFactory {
+
+    /*
+      Call this to instantiate an object that you can use to execute gradle
+      commands directly.
+
+      Note: this function is meant to be backward and forward compatible. So
+      this signature should not change at all, however, it may take and return
+      objects that implement ADDITIONAL interfaces. That is, it will always
+      return a GradleRunnerVersion1, but it may also be an object that implements
+      GradleRunnerVersion2 (notice the 2). The caller will need to dynamically
+      determine that. The GradleRunnerInteractionVersion1 may take an object
+      that also implements GradleRunnerInteractionVersion2. If so, we'll
+      dynamically determine that and handle it. Of course, this all depends on
+      what happens in the future.
+
+      @param  gradleHomeDirectory  the root directory of a gradle installation
+      @param  interaction          this is how we interact with the caller.
+      @param  showDebugInfo        true to show some additional information that
+                                   may be helpful diagnosing problems is this
+                                   fails
+      @return a gradle runner
+      @author mhunsicker
+   */
+   public static GradleRunnerVersion1 createGradleRunner( File gradleHomeDirectory, GradleRunnerInteractionVersion1 interaction, boolean showDebugInfo ) throws Exception {
+       return new GradleRunnerWrapper( gradleHomeDirectory, interaction );
+   }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/UIWrapperFactory.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/UIWrapperFactory.java
new file mode 100644
index 0000000..5f53527
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/UIWrapperFactory.java
@@ -0,0 +1,68 @@
+/*
+ * 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.openapi.wrappers;
+
+import org.gradle.openapi.external.ui.DualPaneUIInteractionVersion1;
+import org.gradle.openapi.external.ui.DualPaneUIVersion1;
+import org.gradle.openapi.external.ui.SinglePaneUIInteractionVersion1;
+import org.gradle.openapi.external.ui.SinglePaneUIVersion1;
+import org.gradle.openapi.wrappers.ui.DualPaneUIWrapper;
+import org.gradle.openapi.wrappers.ui.SinglePaneUIWrapper;
+
+
+/**
+* This factory instantiates Gradle UIs used in IDE plugins. It is meant to be called via the
+* Open API UIFactory class using reflection. This is because it is called dynamically. It is
+* also meant to help shield a Gradle user from changes to different versions of UI. It does
+* so by using wrappers that can dynamically choose what/how to implement. The wrappers usually
+* use the latest, however, some of the functionality requires a matching Open API jar (which
+* will be included with the plugin trying to use this). If the matching functionality is not
+* found (a NoClassDefFoundError is thrown), it will fall back to earlier versions.
+*
+* This class should not be moved or renamed, nor should its functions be renamed or have arguments
+* added to/removed from them. This is to ensure forward/backward compatibility with multiple
+* versions of IDE plugins. Instead, consider changing the interaction that is passed to the functions
+* as a means of having the caller provide different functionality.
+*
+* @author mhunsicker
+*/
+public class UIWrapperFactory {
+
+    /**
+     * Creates a single-pane Gradle UI. The main UI and output panes are self-contained.
+     * @param interaction    this is how we interact with the caller.
+       @param showDebugInfo  true to show some additional information that may be helpful
+                             diagnosing problems is this fails
+     * @return a single pane UI.
+     * @throws Exception
+     */
+    public static SinglePaneUIVersion1 createSinglePaneUI( final SinglePaneUIInteractionVersion1 interaction, boolean showDebugInfo ) throws Exception {
+        return new SinglePaneUIWrapper(interaction);
+    }
+
+    /**
+     * Creates a dual-pane Gradle UI, consisting of a main panel (containing task tree,
+     * favorites, etc) and a separate panel containing the output.
+     * @param interaction    this is how we interact with the caller.
+       @param showDebugInfo  true to show some additional information that may be helpful
+                             diagnosing problems is this fails
+     * @return a dual pane UI.
+     * @throws Exception
+     */
+    public static DualPaneUIVersion1 createDualPaneUI( final DualPaneUIInteractionVersion1 interaction, boolean showDebugInfo ) throws Exception {
+        return new DualPaneUIWrapper( interaction );
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/GradleInterfaceWrapperVersion1.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/GradleInterfaceWrapperVersion1.java
new file mode 100644
index 0000000..b203672
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/GradleInterfaceWrapperVersion1.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.openapi.wrappers.foundation;
+
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.openapi.external.foundation.GradleInterfaceVersion1;
+import org.gradle.openapi.external.foundation.ProjectVersion1;
+import org.gradle.openapi.external.foundation.RequestObserverVersion1;
+import org.gradle.openapi.external.ui.CommandLineArgumentAlteringListenerVersion1;
+import org.gradle.openapi.wrappers.ui.CommandLineArgumentAlteringListenerWrapper;
+import org.gradle.util.GradleVersion;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation of GradleInterfaceVersion1 meant to help shield external users from internal changes.
+ * @author mhunsicker
+ */
+public class GradleInterfaceWrapperVersion1 implements GradleInterfaceVersion1 {
+
+    protected GradlePluginLord gradlePluginLord;
+    private Map<CommandLineArgumentAlteringListenerVersion1, CommandLineArgumentAlteringListenerWrapper> commandLineListenerMap = new HashMap<CommandLineArgumentAlteringListenerVersion1, CommandLineArgumentAlteringListenerWrapper>();
+    private Map<RequestObserverVersion1, RequestObserverWrapper> requestObserverMap = new HashMap<RequestObserverVersion1, RequestObserverWrapper>();
+
+    public GradleInterfaceWrapperVersion1(GradlePluginLord gradlePluginLord) {
+        this.gradlePluginLord = gradlePluginLord;
+    }
+
+    /**
+     * @return the version of gradle being run. This is basically the version from the jar file.
+     */
+    public String getVersion() {
+        return new GradleVersion().getVersion();
+    }
+
+    /**
+     * @return the root projects wrapped in a ProjectWrapper
+     */
+    public List<ProjectVersion1> getRootProjects() {
+
+        return ProjectWrapper.convertProjects( gradlePluginLord.getProjects() );
+    }
+
+    /**
+      Determines if commands are currently being executed or not. Refreshing
+      tasks is not considered busy.
+      @return true if we're busy, false if not.
+   */
+    public boolean isBusy() {
+        return gradlePluginLord.isBusy();
+    }
+
+    public void refreshTaskTree() {
+        gradlePluginLord.addRefreshRequestToQueue();
+    }
+
+    public void executeCommand(String commandLineArguments, String displayName) {
+        gradlePluginLord.addExecutionRequestToQueue( commandLineArguments, displayName );
+    }
+
+    public File getCurrentDirectory() {
+        return gradlePluginLord.getCurrentDirectory();
+    }
+
+    public void setCurrentDirectory(File currentDirectory) {
+        gradlePluginLord.setCurrentDirectory( currentDirectory );
+    }
+
+    public File getGradleHomeDirectory() {
+        return gradlePluginLord.getGradleHomeDirectory();
+    }
+
+    public File getCustomGradleExecutable() {
+        return gradlePluginLord.getCustomGradleExecutor();
+    }
+
+    /**
+     * Sets a custom gradle executable. See getCustomGradleExecutable
+     *
+     * @param customGradleExecutor the path to an executable (or script/batch file)
+     */
+    public void setCustomGradleExecutable(File customGradleExecutor) {
+        gradlePluginLord.setCustomGradleExecutor(customGradleExecutor);
+    }
+
+    /**
+     * This allows you to add a listener that can add additional command line
+     * arguments whenever gradle is executed. This is useful if you've customized
+     * your gradle build and need to specify, for example, an init script.
+     *
+     * @param listener the listener that modifies the command line arguments.
+     */
+    public void addCommandLineArgumentAlteringListener(CommandLineArgumentAlteringListenerVersion1 listener) {
+        CommandLineArgumentAlteringListenerWrapper wrapper = new CommandLineArgumentAlteringListenerWrapper(listener);
+
+        //we have to store our wrapper so you can call remove the listener using your passed-in object
+        commandLineListenerMap.put(listener, wrapper);
+
+        gradlePluginLord.addCommandLineArgumentAlteringListener(wrapper);
+    }
+
+    public void removeCommandLineArgumentAlteringListener(CommandLineArgumentAlteringListenerVersion1 listener) {
+        CommandLineArgumentAlteringListenerWrapper wrapper = commandLineListenerMap.remove(listener);
+        if (wrapper != null) {
+           gradlePluginLord.removeCommandLineArgumentAlteringListener(wrapper);
+        }
+    }
+
+    /**
+     * Adds an observer that is notified when Gradle commands are executed and
+     * completed.
+     *
+     * @param observer the observer that is notified
+     */
+    public void addRequestObserver(RequestObserverVersion1 observer) {
+        RequestObserverWrapper wrapper = new RequestObserverWrapper(observer);
+
+        //we have to store our wrapper so you can call remove the listener using your passed-in object
+        requestObserverMap.put(observer, wrapper);
+
+        gradlePluginLord.addRequestObserver(wrapper, false );
+    }
+
+    /**
+     * Removes a request observer when you no longer wish to receive notifications
+     * about Gradle command being executed.
+     *
+     * @param observer the observer to remove
+     */
+    public void removeRequestObserver(RequestObserverVersion1 observer) {
+        RequestObserverWrapper wrapper = requestObserverMap.remove(observer);
+        if (wrapper != null) {
+           gradlePluginLord.removeRequestObserver(wrapper);
+        }
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/GradleInterfaceWrapperVersion2.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/GradleInterfaceWrapperVersion2.java
new file mode 100644
index 0000000..a2786cc
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/GradleInterfaceWrapperVersion2.java
@@ -0,0 +1,89 @@
+/*
+ * 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.openapi.wrappers.foundation;
+
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.gradleplugin.foundation.favorites.FavoriteTask;
+import org.gradle.gradleplugin.foundation.request.Request;
+import org.gradle.openapi.external.foundation.GradleInterfaceVersion2;
+import org.gradle.openapi.external.foundation.RequestVersion1;
+import org.gradle.openapi.external.foundation.favorites.FavoriteTaskVersion1;
+import org.gradle.openapi.wrappers.foundation.favorites.FavoriteTaskWrapper;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+* Implementation of GradleInterfaceVersion2 meant to help shield external users from internal changes.
+* This adds new functionality to GradleInterfaceWrapperVersion1.
+*
+* @author mhunsicker
+ */
+public class GradleInterfaceWrapperVersion2 extends GradleInterfaceWrapperVersion1 implements GradleInterfaceVersion2 {
+
+    public GradleInterfaceWrapperVersion2(GradlePluginLord gradlePluginLord) {
+        super(gradlePluginLord);
+    }
+
+    private RequestVersion1 wrapRequest( Request request ) {
+        if( request == null ) {
+            return null;
+        }
+
+        return new RequestWrapper(request );
+    }
+
+
+    public RequestVersion1 refreshTaskTree2() {
+        return wrapRequest( gradlePluginLord.addRefreshRequestToQueue() );
+    }
+
+    /**
+    This refreshes the task tree. Useful if you know you've changed something behind
+    gradle's back or when first displaying this UI.
+    @param additionalCommandLineArguments additional command line arguments to be passed to gradle when
+                                          refreshing the task tree.
+    */
+   public RequestVersion1 refreshTaskTree2( String additionalCommandLineArguments ) {
+        return wrapRequest( gradlePluginLord.addRefreshRequestToQueue(additionalCommandLineArguments) );
+    }
+
+    public RequestVersion1 executeCommand2(String commandLineArguments, String displayName) {
+        return wrapRequest( gradlePluginLord.addExecutionRequestToQueue( commandLineArguments, displayName ) );
+    }
+
+    /**
+     * Executes several favorites commands at once as a single command. This has the affect
+     * of simply concatenating all the favorite command lines into a single line.
+     *
+     * @param favorites a list of favorites. If just one favorite, it executes it normally.
+     *                  If multiple favorites, it executes them all at once as a single command.
+     */
+    public RequestVersion1 executeFavorites(List<FavoriteTaskVersion1> favorites) {
+        List<FavoriteTask> tasks = new ArrayList<FavoriteTask>();
+
+        Iterator<FavoriteTaskVersion1> iterator = favorites.iterator();
+        while( iterator.hasNext() )
+        {
+           FavoriteTaskVersion1 favoriteTaskVersion1 = iterator.next();
+           FavoriteTaskWrapper wrapper = (FavoriteTaskWrapper) favoriteTaskVersion1;
+           tasks.add( wrapper.getFavoriteTask() );
+        }
+
+        return wrapRequest( gradlePluginLord.addExecutionRequestToQueue( tasks ) );
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/ProjectWrapper.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/ProjectWrapper.java
new file mode 100644
index 0000000..f9c7c27
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/ProjectWrapper.java
@@ -0,0 +1,156 @@
+/*
+ * 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.openapi.wrappers.foundation;
+
+import org.gradle.foundation.ProjectView;
+import org.gradle.foundation.TaskView;
+import org.gradle.openapi.external.foundation.ProjectVersion1;
+import org.gradle.openapi.external.foundation.TaskVersion1;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Implementation of ProjectVersion1 meant to help shield external users from internal changes.
+ * @author mhunsicker
+ */
+public class ProjectWrapper implements ProjectVersion1 {
+
+    private ProjectView projectView;
+
+    public ProjectWrapper(ProjectView projectView) {
+        this.projectView = projectView;
+    }
+
+    public String getName() {
+        return projectView.getName();
+    }
+
+    public File getFile() {
+        return projectView.getBuildFile();
+    }
+
+    public List<TaskVersion1> getTasks() {
+        return TaskWrapper.convertTasks( projectView.getTasks() );
+    }
+
+    public List<ProjectVersion1> getSubProjects() {
+        return convertProjects( projectView.getSubProjects() );
+    }
+
+    public ProjectVersion1 getParentProject() {
+        return new ProjectWrapper( projectView.getParentProject() );
+    }
+
+    public List<ProjectVersion1> getDependantProjects() {
+        return convertProjects( projectView.getDependsOnProjects() );
+    }
+
+    public ProjectVersion1 getSubProject( String name )
+    {
+        ProjectView subProject = projectView.getSubProject(name);
+        if( subProject == null ) {
+            return null;
+        }
+
+        return new ProjectWrapper(subProject);
+    }
+
+    public String getFullProjectName() {
+        return projectView.getFullProjectName();
+    }
+
+    public ProjectVersion1 getSubProjectFromFullPath(String fullProjectName) {
+        ProjectView projectFromFullPath = projectView.getSubProjectFromFullPath(fullProjectName);
+        if( projectFromFullPath == null ) {
+            return null;
+        }
+        return new ProjectWrapper( projectFromFullPath );
+    }
+
+    public TaskVersion1 getTask(String name) {
+        TaskView taskView = projectView.getTask(name);
+        if( taskView == null ) {
+            return null;
+        }
+        return new TaskWrapper( taskView );
+    }
+
+    /**
+     * Builds a list of default tasks. These are defined by specifying
+     *
+     * defaultTasks 'task name'
+     *
+     * in the gradle file. There can be multiple default tasks. This only returns default tasks directly for this
+     * project and does not return them for subprojects.
+     *
+     * @return a list of default tasks or an empty list if none exist
+     */
+    public List<TaskVersion1> getDefaultTasks() {
+            return TaskWrapper.convertTasks( projectView.getDefaultTasks() );
+        }
+
+    public TaskVersion1 getTaskFromFullPath(String fullTaskName) {
+        TaskView taskView = projectView.getTaskFromFullPath(fullTaskName );
+        if( taskView == null ) {
+            return null;
+        }
+
+        return new TaskWrapper( taskView );
+    }
+
+    /**
+     * Converts the list of ProjectView objects to ProjectVersion1 objects. It just wraps them.
+     * @param projectViewList the source projects
+     * @return the projects wrapped in ProjectWrappers.
+     */
+    public static List<ProjectVersion1> convertProjects( List<ProjectView> projectViewList )
+    {
+        List<ProjectVersion1> returnProjects = new ArrayList<ProjectVersion1>();
+        if( projectViewList != null )
+        {
+            Iterator<ProjectView> projectViewIterator = projectViewList.iterator();
+            while (projectViewIterator.hasNext()) {
+                ProjectView projectView = projectViewIterator.next();
+                returnProjects.add( new ProjectWrapper( projectView ) );
+            }
+        }
+
+        return returnProjects;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if( !( obj instanceof ProjectWrapper ) ) {
+            return false;
+        }
+
+        ProjectWrapper otherProjectWrapper = (ProjectWrapper) obj;
+        return otherProjectWrapper.projectView.equals( projectView );
+    }
+
+    @Override
+    public int hashCode() {
+        return projectView.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return projectView.toString();
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/RequestObserverWrapper.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/RequestObserverWrapper.java
new file mode 100644
index 0000000..10df9a5
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/RequestObserverWrapper.java
@@ -0,0 +1,77 @@
+/*
+ * 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.openapi.wrappers.foundation;
+
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.gradleplugin.foundation.request.ExecutionRequest;
+import org.gradle.gradleplugin.foundation.request.RefreshTaskListRequest;
+import org.gradle.gradleplugin.foundation.request.Request;
+import org.gradle.openapi.external.foundation.RequestObserverVersion1;
+
+/**
+ * * Implementation of RequestObserverVersion1 meant to help shield external users from internal changes.
+ *
+ * @author mhunsicker
+ */
+public class RequestObserverWrapper implements GradlePluginLord.RequestObserver {
+
+    private RequestObserverVersion1 requestObserver;
+
+    public RequestObserverWrapper(RequestObserverVersion1 requestObserver) {
+        this.requestObserver = requestObserver;
+    }
+
+    public void executionRequestAdded(ExecutionRequest request) {
+        requestObserver.executionRequestAdded( new RequestWrapper( request ) );
+    }
+
+    public void refreshRequestAdded(RefreshTaskListRequest request) {
+        requestObserver.refreshRequestAdded( new RequestWrapper( request ) );
+    }
+
+    /**
+     * Notification that a command is about to be executed. This is mostly useful
+     * for IDE's that may need to save their files.
+     */
+    public void aboutToExecuteRequest(Request request) {
+        requestObserver.aboutToExecuteRequest( new RequestWrapper( request ) );
+    }
+
+    /**
+     * Notification that the command has completed execution.
+     *
+     * @param request the original request containing the command that was executed
+     * @param result  the result of the command
+     * @param output  the output from gradle executing the command
+     */
+    public void requestExecutionComplete(Request request, int result, String output) {
+        requestObserver.requestExecutionComplete( new RequestWrapper( request ), result, output );
+    }
+
+    @Override
+    public int hashCode() {
+        return requestObserver.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object otherObject ) {
+        if( !(otherObject instanceof RequestObserverWrapper ) ) {
+            return false;
+        }
+
+        return ( (RequestObserverWrapper) otherObject ).requestObserver.equals( requestObserver );
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/RequestWrapper.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/RequestWrapper.java
new file mode 100644
index 0000000..593fb11
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/RequestWrapper.java
@@ -0,0 +1,98 @@
+/*
+ * 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.openapi.wrappers.foundation;
+
+import org.gradle.gradleplugin.foundation.request.ExecutionRequest;
+import org.gradle.gradleplugin.foundation.request.RefreshTaskListRequest;
+import org.gradle.gradleplugin.foundation.request.Request;
+import org.gradle.openapi.external.foundation.RequestVersion1;
+
+/**
+ * Implementation of RequestVersion1 meant to help shield external users from internal changes.
+ *
+ * @author mhunsicker
+ */
+public class RequestWrapper implements RequestVersion1 {
+    private Request request;
+
+    public RequestWrapper(Request request) {
+        this.request = request;
+    }
+
+    /**
+     * @return the full gradle command line of this request
+     */
+    public String getFullCommandLine() {
+        return request.getFullCommandLine();
+    }
+
+    /**
+     * @return the display name of this request. Often this is the same as the full
+     *         command line, but favorites may specify something more user-friendly.
+     */
+    public String getDisplayName() {
+        return request.getDisplayName();
+    }
+
+    /**
+     * @return whether or not output should always be shown. If false, only show it when
+     *         errors occur.
+     */
+    public boolean forceOutputToBeShown() {
+        return request.forceOutputToBeShown();
+    }
+
+    /**
+     * Cancels this request.
+     */
+    public boolean cancel() {
+        return request.cancel();
+    }
+
+    @Override
+    public int hashCode() {
+        return request.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object otherObject ) {
+        if( !( otherObject instanceof RequestWrapper ) ) {
+            return false;
+        }
+
+        return ((RequestWrapper) otherObject).request.equals( request );
+    }
+
+    /**
+     * @return the type of the request. Either EXECUTION or REFRESH
+     */
+    public String getType() {
+        if( request.getType() == ExecutionRequest.TYPE ) {
+            return EXECUTION_TYPE;
+        }
+
+        if( request.getType() == RefreshTaskListRequest.TYPE ) {
+            return REFRESH_TYPE;
+        }
+
+        return UNKNOWN_TYPE_PREFIX + request.getType();
+    }
+
+    @Override
+    public String toString() {
+        return request.toString();
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/TaskWrapper.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/TaskWrapper.java
new file mode 100644
index 0000000..d7ceb3c
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/TaskWrapper.java
@@ -0,0 +1,94 @@
+/*
+ * 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.openapi.wrappers.foundation;
+
+import org.gradle.foundation.TaskView;
+import org.gradle.openapi.external.foundation.ProjectVersion1;
+import org.gradle.openapi.external.foundation.TaskVersion1;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Implementation of TaskVersion1 meant to help shield external users from internal changes.
+ * @author mhunsicker
+ */
+public class TaskWrapper implements TaskVersion1 {
+
+    private TaskView taskView;
+
+    public TaskWrapper(TaskView taskView) {
+        this.taskView = taskView;
+    }
+
+    public String getName() {
+        return taskView.getName();
+    }
+
+    public String getDescription() {
+        return taskView.getDescription();
+    }
+
+    public boolean isDefault() {
+        return taskView.isDefault();
+    }
+
+    public String getFullTaskName() {
+        return taskView.getFullTaskName();
+    }
+
+    public ProjectVersion1 getProject() {
+        return new ProjectWrapper( taskView.getProject() );
+    }
+
+    /**
+     * Converts the list of TaskView objects to TaskVersion1 objects. It just wraps them.
+     * @param taskViewList the source tasks
+     * @return the tasks wrapped in TaskWrappers.
+     */
+    public static List<TaskVersion1> convertTasks( List<TaskView> taskViewList)
+    {
+        List<TaskVersion1> returnTasks = new ArrayList<TaskVersion1>();
+        Iterator<TaskView> taskViewIterator = taskViewList.iterator();
+        while (taskViewIterator.hasNext()) {
+            TaskView taskView = taskViewIterator.next();
+            returnTasks.add( new TaskWrapper( taskView ) );
+        }
+
+        return returnTasks;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if( !(obj instanceof TaskWrapper ) ) {
+            return false;
+        }
+
+        TaskWrapper otherTaskWrapper = (TaskWrapper) obj;
+        return otherTaskWrapper.taskView.equals( taskView );
+    }
+
+    @Override
+    public int hashCode() {
+        return taskView.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return taskView.toString();
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/favorites/FavoriteTaskWrapper.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/favorites/FavoriteTaskWrapper.java
new file mode 100644
index 0000000..75a08ff
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/favorites/FavoriteTaskWrapper.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.openapi.wrappers.foundation.favorites;
+
+import org.gradle.gradleplugin.foundation.favorites.FavoriteTask;
+import org.gradle.openapi.external.foundation.favorites.FavoriteTaskVersion1;
+
+/**
+ * Implementation of FavoriteTaskVersion1 meant to help shield external users from internal changes.
+ *
+ * @author mhunsicker
+
+ */
+public class FavoriteTaskWrapper implements FavoriteTaskVersion1 {
+
+    private FavoriteTask favoriteTask;
+
+    public FavoriteTaskWrapper(FavoriteTask favoriteTask) {
+        this.favoriteTask = favoriteTask;
+    }
+
+    @Override
+    public boolean equals(Object otherObject ) {
+        if( !( otherObject instanceof FavoriteTaskWrapper ) ) {
+            return false;
+        }
+
+        FavoriteTaskWrapper otherFavoritesTask = (FavoriteTaskWrapper) otherObject;
+        return otherFavoritesTask.favoriteTask.equals( favoriteTask );
+    }
+
+    public String getFullCommandLine() {
+        return favoriteTask.getFullCommandLine();
+    }
+
+    public String getDisplayName() {
+        return favoriteTask.getDisplayName();
+    }
+
+    public boolean alwaysShowOutput() {
+        return favoriteTask.alwaysShowOutput();
+    }
+
+    //Only to be used internally to get the favorite task this represents
+    public FavoriteTask getFavoriteTask() {
+        return favoriteTask;
+    }
+
+    @Override
+    public int hashCode() {
+        return favoriteTask.hashCode();
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/favorites/FavoritesEditorWrapper.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/favorites/FavoritesEditorWrapper.java
new file mode 100644
index 0000000..eb0e686
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/foundation/favorites/FavoritesEditorWrapper.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.openapi.wrappers.foundation.favorites;
+
+import org.gradle.gradleplugin.foundation.favorites.FavoriteTask;
+import org.gradle.gradleplugin.foundation.favorites.FavoritesEditor;
+import org.gradle.gradleplugin.userinterface.swing.generic.SwingEditFavoriteInteraction;
+import org.gradle.openapi.external.foundation.TaskVersion1;
+import org.gradle.openapi.external.foundation.favorites.FavoriteTaskVersion1;
+import org.gradle.openapi.external.foundation.favorites.FavoritesEditorVersion1;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Implementation of FavoritesEditorVersion1 meant to help shield external users from internal changes.
+ * @author mhunsicker
+ */
+public class FavoritesEditorWrapper implements FavoritesEditorVersion1 {
+    private FavoritesEditor favoritesEditor;
+
+    public FavoritesEditorWrapper(FavoritesEditor favoritesEditor) {
+        this.favoritesEditor = favoritesEditor;
+    }
+
+    public FavoriteTaskVersion1 addFavorite(String fullCommandLine, String displayName, boolean alwaysShowOutput) {
+        return convertFavoriteTask( favoritesEditor.addFavorite(fullCommandLine, displayName, alwaysShowOutput ) );
+    }
+
+    public String editFavorite(FavoriteTaskVersion1 favoriteTaskVersion1, final String newFullCommandLine, final String newDisplayName, final boolean newAlwaysShowOutput) {
+        final StringHolder stringHolder = new StringHolder();
+        FavoriteTask favoriteTask = getFavoriteTask( favoriteTaskVersion1 );
+        favoritesEditor.editFavorite(favoriteTask, new FavoritesEditor.EditFavoriteInteraction() {
+            public boolean editFavorite(FavoritesEditor.EditibleFavoriteTask favoriteTask) {
+                favoriteTask.fullCommandLine = newFullCommandLine;
+                favoriteTask.displayName = newDisplayName;
+                favoriteTask.alwaysShowOutput = newAlwaysShowOutput;
+                return true;
+            }
+
+            public void reportError(String error) {
+                stringHolder.string = error;
+            }
+        } );
+
+        return stringHolder.string;
+    }
+
+    //
+            private class StringHolder {
+                private String string;
+            }
+
+    private FavoriteTaskVersion1 convertFavoriteTask( FavoriteTask favoriteTask ) {
+        if( favoriteTask == null )
+        {
+            return null;
+        }
+
+        return new FavoriteTaskWrapper( favoriteTask );
+    }
+
+
+    public List<FavoriteTaskVersion1> getFavoriteTasks() {
+        List<FavoriteTaskVersion1> returnedTasks = new ArrayList<FavoriteTaskVersion1>();
+        Iterator<FavoriteTask> taskIterator = favoritesEditor.getFavoriteTasks().iterator();
+        while (taskIterator.hasNext()) {
+            FavoriteTask favoriteTask = taskIterator.next();
+            returnedTasks.add( new FavoriteTaskWrapper( favoriteTask ) );
+        }
+        return returnedTasks;
+    }
+
+    public FavoriteTaskVersion1 getFavorite(String fullCommandLine) {
+        return convertFavoriteTask( favoritesEditor.getFavorite( fullCommandLine ) );
+    }
+
+    public FavoriteTaskVersion1 getFavoriteByDisplayName(String displayName) {
+        return convertFavoriteTask( favoritesEditor.getFavoriteByDisplayName( displayName ) );
+    }
+
+    public FavoriteTaskVersion1 getFavorite(TaskVersion1 task) {
+        return convertFavoriteTask( favoritesEditor.getFavorite( task.getFullTaskName() ) );
+    }
+
+    public FavoriteTaskVersion1 promptUserToAddFavorite(Window parent) {
+        FavoriteTask favoriteTask = favoritesEditor.addFavorite( new SwingEditFavoriteInteraction( parent, "Add Favorite", true ) );
+        return convertFavoriteTask( favoriteTask );
+    }
+
+    public boolean promptUserToEditFavorite(Window parent, FavoriteTaskVersion1 favorite) {
+        FavoriteTask favoriteTask = getFavoriteTask(favorite);
+        return favoritesEditor.editFavorite( favoriteTask, new SwingEditFavoriteInteraction( parent, "Edit Favorite", true ) );
+    }
+
+    public void removeFavorites( List<FavoriteTaskVersion1> favoritesToRemove) {
+        List<FavoriteTask> favoriteTasksToRemove = new ArrayList<FavoriteTask>();
+
+        Iterator<FavoriteTaskVersion1> iterator = favoritesToRemove.iterator();
+        while( iterator.hasNext() )
+        {
+           FavoriteTaskVersion1 favoriteTaskVersion1 = iterator.next();
+           favoriteTasksToRemove.add( getFavoriteTask( favoriteTaskVersion1 ) );
+        }
+
+        favoritesEditor.removeFavorites(favoriteTasksToRemove);
+    }
+
+    //gets the favorite task out of a FavoriteTaskVersion1.
+    private FavoriteTask getFavoriteTask( FavoriteTaskVersion1 favoriteTaskVersion1 ) {
+        return ((FavoriteTaskWrapper) favoriteTaskVersion1 ).getFavoriteTask();
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/runner/GradleRunnerInteractionWrapper.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/runner/GradleRunnerInteractionWrapper.java
new file mode 100644
index 0000000..a5052bb
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/runner/GradleRunnerInteractionWrapper.java
@@ -0,0 +1,129 @@
+/*
+ * 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.openapi.wrappers.runner;
+
+import org.gradle.StartParameter;
+import org.gradle.api.logging.LogLevel;
+import org.gradle.foundation.ipc.gradle.ExecuteGradleCommandServerProtocol;
+import org.gradle.openapi.external.runner.GradleRunnerInteractionVersion1;
+
+import java.io.File;
+
+/**
+ * Wrapper to shield version changes in GradleRunnerInteractionVersion1
+ * from an external user of gradle open API.
+ *
+ * @author mhunsicker
+ */
+public class GradleRunnerInteractionWrapper implements ExecuteGradleCommandServerProtocol.ExecutionInteraction {
+    private GradleRunnerInteractionVersion1 interactionVersion1;
+
+    public GradleRunnerInteractionWrapper(GradleRunnerInteractionVersion1 interactionVersion1) {
+        this.interactionVersion1 = interactionVersion1;
+    }
+
+    /**
+    * @return the log level. This determines the detail level of information reported via reportLiveOutput and reportExecutionFinished.
+    */
+    public LogLevel getLogLevel() {
+        GradleRunnerInteractionVersion1.LogLevel logLevel = interactionVersion1.getLogLevel();
+        switch (logLevel) {
+            case Quiet:
+                return LogLevel.QUIET;
+            case Lifecycle:
+                return LogLevel.LIFECYCLE;
+            case Debug:
+                return LogLevel.DEBUG;
+        }
+
+        return LogLevel.LIFECYCLE;
+    }
+
+    /**
+    * @return the stack trace level. This determines the detail level of any stack traces should an exception occur.
+    */
+    public StartParameter.ShowStacktrace getStackTraceLevel() {
+        GradleRunnerInteractionVersion1.StackTraceLevel stackTraceLevel = interactionVersion1.getStackTraceLevel();
+        switch (stackTraceLevel) {
+            case InternalExceptions:
+                return StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS;
+            case Always:
+                return StartParameter.ShowStacktrace.ALWAYS;
+            case AlwaysFull:
+                return StartParameter.ShowStacktrace.ALWAYS_FULL;
+        }
+
+        return StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS;
+    }
+
+    /**
+     * Notification that overall execution has been started. This is only called once at the end.
+     */
+    public void reportExecutionStarted() {
+        this.interactionVersion1.reportExecutionStarted();
+    }
+
+   /**
+    * Notification of the total number of tasks that will be executed. This is called after reportExecutionStarted and before any tasks are executed.
+    *
+    * @param size the total number of tasks.
+    */
+   public void reportNumberOfTasksToExecute( int size ) {
+      this.interactionVersion1.reportNumberOfTasksToExecute( size );
+   }
+
+   /**
+     * Notification that a single task has completed. Note: the task you kicked
+     * off probably executes other tasks and this notifies you of those tasks
+     * and provides completion progress.
+     *
+     * @param currentTaskName the task being executed
+     * @param percentComplete the percent complete of all the tasks that make
+     *                        up the task you requested.
+     */
+    public void reportTaskStarted(String currentTaskName, float percentComplete) {
+        this.interactionVersion1.reportTaskStarted(currentTaskName, percentComplete);
+    }
+
+    public void reportTaskComplete(String currentTaskName, float percentComplete) {
+        this.interactionVersion1.reportTaskComplete(currentTaskName, percentComplete);
+    }
+
+    /**
+     * Report real-time output from gradle and its subsystems (such as ant).
+     *
+     * @param output a single line of text to show.
+     */
+    public void reportLiveOutput(String output) {
+        this.interactionVersion1.reportLiveOutput(output);
+    }
+
+    public void reportExecutionFinished(boolean wasSuccessful, String message, Throwable throwable) {
+        this.interactionVersion1.reportExecutionFinished(wasSuccessful, message, throwable);
+    }
+
+    /*
+    * This is called to get a custom gradle executable file. If you don't run
+    * gradle.bat or gradle shell script to run gradle, use this to specify
+    * what you do run. Note: we're going to pass it the arguments that we would
+    * pass to gradle so if you don't like that, see alterCommandLineArguments.
+    * Normaly, this should return null.
+    * @return the Executable to run gradle command or null to use the default
+    */
+    public File getCustomGradleExecutable() {
+        return interactionVersion1.getCustomGradleExecutable();
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/runner/GradleRunnerWrapper.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/runner/GradleRunnerWrapper.java
new file mode 100644
index 0000000..fa9d93e
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/runner/GradleRunnerWrapper.java
@@ -0,0 +1,57 @@
+/*
+ * 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.openapi.wrappers.runner;
+
+import org.gradle.gradleplugin.foundation.runner.GradleRunner;
+import org.gradle.openapi.external.runner.GradleRunnerInteractionVersion1;
+import org.gradle.openapi.external.runner.GradleRunnerVersion1;
+
+import java.io.File;
+
+/**
+ * Wrapper to shield version changes in GradleRunner from an external user of gradle open API.
+ *
+ * @author mhunsicker
+ */
+public class GradleRunnerWrapper implements GradleRunnerVersion1 {
+    private GradleRunner gradleRunner;
+    private File gradleHomeDirectory;
+    private File workingDirectory;
+    private GradleRunnerInteractionWrapper interactionWrapper;
+
+    public GradleRunnerWrapper(File gradleHomeDirectory, GradleRunnerInteractionVersion1 interactionVersion1) {
+        this.gradleHomeDirectory = gradleHomeDirectory;
+        this.workingDirectory = interactionVersion1.getWorkingDirectory();
+        interactionWrapper = new GradleRunnerInteractionWrapper(interactionVersion1);
+        File customGradleExecutable = interactionVersion1.getCustomGradleExecutable();
+
+        gradleRunner = new GradleRunner(workingDirectory, gradleHomeDirectory, customGradleExecutable);
+    }
+
+    public void executeCommand(String commandLine) {
+        gradleRunner.executeCommand(commandLine, interactionWrapper.getLogLevel(),
+                interactionWrapper.getStackTraceLevel(), interactionWrapper);
+    }
+
+    /*
+       Call this to stop the gradle command. This is killing the process, not
+       gracefully exiting.
+    */
+
+    public void killProcess() {
+        gradleRunner.killProcess();
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/AbstractOpenAPIUIWrapper.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/AbstractOpenAPIUIWrapper.java
new file mode 100644
index 0000000..a60d891
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/AbstractOpenAPIUIWrapper.java
@@ -0,0 +1,349 @@
+/*
+ * 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.openapi.wrappers.ui;
+
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.gradleplugin.foundation.request.ExecutionRequest;
+import org.gradle.gradleplugin.foundation.request.RefreshTaskListRequest;
+import org.gradle.gradleplugin.foundation.request.Request;
+import org.gradle.gradleplugin.userinterface.swing.generic.BasicGradleUI;
+import org.gradle.openapi.external.foundation.GradleInterfaceVersion1;
+import org.gradle.openapi.external.foundation.favorites.FavoritesEditorVersion1;
+import org.gradle.openapi.external.ui.*;
+import org.gradle.openapi.wrappers.foundation.favorites.FavoritesEditorWrapper;
+
+import javax.swing.*;
+import java.awt.*;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+* Implementation of BasicGradleUI meant to help shield external users from internal changes.
+* This also provides the basics for the UI regardless of whether the output is in a separate
+ * pane or not.
+ @author mhunsicker
+*/
+public abstract class AbstractOpenAPIUIWrapper<U extends BasicGradleUI>
+{
+    private U basicGradleUI;
+    private Map<GradleTabVersion1, GradleTabVersionWrapper> tabMap = new HashMap<GradleTabVersion1, GradleTabVersionWrapper>();
+
+    protected SettingsNodeVersionWrapper settingsVersionWrapper;
+    protected AlternateUIInteractionVersionWrapper alternateUIInteractionVersionWrapper;
+    protected GradleInterfaceVersion1 gradleInterfaceWrapper;
+
+    private OutputUILordWrapper outputUILordWrapper;
+
+   public AbstractOpenAPIUIWrapper( SettingsNodeVersion1 settings, AlternateUIInteractionVersion1 alternateUIInteraction)
+   {
+      settingsVersionWrapper = new SettingsNodeVersionWrapper(settings);
+      alternateUIInteractionVersionWrapper = new AlternateUIInteractionVersionWrapper(alternateUIInteraction, settingsVersionWrapper );
+   }
+
+   public void initialize( U basicGradleUI ) {
+        this.basicGradleUI = basicGradleUI;
+        basicGradleUI.getGradlePluginLord().addRequestObserver( new GradlePluginLord.RequestObserver()
+        {
+           /**
+            Notification that a command is about to be executed. This is mostly useful
+            for IDE's that may need to save their files.
+
+            @param request the request that's about to be executed
+            @author mhunsicker
+            */
+           public void aboutToExecuteRequest( Request request )
+           {
+               alternateUIInteractionVersionWrapper.aboutToExecuteCommand( request.getFullCommandLine() );
+           }
+
+           public void executionRequestAdded( ExecutionRequest request ) { }
+           public void refreshRequestAdded( RefreshTaskListRequest request ) { }
+           public void requestExecutionComplete( Request request, int result, String output ) { }
+        }, false );
+
+       outputUILordWrapper = new OutputUILordWrapper( basicGradleUI.getOutputUILord() );
+       gradleInterfaceWrapper = instantiateGradleInterfaceWrapper();
+
+    }
+
+    /**
+     * This instantiates our GradleInterfaceVersion object. Additions have been made
+     * to it -- making new versions, so we have a choice of which one to load. We'll
+     * try to load the latest one first, if that fails, we'll fall back on older
+     * versions. The latest version would fail to load because it depends on classes
+     * that are part of the Open API project and it can't find those classes. It
+     * might not find them because this is loaded from the Open API and if its an
+     * older version, those classes won't exist.
+     * @return a version of GradleInterfaceVersionX
+     */
+    protected GradleInterfaceVersion1 instantiateGradleInterfaceWrapper()
+    {
+       try
+       {
+           //try to load the latest version. I'm explicitly using the full package name so any NoClassDefFoundErrors will only
+           //occur within this try/catch block.
+           return new org.gradle.openapi.wrappers.foundation.GradleInterfaceWrapperVersion2( basicGradleUI.getGradlePluginLord() );
+       }
+       catch( NoClassDefFoundError e )
+       {  //if that's not found, fall back to version 1
+          return new org.gradle.openapi.wrappers.foundation.GradleInterfaceWrapperVersion1( basicGradleUI.getGradlePluginLord() );
+       }
+    }
+
+    public U getGradleUI() {
+      return basicGradleUI;
+   }
+
+    /**
+       Call this whenever you're about to show this panel. We'll do whatever
+       initialization is necessary.
+    */
+    public void aboutToShow() {
+        basicGradleUI.aboutToShow();
+    }
+
+    /**
+     * Call this to deteremine if you can close this pane. if we're busy, we'll
+     * ask the user if they want to close.
+     *
+     * @param closeInteraction allows us to interact with the user
+     * @return true if we can close, false if not.
+     */
+    public boolean canClose(final BasicGradleUIVersion1.CloseInteraction closeInteraction) {
+        return basicGradleUI.canClose(new BasicGradleUI.CloseInteraction() {
+            public boolean promptUserToConfirmClosingWhileBusy() {
+                return closeInteraction.promptUserToConfirmClosingWhileBusy();
+            }
+        });
+    }
+
+    /**
+       Call this before you close the pane. This gives it an opportunity to do
+       cleanup. You probably should call canClose before this. It gives the
+       app a chance to cancel if its busy.
+    */
+    public void close() {
+        basicGradleUI.close();
+    }
+
+
+   /**
+      @return the root directory of your gradle project.
+   */
+   public File getCurrentDirectory() {
+       return gradleInterfaceWrapper.getCurrentDirectory();
+   }
+
+   /**
+      @param  currentDirectory the new root directory of your gradle project.
+   */
+   public void setCurrentDirectory(File currentDirectory) {
+       gradleInterfaceWrapper.setCurrentDirectory(currentDirectory);
+   }
+
+   /**
+    * @return the gradle home directory. Where gradle is installed.
+    */
+   public File getGradleHomeDirectory() {
+       return gradleInterfaceWrapper.getGradleHomeDirectory();
+   }
+
+   /**
+    * This is called to get a custom gradle executable file. If you don't run
+    * gradle.bat or gradle shell script to run gradle, use this to specify
+    * what you do run. Note: we're going to pass it the arguments that we would
+    * pass to gradle so if you don't like that, see alterCommandLineArguments.
+    * Normally, this should return null.
+    *
+    * @return the Executable to run gradle command or null to use the default
+    */
+   public File getCustomGradleExecutable() {
+       return gradleInterfaceWrapper.getCustomGradleExecutable();
+   }
+
+    /**
+       Call this to add an additional tab to the gradle UI. You can call this
+       at any time.
+       @param  index             the index of where to add the tab.
+       @param  gradleTabVersion1 the tab to add.
+    */
+    public void addTab(int index, GradleTabVersion1 gradleTabVersion1) {
+        GradleTabVersionWrapper gradleVersionWrapper = new GradleTabVersionWrapper(gradleTabVersion1);
+
+        //we have to store our wrapper so you can call remove tab using your passed-in object
+        tabMap.put(gradleTabVersion1, gradleVersionWrapper);
+
+        basicGradleUI.addGradleTab(index, gradleVersionWrapper);
+    }
+
+    /**
+       Call this to remove one of your own tabs from this.
+
+       @param  gradleTabVersion1 the tab to remove
+    */
+    public void removeTab(GradleTabVersion1 gradleTabVersion1) {
+        GradleTabVersionWrapper gradleTabVersionWrapper = tabMap.remove(gradleTabVersion1);
+        if (gradleTabVersionWrapper != null) {
+           basicGradleUI.removeGradleTab(gradleTabVersionWrapper);
+        }
+    }
+
+    public int getGradleTabCount() {
+       return basicGradleUI.getGradleTabCount();
+    }
+
+    /**
+       @param  index      the index of the tab
+       @return the name of the tab at the specified index.
+    */
+    public String getGradleTabName(int index) {
+        return basicGradleUI.getGradleTabName(index);
+    }
+
+     /**
+      * Returns the index of the gradle tab with the specified name.
+      * @param name the name of the tab
+      * @return the index of the tab or -1 if not found
+      */
+     public int getGradleTabIndex( String name ) {
+         return basicGradleUI.getGradleTabIndex( name );
+     }
+
+    /**
+     * @return the currently selected tab
+     */
+    public int getCurrentGradleTab() {
+        return basicGradleUI.getCurrentGradleTab();
+    }
+
+    /**
+     * Makes the specified tab the current tab.
+     * @param index the index of the tab.
+     */
+    public void setCurrentGradleTab( int index ) {
+        basicGradleUI.setCurrentGradleTab( index );
+    }
+
+    /**
+     * This allows you to add a listener that can add additional command line
+     * arguments whenever gradle is executed. This is useful if you've customized
+     * your gradle build and need to specify, for example, an init script.
+     *
+     * @param listener the listener that modifies the command line arguments.
+     */
+    public void addCommandLineArgumentAlteringListener(CommandLineArgumentAlteringListenerVersion1 listener) {
+        gradleInterfaceWrapper.addCommandLineArgumentAlteringListener(listener);
+    }
+
+    public void removeCommandLineArgumentAlteringListener(CommandLineArgumentAlteringListenerVersion1 listener) {
+        gradleInterfaceWrapper.removeCommandLineArgumentAlteringListener(listener);
+    }
+
+    public OutputUILordVersion1 getOutputLord()
+    {
+        return new OutputUILordWrapper( basicGradleUI.getOutputUILord() );
+    }
+
+
+    public void addOutputObserver( OutputObserverVersion1 observer )
+    {
+        outputUILordWrapper.addOutputObserver( observer );
+    }
+
+    public void removeOutputObserver( OutputObserverVersion1 observer )
+    {
+       outputUILordWrapper.removeOutputObserver( observer );
+    }
+
+    /**
+    Call this to execute the given gradle command.
+
+    @param commandLineArguments  the command line arguments to pass to gradle.
+    @param displayName           the name displayed in the UI for this command
+    */
+    public void executeCommand(String commandLineArguments, String displayName) {
+        //we go through the Swing version because it allows you to specify a display name
+        //for the command.
+        basicGradleUI.executeCommand( commandLineArguments, displayName );
+    }
+
+
+   /**
+    This refreshes the task tree. Useful if you know you've changed something behind
+    gradle's back or when first displaying this UI.
+    */
+   public void refreshTaskTree()
+   {
+      basicGradleUI.refreshTaskTree();
+   }
+
+   /**
+    Determines if commands are currently being executed or not.
+
+    @return true if we're busy, false if not.
+    */
+   public boolean isBusy()
+   {
+      return getGradleUI().isBusy();
+   }
+
+   /**
+    Determines whether output is shown only when errors occur or always
+    @return true to only show output if errors occur, false to show it always.
+    */
+   public boolean getOnlyShowOutputOnErrors()
+   {
+      return getGradleUI().getOutputUILord().getOnlyShowOutputOnErrors();
+   }
+
+   /**
+    This adds the specified component to the setup panel. It is added below the last
+    'default' item. You can only add 1 component here, so if you need to add multiple
+    things, you'll have to handle adding that to yourself to the one component.
+    @param component the component to add.
+    */
+   public void setCustomPanelToSetupTab( JComponent component )
+   {
+      getGradleUI().setCustomPanelToSetupTab( component );
+   }
+
+   /**
+    Sets the font for the output text
+    @param font the new font
+    */
+   public void setOutputTextFont( Font font )
+   {
+      getGradleUI().setOutputTextFont( font );
+   }
+
+    /**
+     * @return an object that works with lower level gradle and contains the current projects and tasks.
+     */
+   public GradleInterfaceVersion1 getGradleInterfaceVersion1()
+   {
+      return gradleInterfaceWrapper;
+   }
+
+   /**
+     * Returns a FavoritesEditor. This is useful for getting a list of all favorites or
+     * modifying them.
+     * @return a FavoritesEditorVersion1. Use this to interact with the favorites.
+     */
+   public FavoritesEditorVersion1 getFavoritesEditor() {
+      return new FavoritesEditorWrapper( basicGradleUI.getGradlePluginLord().getFavoritesEditor() );
+   }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/AlternateUIInteractionVersionWrapper.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/AlternateUIInteractionVersionWrapper.java
new file mode 100644
index 0000000..d7b6b2e
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/AlternateUIInteractionVersionWrapper.java
@@ -0,0 +1,65 @@
+/*
+ * 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.openapi.wrappers.ui;
+
+import org.gradle.gradleplugin.foundation.settings.SettingsNode;
+import org.gradle.openapi.external.ui.AlternateUIInteractionVersion1;
+import org.gradle.gradleplugin.userinterface.AlternateUIInteraction;
+
+import java.io.File;
+
+/**
+ * Wrapper to shield version changes in AlternateUIInteraction from an external user of gradle open API.
+ *
+ * @author mhunsicker
+*/
+public class AlternateUIInteractionVersionWrapper implements AlternateUIInteraction {
+    private AlternateUIInteractionVersion1 alternateUIInteractionVersion1;
+    private SettingsNode settings;
+
+    public AlternateUIInteractionVersionWrapper(AlternateUIInteractionVersion1 alternateUIInteractionVersion1, SettingsNode settings) {
+        this.alternateUIInteractionVersion1 = alternateUIInteractionVersion1;
+        this.settings = settings;
+
+      //when future versions are added, doing the following and then delegating
+      //the new functions to AlternateUIInteractionVersion2 keeps things compatible.
+      //try
+      //{
+      //   if( alternateUIInteractionVersion1 instanceof AlternateUIInteractionVersion2 )
+      //      alternateUIInteractionVersion2 = (AlternateUIInteractionVersion2) alternateUIInteractionVersion1;
+      //}
+      //catch( NoClassDefFoundError e )
+      //{
+      //   //this just means that we're being run with an old version of the open API. We have no alternateUIInteractionVersion2
+      //}
+    }
+
+   public void openFile( File file, int line ) {
+      alternateUIInteractionVersion1.openFile(file, line );
+   }
+
+   public void editFile( File file, int line ) {
+        alternateUIInteractionVersion1.editFile( file, line );
+    }
+
+    public boolean doesSupportEditingOpeningFiles() {
+        return alternateUIInteractionVersion1.doesSupportEditingOpeningFiles();
+    }
+
+    public void aboutToExecuteCommand( String fullCommandLine ) {
+       alternateUIInteractionVersion1.aboutToExecuteCommand( fullCommandLine );
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/CommandLineArgumentAlteringListenerWrapper.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/CommandLineArgumentAlteringListenerWrapper.java
new file mode 100644
index 0000000..36da1cd
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/CommandLineArgumentAlteringListenerWrapper.java
@@ -0,0 +1,43 @@
+/*
+ * 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.openapi.wrappers.ui;
+
+import org.gradle.openapi.external.ui.CommandLineArgumentAlteringListenerVersion1;
+import org.gradle.gradleplugin.foundation.CommandLineArgumentAlteringListener;
+
+/**
+ * Wrapper to shield version changes in GradleTab from an external user of the gradle open API.
+ *
+ * @author mhunsicker
+ */
+public class CommandLineArgumentAlteringListenerWrapper implements CommandLineArgumentAlteringListener {
+    private CommandLineArgumentAlteringListenerVersion1 listenerVersion1;
+
+    public CommandLineArgumentAlteringListenerWrapper(CommandLineArgumentAlteringListenerVersion1 listenerVersion1) {
+        this.listenerVersion1 = listenerVersion1;
+    }
+
+    /**
+     * This is called when you can add additional command line arguments. Return any additional arguments to add. This
+     * doesn't modify the existing commands.
+     *
+     * @param commandLineArguments the command line to execute.
+     * @return any command lines to add or null to leave it alone
+     */
+    public String getAdditionalCommandLineArguments(String commandLineArguments) {
+        return listenerVersion1.getAdditionalCommandLineArguments(commandLineArguments);
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/DualPaneUIWrapper.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/DualPaneUIWrapper.java
new file mode 100644
index 0000000..8bd5e37
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/DualPaneUIWrapper.java
@@ -0,0 +1,70 @@
+/*
+ * 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.openapi.wrappers.ui;
+
+import org.gradle.gradleplugin.userinterface.swing.generic.DualPaneUIInstance;
+import org.gradle.openapi.external.ui.DualPaneUIInteractionVersion1;
+import org.gradle.openapi.external.ui.DualPaneUIVersion1;
+
+import javax.swing.JComponent;
+import java.awt.Component;
+
+/**
+This wraps a DualPaneUIVersion1 for the purpose of being instantiated for
+ an external tool such an IDE plugin. It wraps several interfaces and uses
+ delegation in an effort to make this backward and forward compatible.
+ Most of the work is done in AbstractOpenAPIUIWrapper
+
+ @author mhunsicker
+ */
+public class DualPaneUIWrapper extends AbstractOpenAPIUIWrapper<DualPaneUIInstance> implements DualPaneUIVersion1
+{
+   public DualPaneUIWrapper( DualPaneUIInteractionVersion1 dualPaneUIArguments ) {
+
+       super( dualPaneUIArguments.instantiateSettings(), dualPaneUIArguments.instantiateAlternateUIInteraction() );
+
+       //the main thing this does in instantiate the DualPaneUIInstance.
+       DualPaneUIInstance uiInstance = new DualPaneUIInstance();
+       uiInstance.initialize( settingsVersionWrapper, alternateUIInteractionVersionWrapper );
+       initialize( uiInstance );
+    }
+
+   /**
+    Returns a component that shows the task tree tab, favorites tab, etc.
+    suitable for inserting in your UI.
+
+    @return the main component
+    */
+   public JComponent getMainComponent()
+   {
+      return getGradleUI().getComponent();
+   }
+
+   /**
+    Returns a component that shows the output of the tasks being executed.
+
+    @return the output component
+    */
+   public Component getOutputPanel()
+   {
+      return getGradleUI().getOutputPanel();
+   }
+
+   public int getNumberOfOpenedOutputTabs()
+   {
+      return getGradleUI().getOutputUILord().getTabCount();
+   }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/GradleTabVersionWrapper.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/GradleTabVersionWrapper.java
new file mode 100644
index 0000000..533e078
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/GradleTabVersionWrapper.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.openapi.wrappers.ui;
+
+import org.gradle.openapi.external.ui.GradleTabVersion1;
+import org.gradle.gradleplugin.userinterface.swing.generic.tabs.GradleTab;
+
+import java.awt.Component;
+
+/**
+ * Wrapper to shield version changes in GradleTab from an external user of the gradle open API.
+ *
+ * @author mhunsicker
+ */
+public class GradleTabVersionWrapper implements GradleTab {
+    private GradleTabVersion1 gradleTabVersion1;
+
+    GradleTabVersionWrapper(GradleTabVersion1 gradleTabVersion1) {
+        this.gradleTabVersion1 = gradleTabVersion1;
+
+        //when future versions are added, doing the following and then delegating
+        //the new functions to GradleTabVersion2 keeps things compatible.
+        //if( gradleTabVersion1 instanceof GradleTabVersion2 )
+        //   gradleTabVersion2 = (GradleTabVersion2) gradleTabVersion1;
+    }
+
+    public String getName() {
+        return gradleTabVersion1.getName();
+    }
+
+    public Component createComponent() {
+        return gradleTabVersion1.createComponent();
+    }
+
+    public void aboutToShow() {
+        gradleTabVersion1.aboutToShow();
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/OutputObserverWrapper.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/OutputObserverWrapper.java
new file mode 100644
index 0000000..05a9b23
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/OutputObserverWrapper.java
@@ -0,0 +1,98 @@
+/*
+ * 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.openapi.wrappers.ui;
+
+import org.gradle.gradleplugin.userinterface.swing.generic.OutputUILord;
+import org.gradle.gradleplugin.foundation.request.Request;
+import org.gradle.gradleplugin.foundation.request.ExecutionRequest;
+import org.gradle.gradleplugin.foundation.request.RefreshTaskListRequest;
+import org.gradle.openapi.external.ui.OutputObserverVersion1;
+
+/**
+   Wrapper to shield version changes in OutputUILord.OutputObserver from an external user of the
+   gradle open API.
+
+   @author mhunsicker
+ */
+public class OutputObserverWrapper implements OutputUILord.OutputObserver
+{
+   private OutputObserverVersion1 outputObserverVersion1;
+
+   public OutputObserverWrapper( OutputObserverVersion1 outputObserverVersion1 )
+   {
+      this.outputObserverVersion1 = outputObserverVersion1;
+
+     //when future versions are added, doing the following and then delegating
+     //the new functions to OutputObserverVersion2 keeps things compatible.
+     //if( outputObserverVersion1 instanceof OutputObserverVersion2 )
+     //   outputObserverVersion2 = (OutputObserverVersion2) outputObserverVersion1;
+   }
+
+   /**
+    Notification that a request was added to the output. This means we've got some output
+    that is useful to display.
+
+    Note: this is slightly different from the GradlePluginLord.RequestObserver. While
+    these are directly related, this one really means that it has been added to the UI.
+    <!      Name            Description>
+
+    @param request the request that was added.
+    */
+   public void executionRequestAdded( ExecutionRequest request )
+   {
+      //Note: I don't like passing the request itself here, but a user might need an ID and trying to
+      //map the requests to something else when they can live for an indeterminate amount of time was too complex.
+      //I considered using a straight unique ID
+      outputObserverVersion1.executionRequestAdded( request.getRequestID(), request.getFullCommandLine(), request.getDisplayName(), request.forceOutputToBeShown() );
+   }
+
+   /**
+    Notification that a refresh task list request was added to the output. This means
+    we've got some output that is useful to display.
+
+    Note: this is slightly different from the GradlePluginLord.RequestObserver. While
+    these are directly related, this one really means that it has been added to the UI.
+    <!      Name            Description>
+
+    @param request the request that was added.
+    */
+   public void refreshRequestAdded( RefreshTaskListRequest request )
+   {
+      outputObserverVersion1.refreshRequestAdded( request.getRequestID(), request.forceOutputToBeShown() );
+   }
+
+
+   /**
+    Notification that an output tab was closed. You might want to know this if you want to close your
+    IDE output window when all tabs are closed
+
+    @param remainingTabCount the number of open tabs
+    */
+   public void outputTabClosed( Request request )
+   {
+      outputObserverVersion1.outputTabClosed( request.getRequestID() );
+   }
+
+   /**
+    Notification that execution of a request is complete
+
+    @param request       the original request
+    @param wasSuccessful  */
+   public void reportExecuteFinished( Request request, boolean wasSuccessful )
+   {
+      outputObserverVersion1.requestComplete( request.getRequestID(), wasSuccessful );
+   }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/OutputUILordWrapper.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/OutputUILordWrapper.java
new file mode 100644
index 0000000..d70470c
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/OutputUILordWrapper.java
@@ -0,0 +1,85 @@
+/*
+ * 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.openapi.wrappers.ui;
+
+import org.gradle.gradleplugin.userinterface.swing.generic.OutputUILord;
+import org.gradle.openapi.external.ui.OutputObserverVersion1;
+import org.gradle.openapi.external.ui.OutputUILordVersion1;
+
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+
+/**
+ * Wrapper to shield version changes in OutputUILord from an external user of gradle open API.
+ *
+ * @author mhunsicker
+  */
+public class OutputUILordWrapper implements OutputUILordVersion1 {
+
+    private OutputUILord outputUILord;
+    private Map<OutputObserverVersion1,OutputObserverWrapper> outputObserverMap = new HashMap<OutputObserverVersion1, OutputObserverWrapper>( );
+
+    public OutputUILordWrapper( OutputUILord outputUILord ) {
+        this.outputUILord = outputUILord;
+    }
+
+    public void setOutputTextFont(Font font) {
+        outputUILord.setOutputTextFont(font);
+    }
+
+    public Font getOutputTextFont() {
+        return outputUILord.getOutputTextFont();
+    }
+
+    public void addFileExtension(String extension, String lineNumberDelimiter) {
+        outputUILord.getFileLinkDefinitionLord().addFileExtension( extension, lineNumberDelimiter );
+    }
+
+    public void addPrefixedFileLink(String name, String prefix, String extension, String lineNumberDelimiter) {
+        outputUILord.getFileLinkDefinitionLord().addPrefixedFileLink( name, prefix, extension, lineNumberDelimiter );
+    }
+
+    public List<String> getFileExtensions() {
+        return outputUILord.getFileLinkDefinitionLord().getFileExtensions();
+    }
+
+    public void addOutputObserver( OutputObserverVersion1 observer )
+    {
+       //to avoid versioning conflicts, we have to wrap the observer. This means we have to track the observers.
+       OutputObserverWrapper wrapper = new OutputObserverWrapper( observer );
+       outputObserverMap.put( observer, wrapper );
+
+       outputUILord.addOutputObserver( wrapper, false );
+    }
+
+    public void removeOutputObserver( OutputObserverVersion1 observer )
+    {
+       OutputObserverWrapper wrapper = outputObserverMap.remove(  observer );
+       if( wrapper != null ) {
+          outputUILord.removeOutputObserver( wrapper );
+       }
+    }
+
+    /*
+    This re-executes the last execution command (ignores refresh commands).
+    This is potentially useful for IDEs to hook into (hotkey to execute last command).
+     */
+    public void reExecuteLastCommand()
+    {
+        outputUILord.reExecuteLastCommand();
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/SettingsNodeVersionWrapper.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/SettingsNodeVersionWrapper.java
new file mode 100644
index 0000000..bed33e9
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/SettingsNodeVersionWrapper.java
@@ -0,0 +1,149 @@
+/*
+ * 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.openapi.wrappers.ui;
+
+import org.gradle.gradleplugin.foundation.settings.SettingsNode;
+import org.gradle.openapi.external.ui.SettingsNodeVersion1;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Wrapper to shield version changes in SettingsNode from an external user of gradle open API.
+ *
+ * @author mhunsicker
+ */
+public class SettingsNodeVersionWrapper implements SettingsNode {
+    private SettingsNodeVersion1 settingsNodeVersion1;
+
+    SettingsNodeVersionWrapper(SettingsNodeVersion1 settingsNodeVersion1) {
+        this.settingsNodeVersion1 = settingsNodeVersion1;
+
+        //when future versions are added, doing the following and then delegating
+        //the new functions to SettingsNodeVersion2 keeps things compatible.
+        //if( settingsNodeVersion1 instanceof SettingsNodeVersion2 )
+        //   settingsNodeVersion2 = (SettingsNodeVersion2) settingsNodeVersion1;
+    }
+
+    public void setName(String name) {
+        settingsNodeVersion1.setName(name);
+    }
+
+    public String getName() {
+        return settingsNodeVersion1.getName();
+    }
+
+    public void setValue(String value) {
+        settingsNodeVersion1.setValue(value);
+    }
+
+    public String getValue() {
+        return settingsNodeVersion1.getValue();
+    }
+
+    public void setValueOfChild(String name, String value) {
+        settingsNodeVersion1.setValueOfChild(name, value);
+    }
+
+    public String getValueOfChild(String name, String defaultValue) {
+        return settingsNodeVersion1.getValueOfChild(name, defaultValue);
+    }
+
+    public int getValueOfChildAsInt(String name, int defaultValue) {
+        return settingsNodeVersion1.getValueOfChildAsInt(name, defaultValue);
+    }
+
+    public void setValueOfChildAsInt(String name, int value) {
+        settingsNodeVersion1.setValueOfChildAsInt(name, value);
+    }
+
+    public boolean getValueOfChildAsBoolean(String name, boolean defaultValue) {
+        return settingsNodeVersion1.getValueOfChildAsBoolean(name, defaultValue);
+    }
+
+    public void setValueOfChildAsBoolean(String name, boolean value) {
+        settingsNodeVersion1.setValueOfChildAsBoolean(name, value);
+    }
+
+    public long getValueOfChildAsLong(String name, long defaultValue) {
+        return settingsNodeVersion1.getValueOfChildAsLong(name, defaultValue);
+    }
+
+    public void setValueOfChildAsLong(String name, long value) {
+        settingsNodeVersion1.setValueOfChildAsLong(name, value);
+    }
+
+    public SettingsNode getChildNode(String name) {
+        SettingsNodeVersion1 childNode = settingsNodeVersion1.getChildNode(name);
+        if (childNode == null) {
+            return null;
+        }
+
+        return new SettingsNodeVersionWrapper(childNode);
+    }
+
+    public List<SettingsNode> getChildNodes() {
+        return convertNodes(settingsNodeVersion1.getChildNodes());
+    }
+
+    public List<SettingsNode> getChildNodes(String name) {
+        return convertNodes(settingsNodeVersion1.getChildNodes(name));
+    }
+
+    /*package*/
+
+    static List<SettingsNode> convertNodes(List<SettingsNodeVersion1> nodes) {
+        List<SettingsNode> settingsNodes = new ArrayList<SettingsNode>();
+
+        Iterator<SettingsNodeVersion1> iterator = nodes.iterator();
+        while (iterator.hasNext()) {
+            SettingsNodeVersion1 nodeVersion1 = iterator.next();
+            settingsNodes.add(new SettingsNodeVersionWrapper(nodeVersion1));
+        }
+
+        return settingsNodes;
+    }
+
+    public SettingsNode addChild(String name) {
+        return new SettingsNodeVersionWrapper(settingsNodeVersion1.addChild(name));
+    }
+
+    public SettingsNode addChildIfNotPresent(String name) {
+        return new SettingsNodeVersionWrapper(settingsNodeVersion1.addChildIfNotPresent(name));
+    }
+
+    public SettingsNode getNodeAtPath(String... pathPortions) {
+        SettingsNodeVersion1 node = settingsNodeVersion1.getNodeAtPath(pathPortions);
+        if (node == null) {
+            return null;
+        }
+        return new SettingsNodeVersionWrapper(node);
+    }
+
+    public void removeFromParent() {
+        settingsNodeVersion1.removeFromParent();
+    }
+
+    public void removeAllChildren() {
+        settingsNodeVersion1.removeAllChildren();
+    }
+
+    @Override
+    public String toString() {
+        return settingsNodeVersion1.toString();
+    }
+}
diff --git a/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/SinglePaneUIWrapper.java b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/SinglePaneUIWrapper.java
new file mode 100644
index 0000000..f1332a6
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/java/org/gradle/openapi/wrappers/ui/SinglePaneUIWrapper.java
@@ -0,0 +1,52 @@
+/*
+ * 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.openapi.wrappers.ui;
+
+import org.gradle.gradleplugin.userinterface.swing.generic.SinglePaneUIInstance;
+import org.gradle.openapi.external.ui.SinglePaneUIInteractionVersion1;
+import org.gradle.openapi.external.ui.SinglePaneUIVersion1;
+
+import javax.swing.JComponent;
+
+/**
+ This wraps a SinglePaneUIVersion1 for the purpose of being instantiated for
+ an external tool such an IDE plugin. It wraps several interfaces and uses
+ delegation in an effort to make this backward and forward compatible.
+ Most of the work is done in AbstractOpenAPIUIWrapper
+
+ @author mhunsicker
+  */
+public class SinglePaneUIWrapper extends AbstractOpenAPIUIWrapper<SinglePaneUIInstance> implements SinglePaneUIVersion1 {
+    public SinglePaneUIWrapper( SinglePaneUIInteractionVersion1 singlePaneUIArguments ) {
+
+       super( singlePaneUIArguments.instantiateSettings(), singlePaneUIArguments.instantiateAlternateUIInteraction() );
+
+       //the main thing this does in instantiate the SinglePaneUIInstance.
+       SinglePaneUIInstance singlePaneUIInstance = new SinglePaneUIInstance();
+       singlePaneUIInstance.initialize( settingsVersionWrapper, alternateUIInteractionVersionWrapper );
+       initialize( singlePaneUIInstance );
+    }
+
+   /**
+    Returns this panel as a Swing object suitable for inserting in your UI.
+
+    @return the main component
+    */
+   public JComponent getComponent()
+   {
+      return getGradleUI().getComponent();
+   }
+}
diff --git a/subprojects/gradle-ui/src/main/resources/org/gradle/foundation/ipc/gradle/execute-command-init-script.gradle b/subprojects/gradle-ui/src/main/resources/org/gradle/foundation/ipc/gradle/execute-command-init-script.gradle
new file mode 100644
index 0000000..6e88d7e
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/resources/org/gradle/foundation/ipc/gradle/execute-command-init-script.gradle
@@ -0,0 +1,5 @@
+//this starts up a client object that will send messages back to the server object that started us.
+//for this to work you must have set the port number via a system property.
+
+org.gradle.foundation.ipc.gradle.IPCUtilities.invokeExecuteGradleClient(gradle);
+
diff --git a/subprojects/gradle-ui/src/main/resources/org/gradle/foundation/ipc/gradle/refresh-tasks-init-script.gradle b/subprojects/gradle-ui/src/main/resources/org/gradle/foundation/ipc/gradle/refresh-tasks-init-script.gradle
new file mode 100644
index 0000000..e9a0c72
--- /dev/null
+++ b/subprojects/gradle-ui/src/main/resources/org/gradle/foundation/ipc/gradle/refresh-tasks-init-script.gradle
@@ -0,0 +1,5 @@
+//this starts up a client object that will send messages back to the server object that started us.
+//for this to work you must have set the port number via a system property.
+
+org.gradle.foundation.ipc.gradle.IPCUtilities.invokeTaskListGradleClient(gradle);
+
diff --git a/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/close-highlight.png b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/close-highlight.png
new file mode 100644
index 0000000..a118f6a
Binary files /dev/null and b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/close-highlight.png differ
diff --git a/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/close.png b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/close.png
new file mode 100644
index 0000000..f9956b5
Binary files /dev/null and b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/close.png differ
diff --git a/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/add.png b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/add.png
new file mode 100644
index 0000000..364f820
Binary files /dev/null and b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/add.png differ
diff --git a/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/blank.png b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/blank.png
new file mode 100644
index 0000000..fe1f70b
Binary files /dev/null and b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/blank.png differ
diff --git a/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/edit-filter.png b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/edit-filter.png
new file mode 100644
index 0000000..3a242b7
Binary files /dev/null and b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/edit-filter.png differ
diff --git a/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/edit.png b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/edit.png
new file mode 100644
index 0000000..428f548
Binary files /dev/null and b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/edit.png differ
diff --git a/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/execute.png b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/execute.png
new file mode 100644
index 0000000..6f4f622
Binary files /dev/null and b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/execute.png differ
diff --git a/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/export.png b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/export.png
new file mode 100644
index 0000000..b36052a
Binary files /dev/null and b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/export.png differ
diff --git a/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/filter.png b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/filter.png
new file mode 100644
index 0000000..ceda113
Binary files /dev/null and b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/filter.png differ
diff --git a/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/import.png b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/import.png
new file mode 100644
index 0000000..a4d5f69
Binary files /dev/null and b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/import.png differ
diff --git a/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/move-down.png b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/move-down.png
new file mode 100644
index 0000000..093008d
Binary files /dev/null and b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/move-down.png differ
diff --git a/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/move-up.png b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/move-up.png
new file mode 100644
index 0000000..39c61ab
Binary files /dev/null and b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/move-up.png differ
diff --git a/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/refresh.png b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/refresh.png
new file mode 100644
index 0000000..51d8a0c
Binary files /dev/null and b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/refresh.png differ
diff --git a/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/remove.png b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/remove.png
new file mode 100644
index 0000000..871ed68
Binary files /dev/null and b/subprojects/gradle-ui/src/main/resources/org/gradle/gradleplugin/userinterface/swing/generic/tabs/remove.png differ
diff --git a/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/BuildInformation.java b/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/BuildInformation.java
new file mode 100644
index 0000000..d3c2fb2
--- /dev/null
+++ b/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/BuildInformation.java
@@ -0,0 +1,147 @@
+/*
+ * 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.foundation;
+
+import org.gradle.api.Project;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Collections;
+
+/**
+ * This provides a simple way to hold onto and obtain projects and tasks for testing purposes.
+ *
+ * @author mhunsicker
+ */
+public class BuildInformation {
+    private List<ProjectView> projects;
+
+    public BuildInformation(Project rootProject) {
+        ProjectConverter buildExecuter = new ProjectConverter();
+        projects = buildExecuter.convertProjects(rootProject);
+    }
+
+    public List<ProjectView> getProjects() {
+        return Collections.unmodifiableList(projects);
+    }
+
+    /*
+       Call this to get the root level project with the given name.
+       @param  name       the name of the project.
+       @return the project if it exists.
+       @author mhunsicker
+    */
+
+    public ProjectView getRootLevelProject(String name) {
+        if (name == null) {
+            return null;
+        }
+
+        Iterator<ProjectView> iterator = projects.iterator();
+        while (iterator.hasNext()) {
+            ProjectView projectView = iterator.next();
+            if (name.equals(projectView.getName())) {
+                return projectView;
+            }
+        }
+
+        return null;
+    }
+
+    /*
+       Locates the project that has the specified full path
+       @param  fullProjectPath the full path of the sought project.
+       @return a project or null.
+       @author mhunsicker
+    */
+
+    public ProjectView getProjectFromFullPath(String fullProjectPath) {
+        if (projects.isEmpty()) {
+            return null;
+        }   //we haven't loaded yet
+
+        PathParserPortion pathParserPortion = new PathParserPortion(fullProjectPath);
+        if (pathParserPortion.getFirstPart() == null) {
+            return null;
+        }
+
+        ProjectView rootProject = getRootLevelProject(pathParserPortion.getFirstPart());
+        if (rootProject
+                == null)  //if the root wasn't specified, just go get the first item we have. root' isn't typically specified if a user gives us the path.
+        {
+            if (!projects.isEmpty()) {
+                rootProject = projects.get(0);
+            }
+        }
+
+        if (rootProject == null) {
+            return null;
+        }
+
+        if (!pathParserPortion.hasRemainder()) {
+            return rootProject;
+        }
+
+        return rootProject.getSubProjectFromFullPath(pathParserPortion.getRemainder());
+    }
+
+    /*
+       This gets the task based on the given full path. The root is a little
+       special.
+
+       @param  fullTaskName the full task name (root_project:sub_project:sub_sub_project:task.).
+       @return the task or null if not found.
+       @author mhunsicker
+    */
+
+    public TaskView getTaskFromFullPath(String fullTaskName) {
+        if (projects.isEmpty()) {
+            return null;
+        }   //we haven't loaded yet
+
+        PathParserPortion pathParserPortion = new PathParserPortion(fullTaskName);
+        if (pathParserPortion.getFirstPart() == null) {
+            return null;
+        }
+
+        String remainder = pathParserPortion.getRemainder();
+        ProjectView rootProject = null;
+        if (pathParserPortion.getFirstPart().equals(""))   //this means it starts with a colon, just get the root
+        {
+            if (!projects.isEmpty()) {
+                rootProject = projects.get(0);
+            }
+        } else {  //see if they did specify the root.
+            rootProject = getRootLevelProject(pathParserPortion.getFirstPart());
+            if (rootProject
+                    == null)  //if the root wasn't specified, just go get the first item we have. root' isn't typically specified if a user gives us the path.
+            {
+                if (!projects.isEmpty()) {
+                    rootProject = projects.get(0);
+                    remainder = fullTaskName;
+                }
+            }
+        }
+
+        //we found a match. We only want whatever's left
+        if (rootProject != null) {
+            return rootProject.getTaskFromFullPath(remainder);
+        }
+
+        //we don't have a full path.
+        return null;
+    }
+}
diff --git a/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/CommandLineParsingTest.java b/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/CommandLineParsingTest.java
new file mode 100644
index 0000000..c1b3eca
--- /dev/null
+++ b/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/CommandLineParsingTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.foundation;
+
+import junit.framework.TestCase;
+import org.gradle.initialization.DefaultCommandLine2StartParameterConverter;
+
+/**
+ * This tests aspects of command line parsing that the UI does.
+ *
+ * @author mhunsicker
+ */
+public class CommandLineParsingTest extends TestCase {
+    /**
+     * This verifies that the user can override the current log level on a command line. The issue here is that the UI
+     * defines a default log level that will always be used for all commands, but there are time when you want to
+     * override it. This is useful if someone enters something into the command line tab or just wants to make a
+     * favorite that always uses a specific log level. All they have to do is specify a log level on their command line
+     * and the won't append one. This verifies that the function that does the check if its already defined is working.
+     */
+    public void testOverridingLogLevel() {
+        //first try it with the log level at the end
+        String commandLine = ":build:something -" + DefaultCommandLine2StartParameterConverter.DEBUG;
+
+        CommandLineAssistant commandLineAssistant = new CommandLineAssistant();
+        String[] arguments = CommandLineAssistant.breakUpCommandLine(commandLine);
+        assertTrue(commandLineAssistant.hasLogLevelDefined(arguments));
+
+        //now try it with the log level in the middle
+        commandLine = ":build:something -" + DefaultCommandLine2StartParameterConverter.DEBUG + " :clean";
+        arguments = CommandLineAssistant.breakUpCommandLine(commandLine);
+        assertTrue(commandLineAssistant.hasLogLevelDefined(arguments));
+
+        //now try it with the log level at the beginning
+        commandLine = "-" + DefaultCommandLine2StartParameterConverter.DEBUG + " :clean";
+        arguments = CommandLineAssistant.breakUpCommandLine(commandLine);
+        assertTrue(commandLineAssistant.hasLogLevelDefined(arguments));
+
+        //now try it with 'info' instead of debug
+        commandLine = "-" + DefaultCommandLine2StartParameterConverter.INFO + " :clean";
+        arguments = CommandLineAssistant.breakUpCommandLine(commandLine);
+        assertTrue(commandLineAssistant.hasLogLevelDefined(arguments));
+
+        //lastly verify it doesn't inadvertantly detect a log level
+        commandLine = ":clean";
+        arguments = CommandLineAssistant.breakUpCommandLine(commandLine);
+        assertFalse(commandLineAssistant.hasLogLevelDefined(arguments));
+    }
+
+    /**
+     * This verifies that the user can override the current stack trace level on a command line. The issue here is that
+     * the UI defines a default stack trace level that will always be used for all commands, but there are time when you
+     * want to override it. This is useful if someone enters something into the command line tab or just wants to make a
+     * favorite that always uses a specific stack trace level. All they have to do is specify a stack trace level on
+     * their command line and the won't append one. This verifies that the function that does the check if its already
+     * defined is working.
+     */
+    public void testOverridingStackTraceLevel() {
+        //first try it with the stack trace level at the end
+        String commandLine = ":build:something -" + DefaultCommandLine2StartParameterConverter.FULL_STACKTRACE;
+
+        CommandLineAssistant commandLineAssistant = new CommandLineAssistant();
+        String[] arguments = CommandLineAssistant.breakUpCommandLine(commandLine);
+        assertTrue(commandLineAssistant.hasShowStacktraceDefined(arguments));
+
+        //now try it with the stack trace level in the middle
+        commandLine = ":build:something -" + DefaultCommandLine2StartParameterConverter.FULL_STACKTRACE + " :clean";
+        arguments = CommandLineAssistant.breakUpCommandLine(commandLine);
+        assertTrue(commandLineAssistant.hasShowStacktraceDefined(arguments));
+
+        //now try it with the stack trace level at the beginning
+        commandLine = "-" + DefaultCommandLine2StartParameterConverter.FULL_STACKTRACE + " :clean";
+        arguments = CommandLineAssistant.breakUpCommandLine(commandLine);
+        assertTrue(commandLineAssistant.hasShowStacktraceDefined(arguments));
+
+        //now try it with a different stack trace level
+        commandLine = "-" + DefaultCommandLine2StartParameterConverter.STACKTRACE + " :clean";
+        arguments = CommandLineAssistant.breakUpCommandLine(commandLine);
+        assertTrue(commandLineAssistant.hasShowStacktraceDefined(arguments));
+
+        //lastly verify it doesn't inadvertantly detect a stack trace
+        commandLine = ":clean";
+        arguments = CommandLineAssistant.breakUpCommandLine(commandLine);
+        assertFalse(commandLineAssistant.hasShowStacktraceDefined(arguments));
+    }
+}
diff --git a/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/DOM4JSettingsNodeTest.java b/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/DOM4JSettingsNodeTest.java
new file mode 100644
index 0000000..dd2b52b
--- /dev/null
+++ b/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/DOM4JSettingsNodeTest.java
@@ -0,0 +1,678 @@
+/*
+ * 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.foundation;
+
+import junit.framework.TestCase;
+import org.dom4j.Document;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+import org.gradle.gradleplugin.foundation.Dom4JUtility;
+import org.gradle.gradleplugin.foundation.settings.DOM4JSettingsNode;
+import org.gradle.gradleplugin.foundation.settings.SettingsNode;
+
+import java.util.List;
+
+/**
+ * Tests the DOM4JSettingsNode class.
+ *
+ * @author mhunsicker
+ */
+public class DOM4JSettingsNodeTest extends TestCase {
+    private DOM4JSettingsNode rootNode;
+    private Element rootElement;
+
+    private static final String SAMPLE_NAME_1 = "fred";
+    private static final String SAMPLE_NAME_2 = "jack";
+    private static final String SAMPLE_NAME_3 = "bob";
+
+    @Override
+    protected void setUp() throws Exception {
+        Document document = DocumentHelper.createDocument();
+        rootElement = document.addElement("root");
+        rootNode = new DOM4JSettingsNode(rootElement);
+    }
+
+    /**
+     * This tests that addChild actually works. We'll verify a child isn't present, then add one.
+     */
+    public void testAddChild() {
+        //make sure we have no child named 'fred' at the DOM4J level
+        assertNull(rootElement.element(SAMPLE_NAME_1));
+
+        //as such, we shouldn't have one at the DOM4JSettingsNode level either.
+        assertNull(rootNode.getChildNode(SAMPLE_NAME_1));
+
+        SettingsNode settingsNode = rootNode.addChild(SAMPLE_NAME_1);
+        assertNotNull(settingsNode);
+
+        //and now it should be present under both.
+        assertNotNull(Dom4JUtility.getChild(rootElement, DOM4JSettingsNode.TAG_NAME, DOM4JSettingsNode.NAME_ATTRIBUTE,
+                SAMPLE_NAME_1));
+        assertNotNull(rootNode.getChildNode(SAMPLE_NAME_1));
+    }
+
+    /**
+     * This tests that if you call addChildIfNotPresent, it actually adds a child that is not present. We'll verify a
+     * child isn't present, then add one.
+     */
+    public void testAddingChildrenIfNotPresent() {
+        //make sure we have no child named 'fred' at the DOM4J level
+        assertNull(rootElement.element(SAMPLE_NAME_1));
+
+        //as such, we shouldn't have one at the DOM4JSettingsNode level either.
+        assertNull(rootNode.getChildNode(SAMPLE_NAME_1));
+
+        SettingsNode settingsNode = rootNode.addChildIfNotPresent(SAMPLE_NAME_1);
+        assertNotNull(settingsNode);
+
+        //and now it should be present under both.
+        assertNotNull(Dom4JUtility.getChild(rootElement, DOM4JSettingsNode.TAG_NAME, DOM4JSettingsNode.NAME_ATTRIBUTE,
+                SAMPLE_NAME_1));
+        assertNotNull(rootNode.getChildNode(SAMPLE_NAME_1));
+    }
+
+    /**
+     * This tests that if you call addChildIfNotPresent, it won't add a child if one is already present. We'll verify a
+     * child is present, then call addChildIfNotPresent.
+     */
+    public void testAddingChildrenIfNotPresent2() {
+        rootNode.addChild(SAMPLE_NAME_1);
+
+        assertNotNull(Dom4JUtility.getChild(rootElement, DOM4JSettingsNode.TAG_NAME, DOM4JSettingsNode.NAME_ATTRIBUTE,
+                SAMPLE_NAME_1));
+        assertNotNull(rootNode.getChildNode(SAMPLE_NAME_1));
+
+        List list = Dom4JUtility.getChildren(rootElement, DOM4JSettingsNode.TAG_NAME, DOM4JSettingsNode.NAME_ATTRIBUTE,
+                SAMPLE_NAME_1);
+        assertEquals(1, list.size());  //there should only be the one element.
+
+        SettingsNode settingsNode = rootNode.addChildIfNotPresent(SAMPLE_NAME_1);
+        assertNotNull(settingsNode);
+
+        //it should still be present under both.
+        assertNotNull(Dom4JUtility.getChildren(rootElement, DOM4JSettingsNode.TAG_NAME,
+                DOM4JSettingsNode.NAME_ATTRIBUTE, SAMPLE_NAME_1));
+        assertNotNull(rootNode.getChildNode(SAMPLE_NAME_1));
+
+        //but make sure we didn't add an additional one. There should still be only one element.
+        list = Dom4JUtility.getChildren(rootElement, DOM4JSettingsNode.TAG_NAME, DOM4JSettingsNode.NAME_ATTRIBUTE,
+                SAMPLE_NAME_1);
+        assertEquals(1, list.size());
+    }
+
+    /**
+     * This tests that getChildNode works. We'll add some nodes and make sure they are found correctly. We'll also add a
+     * duplicate named node. It should never be returned because getChildNode only finds the first one. Lastly, we'll
+     * call getChildNode for a node that doesn't exist. It shouldn't be returned.
+     */
+    public void testGetChildNode() {
+        SettingsNode childNode1 = rootNode.addChild(SAMPLE_NAME_1);
+        SettingsNode childNode2 = rootNode.addChild(SAMPLE_NAME_2);
+        SettingsNode childNode3 = rootNode.addChild(SAMPLE_NAME_3);
+        SettingsNode childNode4 = rootNode.addChild(
+                SAMPLE_NAME_2);  //this is a duplicate and should never be found via getChildNode.
+
+        assertNotNull(Dom4JUtility.getChild(rootElement, DOM4JSettingsNode.TAG_NAME, DOM4JSettingsNode.NAME_ATTRIBUTE,
+                SAMPLE_NAME_1));
+        assertNotNull(Dom4JUtility.getChild(rootElement, DOM4JSettingsNode.TAG_NAME, DOM4JSettingsNode.NAME_ATTRIBUTE,
+                SAMPLE_NAME_2));
+        assertNotNull(Dom4JUtility.getChild(rootElement, DOM4JSettingsNode.TAG_NAME, DOM4JSettingsNode.NAME_ATTRIBUTE,
+                SAMPLE_NAME_3));
+        assertNotNull(rootNode.getChildNode(SAMPLE_NAME_1));
+        assertNotNull(rootNode.getChildNode(SAMPLE_NAME_2));
+        assertNotNull(rootNode.getChildNode(SAMPLE_NAME_3));
+
+        SettingsNode foundNode1 = rootNode.getChildNode(SAMPLE_NAME_1);
+        assertEquals(foundNode1, childNode1);
+
+        SettingsNode foundNode2 = rootNode.getChildNode(SAMPLE_NAME_2);
+        assertEquals(foundNode2, childNode2);
+
+        SettingsNode foundNode3 = rootNode.getChildNode(SAMPLE_NAME_3);
+        assertEquals(foundNode3, childNode3);
+
+        //look for the duplicated
+        SettingsNode foundNode2B = rootNode.getChildNode(SAMPLE_NAME_2);
+        assertEquals(foundNode2B, childNode2);  //should still be childNode2.
+
+        //this one shouldn't be found.
+        SettingsNode foundNode4 = rootNode.getChildNode("notpresent");
+        assertNull(foundNode4);
+    }
+
+    /**
+     * This tests getChildNodes. We'll make sure it returns all the child nodes. First we'll make sure it returns
+     * nothing when no children are present. Then we'll add some nodes, and make sure it returns them all. Lastly, we'll
+     * add some more again to make sure they're included in the list.
+     */
+    public void testGetChildNodes() {
+        //try it with no nodes
+        List<SettingsNode> children = rootNode.getChildNodes();
+        assertEquals(0, children.size());
+
+        //add some test nodes
+        SettingsNode childNode1 = rootNode.addChild(SAMPLE_NAME_1);
+        SettingsNode childNode2 = rootNode.addChild(SAMPLE_NAME_2);
+        SettingsNode childNode3 = rootNode.addChild(SAMPLE_NAME_3);
+        SettingsNode childNode4 = rootNode.addChild(SAMPLE_NAME_2);  //this is a duplicate of childNode2
+
+        //all should be returned
+        children = rootNode.getChildNodes();
+        TestUtility.assertListContents(children, childNode1, childNode2, childNode3, childNode4);
+
+        //add some more nodes
+        SettingsNode childNode5 = rootNode.addChild(SAMPLE_NAME_1);
+        SettingsNode childNode6 = rootNode.addChild(SAMPLE_NAME_2);
+
+        //again, all should be returned
+        children = rootNode.getChildNodes();
+        TestUtility.assertListContents(children, childNode1, childNode2, childNode3, childNode4, childNode5,
+                childNode6);
+    }
+
+    /**
+     * This tests getChildNodes that takes a sought node name. We'll make sure it returns all the child nodes. First
+     * we'll make sure it returns nothing when no children are present. Then we'll add some nodes and make sure it
+     * returns the correct ones. Lastly, we'll add some more again to make sure they're appropriately included in the
+     * list.
+     */
+    public void testGetChildNodesByName() {
+        //try it with no nodes
+        List<SettingsNode> children = rootNode.getChildNodes();
+        assertEquals(0, children.size());
+
+        //add some test nodes
+        SettingsNode childNode1 = rootNode.addChild(SAMPLE_NAME_1);
+        SettingsNode childNode2 = rootNode.addChild(SAMPLE_NAME_2);
+        SettingsNode childNode3 = rootNode.addChild(SAMPLE_NAME_3);
+        SettingsNode childNode4 = rootNode.addChild(SAMPLE_NAME_2);  //this is a duplicate of childNode2
+
+        //only 2 and 4 match
+        children = rootNode.getChildNodes(SAMPLE_NAME_2);
+        TestUtility.assertListContents(children, childNode2, childNode4);
+
+        //add some more nodes. Only 1 is a match
+        SettingsNode childNode5 = rootNode.addChild(SAMPLE_NAME_1);
+        SettingsNode childNode6 = rootNode.addChild(SAMPLE_NAME_2);
+
+        //node 6 should also returned now
+        children = rootNode.getChildNodes(SAMPLE_NAME_2);
+        TestUtility.assertListContents(children, childNode2, childNode4, childNode6);
+    }
+
+    /**
+     * This verifies that getName and setName work correctly. We call each and make sure the value is stored and
+     * retrieved correctly.
+     */
+    public void testName() {
+        SettingsNode childNode1 = rootNode.addChild(SAMPLE_NAME_1);
+
+        assertEquals(SAMPLE_NAME_1, childNode1.getName());
+
+        childNode1.setName(SAMPLE_NAME_3);
+
+        assertEquals(SAMPLE_NAME_3, childNode1.getName());
+    }
+
+    /**
+     * This verifies that getValue and setValue work correctly. We call each and make sure the value is stored and
+     * retrieved correctly.
+     */
+    public void testValue() {
+        SettingsNode childNode1 = rootNode.addChild(SAMPLE_NAME_1);
+
+        assertNull(childNode1.getValue());
+
+        childNode1.setValue("myvalue");
+
+        assertEquals("myvalue", childNode1.getValue());
+
+        childNode1.setValue("someothervalue");
+
+        assertEquals("someothervalue", childNode1.getValue());
+    }
+
+    /**
+     * This tests that removeFromParent works. We'll add some sample nodes and then delete them one by one and verify
+     * they're really removed. We'll also add two with the same name just to make sure that the wrong one isn't
+     * deleted.
+     */
+    public void testRemoveFromParent() {
+        //make sure we have no children first
+        List<SettingsNode> children = rootNode.getChildNodes();
+        assertEquals(0, children.size());
+
+        //add some sample nodes.
+        SettingsNode childNode1 = rootNode.addChild(SAMPLE_NAME_1);
+        SettingsNode childNode2 = rootNode.addChild(SAMPLE_NAME_2);
+        SettingsNode childNode3 = rootNode.addChild(SAMPLE_NAME_3);
+        SettingsNode childNode4 = rootNode.addChild(SAMPLE_NAME_2);  //notice this has the same name as childNode2
+
+        //make sure they're all present as expected
+        children = rootNode.getChildNodes();
+        TestUtility.assertListContents(children, childNode1, childNode2, childNode3, childNode4);
+
+        //now give the two with the same names different values
+        childNode2.setValue("first");
+        childNode4.setValue("second");
+
+        //delete the 'first' one with SAMPLE_NAME_2
+        childNode2.removeFromParent();
+
+        //make sure its not longer present
+        children = rootNode.getChildNodes();
+        TestUtility.assertListContents(children, childNode1, childNode3, childNode4);
+
+        //make sure that we didn't delete the wrong node with SAMPLE_NAME_2. The 'second' one should still be present.
+        SettingsNode foundNode = rootNode.getChildNode(SAMPLE_NAME_2);
+        assertEquals("second", foundNode.getValue());
+
+        //delete another one and make sure its no longer present
+        childNode3.removeFromParent();
+        children = rootNode.getChildNodes();
+        TestUtility.assertListContents(children, childNode1, childNode4);
+
+        //delete yet another one and make sure its no longer present
+        childNode1.removeFromParent();
+        children = rootNode.getChildNodes();
+        TestUtility.assertListContents(children, childNode4);
+
+        //delete the last one and make sure that the children are now empty.
+        childNode4.removeFromParent();
+        children = rootNode.getChildNodes();
+        assertEquals(0, children.size());
+
+        //just for grins, try to delete one that's already deleted. It shouldn't do anything (like crash)
+        childNode3.removeFromParent();
+        children = rootNode.getChildNodes();
+        assertEquals(0, children.size());
+
+        //just to be paranoid, verify they're gone using getChildNode. Each of these should return nothing.
+        assertNull(rootNode.getChildNode(SAMPLE_NAME_1));
+        assertNull(rootNode.getChildNode(SAMPLE_NAME_2));
+        assertNull(rootNode.getChildNode(SAMPLE_NAME_3));
+
+        //make sure the nodes are gone from a DOM4J standpoint.
+        assertEquals(0, rootElement.elements().size());
+    }
+
+    /**
+     * This tests removeAllChildren. We'll add some nodes and call removeAllChildren and then make sure they're no
+     * longer present.
+     */
+    public void testRemoveAllChildren() {
+        //make sure we have no children first
+        List<SettingsNode> children = rootNode.getChildNodes();
+        assertEquals(0, children.size());
+
+        //add some sample nodes.
+        SettingsNode childNode1 = rootNode.addChild(SAMPLE_NAME_1);
+        SettingsNode childNode2 = rootNode.addChild(SAMPLE_NAME_2);
+        SettingsNode childNode3 = rootNode.addChild(SAMPLE_NAME_3);
+        SettingsNode childNode4 = rootNode.addChild(SAMPLE_NAME_2);
+
+        //make sure they're all present as expected
+        children = rootNode.getChildNodes();
+        TestUtility.assertListContents(children, childNode1, childNode2, childNode3, childNode4);
+
+        //now remove all children
+        rootNode.removeAllChildren();
+
+        //and make sure they're gone
+        children = rootNode.getChildNodes();
+        assertEquals(0, children.size());
+
+        //just to be paranoid, verify they're gone using getChildNode. Each of these should return nothing.
+        assertNull(rootNode.getChildNode(SAMPLE_NAME_1));
+        assertNull(rootNode.getChildNode(SAMPLE_NAME_2));
+        assertNull(rootNode.getChildNode(SAMPLE_NAME_3));
+
+        //make sure the nodes are gone from a DOM4J standpoint.
+        assertEquals(0, rootElement.elements().size());
+    }
+
+    /**
+     * This tests getNodeAtPath. We want to make sure it locates nodes via path from several locations.
+     */
+    public void testGetNodeAtPath() {
+        //add some sample nodes. I'm indenting these to better show the structure I'm making
+        SettingsNode childNode1 = rootNode.addChild(SAMPLE_NAME_1);
+        SettingsNode grandChildNodeA1 = childNode1.addChild("sub_nodeA1");
+        SettingsNode greatGrandChildNodeA11 = grandChildNodeA1.addChild("sub_sub_nodeA11");
+        SettingsNode greatGrandChildNodeA12 = grandChildNodeA1.addChild("sub_sub_nodeA12");
+        SettingsNode grandChildNodeA2 = childNode1.addChild("sub_nodeA2");
+        SettingsNode greatGrandChildNodeA21 = grandChildNodeA2.addChild("sub_sub_nodeA21");
+        SettingsNode greatGrandChildNodeA22 = grandChildNodeA2.addChild("sub_sub_nodeA22");
+
+        SettingsNode childNode2 = rootNode.addChild(SAMPLE_NAME_2);
+        SettingsNode grandChildNodeB1 = childNode2.addChild("sub_nodeB1");
+        SettingsNode greatGrandChildNodeB11 = grandChildNodeB1.addChild("sub_sub_nodeB11");
+        SettingsNode greatGrandChildNodeB12 = grandChildNodeB1.addChild("sub_sub_nodeB12");
+        SettingsNode grandChildNodeB2 = childNode2.addChild("sub_nodeB2");
+
+        SettingsNode childNode3 = rootNode.addChild(SAMPLE_NAME_3);
+
+        //now start searching for some nodes
+        SettingsNode foundNode1 = rootNode.getNodeAtPath(SAMPLE_NAME_1, "sub_nodeA2", "sub_sub_nodeA22");
+        assertEquals(greatGrandChildNodeA22, foundNode1);
+
+        //try searching from something other than the root. It's still relative to the starting node.
+        SettingsNode foundNode2 = childNode2.getNodeAtPath("sub_nodeB1", "sub_sub_nodeB11");
+        assertEquals(greatGrandChildNodeB11, foundNode2);
+
+        //try searching for something that doesn't exist at the first level of the sought path.
+        SettingsNode foundNode3 = rootNode.getNodeAtPath("nonexistent", "sub_nodeA2", "sub_sub_nodeA22");
+        assertNull(foundNode3);
+
+        //try searching for something that doesn't exist at the second level of the sought path
+        SettingsNode foundNode4 = rootNode.getNodeAtPath(SAMPLE_NAME_3, "sub_nodeA2", "sub_sub_nodeA22");
+        assertNull(foundNode4);
+
+        //try searching for something that doesn't exist at the last level of the sought path
+        SettingsNode foundNode5 = rootNode.getNodeAtPath(SAMPLE_NAME_2, "sub_nodeB2", "sub_sub_nodeB22");
+        assertNull(foundNode5);
+
+        //try searching for a node using a single path
+        SettingsNode foundNode6 = rootNode.getNodeAtPath(SAMPLE_NAME_3);
+        assertEquals(childNode3, foundNode6);
+    }
+
+    /**
+     * This tests setValueOfChild. We're going to make sure the value is set as well as that repeated calls to this
+     * don't add child nodes.
+     */
+    public void testSetValueOfChild() {
+        //make sure we have no children first
+        List<SettingsNode> children = rootNode.getChildNodes();
+        assertEquals(0, children.size());
+
+        //set the value of a child
+        rootNode.setValueOfChild(SAMPLE_NAME_1, "myValue");
+
+        //verify it was set properly
+        SettingsNode childNode1 = rootNode.getChildNode(SAMPLE_NAME_1);
+        assertNotNull(childNode1);
+        assertEquals("myValue", childNode1.getValue());
+
+        //make sure there's only 1 child.
+        children = rootNode.getChildNodes();
+        assertEquals(1, children.size());
+
+        //set the value again. This should set the value and NOT add an additional node
+        rootNode.setValueOfChild(SAMPLE_NAME_1, "newvalue");
+        childNode1 = rootNode.getChildNode(SAMPLE_NAME_1);
+        assertNotNull(childNode1);
+        assertEquals("newvalue", childNode1.getValue());
+
+        //make sure there's still only 1 child.
+        children = rootNode.getChildNodes();
+        assertEquals(1, children.size());
+    }
+
+    /**
+     * This tests getValueOfChild. We're interested in that it gets the child value correctly, but also that it returns
+     * the default value if either the node or its value isn't present.
+     */
+    public void testGetValueOfChild() {
+        //make sure we have no children first
+        List<SettingsNode> children = rootNode.getChildNodes();
+        assertEquals(0, children.size());
+
+        //set the value of a child
+        rootNode.setValueOfChild(SAMPLE_NAME_1, "myValue");
+        rootNode.setValueOfChild(SAMPLE_NAME_2, "otherValue");
+        rootNode.setValueOfChild(SAMPLE_NAME_3, "lastValue");
+
+        assertEquals("otherValue", rootNode.getValueOfChild(SAMPLE_NAME_2, "default2"));
+        assertEquals("myValue", rootNode.getValueOfChild(SAMPLE_NAME_1, "default1"));
+        assertEquals("lastValue", rootNode.getValueOfChild(SAMPLE_NAME_3, "default3"));
+
+        //now try it with one that doesn't exist. We should get the default value
+        assertEquals("default4", rootNode.getValueOfChild("nonexistent", "default4"));
+
+        //now add a single node but don't give it a value (which means its null)
+        SettingsNode lastNode = rootNode.addChild("valueless");
+        assertNull(lastNode.getValue());
+
+        //now try to get its value. We should get the default value
+        assertEquals("default5", rootNode.getValueOfChild("valueless", "default5"));
+    }
+
+    /**
+     * This tests setValueOfChildAsInt. We're going to make sure the value is set as well as that repeated calls to this
+     * don't add child nodes.
+     */
+    public void testSetValueOfChildAsInt() {
+        //make sure we have no children first
+        List<SettingsNode> children = rootNode.getChildNodes();
+        assertEquals(0, children.size());
+
+        //set the value of a child
+        rootNode.setValueOfChildAsInt(SAMPLE_NAME_1, 8);
+
+        //verify it was set properly
+        SettingsNode childNode1 = rootNode.getChildNode(SAMPLE_NAME_1);
+        assertNotNull(childNode1);
+        assertEquals("8", childNode1.getValue());
+
+        //make sure there's only 1 child.
+        children = rootNode.getChildNodes();
+        assertEquals(1, children.size());
+
+        //set the value again. This should set the value and NOT add an additional node
+        rootNode.setValueOfChildAsInt(SAMPLE_NAME_1, 39);
+        childNode1 = rootNode.getChildNode(SAMPLE_NAME_1);
+        assertNotNull(childNode1);
+        assertEquals("39", childNode1.getValue());
+
+        //make sure there's still only 1 child.
+        children = rootNode.getChildNodes();
+        assertEquals(1, children.size());
+    }
+
+    /**
+     * This tests getValueOfChildAsInt. We're interested in that it gets the child value correctly, but also that it
+     * returns the default value if either the node, its value isn't present, or the valid isn't illegal as an int.
+     */
+    public void testGetValueOfChildAsInt() {
+        //make sure we have no children first
+        List<SettingsNode> children = rootNode.getChildNodes();
+        assertEquals(0, children.size());
+
+        //set the value of a child
+        rootNode.setValueOfChild(SAMPLE_NAME_1, "1");
+        rootNode.setValueOfChild(SAMPLE_NAME_2, "84");
+        rootNode.setValueOfChild(SAMPLE_NAME_3, "0983");
+
+        assertEquals(84, rootNode.getValueOfChildAsInt(SAMPLE_NAME_2, 55));
+        assertEquals(1, rootNode.getValueOfChildAsInt(SAMPLE_NAME_1, 66));
+        assertEquals(983, rootNode.getValueOfChildAsInt(SAMPLE_NAME_3, 77));
+
+        //now try it with one that doesn't exist. We should get the default value
+        assertEquals(44, rootNode.getValueOfChildAsInt("nonexistent", 44));
+
+        //now add a single node but don't give it a value (which means its null)
+        SettingsNode valuelessNode = rootNode.addChild("valueless");
+        assertNull(valuelessNode.getValue());
+
+        //now try to get its value. We should get the default value
+        assertEquals(17, rootNode.getValueOfChildAsInt("valueless", 17));
+
+        //now add a single node that has an illegal value
+        SettingsNode illegalNode = rootNode.addChild("illegal");
+        illegalNode.setValue("abcdefg");
+
+        //now try to get its value. We should get the default value
+        assertEquals(333, rootNode.getValueOfChildAsInt("illegal", 333));
+    }
+
+    /**
+     * This tests setValueOfChildAsLong. We're going to make sure the value is set as well as that repeated calls to
+     * this don't add child nodes.
+     */
+    public void testSetValueOfChildAsLong() {
+        //make sure we have no children first
+        List<SettingsNode> children = rootNode.getChildNodes();
+        assertEquals(0, children.size());
+
+        //set the value of a child
+        rootNode.setValueOfChildAsLong(SAMPLE_NAME_1, 8000000000l);
+
+        //verify it was set properly
+        SettingsNode childNode1 = rootNode.getChildNode(SAMPLE_NAME_1);
+        assertNotNull(childNode1);
+        assertEquals("8000000000", childNode1.getValue());
+
+        //make sure there's only 1 child.
+        children = rootNode.getChildNodes();
+        assertEquals(1, children.size());
+
+        //set the value again. This should set the value and NOT add an additional node
+        rootNode.setValueOfChildAsLong(SAMPLE_NAME_1, 3900000000l);
+        childNode1 = rootNode.getChildNode(SAMPLE_NAME_1);
+        assertNotNull(childNode1);
+        assertEquals("3900000000", childNode1.getValue());
+
+        //make sure there's still only 1 child.
+        children = rootNode.getChildNodes();
+        assertEquals(1, children.size());
+    }
+
+    /**
+     * This tests getValueOfChildAsLong. We're interested in that it gets the child value correctly, but also that it
+     * returns the default value if either the node, its value isn't present, or the valid isn't illegal as an long.
+     */
+    public void testGetValueOfChildAsLong() {
+        //make sure we have no children first
+        List<SettingsNode> children = rootNode.getChildNodes();
+        assertEquals(0, children.size());
+
+        //set the value of a child
+        rootNode.setValueOfChild(SAMPLE_NAME_1, "1000000000");
+        rootNode.setValueOfChild(SAMPLE_NAME_2, "8400000000");
+        rootNode.setValueOfChild(SAMPLE_NAME_3, "0983000000");
+
+        assertEquals(8400000000l, rootNode.getValueOfChildAsLong(SAMPLE_NAME_2, 5500000000l));
+        assertEquals(1000000000l, rootNode.getValueOfChildAsLong(SAMPLE_NAME_1, 6600000000l));
+        assertEquals(983000000l, rootNode.getValueOfChildAsLong(SAMPLE_NAME_3, 7700000000l));
+
+        //now try it with one that doesn't exist. We should get the default value
+        assertEquals(4400000000l, rootNode.getValueOfChildAsLong("nonexistent", 4400000000l));
+
+        //now add a single node but don't give it a value (which means its null)
+        SettingsNode valuelessNode = rootNode.addChild("valueless");
+        assertNull(valuelessNode.getValue());
+
+        //now try to get its value. We should get the default value
+        assertEquals(1700000000l, rootNode.getValueOfChildAsLong("valueless", 1700000000l));
+
+        //now add a single node that has an illegal value
+        SettingsNode illegalNode = rootNode.addChild("illegal");
+        illegalNode.setValue("abcdefg");
+
+        //now try to get its value. We should get the default value
+        assertEquals(33300000000l, rootNode.getValueOfChildAsLong("illegal", 33300000000l));
+    }
+
+    /**
+     * This tests setValueOfChildAsBoolean. We're going to make sure the value is set as well as that repeated calls to
+     * this don't add child nodes.
+     */
+    public void testSetValueOfChildAsBoolean() {
+        //make sure we have no children first
+        List<SettingsNode> children = rootNode.getChildNodes();
+        assertEquals(0, children.size());
+
+        //set the value of a child
+        rootNode.setValueOfChildAsBoolean(SAMPLE_NAME_1, true);
+
+        //verify it was set properly
+        SettingsNode childNode1 = rootNode.getChildNode(SAMPLE_NAME_1);
+        assertNotNull(childNode1);
+        assertEquals("true", childNode1.getValue());
+
+        //make sure there's only 1 child.
+        children = rootNode.getChildNodes();
+        assertEquals(1, children.size());
+
+        //set the value again. This should set the value and NOT add an additional node
+        rootNode.setValueOfChildAsBoolean(SAMPLE_NAME_1, false);
+        childNode1 = rootNode.getChildNode(SAMPLE_NAME_1);
+        assertNotNull(childNode1);
+        assertEquals("false", childNode1.getValue());
+
+        //make sure there's still only 1 child.
+        children = rootNode.getChildNodes();
+        assertEquals(1, children.size());
+
+        //set the value again to the same value. Again, this should set the value and NOT add an additional node
+        rootNode.setValueOfChildAsBoolean(SAMPLE_NAME_1, false);
+        childNode1 = rootNode.getChildNode(SAMPLE_NAME_1);
+        assertNotNull(childNode1);
+        assertEquals("false", childNode1.getValue());
+
+        //make sure there's still only 1 child.
+        children = rootNode.getChildNodes();
+        assertEquals(1, children.size());
+    }
+
+    /**
+     * This tests getValueOfChildAsBoolean. We're interested in that it gets the child value correctly, but also that it
+     * returns the default value if either the node, its value isn't present, or the valid isn't illegal as a boolean.
+     * Because we're dealing with just true and false, I'll test several of these twice; once with a default of true and
+     * again with a default of false. This is just to be paranoid.
+     */
+    public void testGetValueOfChildAsBoolean() {
+        //make sure we have no children first
+        List<SettingsNode> children = rootNode.getChildNodes();
+        assertEquals(0, children.size());
+
+        //set the value of a child
+        rootNode.setValueOfChild(SAMPLE_NAME_1, "true");
+        rootNode.setValueOfChild(SAMPLE_NAME_2, "false");
+        rootNode.setValueOfChild(SAMPLE_NAME_3, "true");
+
+        assertEquals(false, rootNode.getValueOfChildAsBoolean(SAMPLE_NAME_2, true));
+        assertEquals(true, rootNode.getValueOfChildAsBoolean(SAMPLE_NAME_1, false));
+        assertEquals(true, rootNode.getValueOfChildAsBoolean(SAMPLE_NAME_3, false));
+
+        //now try it with one that doesn't exist. We should get the default value
+        assertEquals(true, rootNode.getValueOfChildAsBoolean("nonexistent", true));
+
+        //see header
+        assertEquals(false, rootNode.getValueOfChildAsBoolean("nonexistent2", false));
+
+        //now add a single node but don't give it a value (which means its null)
+        SettingsNode valuelessNode = rootNode.addChild("valueless");
+        assertNull(valuelessNode.getValue());
+
+        //now try to get its value. We should get the default value
+        assertEquals(true, rootNode.getValueOfChildAsBoolean("valueless", true));
+
+        //see header
+        assertEquals(false, rootNode.getValueOfChildAsBoolean("valueless", false));
+
+        //now add a single node that has an illegal value
+        SettingsNode illegalNode = rootNode.addChild("illegal");
+        illegalNode.setValue("abcdefg");
+
+        //now try to get its value. We should get the default value
+        assertEquals(true, rootNode.getValueOfChildAsBoolean("illegal", true));
+
+        //see header
+        assertEquals(false, rootNode.getValueOfChildAsBoolean("illegal", false));
+    }
+}
diff --git a/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/FavoritesTest.java b/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/FavoritesTest.java
new file mode 100644
index 0000000..0f48f71
--- /dev/null
+++ b/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/FavoritesTest.java
@@ -0,0 +1,766 @@
+/*
+ * 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.foundation;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.gradleplugin.foundation.favorites.FavoriteTask;
+import org.gradle.gradleplugin.foundation.favorites.FavoritesEditor;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests aspects of favorite tasks and the favorites editor.
+ *
+ * @author mhunsicker
+ */
+public class FavoritesTest extends TestCase {
+    private BuildInformation buildInformation;
+
+    private ProjectView myRootProject;
+
+    private ProjectView mySubProject1;
+    private TaskView mySubProject1Comple;
+    private TaskView mySubProject1Lib;
+    private TaskView mySubProject1Doc;
+
+    private ProjectView mySubSubProject;
+    private TaskView mySubSubProjectCompile;
+    private TaskView mySubSubProjectLib;
+    private TaskView mySubSubProjectDoc;
+
+    private ProjectView mySubProject2;
+    private TaskView mySubProject2Lib;
+    private TaskView mySubProject2doc;
+    private TaskView mySubProject2Compile;
+    private JUnit4Mockery context;
+
+    @Before
+    public void setUp() throws Exception {
+        context = new JUnit4Mockery();
+
+        Task subsubCompileTask = TestUtility.createTask(context, "compile", "compile description");
+        Task subsubLibTask = TestUtility.createTask(context, "lib", "lib description");
+        Task subsubDocTask = TestUtility.createTask(context, "doc", "doc description");
+        Project subsubProject = TestUtility.createMockProject(context, "mysubsubproject", "filepath3", 2, null,
+                new Task[]{subsubCompileTask, subsubLibTask, subsubDocTask}, null, (Project[]) null);
+
+        Task subCompileTask1 = TestUtility.createTask(context, "compile", "compile description");
+        Task subLibTask1 = TestUtility.createTask(context, "lib", "lib description");
+        Task subDocTask1 = TestUtility.createTask(context, "doc", "doc description");
+        Project subProject1 = TestUtility.createMockProject(context, "mysubproject1", "filepath2a", 1,
+                new Project[]{subsubProject}, new Task[]{subCompileTask1, subLibTask1, subDocTask1}, null,
+                (Project[]) null);
+
+        Task subCompileTask2 = TestUtility.createTask(context, "compile", "compile description");
+        Task subLibTask2 = TestUtility.createTask(context, "lib", "lib description");
+        Task subDocTask2 = TestUtility.createTask(context, "doc", "doc description");
+        Project subProject2 = TestUtility.createMockProject(context, "mysubproject2", "filepath2b", 1, null,
+                new Task[]{subCompileTask2, subLibTask2, subDocTask2}, null, (Project[]) null);
+
+        Project rootProject = TestUtility.createMockProject(context, "myrootproject", "filepath1", 0,
+                new Project[]{subProject1, subProject2}, null, null, (Project[]) null);
+
+        buildInformation = new BuildInformation(rootProject);
+
+        //now get the converted objects to simplify our matching
+        myRootProject = buildInformation.getProjectFromFullPath("myrootproject");
+        Assert.assertNotNull(myRootProject);
+        mySubProject1 = buildInformation.getProjectFromFullPath("myrootproject:mysubproject1");
+        Assert.assertNotNull(mySubProject1);
+        mySubProject1Comple = buildInformation.getTaskFromFullPath("myrootproject:mysubproject1:compile");
+        Assert.assertNotNull(mySubProject1Comple);
+        mySubProject1Lib = buildInformation.getTaskFromFullPath("myrootproject:mysubproject1:lib");
+        Assert.assertNotNull(mySubProject1Lib);
+        mySubProject1Doc = buildInformation.getTaskFromFullPath("myrootproject:mysubproject1:doc");
+        Assert.assertNotNull(mySubProject1Doc);
+        mySubSubProject = buildInformation.getProjectFromFullPath("myrootproject:mysubproject1:mysubsubproject");
+        Assert.assertNotNull(mySubSubProject);
+        mySubSubProjectCompile = buildInformation.getTaskFromFullPath(
+                "myrootproject:mysubproject1:mysubsubproject:compile");
+        Assert.assertNotNull(mySubSubProjectCompile);
+        mySubSubProjectLib = buildInformation.getTaskFromFullPath("myrootproject:mysubproject1:mysubsubproject:lib");
+        Assert.assertNotNull(mySubSubProjectLib);
+        mySubSubProjectDoc = buildInformation.getTaskFromFullPath("myrootproject:mysubproject1:mysubsubproject:doc");
+        Assert.assertNotNull(mySubSubProjectDoc);
+        mySubProject2 = buildInformation.getProjectFromFullPath("myrootproject:mysubproject2");
+        Assert.assertNotNull(mySubProject2);
+        mySubProject2Compile = buildInformation.getTaskFromFullPath("myrootproject:mysubproject2:compile");
+        Assert.assertNotNull(mySubProject2Compile);
+        mySubProject2Lib = buildInformation.getTaskFromFullPath("myrootproject:mysubproject2:lib");
+        Assert.assertNotNull(mySubProject2Lib);
+        mySubProject2doc = buildInformation.getTaskFromFullPath("myrootproject:mysubproject2:doc");
+        Assert.assertNotNull(mySubProject2doc);
+    }
+
+    /**
+     * This tests adding a favorite task. We'll verify that we get notified and that the task is added properly.
+     */
+    @Test
+    public void testAddingFavorites() {
+        FavoritesEditor editor = new FavoritesEditor();
+
+        Assert.assertTrue(editor.getFavoriteTasks().isEmpty());
+
+        final FavoritesEditor.FavoriteTasksObserver observer = context.mock(
+                FavoritesEditor.FavoriteTasksObserver.class);
+        context.checking(new Expectations() {{
+            one(observer).favoritesChanged();
+        }});
+
+        editor.addFavoriteTasksObserver(observer, false);
+
+        editor.addFavorite(mySubProject1Comple, true);
+
+        context.assertIsSatisfied();
+
+        //make sure it was added properly
+        FavoriteTask favoriteTask = editor.getFavoriteTasks().get(0);
+        Assert.assertEquals("mysubproject1:compile", favoriteTask.getDisplayName());
+        Assert.assertEquals("mysubproject1:compile", favoriteTask.getFullCommandLine());
+        Assert.assertTrue(favoriteTask.alwaysShowOutput());
+
+        //now add another one, this time set alwaysShowOutput to false
+        context.checking(new Expectations() {{
+            one(observer)
+                    .favoritesChanged(); //reset our favorites changed notification so we know we're getting another one (I don't want to just verify that we got 2 messages. I want to make sure they arrived at the correct time.
+        }});
+
+        editor.addFavorite(mySubSubProjectDoc, false);
+
+        context.assertIsSatisfied();
+
+        //make sure it was added properly
+        favoriteTask = editor.getFavoriteTasks().get(1);
+        Assert.assertEquals("mysubproject1:mysubsubproject:doc", favoriteTask.getDisplayName());
+        Assert.assertEquals("mysubproject1:mysubsubproject:doc", favoriteTask.getFullCommandLine());
+        Assert.assertFalse(favoriteTask.alwaysShowOutput());
+    }
+
+    /**
+     * Tests removing a favorite. We add one, make sure its right, then remove it and make sure that it goes away as
+     * well as that we're notified.
+     */
+    @Test
+    public void testRemovingFavorites() {
+        FavoritesEditor editor = new FavoritesEditor();
+
+        Assert.assertTrue(editor.getFavoriteTasks().isEmpty());
+
+        editor.addFavorite(mySubProject1Comple, true);
+
+        //make sure it was added properly
+        FavoriteTask favoriteTask = editor.getFavoriteTasks().get(0);
+        Assert.assertEquals("mysubproject1:compile", favoriteTask.getDisplayName());
+        Assert.assertEquals("mysubproject1:compile", favoriteTask.getFullCommandLine());
+        Assert.assertTrue(favoriteTask.alwaysShowOutput());
+
+        //create an observer so we can make sure we're notified of the deletion.
+        final FavoritesEditor.FavoriteTasksObserver observer = context.mock(
+                FavoritesEditor.FavoriteTasksObserver.class);
+        context.checking(new Expectations() {{
+            one(observer).favoritesChanged();
+        }});
+
+        editor.addFavoriteTasksObserver(observer, false);
+
+        //now remove the task
+        List<FavoriteTask> tasks = new ArrayList<FavoriteTask>();
+        tasks.add(favoriteTask);
+        editor.removeFavorites(tasks);
+
+        //make sure we were notified
+        context.assertIsSatisfied();
+
+        //there shouldn't be any more favorites.
+        Assert.assertTrue(editor.getFavoriteTasks().isEmpty());
+    }
+
+    /**
+     * This tests removing a favorite that isn't in our favorites editor. We just want to make sure we're not notified
+     * of a change and that this doesn't blow up.
+     */
+    public void testRemovingNonExistantFavorite() {
+        //remove a task that doesn't exist. We should NOT be notified and it should not blow up
+
+        FavoritesEditor otherEditor = new FavoritesEditor();
+
+        Assert.assertTrue(otherEditor.getFavoriteTasks().isEmpty());
+
+        otherEditor.addFavorite(mySubProject1Comple, true);
+
+        FavoriteTask favoriteTask = otherEditor.getFavoriteTasks().get(0);
+
+        //create another editor. This is the one we're really interested in.
+        FavoritesEditor interestedEditor = new FavoritesEditor();
+
+        //create an observer so we can make sure we're NOT notified of the deletion. We won't assign it any expectations.
+        final FavoritesEditor.FavoriteTasksObserver observer = context.mock(
+                FavoritesEditor.FavoriteTasksObserver.class);
+
+        interestedEditor.addFavoriteTasksObserver(observer, false);
+
+        //now remove the task
+        List<FavoriteTask> tasks = new ArrayList<FavoriteTask>();
+        tasks.add(favoriteTask);
+        interestedEditor.removeFavorites(tasks);
+
+        //it should still exist in the original
+        Assert.assertEquals(1, otherEditor.getFavoriteTasks().size());
+
+        //nothing exists in the new one.
+        Assert.assertTrue(interestedEditor.getFavoriteTasks().isEmpty());
+    }
+
+    /**
+     * Here we edit a favorite. We want to make sure that we get notified of the edit and that we the edited values are
+     * stored properly. We'll add a favorite, then we perform an edit. Lastly, we verify our values. Notice that we're
+     * going to change the task's full name. This should update the task inside the favorite.
+     */
+    @Test
+    public void testEditingFavorite() {
+        FavoritesEditor editor = new FavoritesEditor();
+
+        Assert.assertTrue(editor.getFavoriteTasks().isEmpty());
+
+        editor.addFavorite(mySubProject1Comple, true);
+
+        //make sure it was added properly
+        FavoriteTask favoriteTask = editor.getFavoriteTasks().get(0);
+        Assert.assertEquals("mysubproject1:compile", favoriteTask.getDisplayName());
+        Assert.assertEquals("mysubproject1:compile", favoriteTask.getFullCommandLine());
+        Assert.assertTrue(favoriteTask.alwaysShowOutput());
+
+        //create an observer so we can make sure we're notified of the edit.
+        final FavoritesEditor.FavoriteTasksObserver observer = context.mock(
+                FavoritesEditor.FavoriteTasksObserver.class);
+        context.checking(new Expectations() {{
+            one(observer).favoritesChanged();
+        }});
+
+        editor.addFavoriteTasksObserver(observer, false);
+
+        //now perform the edit.
+        editor.editFavorite(favoriteTask, new FavoritesEditor.EditFavoriteInteraction() {
+            public boolean editFavorite(FavoritesEditor.EditibleFavoriteTask favoriteTask) {
+                favoriteTask.alwaysShowOutput = false;
+                favoriteTask.displayName = "newname";
+                favoriteTask.fullCommandLine
+                        = "myrootproject:mysubproject1:mysubsubproject:lib";   //change the task's full name
+                return true;
+            }
+
+            public void reportError(String error) {
+                throw new AssertionFailedError("unexpected error: " + error);
+            }
+        });
+
+        //make sure we were notified
+        context.assertIsSatisfied();
+
+        //make sure the settings were changed
+        favoriteTask = editor.getFavoriteTasks().get(0);
+        Assert.assertEquals("newname", favoriteTask.getDisplayName());
+        Assert.assertEquals("myrootproject:mysubproject1:mysubsubproject:lib", favoriteTask.getFullCommandLine());
+        Assert.assertTrue(!favoriteTask.alwaysShowOutput());
+    }
+
+    /**
+     * This edits a favorite, but specifically, we change the task's full name to something that doesn't exist. We don't
+     * want this to be an error. Maybe the task is temporarily unavailable because of a compile error. We shouldn't stop
+     * everything or throw it away just because the task isn't present. The UI should provide some indication of this
+     * however.
+     */
+    @Test
+    public void testChangingFullNameToNonExistantTask() {
+        FavoritesEditor editor = new FavoritesEditor();
+
+        Assert.assertTrue(editor.getFavoriteTasks().isEmpty());
+
+        editor.addFavorite(mySubProject1Comple, true);
+
+        //make sure it was added properly
+        FavoriteTask favoriteTask = editor.getFavoriteTasks().get(0);
+        Assert.assertEquals("mysubproject1:compile", favoriteTask.getDisplayName());
+        Assert.assertEquals("mysubproject1:compile", favoriteTask.getFullCommandLine());
+        Assert.assertTrue(favoriteTask.alwaysShowOutput());
+
+        //create an observer so we can make sure we're notified of the edit.
+        final FavoritesEditor.FavoriteTasksObserver observer = context.mock(
+                FavoritesEditor.FavoriteTasksObserver.class);
+        context.checking(new Expectations() {{
+            one(observer).favoritesChanged();
+        }});
+
+        editor.addFavoriteTasksObserver(observer, false);
+
+        //now perform the edit.
+        editor.editFavorite(favoriteTask, new FavoritesEditor.EditFavoriteInteraction() {
+            public boolean editFavorite(FavoritesEditor.EditibleFavoriteTask favoriteTask) {
+                favoriteTask.displayName = "newname";
+                favoriteTask.fullCommandLine = "nonexistanttask";   //change the task's full name
+                return true;
+            }
+
+            public void reportError(String error) {
+                throw new AssertionFailedError("unexpected error: " + error);
+            }
+        });
+
+        //make sure we were notified
+        context.assertIsSatisfied();
+
+        //make sure the settings were changed
+        favoriteTask = editor.getFavoriteTasks().get(0);
+        Assert.assertEquals("newname", favoriteTask.getDisplayName());
+        Assert.assertEquals("nonexistanttask", favoriteTask.getFullCommandLine());
+        Assert.assertFalse(!favoriteTask.alwaysShowOutput());
+
+        //now change the full name back. Make sure the task is changed back.
+
+        //reset our expectations. We'll get notified again.
+        context.checking(new Expectations() {{
+            one(observer).favoritesChanged();
+        }});
+
+        //now perform the edit.
+        editor.editFavorite(favoriteTask, new FavoritesEditor.EditFavoriteInteraction() {
+            public boolean editFavorite(FavoritesEditor.EditibleFavoriteTask favoriteTask) {
+                favoriteTask.displayName = "newname";
+                favoriteTask.fullCommandLine = "mysubproject1:compile";   //change the task's full name
+                return true;
+            }
+
+            public void reportError(String error) {
+                throw new AssertionFailedError("unexpected error: " + error);
+            }
+        });
+
+        //make sure we were notified
+        context.assertIsSatisfied();
+
+        //make sure the settings were changed
+        favoriteTask = editor.getFavoriteTasks().get(0);
+        Assert.assertEquals("newname", favoriteTask.getDisplayName());
+        Assert.assertEquals("mysubproject1:compile", favoriteTask.getFullCommandLine());
+        Assert.assertFalse(!favoriteTask.alwaysShowOutput());
+    }
+
+    /**
+     * This edits a favorite and cancels. We want to make sure that none of our changes during the editing are saved.
+     */
+    @Test
+    public void testCancelingEditingFavorite() {
+        FavoritesEditor editor = new FavoritesEditor();
+
+        Assert.assertTrue(editor.getFavoriteTasks().isEmpty());
+
+        editor.addFavorite(mySubProject1Comple, true);
+
+        //make sure it was added properly
+        FavoriteTask favoriteTask = editor.getFavoriteTasks().get(0);
+        Assert.assertEquals("mysubproject1:compile", favoriteTask.getDisplayName());
+        Assert.assertEquals("mysubproject1:compile", favoriteTask.getFullCommandLine());
+        Assert.assertTrue(favoriteTask.alwaysShowOutput());
+
+        //create an observer so we can make sure we're NOT notified of the edit. We'll provide no expectations for this mock object.
+        final FavoritesEditor.FavoriteTasksObserver observer = context.mock(
+                FavoritesEditor.FavoriteTasksObserver.class);
+
+        editor.addFavoriteTasksObserver(observer, false);
+
+        //now perform the edit, but cancel out.
+        editor.editFavorite(favoriteTask, new FavoritesEditor.EditFavoriteInteraction() {
+            public boolean editFavorite(FavoritesEditor.EditibleFavoriteTask favoriteTask) {
+                favoriteTask.displayName = "newname";
+                favoriteTask.fullCommandLine = "nonexistanttask";   //change the task's full name
+                favoriteTask.alwaysShowOutput = !favoriteTask.alwaysShowOutput;
+                return false;  //return false to cancel!
+            }
+
+            public void reportError(String error) {
+                throw new AssertionFailedError("unexpected error: " + error);
+            }
+        });
+
+        //make sure nothing was changed
+        favoriteTask = editor.getFavoriteTasks().get(0);
+        Assert.assertEquals("mysubproject1:compile", favoriteTask.getDisplayName());
+        Assert.assertEquals("mysubproject1:compile", favoriteTask.getFullCommandLine());
+        Assert.assertTrue(favoriteTask.alwaysShowOutput());
+    }
+
+    /**
+     * This edits a favorite so the task is the same as an existing favorite. This doesn't make any sense to have two of
+     * these. We're expecting an error from this.
+     */
+    @Test
+    public void testEditingFavoriteFullNameAlreadyExists() {
+        FavoritesEditor editor = new FavoritesEditor();
+
+        Assert.assertTrue(editor.getFavoriteTasks().isEmpty());
+
+        //add two tasks
+        editor.addFavorite(mySubProject1Comple, true);
+        editor.addFavorite(mySubSubProjectLib, true);
+
+        //make sure they were added properly
+        FavoriteTask favoriteTask1 = editor.getFavoriteTasks().get(0);
+        Assert.assertEquals("mysubproject1:compile", favoriteTask1.getFullCommandLine());
+
+        FavoriteTask favoriteTask2 = editor.getFavoriteTasks().get(1);
+        Assert.assertEquals("mysubproject1:mysubsubproject:lib", favoriteTask2.getFullCommandLine());
+
+        //now perform the actual edit.
+        editExpectingNoError(editor, favoriteTask1, "new name", favoriteTask2.getFullCommandLine());
+    }
+
+    /**
+     * This edits the favorite and expects NO error. It makes sure the error was received and that the original task was
+     * not altered.
+     */
+    private void editExpectingNoError(FavoritesEditor editor, FavoriteTask favoriteTaskToEdit, String newName,
+                                    String newFullName) {
+        String originalFullName = favoriteTaskToEdit.getFullCommandLine();
+
+        //create an observer so we can make sure we're notified of the edit.
+        final FavoritesEditor.FavoriteTasksObserver observer = context.mock(
+                FavoritesEditor.FavoriteTasksObserver.class);
+        context.checking(new Expectations() {{
+            one(observer).favoritesChanged();
+        }});
+
+        editor.addFavoriteTasksObserver(observer, false);
+
+        //now perform the edit.
+        ValidationErrorTestEditFavoriteInteraction interaction = new ValidationErrorTestEditFavoriteInteraction(newName,
+                newFullName);
+        editor.editFavorite(favoriteTaskToEdit, interaction);
+
+        //make sure we did get an error message.
+        Assert.assertFalse(interaction.receivedErrorMessage);
+
+        //make sure the settings were changed. We'll go by what the editor has, not just our local favoriteTaskToEdit.
+        favoriteTaskToEdit = editor.getFavorite(originalFullName);
+        Assert.assertNull(favoriteTaskToEdit);   //the original name should no longer be present
+
+        favoriteTaskToEdit = editor.getFavorite(newFullName);
+        Assert.assertNotNull(favoriteTaskToEdit);   //the new name should be present
+
+        Assert.assertEquals(newName, favoriteTaskToEdit.getDisplayName());
+        Assert.assertEquals(newFullName, favoriteTaskToEdit.getFullCommandLine());
+    }
+
+
+    /**
+     * This edits the favorite and expects an error. It makes sure the error was recieved and that the original task was
+     * not altered.
+     */
+    private void editExpectingError(FavoritesEditor editor, FavoriteTask favoriteTaskToEdit, String newName,
+                                    String newFullName) {
+        String originalDisplayName = favoriteTaskToEdit.getDisplayName();
+        String originalFullName = favoriteTaskToEdit.getFullCommandLine();
+
+        //create an observer so we can make sure we're NOT notified of the edit. It's going to generate an error and we'll cancel.
+        final FavoritesEditor.FavoriteTasksObserver observer = context.mock(
+                FavoritesEditor.FavoriteTasksObserver.class);
+
+        editor.addFavoriteTasksObserver(observer, false);
+
+        //now perform the edit.
+        ValidationErrorTestEditFavoriteInteraction interaction = new ValidationErrorTestEditFavoriteInteraction(newName,
+                newFullName);
+        editor.editFavorite(favoriteTaskToEdit, interaction);
+
+        //make sure we did get an error message.
+        Assert.assertTrue(interaction.receivedErrorMessage);
+
+        //make sure the settings were NOT changed. We'll go by what the editor has, not just our local favoriteTaskToEdit.
+        favoriteTaskToEdit = editor.getFavorite(originalFullName);
+        Assert.assertNotNull(favoriteTaskToEdit);
+
+        Assert.assertEquals(originalDisplayName, favoriteTaskToEdit.getDisplayName());
+        Assert.assertEquals(originalFullName, favoriteTaskToEdit.getFullCommandLine());
+        Assert.assertTrue(favoriteTaskToEdit.alwaysShowOutput());
+    }
+
+    /**
+     * This implementation is very specific. It expects to get an error after it returns from editFavorite. It will then
+     * cancel on the second call to editFavorite.
+     */
+    private class ValidationErrorTestEditFavoriteInteraction implements FavoritesEditor.EditFavoriteInteraction {
+        boolean receivedErrorMessage;
+
+        private String newDisplayName;
+        private String newFullCommandLine;
+
+        private ValidationErrorTestEditFavoriteInteraction(String newDisplayName, String newFullCommandLine) {
+            this.newDisplayName = newDisplayName;
+            this.newFullCommandLine = newFullCommandLine;
+        }
+
+        public boolean editFavorite(FavoritesEditor.EditibleFavoriteTask favoriteTask) {
+            if (receivedErrorMessage) {
+                return false;
+            }  //cancel once we've received an error.
+
+            favoriteTask.alwaysShowOutput = false;
+            favoriteTask.displayName = newDisplayName;
+            favoriteTask.fullCommandLine = newFullCommandLine;
+            return true;
+        }
+
+        public void reportError(String error) {
+            receivedErrorMessage = true;
+        }
+    }
+
+    /**
+     * This edits a favorite so the display name is the same as an existing favorite. This should not be allowed. We're
+     * expecting an error from this.
+     */
+    @Test
+    public void testEditingFavoriteDisplayNameAlreadyExists() {
+        FavoritesEditor editor = new FavoritesEditor();
+
+        Assert.assertTrue(editor.getFavoriteTasks().isEmpty());
+
+        //add two tasks
+        editor.addFavorite(mySubProject1Comple, true);
+        editor.addFavorite(mySubSubProjectLib, true);
+
+        //make sure they were added properly
+        FavoriteTask favoriteTask1 = editor.getFavoriteTasks().get(0);
+        Assert.assertEquals("mysubproject1:compile", favoriteTask1.getFullCommandLine());
+
+        FavoriteTask favoriteTask2 = editor.getFavoriteTasks().get(1);
+        Assert.assertEquals("mysubproject1:mysubsubproject:lib", favoriteTask2.getFullCommandLine());
+
+
+        //create an observer so we can make sure we're notified of the edit.
+        final FavoritesEditor.FavoriteTasksObserver observer = context.mock(
+                FavoritesEditor.FavoriteTasksObserver.class);
+        context.checking(new Expectations() {{
+            one(observer).favoritesChanged();
+        }});
+        editor.addFavoriteTasksObserver(observer, false);
+
+
+
+        //we're about to perform the actual edit. Leave the full name alone, but use the other favorite's name
+        FavoriteTask favoriteTaskToEdit = favoriteTask1;
+        String newName = favoriteTask2.getDisplayName();
+        String newFullName = favoriteTask1.getFullCommandLine();
+        String originalFullName = favoriteTaskToEdit.getFullCommandLine();
+
+        ValidationErrorTestEditFavoriteInteraction interaction = new ValidationErrorTestEditFavoriteInteraction(newName,
+                newFullName);
+        //now perform the edit.
+        editor.editFavorite(favoriteTaskToEdit, interaction);
+
+        //make sure we did get an error message.
+        Assert.assertFalse(interaction.receivedErrorMessage);
+
+        //make sure the settings were changed. We'll go by what the editor has, not just our local favoriteTaskToEdit.
+        favoriteTaskToEdit = editor.getFavorite(originalFullName);
+        Assert.assertNotNull(favoriteTaskToEdit);   //the original name should no longer be present
+
+        favoriteTaskToEdit = editor.getFavorite(newFullName);
+        Assert.assertNotNull(favoriteTaskToEdit);   //the new name should be present
+
+        Assert.assertEquals(newName, favoriteTaskToEdit.getDisplayName());
+        Assert.assertEquals(newFullName, favoriteTaskToEdit.getFullCommandLine());
+    }
+
+    /**
+     * Edits a favorite and makes the full name blank. This is not allowed. We're expecting an error.
+     */
+    @Test
+    public void testEditingFavoriteBlankFullName() {
+        FavoritesEditor editor = new FavoritesEditor();
+
+        Assert.assertTrue(editor.getFavoriteTasks().isEmpty());
+
+        //add a task
+        editor.addFavorite(mySubProject1Comple, true);
+
+        //make sure they were added properly
+        FavoriteTask favoriteTask = editor.getFavoriteTasks().get(0);
+        Assert.assertEquals("mysubproject1:compile", favoriteTask.getFullCommandLine());
+
+        //now perform the actual edit. Leave the display name alone, but use a blank full name
+        editExpectingError(editor, favoriteTask, favoriteTask.getDisplayName(), "");
+    }
+
+    /**
+     * Edits a favorite and makes the display name blank. This is not allowed. We're expecting an error.
+     */
+    @Test
+    public void testEditingFavoriteBlankDisplayName() {
+        FavoritesEditor editor = new FavoritesEditor();
+
+        Assert.assertTrue(editor.getFavoriteTasks().isEmpty());
+
+        //add a task
+        editor.addFavorite(mySubProject1Comple, true);
+
+        //make sure they were added properly
+        FavoriteTask favoriteTask = editor.getFavoriteTasks().get(0);
+        Assert.assertEquals("mysubproject1:compile", favoriteTask.getFullCommandLine());
+
+        //now perform the actual edit. Leave the full name alone, but use a blank full name
+        editExpectingError(editor, favoriteTask, "", favoriteTask.getFullCommandLine());
+    }
+
+    /**
+     * Tests moving favorites up. This mechansim is more advanced than just move the item up one. If you select multiple
+     * things with non-selected items between them and then repeatedly move up, this will 'bunch up' the items at the
+     * top while keeping the items in relative order between themselves. This seems like most users would actually want
+     * to happen (but I'm sure someone won't like it). This moves every other item and keeps moving them until they all
+     * wind up at the top.
+     */
+    @Test
+    public void testMoveUp() {
+        FavoritesEditor editor = new FavoritesEditor();
+
+        Assert.assertTrue(editor.getFavoriteTasks().isEmpty());
+
+        FavoriteTask mySubProject1CompleFavorite = editor.addFavorite(mySubProject1Comple, false);
+        FavoriteTask mySubProject1LibFavorite = editor.addFavorite(mySubProject1Lib, false);
+        FavoriteTask mySubProject1DocFavorite = editor.addFavorite(mySubProject1Doc, false);
+        FavoriteTask mySubSubProjectCompileFavorite = editor.addFavorite(mySubSubProjectCompile, false);
+        FavoriteTask mySubSubProjectLibFavorite = editor.addFavorite(mySubSubProjectLib, false);
+        FavoriteTask mySubSubProjectDocFavorite = editor.addFavorite(mySubSubProjectDoc, false);
+
+        List<FavoriteTask> favoritesToMove = new ArrayList<FavoriteTask>();
+
+        favoritesToMove.add(mySubProject1LibFavorite);
+        favoritesToMove.add(mySubSubProjectCompileFavorite);
+        favoritesToMove.add(mySubSubProjectDocFavorite);
+
+        //our observer will make sure the order is correct.
+        TestOrderFavoritesObserver observer = new TestOrderFavoritesObserver(editor, mySubProject1LibFavorite,
+                mySubProject1CompleFavorite, mySubSubProjectCompileFavorite, mySubProject1DocFavorite,
+                mySubSubProjectDocFavorite, mySubSubProjectLibFavorite);
+        editor.addFavoriteTasksObserver(observer, false);
+
+        editor.moveFavoritesBefore(favoritesToMove);
+
+        //we're going to move them again, set the new expected order.
+        observer.setExpectedOrder(mySubProject1LibFavorite, mySubSubProjectCompileFavorite, mySubProject1CompleFavorite,
+                mySubSubProjectDocFavorite, mySubProject1DocFavorite, mySubSubProjectLibFavorite);
+
+        editor.moveFavoritesBefore(favoritesToMove);
+
+        //move again. Set the new order. Notice that both mySubProject1LibFavorite mySubSubProjectCompileFavorite has stopped moving.
+        observer.setExpectedOrder(mySubProject1LibFavorite, mySubSubProjectCompileFavorite, mySubSubProjectDocFavorite,
+                mySubProject1CompleFavorite, mySubProject1DocFavorite, mySubSubProjectLibFavorite);
+
+        editor.moveFavoritesBefore(favoritesToMove);
+
+        //one last time. Set the new order. Notice that the items have stopped moving. They're all at the top.
+        observer.setExpectedOrder(mySubProject1LibFavorite, mySubSubProjectCompileFavorite, mySubSubProjectDocFavorite,
+                mySubProject1CompleFavorite, mySubProject1DocFavorite, mySubSubProjectLibFavorite);
+
+        editor.moveFavoritesBefore(favoritesToMove);
+    }
+
+    //
+
+    /**
+     * Observer that listens for favoritesReordered messages. When it gets them, it compares the new order with an
+     * expected order.
+     */
+    private class TestOrderFavoritesObserver implements FavoritesEditor.FavoriteTasksObserver {
+        private FavoritesEditor editor;
+        private FavoriteTask[] expectedOrder;
+
+        private TestOrderFavoritesObserver(FavoritesEditor editor, FavoriteTask... expectedOrder) {
+            this.editor = editor;
+            this.expectedOrder = expectedOrder;
+        }
+
+        public void setExpectedOrder(FavoriteTask... expectedOrder) {
+            this.expectedOrder = expectedOrder;
+        }
+
+        public void favoritesChanged() {
+            throw new AssertionFailedError("Did not expect to get a favoritesChanged notification!");
+        }
+
+        public void favoritesReordered(List<FavoriteTask> favoritesReordered) {
+            Object[] objects = editor.getFavoriteTasks().toArray();
+            Assert.assertArrayEquals(expectedOrder, objects);
+        }
+    }
+
+    /**
+     * Same as testMoveUp, but moving down. See it for more information.
+     */
+    @Test
+    public void testMoveDown() {
+        FavoritesEditor editor = new FavoritesEditor();
+
+        Assert.assertTrue(editor.getFavoriteTasks().isEmpty());
+
+        FavoriteTask mySubProject1CompleFavorite = editor.addFavorite(mySubProject1Comple, false);
+        FavoriteTask mySubProject1LibFavorite = editor.addFavorite(mySubProject1Lib, false);
+        FavoriteTask mySubProject1DocFavorite = editor.addFavorite(mySubProject1Doc, false);
+        FavoriteTask mySubSubProjectCompileFavorite = editor.addFavorite(mySubSubProjectCompile, false);
+        FavoriteTask mySubSubProjectLibFavorite = editor.addFavorite(mySubSubProjectLib, false);
+        FavoriteTask mySubSubProjectDocFavorite = editor.addFavorite(mySubSubProjectDoc, false);
+
+        List<FavoriteTask> favoritesToMove = new ArrayList<FavoriteTask>();
+
+        favoritesToMove.add(mySubProject1CompleFavorite);
+        favoritesToMove.add(mySubProject1DocFavorite);
+        favoritesToMove.add(mySubSubProjectLibFavorite);
+
+        //our observer will make sure the order is correct.
+        TestOrderFavoritesObserver observer = new TestOrderFavoritesObserver(editor, mySubProject1LibFavorite,
+                mySubProject1CompleFavorite, mySubSubProjectCompileFavorite, mySubProject1DocFavorite,
+                mySubSubProjectDocFavorite, mySubSubProjectLibFavorite);
+        editor.addFavoriteTasksObserver(observer, false);
+
+        editor.moveFavoritesAfter(favoritesToMove);
+
+        //we're going to move them again, set the new expected order.
+        observer.setExpectedOrder(mySubProject1LibFavorite, mySubSubProjectCompileFavorite, mySubProject1CompleFavorite,
+                mySubSubProjectDocFavorite, mySubProject1DocFavorite, mySubSubProjectLibFavorite);
+
+        editor.moveFavoritesAfter(favoritesToMove);
+
+        //move again. Set the new order. Notice that both mySubProject1DocFavorite and mySubSubProjectLibFavorite has stopped moving.
+        observer.setExpectedOrder(mySubProject1LibFavorite, mySubSubProjectCompileFavorite, mySubSubProjectDocFavorite,
+                mySubProject1CompleFavorite, mySubProject1DocFavorite, mySubSubProjectLibFavorite);
+
+        editor.moveFavoritesAfter(favoritesToMove);
+
+        //one last time. Set the new order. Notice that the items have stopped moving. They're all at the bottom.
+        observer.setExpectedOrder(mySubProject1LibFavorite, mySubSubProjectCompileFavorite, mySubSubProjectDocFavorite,
+                mySubProject1CompleFavorite, mySubProject1DocFavorite, mySubSubProjectLibFavorite);
+
+        editor.moveFavoritesAfter(favoritesToMove);
+    }
+}
diff --git a/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/FileLinkTests.java b/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/FileLinkTests.java
new file mode 100644
index 0000000..511d7de
--- /dev/null
+++ b/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/FileLinkTests.java
@@ -0,0 +1,238 @@
+/*
+ * 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.foundation;
+
+import junit.framework.TestCase;
+import org.gradle.foundation.output.FileLink;
+import org.gradle.foundation.output.FileLinkDefinitionLord;
+import org.gradle.foundation.output.OutputParser;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ These test several aspects of parsing output looking for files.
+ @author mhunsicker
+ */
+public class FileLinkTests extends TestCase
+{
+   public static void parseOutputTest( String textToSearch, FileLink ... expectedResults )
+   {
+      parseTest( textToSearch, false, expectedResults );
+   }
+
+   public static void parseTest( String textToSearch, boolean verifyFileExists, FileLink ... expectedResults )
+   {
+      OutputParser outputParser = new OutputParser( new FileLinkDefinitionLord(), verifyFileExists );
+      List<FileLink> fileLinks = outputParser.parseText( textToSearch );
+
+      TestUtility.assertListContents( fileLinks, expectedResults );
+   }
+
+   public void testCompileErrors()
+   {
+      String outputText = ":distributionDiskResources SKIPPED\n" +
+            ":installDiskResources SKIPPED\n" +
+            ":idea-plugins:ideagradle:compileJava\n" +
+            "[ant:javac] /home/user/project/modules/plugins/src/main/java/com/thing/plugins/gradle/ui/GradleComponent.java:186: cannot find symbol\n" +
+            "[ant:javac] symbol  : constructor Integer()\n" +
+            "[ant:javac] location: class java.lang.Integer\n" +
+            "[ant:javac]       SwingUtilities.invokeLater( new Integer() );\n" +
+            "[ant:javac]                                   ^\n";
+
+      parseOutputTest( outputText, new FileLink( new File( "/home/user/project/modules/plugins/src/main/java/com/thing/plugins/gradle/ui/GradleComponent.java" ), 114, 215, 186 ) );
+   }
+
+   public void testNotes()
+   {
+      String outputText = ":distributionDiskResources SKIPPED\n" +
+            ":installDiskResources SKIPPED\n" +
+            ":idea-plugins:ideagradle:compileJava\n" +
+            "[ant:javac] Note: /home/user/project/modules/plugins/gradle/src/main/java/com/thing/plugins/gradle/ui/GradleComponent.java uses or overrides a deprecated API.\n" +
+            "[ant:javac] Note: Recompile with -Xlint:deprecation for details.\n" +
+            "[ant:javac] 1 error\n" +
+            "Total time: 4.622 secs";
+
+      parseOutputTest( outputText, new FileLink( new File( "/home/user/project/modules/plugins/gradle/src/main/java/com/thing/plugins/gradle/ui/GradleComponent.java" ), 120, 224, -1 ) );
+   }
+
+   /**
+    Tests to see if we can find gradle files in the output. This message shows up when the build fails. We'll test two types (that probably wouldn't
+    occur naturally together. One with the line number, one without.
+    */
+   public void testGradleBuildFile()
+   {
+      String outputText = "FAILURE: Build failed with an exception.\n" +
+            "\n" +
+            "* Where:\n" +
+            "Build file '/home/user/gradle/build.gradle' line: 431\n" +
+            "\n" +
+            "* What went wrong:\n" +
+            "blah blah blah\n" +
+            "Build file '/home/user/gradle/build.gradle'" +
+            "Total time: 4.622 secs";
+
+      parseOutputTest( outputText, new FileLink( new File( "/home/user/gradle/build.gradle" ), 63, 104, 431 ),
+                                               new FileLink( new File( "/home/user/gradle/build.gradle" ), 152, 182, -1 ) );
+   }
+
+
+   /**
+    This attempts to find a single ant:checkstyle error that occurs when the checkstyle fails. This error consists of
+    the file, the line of the problem, and the cause.
+    */
+   public void testCheckstyleSingleErrorOutput()
+   {
+      String outputText = ":processResources\n" +
+            ":idea-plugins:compile\n" +
+            ":compile\n" +
+            ":copyNativeLibs\n" +
+            ":distributionDiskResources SKIPPED\n" +
+            ":installDiskResources SKIPPED\n" +
+            "[ant:checkstyle] /home/user/gradle/subprojects/gradle-core/src/main/groovy/org/gradle/util/exec/ExecHandleShutdownHookAction.java:38: 'if' construct must use '{}'s." +
+            "FAILURE: Build failed with an exception.\n" +
+            "\n" +
+            "* What went wrong:\n" +
+            "blah blah blah\n" +
+            "Total time: 4.622 secs";
+
+      parseOutputTest( outputText, new FileLink( new File( "/home/user/gradle/subprojects/gradle-core/src/main/groovy/org/gradle/util/exec/ExecHandleShutdownHookAction.java" ), 147, 262, 38 ) );
+   }
+
+   /**
+    This attemps to find the checkstyle error report file. This file will list all errors and where they are located.
+    The file link we're looking for just has the file's path
+    */
+   public void testCheckstyleReportErrorFile()
+   {
+      String outputText = ":processResources\n" +
+            ":plugins:compile\n" +
+            ":compile\n" +
+            ":copyNativeLibs\n" +
+            ":distributionDiskResources SKIPPED\n" +
+            ":installDiskResources SKIPPED\n" +
+            "FAILURE: Build failed with an exception.\n" +
+            "\n" +
+            "* What went wrong:\n" +
+            "Cause: Checkstyle check violations were found in main Java source. See the report at /home/user/gradle/subprojects/gradle-core/build/checkstyle/main.xml\n" +
+            "Total time: 4.622 secs" +
+            "blah blah blah";
+
+      parseOutputTest( outputText, new FileLink( new File( "/home/user/gradle/subprojects/gradle-core/build/checkstyle/main.xml" ), 271, 338, -1 ) );
+   }
+
+   /**
+    Tests that any HTML reports (prefixed with "See the report at ") are found.
+    */
+   public void testHTMLReport()
+   {
+      String outputText = "* What went wrong:\n" +
+            "Execution failed for task ':ui:codenarcTest'.\n" +
+            "Cause: CodeNarc check violations were found in test Groovy source. See the report at /home/user/gradle/subprojects/gradle-ui/build/reports/codenarc/test.html.\n" +
+            "\n" +
+            "* Try:\n" +
+            "Run with -s or -d option to get more details. Run with -S option to get the full (very verbose) stacktrace.";
+
+      parseOutputTest( outputText, new FileLink( new File( "/home/user/gradle/subprojects/gradle-ui/build/reports/codenarc/test.html" ), 150, 222, -1 ) );
+   }
+
+   /**
+    This tests a bug I discovered while coding this where the path matched by the Note: would start with the space
+    before the '/', but the Build file would start at the '/'. This test is here because my first attempt
+    I tracked the problem down to BasicFileLinkDefintion. It was assuming the file started at immediately after
+    the prefix. This was incorrect. There may be spaces between the prefix and the file (and I didn't want to put
+    spaces in the prefix). So I added BasicFileLinkDefintion.getStartOfFile() to skip over these spaces.
+    */
+   public void testOffByOneCharacterBug()
+   {
+      String outputText = "[ant:javac] Note: /home/user/modules/f1j/src/main/java/com/thing/DesignerManager.java uses or overrides a deprecated API.\n" +
+                          "Build file '/home/user/modules/build.gradle'";
+
+      parseOutputTest( outputText, new FileLink( new File( "/home/user/modules/f1j/src/main/java/com/thing/DesignerManager.java" ), 18, 85, -1 ),
+                                         new FileLink( new File( "/home/user/modules/build.gradle" ), 134, 165, -1 ) );
+   }
+
+   /**
+      This test that test reports file is found. This is a special case since we're not given
+      the actual file path and instead given only its parent path. The code assumes the report file
+      is present.
+      */
+   public void testFailedTestsReportFile()
+   {
+      String outputText = "FAILURE: Build failed with an exception.\n" +
+            "\n" +
+            "* Where:\n" +
+            "Build file '/home/user/gradle/gradle/build.gradle'\n" +
+            "\n" +
+            "* What went wrong:\n" +
+            "Execution failed for task ':integTest'.\n" +
+            "Cause: There were failing tests. See the report at /home/user/gradle/gradle/build/reports/tests.\n" +
+            "Total time: 4.622 secs\n" +
+            "blah blah blah";
+
+      parseOutputTest( outputText, new FileLink( new File( "/home/user/gradle/gradle/build.gradle" ), 63, 100, -1 ),
+                                         new FileLink( new File( "/home/user/gradle/gradle/build/reports/tests/index.html" ), 213, 258, -1 ) );
+   }
+
+   /**
+    This tests for multiple files found in a single output.
+    */
+   public void testMultiples()
+   {
+      String outputText = ":distributionDiskResources SKIPPED\n" +
+            ":installDiskResources SKIPPED\n" +
+            ":idea-plugins:ideagradle:compileJava\n\n" +
+            "[ant:checkstyle] /home/user/modules/gradle/subprojects/gradle-core/src/main/groovy/org/gradle/util/exec/ExecHandleShutdownHookAction.java:38: 'if' construct must use '{}'s.\n" +
+            "Note: /home/user/modules/gradle/subprojects/gradle-core/src/test/groovy/org/gradle/integtests/DistributionIntegrationTestRunner.java uses or overrides a deprecated API.\n" +
+            "\n" +
+            "Cause: Checkstyle check violations were found in main Java source. See the report at /home/user/modules/gradle/subprojects/gradle-core/build/checkstyle/main.xml.\n" +
+            "\n" +
+            "\n" +
+            "Build file '/home/user/modules/gradle/subprojects/gradle-ui/ui.gradle'\n" +
+            "\n" +
+            "* What went wrong:\n" +
+            "Execution failed for task ':ui:codenarcTest'.\n" +
+            "Cause: CodeNarc check violations were found in test Groovy source. See the report at /home/user/modules/gradle/subprojects/gradle-ui/build/reports/codenarc/test.html.\n" +
+            "\n" +
+            "* Try:\n" +
+            "Run with -s or -d option to get more details. Run with -S option to get the full (very verbose) stacktrace.";
+
+            parseOutputTest( outputText, new FileLink( new File( "/home/user/modules/gradle/subprojects/gradle-core/src/main/groovy/org/gradle/util/exec/ExecHandleShutdownHookAction.java" ), 120, 243, 38 ),
+                                                       new FileLink( new File( "/home/user/modules/gradle/subprojects/gradle-core/src/test/groovy/org/gradle/integtests/DistributionIntegrationTestRunner.java" ), 282, 408, -1 ),
+                                                       new FileLink( new File( "/home/user/modules/gradle/subprojects/gradle-core/build/checkstyle/main.xml" ), 531, 606, -1 ),
+                                                       new FileLink( new File( "/home/user/modules/gradle/subprojects/gradle-ui/ui.gradle" ), 622, 679, -1 ),
+                                                       new FileLink( new File( "/home/user/modules/gradle/subprojects/gradle-ui/build/reports/codenarc/test.html" ), 832, 912, -1 ) );
+   }
+
+    /**
+     * This tests that different cases of file extensions do not prevent files from being found. I
+     * want to explicitly test this because I had to explicitly search for case insensitive matches.
+     */
+   public void testFileExtensionCaseSensitivity()
+   {
+       String outputText = ":distributionDiskResources SKIPPED\n" +
+            ":installDiskResources SKIPPED\n" +
+            "/home/user/files/Thing.java:38: 'if' construct must use '{}'s.\n" +
+            "/home/user/files/Thing2.JAVA:929: cannot find symbol\n" +
+            "/home/user/files/Thing3.JaVa:77: incompatible types\n" +
+            "* Try:\n" +
+            "Run with -s or -d option to get more details. Run with -S option to get the full (very verbose) stacktrace.";
+       
+       parseOutputTest( outputText, new FileLink( new File( "/home/user/files/Thing.java" ), 65, 95, 38 ),
+                                    new FileLink( new File( "/home/user/files/Thing2.JAVA" ), 128, 160, 929 ),
+                                    new FileLink( new File( "/home/user/files/Thing3.JaVa" ), 181, 212, 77 ) );
+   }
+}
diff --git a/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/FilterTest.java b/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/FilterTest.java
new file mode 100644
index 0000000..d84b750
--- /dev/null
+++ b/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/FilterTest.java
@@ -0,0 +1,212 @@
+/*
+ * 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.foundation;
+
+import junit.framework.TestCase;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.foundation.visitors.TaskTreePopulationVisitor;
+import org.gradle.gradleplugin.foundation.filters.AllowAllProjectAndTaskFilter;
+import org.gradle.gradleplugin.foundation.filters.BasicFilterEditor;
+import org.gradle.gradleplugin.foundation.filters.ProjectAndTaskFilter;
+import org.jmock.integration.junit4.JUnit4Mockery;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Test various aspects of filtering out tasks and projects from the GradlePluginLord.
+ *
+ * @author mhunsicker
+ */
+public class FilterTest extends TestCase {
+    private BuildInformation buildInformation;
+
+    private ProjectView myRootProject;
+
+    private ProjectView mySubProject1;
+    private TaskView mySubProject1Comple;
+    private TaskView mySubProject1Lib;
+    private TaskView mySubProject1Doc;
+
+    private ProjectView mySubSubProject;
+    private TaskView mySubSubProjectCompile;
+    private TaskView mySubSubProjectLib;
+    private TaskView mySubSubProjectDoc;
+
+    private ProjectView mySubProject2;
+    private TaskView mySubProject2Lib;
+    private TaskView mySubProject2doc;
+    private TaskView mySubProject2Compile;
+
+    protected void setUp() throws Exception {
+        JUnit4Mockery context = new JUnit4Mockery();
+
+        Task subsubCompileTask = TestUtility.createTask(context, "compile", "compile description");
+        Task subsubLibTask = TestUtility.createTask(context, "lib", "lib description");
+        Task subsubDocTask = TestUtility.createTask(context, "doc", "doc description");
+        Project subsubProject = TestUtility.createMockProject(context, "mysubsubproject", "filepath3", 2, null,
+                new Task[]{subsubCompileTask, subsubLibTask, subsubDocTask}, null, (Project[]) null);
+
+        Task subCompileTask1 = TestUtility.createTask(context, "compile", "compile description");
+        Task subLibTask1 = TestUtility.createTask(context, "lib", "lib description");
+        Task subDocTask1 = TestUtility.createTask(context, "doc", "doc description");
+        Project subProject1 = TestUtility.createMockProject(context, "mysubproject1", "filepath2a", 1,
+                new Project[]{subsubProject}, new Task[]{subCompileTask1, subLibTask1, subDocTask1}, null,
+                (Project[]) null);
+
+        Task subCompileTask2 = TestUtility.createTask(context, "compile", "compile description");
+        Task subLibTask2 = TestUtility.createTask(context, "lib", "lib description");
+        Task subDocTask2 = TestUtility.createTask(context, "doc", "doc description");
+        Project subProject2 = TestUtility.createMockProject(context, "mysubproject2", "filepath2b", 1, null,
+                new Task[]{subCompileTask2, subLibTask2, subDocTask2}, null, (Project[]) null);
+
+        Project rootProject = TestUtility.createMockProject(context, "myrootproject", "filepath1", 0,
+                new Project[]{subProject1, subProject2}, null, null, (Project[]) null);
+
+        buildInformation = new BuildInformation(rootProject);
+
+        //now get the converted objects to simplify our matching
+        myRootProject = buildInformation.getProjectFromFullPath("myrootproject");
+        assertNotNull(myRootProject);
+        mySubProject1 = buildInformation.getProjectFromFullPath("myrootproject:mysubproject1");
+        assertNotNull(mySubProject1);
+        mySubProject1Comple = buildInformation.getTaskFromFullPath("myrootproject:mysubproject1:compile");
+        assertNotNull(mySubProject1Comple);
+        mySubProject1Lib = buildInformation.getTaskFromFullPath("myrootproject:mysubproject1:lib");
+        assertNotNull(mySubProject1Lib);
+        mySubProject1Doc = buildInformation.getTaskFromFullPath("myrootproject:mysubproject1:doc");
+        assertNotNull(mySubProject1Doc);
+        mySubSubProject = buildInformation.getProjectFromFullPath("myrootproject:mysubproject1:mysubsubproject");
+        assertNotNull(mySubSubProject);
+        mySubSubProjectCompile = buildInformation.getTaskFromFullPath(
+                "myrootproject:mysubproject1:mysubsubproject:compile");
+        assertNotNull(mySubSubProjectCompile);
+        mySubSubProjectLib = buildInformation.getTaskFromFullPath("myrootproject:mysubproject1:mysubsubproject:lib");
+        assertNotNull(mySubSubProjectLib);
+        mySubSubProjectDoc = buildInformation.getTaskFromFullPath("myrootproject:mysubproject1:mysubsubproject:doc");
+        assertNotNull(mySubSubProjectDoc);
+        mySubProject2 = buildInformation.getProjectFromFullPath("myrootproject:mysubproject2");
+        assertNotNull(mySubProject2);
+        mySubProject2Compile = buildInformation.getTaskFromFullPath("myrootproject:mysubproject2:compile");
+        assertNotNull(mySubProject2Compile);
+        mySubProject2Lib = buildInformation.getTaskFromFullPath("myrootproject:mysubproject2:lib");
+        assertNotNull(mySubProject2Lib);
+        mySubProject2doc = buildInformation.getTaskFromFullPath("myrootproject:mysubproject2:doc");
+        assertNotNull(mySubProject2doc);
+    }
+
+    /**
+     * This tests the 'allow all' filter. We just want to make sure it doesn't filter out anything. This also verifies
+     * the project and task visitor works.
+     */
+    public void testAllowAllFiltering() {
+        TestVisitor testVisitor = new TestVisitor();
+
+        TaskTreePopulationVisitor.visitProjectAndTasks(buildInformation.getProjects(), testVisitor,
+                new AllowAllProjectAndTaskFilter(), null);
+
+        //everything should show up
+        testVisitor.setExpectedProjects(myRootProject, mySubProject1, mySubSubProject, mySubProject2);
+        testVisitor.setExpectedTasks(mySubProject1Lib, mySubProject1Doc, mySubSubProjectLib, mySubSubProjectDoc,
+                mySubProject2Lib, mySubProject2doc, mySubProject1Comple, mySubSubProjectCompile, mySubProject2Compile);
+        testVisitor.verifyResults();
+    }
+
+    /**
+     * This tests filtering out a task. We should see all other tasks. We want to also verify that the task is filtered
+     * out at all levels (projects and sub projects).
+     */
+    public void testTaskFiltering() {
+        //filter out tasks named 'lib'
+        BasicFilterEditor editor = new BasicFilterEditor();
+        editor.hideTasksByName("lib");
+        ProjectAndTaskFilter filter = editor.createFilter();
+
+        TestVisitor testVisitor = new TestVisitor();
+        TaskTreePopulationVisitor.visitProjectAndTasks(buildInformation.getProjects(), testVisitor, filter, null);
+
+        testVisitor.setExpectedProjects(myRootProject, mySubProject1, mySubSubProject, mySubProject2);
+        testVisitor.setExpectedTasks(mySubProject1Doc, mySubSubProjectDoc, mySubProject2doc, mySubProject1Comple,
+                mySubSubProjectCompile, mySubProject2Compile);
+        testVisitor.verifyResults();
+    }
+
+    /**
+     * This visitor records the task and projects that it finds and upon calling verifyResults() compares it to what was
+     * expected.
+     */
+    private class TestVisitor implements TaskTreePopulationVisitor.Visitor<Object, Object> {
+        private List<TaskView> expectedTasks;
+        private List<ProjectView> expectedProjects;
+
+        private List<TaskView> foundTasks = new ArrayList<TaskView>();
+        private List<ProjectView> foundProjects = new ArrayList<ProjectView>();
+
+        public void setExpectedProjects(ProjectView... expectedProjectsFullName) {
+            this.expectedProjects = Arrays.asList(expectedProjectsFullName);
+        }
+
+        public void setExpectedTasks(TaskView... expectedTasksFullName) {
+            this.expectedTasks = Arrays.asList(expectedTasksFullName);
+        }
+
+        public Object visitProject(ProjectView project, int indexOfProject, Object parentProjectObject) {
+            foundProjects.add(project);
+
+            return null;
+        }
+
+        public Object visitTask(TaskView task, int indexOfTask, ProjectView tasksProject, Object userProjectObject) {
+            foundTasks.add(task);
+
+            return null;
+        }
+
+        public void completedVisitingProject(Object parentProjectObject, List<Object> projectObjects,
+                                             List<Object> taskObjects) {
+        }
+
+        /**
+         * Call this after visiting all projects and tasks to verify that we found what we expected.
+         */
+        public void verifyResults() {
+            TestUtility.assertListContents(foundProjects, expectedProjects);
+            TestUtility.assertListContents(foundTasks, expectedTasks);
+        }
+    }
+
+    /**
+     * This tests filtering out a project. We expect all tasks, sub projects and sub project's tasks to be hidden.
+     */
+    public void testProjectFiltering() {
+        BasicFilterEditor editor = new BasicFilterEditor();
+        editor.hideProjectsByName("mysubproject1");
+        ProjectAndTaskFilter filter = editor.createFilter();
+
+        TestVisitor testVisitor = new TestVisitor();
+
+        TaskTreePopulationVisitor.visitProjectAndTasks(buildInformation.getProjects(), testVisitor, filter, null);
+
+        testVisitor.setExpectedProjects(myRootProject, mySubProject2);
+
+        //none of the tasks from my sub project1 or my sub sub project will show up
+        testVisitor.setExpectedTasks(mySubProject2Lib, mySubProject2doc, mySubProject2Compile);
+
+        testVisitor.verifyResults();
+    }
+}
diff --git a/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/LiveOutputParserTests.java b/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/LiveOutputParserTests.java
new file mode 100644
index 0000000..4843027
--- /dev/null
+++ b/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/LiveOutputParserTests.java
@@ -0,0 +1,261 @@
+/*
+ * 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.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;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ Tests aspects of LiveOutputParser. This finds FileLinks within text that
+ is being output constantly.
+
+ @author mhunsicker
+ */
+public class LiveOutputParserTests extends TestCase
+{
+   private LiveOutputParser parser;
+    private FileLinkDefinitionLord definitionLord;
+
+    @Override
+   protected void setUp() throws Exception
+   {
+       definitionLord = new FileLinkDefinitionLord();
+       parser = new LiveOutputParser(definitionLord, false );
+   }
+
+   @Override
+   protected void tearDown() throws Exception
+   {
+      definitionLord = null;
+      parser = null; //clean up after ourselves. Some test runners keep all tests in memory. This makes sure our parser isn't consuming any.
+   }
+
+   /**
+    This does a basic test. Text is output in several waves breaking within lines.
+    There is a single file link in it. We should find it. Specifically, this is going
+    to break up the file so it comes in multiple parts.
+    */
+   public void testBasic()
+   {
+      FileLink expectedFileLink = new FileLink( new File( "/home/user/project/modules/plugins/src/main/java/com/thing/plugins/gradle/ui/GradleComponent.java" ), 114, 215, 186 );
+
+      appendTextWithoutFileLinks( ":distributionDiskResources " );
+      appendTextWithoutFileLinks( "SKIPPED\n:installDiskResources SKIPPED\n" );
+      appendTextWithoutFileLinks( ":idea-plugins:ideagradle:compileJava\n" );
+      appendTextWithoutFileLinks( "[ant:javac] /home/user/project/modules/plugins");
+      appendTextWithoutFileLinks( "/src/main/java/com/thing/plugins/gradle/ui/Gradle" );
+      appendTextWithFileLinks( "Component.java:186: cannot find symbol\n", expectedFileLink );  //here's where we expect to get some results
+      appendTextWithoutFileLinks( "[ant:javac] symbol  : constructor Integer()\n" );
+      appendTextWithoutFileLinks( "[ant:javac] location: class java.lang.Integer\n"  );
+      appendTextWithoutFileLinks( "[ant:javac]       SwingUtilities.invokeLater( new Integer() );\n" );
+      appendTextWithoutFileLinks( "[ant:javac]                                   ^\n" );
+
+      //at the end, verify we only found what was expected 
+      TestUtility.assertListContents( parser.getFileLinks(), expectedFileLink );
+   }
+
+   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 ) );
+      }
+   }
+
+   private void appendTextWithFileLinks( String text, FileLink ... expectedResults )
+   {
+      List<FileLink> fileLinks = parser.appendText( text );
+      TestUtility.assertListContents( fileLinks, expectedResults );
+   }
+
+   /**
+    This tests live output coming in where the result is multiple FileLinks. We'll just add many lines
+    some have FileLinks some don't. We want to make sure the LiveOutputParser tracks all of them correctly.
+    */
+   public void testMultipleFiles()
+   {
+      FileLink fileLink1 = new FileLink( new File( "/home/user/modules/gradle/subprojects/gradle-core/src/main/groovy/org/gradle/util/exec/ExecHandleShutdownHookAction.java" ), 120, 243, 38 );
+      FileLink fileLink2 = new FileLink( new File( "/home/user/modules/gradle/subprojects/gradle-core/src/test/groovy/org/gradle/integtests/DistributionIntegrationTestRunner.java" ), 282, 408, -1 );
+      FileLink fileLink3 = new FileLink( new File( "/home/user/modules/gradle/subprojects/gradle-core/build/checkstyle/main.xml" ), 531, 606, -1 );
+      FileLink fileLink4 = new FileLink( new File( "/home/user/modules/gradle/subprojects/gradle-ui/ui.gradle" ), 622, 679, -1 );
+      FileLink fileLink5 = new FileLink( new File( "/home/user/modules/gradle/subprojects/gradle-ui/build/reports/codenarc/test.html" ), 832, 912, -1 );
+
+      appendTextWithoutFileLinks( ":distributionDiskResources SKIPPED\n:installDiskResources" );
+      appendTextWithoutFileLinks( " SKIPPED\n:idea-plugins:ideagradle:compileJava\n\n" );
+      appendTextWithoutFileLinks( "[ant:checkstyle] /home/user/modules/gradle/subprojects" );
+      appendTextWithFileLinks( "/gradle-core/src/main/groovy/org/gradle/util/exec/ExecHandleShutdownHookAction.java:38: 'if' construct must use '{}'s.\n", fileLink1 );
+      appendTextWithFileLinks( "Note: /home/user/modules/gradle/subprojects/gradle-core/src/test/groovy/org/gradle/integtests/DistributionIntegrationTestRunner.java uses or overrides a deprecated API.\n", fileLink2 );
+      appendTextWithoutFileLinks( "\n" );
+      appendTextWithoutFileLinks( "Cause: Checkstyle check violations were found in main " );
+      appendTextWithoutFileLinks( "Java source. See the report " );
+      appendTextWithFileLinks( "at /home/user/modules/gradle/subprojects/gradle-core/build/checkstyle/main.xml.\n", fileLink3 );
+      appendTextWithoutFileLinks( "\n" );
+      appendTextWithoutFileLinks( "\n" );
+      appendTextWithFileLinks( "Build file '/home/user/modules/gradle/subprojects/gradle-ui/ui.gradle'\n", fileLink4 );
+      appendTextWithoutFileLinks( "\n" );
+      appendTextWithoutFileLinks( "* What went wrong:\n" );
+      appendTextWithoutFileLinks( "Execution failed for task ':ui:codenarcTest'.\n" );
+      appendTextWithoutFileLinks( "Cause: CodeNarc check " );
+      appendTextWithFileLinks( "violations were found in test Groovy source. See the report at /home/user/modules/gradle/subprojects/gradle-ui/build/reports/codenarc/test.html.\n", fileLink5 );
+      appendTextWithoutFileLinks( "\n" );
+      appendTextWithoutFileLinks( "* Try:\n" );
+      appendTextWithoutFileLinks( "Run with -s or -d option to get more details. Run with -S option to get the full (very verbose) stacktrace." );
+
+      //at the end, verify we only found what was expected
+      TestUtility.assertListContents( parser.getFileLinks(), fileLink1, fileLink2, fileLink3, fileLink4, fileLink5 );
+   }
+
+   /**
+    This tests is we can successfully find FileLinks if several of them come in at once in one big block of multi-lined text.
+    We'll add some text one line at a time, then add a single FileLink (on a single line), then add many many lines at once that
+    has 4 FileLinks in it.
+    */
+   public void testMultiplesAtOnce()
+   {
+      FileLink fileLink1 = new FileLink( new File( "/home/user/modules/gradle/subprojects/gradle-core/src/main/groovy/org/gradle/util/exec/ExecHandleShutdownHookAction.java" ), 120, 243, 38 );
+      FileLink fileLink2 = new FileLink( new File( "/home/user/modules/gradle/subprojects/gradle-core/src/test/groovy/org/gradle/integtests/DistributionIntegrationTestRunner.java" ), 282, 408, -1 );
+      FileLink fileLink3 = new FileLink( new File( "/home/user/modules/gradle/subprojects/gradle-core/build/checkstyle/main.xml" ), 531, 606, -1 );
+      FileLink fileLink4 = new FileLink( new File( "/home/user/modules/gradle/subprojects/gradle-ui/ui.gradle" ), 622, 679, -1 );
+      FileLink fileLink5 = new FileLink( new File( "/home/user/modules/gradle/subprojects/gradle-ui/build/reports/codenarc/test.html" ), 832, 912, -1 );
+
+      appendTextWithoutFileLinks( ":distributionDiskResources SKIPPED\n" );
+      appendTextWithoutFileLinks( ":installDiskResources SKIPPED\n" );
+      appendTextWithoutFileLinks( ":idea-plugins:ideagradle:compileJava\n\n" );
+      appendTextWithFileLinks( "[ant:checkstyle] /home/user/modules/gradle/subprojects/gradle-core/src/main/groovy/org/gradle/util/exec/ExecHandleShutdownHookAction.java:38: 'if' construct must use '{}'s.\n", fileLink1 );
+      appendTextWithoutFileLinks( "Note: /home/user/modules/gradle/" ); //does NOT end with a newline. This is just to push a potential edge case
+
+      String remaindingOutputText = "subprojects/gradle-core/src/test/groovy/org/gradle/integtests/DistributionIntegrationTestRunner.java uses or overrides a deprecated API.\n" +
+            "\n" +
+            "Cause: Checkstyle check violations were found in main Java source. See the report at /home/user/modules/gradle/subprojects/gradle-core/build/checkstyle/main.xml.\n" +
+            "\n" +
+            "\n" +
+            "Build file '/home/user/modules/gradle/subprojects/gradle-ui/ui.gradle'\n" +
+            "\n" +
+            "* What went wrong:\n" +
+            "Execution failed for task ':ui:codenarcTest'.\n" +
+            "Cause: CodeNarc check violations were found in test Groovy source. See the report at /home/user/modules/gradle/subprojects/gradle-ui/build/reports/codenarc/test.html.\n" +
+            "\n" +
+            "* Try:\n" +
+            "Run with -s or -d option to get more details. Run with -S option to get the full (very verbose) stacktrace.";
+
+      //now add that one large chunk. We should find the last four
+      appendTextWithFileLinks( remaindingOutputText, fileLink2, fileLink3, fileLink4, fileLink5 );
+
+      //at the end, verify we only found what was expected
+      TestUtility.assertListContents( parser.getFileLinks(), fileLink1, fileLink2, fileLink3, fileLink4, fileLink5 );
+   }
+
+    /**
+     * This verifies that we can find a link to a groovy file as well as its line number. This was actually
+     * a bug and this test is based off of actual data (tests failed due to a compile error in groovy).
+     * I tracked the problem down to the space after the delimiter (".groovy: 24" vs. ".groovy:24" ).
+     * After running this test with actual data, I'm going to test it with a line that doesn't have a space
+     * just to make sure both work.
+     */
+   public void testGroovyFileLineDelimiter()
+   {
+       FileLink fileLink1 = new FileLink( new File( "/home/user/gradle/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/GradleRunnerFactoryTest.groovy" ), 183, 306, 24 );
+       FileLink fileLink2 = new FileLink( new File( "/home/user/gradle/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/GradleRunnerFactoryTest.groovy" ), 481, 603, 85 );
+
+       appendTextWithoutFileLinks( ":distributionDiskResources SKIPPED\n" );
+       appendTextWithoutFileLinks( ":installDiskResources SKIPPED\n" );
+       appendTextWithoutFileLinks( ":idea-plugins:ideagradle:compileJava\n\n" );
+       appendTextWithoutFileLinks( "org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:\n" );
+
+       //notice the space between "GradleRunnerFactoryTest.groovy:" and "24:". That was causing our problem
+       appendTextWithFileLinks( "/home/user/gradle/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/GradleRunnerFactoryTest.groovy: 24: unable to find class 'DistributionIntegrationTestRunner.class' for annotation attribute constant\n", fileLink1 );
+       appendTextWithoutFileLinks( " @ line 24, column 10.\n" );
+       appendTextWithoutFileLinks( "   @RunWith(DistributionIntegrationTestRunner.class)\n" );
+
+       //now test it without a space between the delimiter and line number to make sure it works both ways
+       appendTextWithFileLinks( "/home/user/gradle/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/GradleRunnerFactoryTest.groovy:85: unable to find class 'DistributionIntegrationTestRunner.class' for annotation attribute constant\n", fileLink2 );
+   }
+
+    /**
+     * This tests that you can dynamically add file extensions to the parser.
+     * We're going to add 2 fake extensions (one with line number delimiter, one without)
+     * then verify that the parser correctly parses the output with said extensions.
+     */
+   public void testAddingFileExtensions()
+   {
+       String myExtension1 = ".mytxtextension";
+       String myExtension2 = ".othertxtextension";
+
+       //make sure this fake extension isn't already in use
+       assertFalse( "Fake extension 1 already present. This test is not setup correctly!", definitionLord.getFileExtensions().contains(myExtension1) );
+       assertFalse( "Fake extension 2 already present. This test is not setup correctly!", definitionLord.getFileExtensions().contains(myExtension2) );
+
+       definitionLord.addFileExtension(myExtension1, ":" );
+       definitionLord.addFileExtension(myExtension2, null ); //this one has no line delimiter
+
+       //make sure it was added
+       assertTrue( "Fake extension 1 was not added. ", definitionLord.getFileExtensions().contains(myExtension1) );
+       assertTrue( "Fake extension 2 was not added. ", definitionLord.getFileExtensions().contains(myExtension2) );
+
+       //now verify the extension is used
+
+       FileLink fileLink1 = new FileLink( new File( "/home/user/gradle/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/GradleRunnerFactoryTest.mytxtextension" ), 183, 313, 24 );
+       FileLink fileLink2 = new FileLink( new File( "/home/user/gradle/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/GradleRunnerFactoryTest.othertxtextension" ), 488, 618, -1 );
+       FileLink fileLink3 = new FileLink( new File( "/home/user/modules/gradle/subprojects/gradle-core/src/main/groovy/org/gradle/util/exec/ExecHandleShutdownHookAction.java" ), 632, 755, 38 );
+
+       appendTextWithoutFileLinks( ":distributionDiskResources SKIPPED\n" );
+       appendTextWithoutFileLinks( ":installDiskResources SKIPPED\n" );
+       appendTextWithoutFileLinks( ":idea-plugins:ideagradle:compileJava\n\n" );
+       appendTextWithoutFileLinks( "org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:\n" );
+       appendTextWithFileLinks( "/home/user/gradle/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/GradleRunnerFactoryTest.mytxtextension:24: unable to find class 'DistributionIntegrationTestRunner.class' for annotation attribute constant\n", fileLink1 );
+       appendTextWithoutFileLinks( " @ line 24, column 10.\n" );
+       appendTextWithoutFileLinks( "   @RunWith(DistributionIntegrationTestRunner.class)\n" );
+       appendTextWithFileLinks( "/home/user/gradle/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/GradleRunnerFactoryTest.othertxtextension: other error\n", fileLink2 );
+
+       //do this just to make sure adding our custom extension didn't break existing extensions
+       appendTextWithFileLinks( "/home/user/modules/gradle/subprojects/gradle-core/src/main/groovy/org/gradle/util/exec/ExecHandleShutdownHookAction.java:38: 'if' construct must use '{}'s.\n", fileLink3 );
+   }
+
+   /**
+   * This tests that you can dynamically add file prefixes to the parser.
+   * We're going to add 2 fake prefixes (one with line number delimiter, one without)
+   * then verify that the parser correctly parses the output with said prefixes.
+   */
+   public void testAddingPrefixedFileLink()
+   {
+       definitionLord.addPrefixedFileLink( "Test Crap 1", "Some Garbage:", ".txt", ":" );
+       definitionLord.addPrefixedFileLink( "Test Crap 2", "Some Trash:", ".txt", null );    //no line delimiter on this one
+
+       FileLink fileLink1 = new FileLink( new File( "/home/user/gradle/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/GradleRunnerFactoryTest.txt" ), 206, 325, 24 );
+       FileLink fileLink2 = new FileLink( new File( "/home/user/gradle/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/GradleRunnerFactoryTest.txt" ), 517, 633, -1 );
+       FileLink fileLink3 = new FileLink( new File( "/home/user/modules/gradle/subprojects/gradle-core/src/main/groovy/org/gradle/util/exec/ExecHandleShutdownHookAction.java" ), 651, 774, 38 );
+
+
+       appendTextWithoutFileLinks( ":distributionDiskResources SKIPPED\n" );
+       appendTextWithoutFileLinks( ":installDiskResources SKIPPED\n" );
+       appendTextWithoutFileLinks( ":idea-plugins:ideagradle:compileJava\n\n" );
+       appendTextWithoutFileLinks( "org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:\n" );
+       appendTextWithFileLinks( "Blah blah Some Garbage:/home/user/gradle/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/GradleRunnerFactoryTest.txt:24: unable to find class 'DistributionIntegrationTestRunner.class' for annotation attribute constant\n", fileLink1 );
+       appendTextWithoutFileLinks( " @ line 24, column 10.\n" );
+       appendTextWithoutFileLinks( "   @RunWith(DistributionIntegrationTestRunner.class)\n" );
+       appendTextWithFileLinks( "Blah Some Trash: /home/user/gradle/subprojects/gradle-open-api/src/integTest/groovy/org/gradle/integtests/GradleRunnerFactoryTest.txt Some other error\n", fileLink2 );
+
+       //do this just to make sure adding our prefixed links didn't break existing extensions
+       appendTextWithFileLinks( "/home/user/modules/gradle/subprojects/gradle-core/src/main/groovy/org/gradle/util/exec/ExecHandleShutdownHookAction.java:38: 'if' construct must use '{}'s.\n", fileLink3 );
+   }
+}
diff --git a/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/TestUtility.java b/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/TestUtility.java
new file mode 100644
index 0000000..778fbb2
--- /dev/null
+++ b/subprojects/gradle-ui/src/test/groovy/org/gradle/foundation/TestUtility.java
@@ -0,0 +1,450 @@
+/*
+ * 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.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;
+import org.gradle.foundation.ipc.gradle.ExecuteGradleCommandServerProtocol;
+import org.gradle.gradleplugin.foundation.DOM4JSerializer;
+import org.gradle.gradleplugin.foundation.GradlePluginLord;
+import org.gradle.gradleplugin.foundation.request.ExecutionRequest;
+import org.gradle.gradleplugin.foundation.request.RefreshTaskListRequest;
+import org.gradle.gradleplugin.foundation.request.Request;
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+
+import javax.swing.filechooser.FileFilter;
+import java.io.File;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Utility class for initializing various test objects related.
+ *
+ * @author mhunsicker
+ */
+public class TestUtility {
+    private static long uniqueNameCounter = 1; //used to make unique names for JMock objects.
+
+    /**
+     * Creates a mock project with the specified properties.
+     *
+     * Note: depth is 0 for a root project. 1 for a root project's subproject, etc.
+    */
+    public static Project createMockProject(JUnit4Mockery context, final String name, final String buildFilePath, final int depth, Project[] subProjectArray, Task[] tasks, String[] defaultTasks, Project... dependsOnProjects) {
+        final Project project = context.mock(Project.class, "[project]_" + name + '_' + uniqueNameCounter++);
+
+        context.checking(new Expectations() {{
+            allowing(project).getName();
+            will(returnValue(name));
+            allowing(project).getBuildFile();
+            will(returnValue(new File(buildFilePath)));
+            allowing(project).getDepth();
+            will(returnValue(depth));
+        }});
+
+        attachSubProjects(context, project, subProjectArray);
+        attachTasks(context, project, tasks);
+        assignDefaultTasks(context, project, defaultTasks);
+        assignDependsOnProjects(context, project, dependsOnProjects);
+
+        return project;
+    }
+
+    /**
+     * This makes the sub projects children of the parent project. If you call this repeatedly on the same
+     * parentProject, any previous sub projects will be replaced with the new ones.
+     *
+     * @param context the mock context
+     * @param parentProject where to attach the sub projects. This must be a mock object.
+     * @param subProjectArray the sub projects to attach to the parent. These must be mock objects. Pass in null or an
+     * empty array to set no sub projects.
+    */
+    public static void attachSubProjects(JUnit4Mockery context, final Project parentProject, Project... subProjectArray) {
+        final Set<Project> set = new LinkedHashSet<Project>();   //using a LinkedHashSet rather than TreeSet (which is what gradle uses) so I don't have to deal with compareTo() being called on mock objects.
+
+        if (subProjectArray != null && subProjectArray.length != 0) {
+            set.addAll(Arrays.asList(subProjectArray));
+
+            //set the parent project of the sub projects
+            for (int index = 0; index < subProjectArray.length; index++) {
+                final Project subProject = subProjectArray[index];
+
+                context.checking(new Expectations() {{
+                    allowing(subProject).getParent();
+                    will(returnValue(parentProject));
+                }});
+            }
+        }
+
+        //populate the subprojects (this may be an empty set)
+        context.checking(new Expectations() {{
+            allowing(parentProject).getSubprojects();
+            will(returnValue(set));
+        }});
+    }
+
+    /**
+     * Creates a mock task with the specified properites.
+    */
+    public static Task createTask(JUnit4Mockery context, final String name, final String description) {
+        final Task task = context.mock(Task.class, "[task]_" + name + '_' + uniqueNameCounter++);
+
+        context.checking(new Expectations() {{
+            allowing(task).getName();
+            will(returnValue(name));
+            allowing(task).getDescription();
+            will(returnValue(description));
+        }});
+
+        return task;
+    }
+
+    /**
+     * This makes the tasks children of the parent project. If you call this repeatedly on the same parentProject, any
+     * previous tasks will be replaced with the new ones.
+     *
+     * @param context the mock context
+     * @param parentProject where to attach the sub projects. This must be a mock object.
+     * @param taskArray the tasks to attach to the parent. these must be mock objects. Pass in null or an empty array to
+     * set no tasks.
+    */
+    public static void attachTasks(JUnit4Mockery context, final Project parentProject, Task... taskArray) {
+        //first, make our project return our task container
+        final TaskContainer taskContainer = context.mock(TaskContainer.class, "[taskcontainer]_" + parentProject.getName() + '_' + uniqueNameCounter++);
+
+        context.checking(new Expectations() {{
+            allowing(parentProject).getTasks();
+            will(returnValue(taskContainer));
+        }});
+
+        final Set<Task> set = new LinkedHashSet<Task>();   //using a LinkedHashSet rather than TreeSet (which is what gradle uses) so I don't have to deal with compareTo() being called on mock objects.
+
+        if (taskArray != null && taskArray.length != 0) {
+            set.addAll(Arrays.asList(taskArray));
+
+            //set the parent project of the tasks
+            for (int index = 0; index < taskArray.length; index++) {
+                final Task task = taskArray[index];
+
+                context.checking(new Expectations() {{
+                    allowing(task).getProject();
+                    will(returnValue(parentProject));
+                }});
+            }
+        }
+
+        //populate the task container (this may be an empty set)
+        context.checking(new Expectations() {{
+            allowing(taskContainer).getAll();
+            will(returnValue(set));
+        }});
+    }
+
+    private static void assignDefaultTasks(JUnit4Mockery context, final Project project, final String... defaultTasksArray) {
+        final List<String> defaultTaskList = new ArrayList<String>();
+
+        if (defaultTasksArray != null && defaultTasksArray.length != 0) {
+            defaultTaskList.addAll(Arrays.asList(defaultTasksArray));
+        }
+
+        context.checking(new Expectations() {{
+            allowing(project).getDefaultTasks();
+            will(returnValue(defaultTaskList));
+        }});
+    }
+
+    private static void assignDependsOnProjects(JUnit4Mockery context, final Project project, final Project... dependsOnProjects) {
+        final Set<Project> set = new LinkedHashSet<Project>();   //using a LinkedHashSet rather than TreeSet (which is what gradle uses) so I don't have to deal with compareTo() being called on mock objects.
+
+        if (dependsOnProjects != null && dependsOnProjects.length != 0) {
+            set.addAll(Arrays.asList(dependsOnProjects));
+        }
+
+        //populate the subprojects (this may be an empty set)
+        context.checking(new Expectations() {{
+            allowing(project).getDependsOnProjects();
+            will(returnValue(set));
+        }});
+    }
+
+    public static <T> void assertListContents(List<T> actualObjects, T... expectedObjectsArray) {
+        assertListContents(actualObjects, Arrays.asList(expectedObjectsArray));
+    }
+
+    public static <T> void assertListContents(List<T> actualObjects, List<T> expectedObjects) {
+        assertUnorderedListContents(actualObjects, expectedObjects);
+    }
+
+    /**
+     * This asserts the contents of the list are as expected. The important aspect of this function is that we don't
+     * care about ordering. We just want to make sure the contents are the same.
+     *
+     * @param actualObjecs the list to check
+     * @param expectedObjects what we expect in the list
+    */
+    public static <T> void assertUnorderedListContents(List<T> actualObjecs, List<T> expectedObjects) {
+        List<T> expectedObjecsList = new ArrayList<T>(expectedObjects);   //make a copy of it, so we can modify it.
+
+        while (!expectedObjecsList.isEmpty()) {
+            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));
+        }
+        }
+
+        if (actualObjecs.size() != expectedObjects.size()) {
+            throw new AssertionFailedError(
+                    "Expected " + expectedObjects.size() + " items but found " + actualObjecs.size() + "\nExpected:\n"
+                            + dumpList(expectedObjects) + "\nActual:\n" + dumpList(actualObjecs));
+    }
+    }
+
+    //function for getting a prettier dump of a list.
+
+    public static String dumpList(List list) {
+        if (list == null) {
+            return "[null]";
+        }
+        if (list.isEmpty()) {
+            return "[empty]";
+        }
+
+        StringBuilder builder = new StringBuilder();
+        Iterator iterator = list.iterator();
+        while (iterator.hasNext()) {
+            Object object = iterator.next();
+            if (object == null) {
+                builder.append("**** [null object in list] ****\n");
+            } else {
+                builder.append(object.toString()).append('\n');
+        }
+        }
+
+        return builder.toString();
+    }
+
+    /**
+     * This is an ExportInteraction implemention meant to be used by tests. You pass it a file to use and we'll return
+     * that in promptForFile. This also checks to ensure something doesn't happen where we get into an endless loop if
+     * promptForFile is called repeatedly. This can happen if promptForFile is called and its return value fails some
+     * form of validation which makes promptForFile get called again or if you deny overwriting the file. You'll get
+     * prompted again.
+    */
+    public static class TestExportInteraction implements DOM4JSerializer.ExportInteraction {
+        private File file;
+        private boolean confirmOverwrite;
+        private int promptCount;
+
+        public TestExportInteraction(File file, boolean confirmOverwrite) {
+            this.file = file;
+            this.confirmOverwrite = confirmOverwrite;
+        }
+
+        public File promptForFile(FileFilter fileFilters) {
+            if (promptCount == 100) {
+                throw new AssertionFailedError("Possible endless loop. PromptForFile has been called 100 times.");
+            }
+
+            promptCount++;
+            return file;
+        }
+
+        /**
+         * The file already exists. Confirm whether or not you want to overwrite it.
+         *
+         * @param file the file in question
+         * @return true to overwrite it, false not to.
+        */
+        public boolean confirmOverwritingExisingFile(File file) {
+            return confirmOverwrite;
+        }
+
+        public void reportError(String error) {
+            throw new AssertionFailedError("Unexpected error: " + error);
+        }
+    }
+
+    /**
+     * This is an ImportInteraction implemention meant to be used by tests. See TestExportInteraction for more
+     * information.
+    */
+    public static class TestImportInteraction implements DOM4JSerializer.ImportInteraction {
+        private File file;
+        private int promptCount;
+
+        public TestImportInteraction(File file) {
+            this.file = file;
+        }
+
+        public File promptForFile(FileFilter fileFilters) {
+            if (promptCount == 100) {
+                throw new AssertionFailedError("Possible endless loop. PromptForFile has been called 100 times.");
+            }
+
+            promptCount++;
+            return file;
+        }
+
+        public void reportError(String error) {
+            throw new AssertionFailedError("Unexpected error: " + error);
+        }
+    }
+
+    //wrapper around File.createTempFile just so we don't have to deal with the exception for tests.
+
+    /**
+     * This refreshes the projects but blocks until it is complete (its being executed in a separate process).
+     *
+     * @param gradlePluginLord the plugin lord (will be used to execute the command and store the results).
+     * @param maximumWaitSeconds how many seconds to wait before considering this a failure.
+    */
+    public static void refreshProjectsBlocking(GradlePluginLord gradlePluginLord, int maximumWaitSeconds) {
+        refreshProjectsBlocking(gradlePluginLord, new ExecuteGradleCommandServerProtocol.ExecutionInteraction() {
+            public void reportExecutionStarted() {
+            }
+
+           /**
+            Notification of the total number of tasks that will be executed. This is
+            called after reportExecutionStarted and before any tasks are executed.
+
+            @param size the total number of tasks.
+            */
+           public void reportNumberOfTasksToExecute( int size ) {
+           }
+
+           public void reportExecutionFinished(boolean wasSuccessful, String message, Throwable throwable) {
+            }
+
+            public void reportTaskStarted(String message, float percentComplete) {
+            }
+
+            public void reportTaskComplete(String message, float percentComplete) {
+            }
+
+            public void reportLiveOutput(String message) {
+            }
+        }, maximumWaitSeconds);
+    }
+
+    public static void refreshProjectsBlocking(GradlePluginLord gradlePluginLord, final ExecuteGradleCommandServerProtocol.ExecutionInteraction executionInteraction, int maximumWaitSeconds) {
+        gradlePluginLord.startExecutionQueue();   //make sure its started
+
+        final AtomicBoolean isComplete = new AtomicBoolean();
+
+        GradlePluginLord.RequestObserver observer = new GradlePluginLord.RequestObserver() {
+           public void executionRequestAdded( ExecutionRequest request ) {}
+           public void refreshRequestAdded( RefreshTaskListRequest request )
+           {
+              request.setExecutionInteraction( executionInteraction );
+           }
+           public void aboutToExecuteRequest( Request request ) { }
+
+           public void requestExecutionComplete( Request request, int result, String output ) {
+               isComplete.set(true);
+           }
+        };
+
+        gradlePluginLord.addRequestObserver( observer, false );   //add the observer before we add the request due to timing issues. It's possible for it to completely execute before we return from addRefreshRequestToQueue.
+        Request request = gradlePluginLord.addRefreshRequestToQueue();
+
+        //make sure we've got a request
+        Assert.assertNotNull(request);
+
+        //now sleep until we're complete, but bail if we wait too long
+        int totalWaitTime = 0;
+        while (!isComplete.get() && totalWaitTime <= maximumWaitSeconds) {
+            try {
+                Thread.sleep(1000);
+            }
+            catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+
+            totalWaitTime += 1;
+        }
+
+        gradlePluginLord.removeRequestObserver( observer );
+
+        if (!isComplete.get()) //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: " + maximumWaitSeconds + " seconds. Considering this failed.");
+        }
+    }
+
+   /**
+    This executes a command and waits until it is finished.
+
+    @param gradlePluginLord the plugin lord
+    @param fullCommandLine the command to execute
+    @param displayName the display name of the command. It doesn't usuall matter.
+    @param executionInteraction this gets the results of the execution
+    @param maximumWaitSeconds the maximum time to wait before considering it failed.
+    */
+    public static void executeBlocking(GradlePluginLord gradlePluginLord, String fullCommandLine, String displayName, final ExecuteGradleCommandServerProtocol.ExecutionInteraction executionInteraction, int maximumWaitSeconds) {
+        gradlePluginLord.startExecutionQueue();   //make sure its started
+
+        final AtomicBoolean isComplete = new AtomicBoolean();
+
+        GradlePluginLord.RequestObserver observer = new GradlePluginLord.RequestObserver() {
+           public void executionRequestAdded( ExecutionRequest request )
+           {
+              request.setExecutionInteraction( executionInteraction );
+           }
+           public void refreshRequestAdded( RefreshTaskListRequest request ) { }
+           public void aboutToExecuteRequest( Request request ) { }
+
+           public void requestExecutionComplete( Request request, int result, String output ) {
+               isComplete.set(true);
+           }
+        };
+
+        gradlePluginLord.addRequestObserver( observer, false );   //add the observer before we add the request due to timing issues. It's possible for it to completely execute before we return from addExecutionRequestToQueue.
+        Request request = gradlePluginLord.addExecutionRequestToQueue( fullCommandLine, displayName );
+
+        //make sure we've got a request
+        Assert.assertNotNull(request);
+
+        //now sleep until we're complete, but bail if we wait too long
+        int totalWaitTime = 0;
+        while (!isComplete.get() && totalWaitTime <= maximumWaitSeconds) {
+            try {
+                Thread.sleep(1000);
+            }
+            catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+
+            totalWaitTime += 1;
+        }
+
+        gradlePluginLord.removeRequestObserver( observer );
+
+        if (!isComplete.get()) //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.");
+        }
+    }
+}
+
diff --git a/subprojects/gradle-ui/src/test/groovy/org/gradle/integtests/FavoritesIntegrationTest.java b/subprojects/gradle-ui/src/test/groovy/org/gradle/integtests/FavoritesIntegrationTest.java
new file mode 100644
index 0000000..4697861
--- /dev/null
+++ b/subprojects/gradle-ui/src/test/groovy/org/gradle/integtests/FavoritesIntegrationTest.java
@@ -0,0 +1,396 @@
+/*
+ * 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.api.Project;
+import org.gradle.api.Task;
+import org.gradle.foundation.BuildInformation;
+import org.gradle.foundation.ProjectView;
+import org.gradle.foundation.TaskView;
+import org.gradle.foundation.TestUtility;
+import org.gradle.gradleplugin.foundation.favorites.FavoriteTask;
+import org.gradle.gradleplugin.foundation.favorites.FavoritesEditor;
+import org.gradle.util.TemporaryFolder;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import javax.swing.filechooser.FileFilter;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Performs integration tests on favorite tasks.
+ *
+ * @author mhunsicker
+ */
+public class FavoritesIntegrationTest {
+    @Rule
+    public final TemporaryFolder tempDir = new TemporaryFolder();
+    private BuildInformation buildInformation;
+
+    private ProjectView myRootProject;
+
+    private ProjectView mySubProject1;
+    private TaskView mySubProject1Comple;
+    private TaskView mySubProject1Lib;
+    private TaskView mySubProject1Doc;
+
+    private ProjectView mySubSubProject;
+    private TaskView mySubSubProjectCompile;
+    private TaskView mySubSubProjectLib;
+    private TaskView mySubSubProjectDoc;
+
+    private ProjectView mySubProject2;
+    private TaskView mySubProject2Lib;
+    private TaskView mySubProject2doc;
+    private TaskView mySubProject2Compile;
+    private JUnit4Mockery context;
+
+    @Before
+    public void setUp() throws Exception {
+        context = new JUnit4Mockery();
+
+        Task subsubCompileTask = TestUtility.createTask(context, "compile", "compile description");
+        Task subsubLibTask = TestUtility.createTask(context, "lib", "lib description");
+        Task subsubDocTask = TestUtility.createTask(context, "doc", "doc description");
+        Project subsubProject = TestUtility.createMockProject(context, "mysubsubproject", "filepath3", 2, null,
+                new Task[]{subsubCompileTask, subsubLibTask, subsubDocTask}, null, (Project[]) null);
+
+        Task subCompileTask1 = TestUtility.createTask(context, "compile", "compile description");
+        Task subLibTask1 = TestUtility.createTask(context, "lib", "lib description");
+        Task subDocTask1 = TestUtility.createTask(context, "doc", "doc description");
+        Project subProject1 = TestUtility.createMockProject(context, "mysubproject1", "filepath2a", 1,
+                new Project[]{subsubProject}, new Task[]{subCompileTask1, subLibTask1, subDocTask1}, null,
+                (Project[]) null);
+
+        Task subCompileTask2 = TestUtility.createTask(context, "compile", "compile description");
+        Task subLibTask2 = TestUtility.createTask(context, "lib", "lib description");
+        Task subDocTask2 = TestUtility.createTask(context, "doc", "doc description");
+        Project subProject2 = TestUtility.createMockProject(context, "mysubproject2", "filepath2b", 1, null,
+                new Task[]{subCompileTask2, subLibTask2, subDocTask2}, null, (Project[]) null);
+
+        Project rootProject = TestUtility.createMockProject(context, "myrootproject", "filepath1", 0,
+                new Project[]{subProject1, subProject2}, null, null, (Project[]) null);
+
+        buildInformation = new BuildInformation(rootProject);
+
+        //now get the converted objects to simplify our matching
+        myRootProject = buildInformation.getProjectFromFullPath("myrootproject");
+        Assert.assertNotNull(myRootProject);
+        mySubProject1 = buildInformation.getProjectFromFullPath("myrootproject:mysubproject1");
+        Assert.assertNotNull(mySubProject1);
+        mySubProject1Comple = buildInformation.getTaskFromFullPath("myrootproject:mysubproject1:compile");
+        Assert.assertNotNull(mySubProject1Comple);
+        mySubProject1Lib = buildInformation.getTaskFromFullPath("myrootproject:mysubproject1:lib");
+        Assert.assertNotNull(mySubProject1Lib);
+        mySubProject1Doc = buildInformation.getTaskFromFullPath("myrootproject:mysubproject1:doc");
+        Assert.assertNotNull(mySubProject1Doc);
+        mySubSubProject = buildInformation.getProjectFromFullPath("myrootproject:mysubproject1:mysubsubproject");
+        Assert.assertNotNull(mySubSubProject);
+        mySubSubProjectCompile = buildInformation.getTaskFromFullPath(
+                "myrootproject:mysubproject1:mysubsubproject:compile");
+        Assert.assertNotNull(mySubSubProjectCompile);
+        mySubSubProjectLib = buildInformation.getTaskFromFullPath("myrootproject:mysubproject1:mysubsubproject:lib");
+        Assert.assertNotNull(mySubSubProjectLib);
+        mySubSubProjectDoc = buildInformation.getTaskFromFullPath("myrootproject:mysubproject1:mysubsubproject:doc");
+        Assert.assertNotNull(mySubSubProjectDoc);
+        mySubProject2 = buildInformation.getProjectFromFullPath("myrootproject:mysubproject2");
+        Assert.assertNotNull(mySubProject2);
+        mySubProject2Compile = buildInformation.getTaskFromFullPath("myrootproject:mysubproject2:compile");
+        Assert.assertNotNull(mySubProject2Compile);
+        mySubProject2Lib = buildInformation.getTaskFromFullPath("myrootproject:mysubproject2:lib");
+        Assert.assertNotNull(mySubProject2Lib);
+        mySubProject2doc = buildInformation.getTaskFromFullPath("myrootproject:mysubproject2:doc");
+        Assert.assertNotNull(mySubProject2doc);
+    }
+
+    /**
+     * This creates favorites, saves them to a file, then reads them from that file.
+     */
+    @Test
+    public void testSavingRestoringFavorites() {
+        FavoritesEditor originalEditor = new FavoritesEditor();
+
+        Assert.assertTrue(originalEditor.getFavoriteTasks().isEmpty());
+
+        //add two tasks
+        FavoriteTask favoriteTask1 = originalEditor.addFavorite(mySubProject1Comple, true);
+        FavoriteTask favoriteTask2 = originalEditor.addFavorite(mySubSubProjectLib, false);
+
+        //now change the display name so its not the same as the full name, just so know each field is working.
+        originalEditor.editFavorite(favoriteTask1, new FavoritesEditor.EditFavoriteInteraction() {
+            public boolean editFavorite(FavoritesEditor.EditibleFavoriteTask favoriteTask) {
+                favoriteTask.displayName = "favorite 1";
+                return true;
+            }
+
+            public void reportError(String error) {
+                throw new AssertionFailedError("Unexpected error");
+            }
+        });
+
+        //make sure they were added properly
+        FavoriteTask originalFavoriteTask1 = originalEditor.getFavoriteTasks().get(0);
+        assertFavorite(originalFavoriteTask1, "mysubproject1:compile", "favorite 1", true);
+
+        FavoriteTask originalFavoriteTask2 = originalEditor.getFavoriteTasks().get(1);
+        assertFavorite(originalFavoriteTask2, "mysubproject1:mysubsubproject:lib", "mysubproject1:mysubsubproject:lib",
+                false);
+
+        File file = tempDir.createFile("fred.favorite-tasks");
+        originalEditor.exportToFile(new TestUtility.TestExportInteraction(file,
+                true)); //confirm overwrite because the above function actually creates the file.
+
+        FavoritesEditor newEditor = new FavoritesEditor();
+        newEditor.importFromFile(new TestUtility.TestImportInteraction(file));
+
+        //make sure they're the same
+        FavoriteTask readInFavoriteTask1 = originalEditor.getFavoriteTasks().get(0);
+        assertFavorite(readInFavoriteTask1, originalFavoriteTask1);
+
+        FavoriteTask readInFavoriteTask2 = originalEditor.getFavoriteTasks().get(1);
+        assertFavorite(readInFavoriteTask2, originalFavoriteTask2);
+    }
+
+    /**
+     * This verifies that the serialization mechananism corrects the extension so that it is correct. We'll save a file
+     * with the wrong extension. The save mechanism should save it with the correct extension appended to the end
+     * (leaving the wrong extension in tact, just not at the end).
+     */
+    @Test
+    public void testEnsureFileHasCorrectExtension() {
+        FavoritesEditor originalEditor = new FavoritesEditor();
+
+        Assert.assertTrue(originalEditor.getFavoriteTasks().isEmpty());
+
+        //add a favorite
+        FavoriteTask favoriteTask1 = originalEditor.addFavorite(mySubProject1Comple, true);
+
+        //specify a wrong extension. It should actually end in ".favorite-tasks"
+        File incorrectFile = tempDir.createFile("fred.wrong");
+        File correctFile = new File(incorrectFile.getParentFile(), incorrectFile.getName() + ".favorite-tasks");
+
+        //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.");
+        }
+
+        //do the export
+        originalEditor.exportToFile(new TestUtility.TestExportInteraction(incorrectFile,
+                true)); //confirm overwrite because the above function actually creates the file.
+
+        //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()
+                            + "'");
+        }
+
+        //now read in the file to verify it actually worked.
+        FavoritesEditor newEditor = new FavoritesEditor();
+        newEditor.importFromFile(new TestUtility.TestImportInteraction(correctFile));
+
+        FavoriteTask readInFavoriteTask = newEditor.getFavoriteTasks().get(0);
+        assertFavorite(readInFavoriteTask, favoriteTask1);
+    }
+
+    private void assertFavorite(FavoriteTask favoriteTaskToTest, String expectedFullTaskName,
+                                String expectedDisplayName, boolean expectedAlwaysShowOutput) {
+        Assert.assertEquals(expectedFullTaskName, favoriteTaskToTest.getFullCommandLine());
+        Assert.assertEquals(expectedDisplayName, favoriteTaskToTest.getDisplayName());
+        Assert.assertEquals(expectedAlwaysShowOutput, favoriteTaskToTest.alwaysShowOutput());
+    }
+
+    private void assertFavorite(FavoriteTask favoriteTaskToTest, FavoriteTask expectedFavoriteTask) {
+        assertFavorite(favoriteTaskToTest, expectedFavoriteTask.getFullCommandLine(),
+                expectedFavoriteTask.getDisplayName(), expectedFavoriteTask.alwaysShowOutput());
+    }
+
+    /**
+     * This confirms that overwriting a file requires confirmation. We'll create a file (just by creating a temporary
+     * file), then try to save to it.
+     */
+    @Test
+    public void testConfirmOverwrite() {  //we should be prompted to confirm overwriting an existing file.
+
+        FavoritesEditor originalEditor = new FavoritesEditor();
+
+        Assert.assertTrue(originalEditor.getFavoriteTasks().isEmpty());
+
+        //add a favorite
+        FavoriteTask favoriteTask1 = originalEditor.addFavorite(mySubProject1Comple, true);
+
+        File file = tempDir.createFile("test.favorite-tasks");
+
+        //make sure the file exists, so we know our save will be overwritting something.
+        Assert.assertTrue(file.exists());
+
+        long originalSize = file.length();
+
+        TestOverwriteConfirmExportInteraction exportInteraction = new TestOverwriteConfirmExportInteraction(file,
+                false);
+
+        //do the export
+        originalEditor.exportToFile(exportInteraction);
+
+        //make sure we were prompted to confirm overwriting
+        Assert.assertTrue(exportInteraction.wasConfirmed);
+
+        //make sure the size didn't change. This means we didn't write to it.
+        Assert.assertEquals(originalSize, file.length());
+    }
+
+    /**
+     * This exists soley so we can track if confirmOverwritingExisingFile was called.
+     */
+    private class TestOverwriteConfirmExportInteraction extends TestUtility.TestExportInteraction {
+        public boolean wasConfirmed;
+
+        private TestOverwriteConfirmExportInteraction(File file, boolean confirmOverwrite) {
+            super(file, confirmOverwrite);
+        }
+
+        public File promptForFile(FileFilter fileFilters) {
+            if (wasConfirmed)   //once we confirm it, just return null.
+            {
+                return null;
+            }
+
+            return super.promptForFile(fileFilters);
+        }
+
+        /**
+         * The file already exists. Confirm whether or not you want to overwrite it.
+         *
+         * @param file the file in question
+         * @return true to overwrite it, false not to.
+         */
+        @Override
+        public boolean confirmOverwritingExisingFile(File file) {
+            wasConfirmed = true;
+            return false;
+        }
+    }
+
+    /**
+     * This tests duplicating a single favorite. First, we'll create some, then duplicate one.
+     */
+    @Test
+    public void testDuplicateSingleFavorite() {
+        FavoritesEditor editor = new FavoritesEditor();
+
+        //add two tasks
+        FavoriteTask favoriteTask1 = editor.addFavorite(mySubProject1Comple, true);
+        FavoriteTask favoriteTask2 = editor.addFavorite(mySubSubProjectLib, false);
+        FavoriteTask favoriteTask3 = editor.addFavorite(mySubSubProjectDoc, false);
+
+        //now change the display names and the alwaysShowOutput field, just so we can verify that all fields are copied.
+        editFavorite(editor, favoriteTask1, "name1", false );
+        editFavorite(editor, favoriteTask2, "name2", true );
+        editFavorite(editor, favoriteTask3, "name3", false );
+
+        //duplicate a single task
+        FavoriteTask favoriteTask4 = editor.duplicateFavorite( favoriteTask1 );
+        Assert.assertNotNull( favoriteTask4 );
+        Assert.assertEquals( favoriteTask1.getFullCommandLine(), favoriteTask4.getFullCommandLine() );
+        Assert.assertEquals( favoriteTask1.getDisplayName(), favoriteTask4.getDisplayName() );
+        Assert.assertEquals( favoriteTask1.alwaysShowOutput(), favoriteTask4.alwaysShowOutput() );
+
+        //there should be 4 tasks now
+        Assert.assertEquals( 4, editor.getFavoriteTasks().size() );
+
+        //now duplicate another one
+        FavoriteTask favoriteTask5 = editor.duplicateFavorite( favoriteTask2 );
+        Assert.assertNotNull(favoriteTask5);
+        Assert.assertEquals( favoriteTask2.getFullCommandLine(), favoriteTask5.getFullCommandLine() );
+        Assert.assertEquals( favoriteTask2.getDisplayName(), favoriteTask5.getDisplayName() );
+        Assert.assertEquals( favoriteTask2.alwaysShowOutput(), favoriteTask5.alwaysShowOutput() );
+
+        //there should be 5 tasks now
+        Assert.assertEquals( 5, editor.getFavoriteTasks().size() );
+    }
+
+    /**
+     * This tests duplicating multiple favorites at once. First, we'll create some, then duplicate them.
+     */
+    @Test
+    public void testDuplicatingMultipleFavorites() {
+        FavoritesEditor editor = new FavoritesEditor();
+
+        //add two tasks
+        FavoriteTask favoriteTask1 = editor.addFavorite(mySubProject1Comple, true);
+        FavoriteTask favoriteTask2 = editor.addFavorite(mySubSubProjectLib, false);
+        FavoriteTask favoriteTask3 = editor.addFavorite(mySubSubProjectDoc, false);
+
+        //now change the display names and the alwaysShowOutput field, just so we can verify that all fields are copied.
+        editFavorite(editor, favoriteTask1, "name1", false );
+        editFavorite(editor, favoriteTask2, "name2", true );
+        editFavorite(editor, favoriteTask3, "name3", false );
+
+        //get the ones to dupicate in a list
+        List<FavoriteTask> tasksToCopy = new ArrayList<FavoriteTask>();
+        tasksToCopy.add( favoriteTask1 );
+        tasksToCopy.add( favoriteTask2 );
+
+        //now peform the duplication
+        editor.duplicateFavorites( tasksToCopy );
+
+        //there should be 5 tasks now
+        Assert.assertEquals( 5, editor.getFavoriteTasks().size() );
+
+        //the 4th one (3 from index 0) should be the same as the first one
+        FavoriteTask favoriteTask4 = editor.getFavoriteTasks().get( 3 );
+
+        Assert.assertNotNull( favoriteTask4 );
+        Assert.assertEquals( favoriteTask1.getFullCommandLine(), favoriteTask4.getFullCommandLine() );
+        Assert.assertEquals( favoriteTask1.getDisplayName(), favoriteTask4.getDisplayName() );
+        Assert.assertEquals( favoriteTask1.alwaysShowOutput(), favoriteTask4.alwaysShowOutput() );
+
+        //the 5th one (4 from index 0) should be the same as the second one
+        FavoriteTask favoriteTask5 = editor.getFavoriteTasks().get( 4 );
+        Assert.assertNotNull(favoriteTask5);
+        Assert.assertEquals( favoriteTask2.getFullCommandLine(), favoriteTask5.getFullCommandLine() );
+        Assert.assertEquals( favoriteTask2.getDisplayName(), favoriteTask5.getDisplayName() );
+        Assert.assertEquals( favoriteTask2.alwaysShowOutput(), favoriteTask5.alwaysShowOutput() );       
+    }
+
+    /**
+    This sets the display name of the favorite task to the specified new name.
+     */
+    private void editFavorite( FavoritesEditor editor, FavoriteTask favoriteTask, final String newDisplayName, final boolean newAlwaysShowOutput )
+    {
+       editor.editFavorite( favoriteTask, new FavoritesEditor.EditFavoriteInteraction() {
+            public boolean editFavorite(FavoritesEditor.EditibleFavoriteTask favoriteTask) {
+                favoriteTask.displayName = newDisplayName;
+                favoriteTask.alwaysShowOutput = newAlwaysShowOutput;
+                return true;
+            }
+
+            public void reportError(String error) {
+                throw new AssertionFailedError("Unexpected error");
+            }
+        });
+
+        Assert.assertEquals( newDisplayName, favoriteTask.getDisplayName() );
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-ui/src/test/groovy/org/gradle/integtests/LiveOutputIntegrationTest.groovy b/subprojects/gradle-ui/src/test/groovy/org/gradle/integtests/LiveOutputIntegrationTest.groovy
new file mode 100644
index 0000000..6782090
--- /dev/null
+++ b/subprojects/gradle-ui/src/test/groovy/org/gradle/integtests/LiveOutputIntegrationTest.groovy
@@ -0,0 +1,182 @@
+/*
+ * 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
+
+import org.gradle.foundation.TestUtility
+import org.gradle.foundation.ipc.gradle.ExecuteGradleCommandServerProtocol
+import org.gradle.gradleplugin.foundation.GradlePluginLord
+import org.gradle.gradleplugin.foundation.runner.GradleRunner
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.Sample
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+This tests the that live output is gathered while executing a task.
+ at author mhunsicker
+*/
+ at RunWith(DistributionIntegrationTestRunner.class)
+class LiveOutputIntegrationTest {
+
+    static final String JAVA_PROJECT_NAME = 'javaproject'
+    static final String SHARED_NAME = 'shared'
+    static final String API_NAME = 'api'
+    static final String WEBAPP_NAME = 'webservice'
+    static final String SERVICES_NAME = 'services'
+    static final String WEBAPP_PATH = "$SERVICES_NAME/$WEBAPP_NAME" as String
+
+    private File javaprojectDir
+
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('java/quickstart')
+
+    @Before
+    void setUp() {
+        javaprojectDir = sample.dir
+    }
+
+    /**
+This executes 'build' on the java multiproject sample. We want to make sure that
+we do get live output from gradle. We're not concerned with what it is, because
+that's likely to change over time. This version executes the command via GradlePlugin.
+
+ at author mhunsicker
+*/
+
+    @Test
+    public void liveOutputObtainedViaGradlePlugin() {
+       System.out.println("project dir: " + javaprojectDir );
+        // Build and test projects
+        executer.inDirectory(javaprojectDir).withTasks('assemble').run();
+
+        File multiProjectDirectory = sample.getDir();
+        Assert.assertTrue(multiProjectDirectory.exists()); //make sure things are setup the way we expect
+
+        GradlePluginLord gradlePluginLord = new GradlePluginLord();
+        gradlePluginLord.setCurrentDirectory(multiProjectDirectory);
+        gradlePluginLord.setGradleHomeDirectory(dist.gradleHomeDir);
+
+        gradlePluginLord.startExecutionQueue(); //for tests, we'll need to explicitly start the execution queue (unless we do a refresh via the TestUtility).
+
+        TestExecutionInteraction executionInteraction = new TestExecutionInteraction();
+
+        //execute a command. We don't really care what the command is, just something that generates output
+        TestUtility.executeBlocking( gradlePluginLord, "-t", "Test Execution", executionInteraction, 45 )
+
+        verifyLiveOutputObtained( executionInteraction );
+    }
+
+    /**
+This executes 'build' on the java multiproject sample. We want to make sure that
+we do get live output from gradle. We're not concerned with what it is, because
+that's likely to change over time. This version executes the command via GradleRunner.
+
+ at author mhunsicker
+*/
+    @Test
+    public void liveOutputObtainedViaGradleRunner() {
+        // Build and test projects
+        executer.inDirectory(javaprojectDir).withTasks('assemble').run();
+
+        File multiProjectDirectory = sample.getDir();
+        Assert.assertTrue(multiProjectDirectory.exists()); //make sure things are setup the way we expect
+
+        GradleRunner gradleRunner = new GradleRunner( multiProjectDirectory, dist.gradleHomeDir, null );
+
+        TestExecutionInteraction executionInteraction = new TestExecutionInteraction();
+
+        //execute a command. We don't really care what the command is, just something that generates output
+        gradleRunner.executeCommand("-t", org.gradle.api.logging.LogLevel.LIFECYCLE,
+                                            org.gradle.StartParameter.ShowStacktrace.INTERNAL_EXCEPTIONS,
+                                            executionInteraction);
+
+        //now sleep until we're complete, but bail if we wait too long
+        int maximumWaitSeconds = 45; //this is totally arbitrary and is worse case senario.
+        int totalWaitTime = 0;
+        while( !executionInteraction.executionFinishedReported && totalWaitTime <= maximumWaitSeconds) {
+            try {
+               println "Waiting. Has Finished: " + executionInteraction.executionFinishedReported + ". Total Time: "+ totalWaitTime
+                Thread.sleep(1000);
+            }
+            catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+
+            totalWaitTime += 1;
+        }
+
+        verifyLiveOutputObtained( executionInteraction );
+    }
+
+
+
+   /**
+  This verifies that it has live output. It also checks that we received some final output as well
+  as that the execution was successful
+  */
+   private void verifyLiveOutputObtained( TestExecutionInteraction executionInteraction )
+   {
+      //Make sure we were successful. If we weren't successful, that probably indicates a different problem and the test itself may be invalid.
+      Assert.assertTrue( "Verifying execution was successful failed", executionInteraction.wasSuccessful )
+
+      //verify that we actually finished. If not, then we timed out, which may mean the machine is really slow or that there's a serious problem.
+      Assert.assertTrue( "Verifying execution finished in a timely manner", executionInteraction.executionFinishedReported );
+
+      //make sure we received some output! I just made up 30 because I wanted more than just 1 character and there should actually be dozens of characters.
+      Assert.assertTrue( "Verifying live output was obtained", executionInteraction.liveOutput.length() >= 30 )
+
+      //We should also get a final message. Note: this is usually a little different from the live output, if for not other reason than
+      //timing issues of when the last live output is sent. The final message should have everything, but we might not get the last
+      //live output. As such, we won't verify they're equal.
+      Assert.assertTrue( "Verifying the final output message was received", executionInteraction.finalMessage.length() > 30 )
+   }
+}
+
+//this class just holds onto our liveOutput and also tracks whether or not we've finished.
+public class TestExecutionInteraction implements ExecuteGradleCommandServerProtocol.ExecutionInteraction
+{
+   public StringBuilder liveOutput = new StringBuilder();
+   public boolean executionFinishedReported = false;
+   public boolean wasSuccessful = false;
+   public String finalMessage;
+
+   public void reportLiveOutput(String message)
+   {
+      liveOutput.append( message );
+   }
+
+   //when we finish executing, we'll make sure we got some type of live output from gradle.
+   public void reportExecutionFinished(boolean wasSuccessful, String message, Throwable throwable)
+   {
+      println "Received execution finished"
+      executionFinishedReported = true
+      this.wasSuccessful = true
+      this.finalMessage = message
+   }
+
+   public void reportExecutionStarted() { }
+   public void reportNumberOfTasksToExecute(int size) { }
+   public void reportTaskStarted(String message, float percentComplete) { }
+   public void reportTaskComplete(String message, float percentComplete) { }
+
+
+
+}
\ No newline at end of file
diff --git a/subprojects/gradle-ui/src/test/groovy/org/gradle/integtests/MultiprojectProjectAndTaskListIntegrationTest.groovy b/subprojects/gradle-ui/src/test/groovy/org/gradle/integtests/MultiprojectProjectAndTaskListIntegrationTest.groovy
new file mode 100644
index 0000000..1f2c46e
--- /dev/null
+++ b/subprojects/gradle-ui/src/test/groovy/org/gradle/integtests/MultiprojectProjectAndTaskListIntegrationTest.groovy
@@ -0,0 +1,249 @@
+/*
+ * 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
+
+import org.gradle.foundation.ProjectView
+import org.gradle.foundation.TaskView
+import org.gradle.gradleplugin.foundation.GradlePluginLord
+import org.gradle.integtests.fixtures.GradleDistribution
+import org.gradle.integtests.fixtures.GradleDistributionExecuter
+import org.gradle.integtests.fixtures.Sample
+import org.gradle.openapi.external.foundation.ProjectVersion1
+import org.gradle.openapi.wrappers.foundation.GradleInterfaceWrapperVersion1
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ This tests the multiproject sample with the GradleView mechanism.
+ @author mhunsicker
+ */
+ at RunWith(DistributionIntegrationTestRunner.class)
+class MultiprojectProjectAndTaskListIntegrationTest {
+
+    static final String JAVA_PROJECT_NAME = 'javaproject'
+    static final String SHARED_NAME = 'shared'
+    static final String API_NAME = 'api'
+    static final String WEBAPP_NAME = 'webservice'
+    static final String SERVICES_NAME = 'services'
+    static final String WEBAPP_PATH = "$SERVICES_NAME/$WEBAPP_NAME" as String
+
+    private File javaprojectDir
+
+    @Rule public final GradleDistribution dist = new GradleDistribution()
+    @Rule public final GradleDistributionExecuter executer = new GradleDistributionExecuter()
+    @Rule public final Sample sample = new Sample('java/quickstart')
+
+    @Before
+    void setUp() {
+        javaprojectDir = sample.getDir()
+    }
+
+    /*
+       This tests against the multiproject sample. It expects to find not just
+       the root level projects, but also the nested sub projects
+       (services:webservice). This isn't really interested in the actual tasks
+       themselves (I fear those may change too often to worry with keeping the
+       test up to date).
+
+       @author mhunsicker
+    */
+
+    @Test
+    public void multiProjectjavaProjectSample() {
+        // Build and test projects
+        executer.inDirectory(javaprojectDir).withTasks('assemble').run();
+
+        File multiProjectDirectory = sample.getDir();
+        Assert.assertTrue(multiProjectDirectory.exists());
+
+        GradlePluginLord gradlePluginLord = new GradlePluginLord();
+        gradlePluginLord.setCurrentDirectory(multiProjectDirectory);
+        gradlePluginLord.setGradleHomeDirectory(dist.gradleHomeDir);
+
+        //refresh the projects and wait. This will throw an exception if it fails.
+        org.gradle.foundation.TestUtility.refreshProjectsBlocking(gradlePluginLord, 20);  //we'll give this 20 seconds to complete
+
+        //get the root project
+        List<ProjectView> projects = gradlePluginLord.getProjects();
+        Assert.assertNotNull(projects);
+
+        //make sure there weren't other root projects found.
+        Assert.assertEquals(1, projects.size());
+
+        ProjectView rootProject = projects.get(0);
+        Assert.assertNotNull(rootProject);
+        Assert.assertEquals("multiproject", rootProject.getName());
+
+        //now check for sub projects, api, shared, and services
+        ProjectView apiProject = rootProject.getSubProject("api");
+        Assert.assertNotNull(apiProject);
+        Assert.assertTrue(apiProject.getSubProjects().isEmpty());  //this has no sub projects
+
+        ProjectView sharedProject = rootProject.getSubProject("shared");
+        Assert.assertNotNull(sharedProject);
+        Assert.assertTrue(sharedProject.getSubProjects().isEmpty());  //this has no sub projects
+
+        ProjectView servicesProject = rootProject.getSubProject("services");
+        Assert.assertNotNull(servicesProject);
+
+        //services has a sub project
+        ProjectView webservicesProject = servicesProject.getSubProject("webservice");
+        Assert.assertNotNull(webservicesProject);
+        Assert.assertTrue(webservicesProject.getSubProjects().isEmpty());  //this has no sub projects
+
+        //make sure we didn't inadvertantly find other sub projects.
+        Assert.assertEquals(3, rootProject.getSubProjects().size());
+    }
+
+   /**
+    This tests that the wrappers for projects and tasks are working
+    */
+   @Test
+   public void testOpenAPIWrapperProjectAndTaskList()
+   {
+     // Build and test projects
+        executer.inDirectory(javaprojectDir).withTasks('assemble').run();
+
+        File multiProjectDirectory = sample.getDir();
+        Assert.assertTrue(multiProjectDirectory.exists());
+
+        GradlePluginLord gradlePluginLord = new GradlePluginLord();
+        gradlePluginLord.setCurrentDirectory(multiProjectDirectory);
+        gradlePluginLord.setGradleHomeDirectory(dist.gradleHomeDir);
+
+        GradleInterfaceWrapperVersion1 wrapper = new GradleInterfaceWrapperVersion1( gradlePluginLord );
+
+        //the rest of this uses the open API mechanism to access the projects and tasks
+
+        //refresh the projects and wait. This will throw an exception if it fails.
+        org.gradle.foundation.TestUtility.refreshProjectsBlocking(gradlePluginLord, 20);  //we'll give this 20 seconds to complete
+
+        //get the root project
+        List<ProjectVersion1> projects = wrapper.getRootProjects();
+        Assert.assertNotNull(projects);
+
+        //make sure there weren't other root projects found.
+        Assert.assertEquals(1, projects.size());
+
+        ProjectVersion1 rootProject = projects.get(0);
+        Assert.assertNotNull(rootProject);
+        Assert.assertEquals("multiproject", rootProject.getName());
+
+        //now check for sub projects, api, shared, and services
+        ProjectVersion1 apiProject = rootProject.getSubProject("api");
+        Assert.assertNotNull(apiProject);
+        Assert.assertTrue(apiProject.getSubProjects().isEmpty());  //this has no sub projects
+
+        ProjectVersion1 sharedProject = rootProject.getSubProject("shared");
+        Assert.assertNotNull(sharedProject);
+        Assert.assertTrue(sharedProject.getSubProjects().isEmpty());  //this has no sub projects
+
+        ProjectVersion1 servicesProject = rootProject.getSubProject("services");
+        Assert.assertNotNull(servicesProject);
+
+        //services has a sub project
+        ProjectVersion1 webservicesProject = servicesProject.getSubProject("webservice");
+        Assert.assertNotNull(webservicesProject);
+        Assert.assertTrue(webservicesProject.getSubProjects().isEmpty());  //this has no sub projects
+
+        //make sure we didn't inadvertantly find other sub projects.
+        Assert.assertEquals(3, rootProject.getSubProjects().size());
+
+        //I don't want to keep the actual tasks in synch, but let's make sure there's something there.
+        def tasks = apiProject.getTasks()
+        Assert.assertNotNull( tasks );
+        Assert.assertFalse( tasks.isEmpty() );
+   }
+
+   /**
+   * This tests ProjectView.getSubProjectFromFullPath. Specifically, the first character
+    * is optionally a colon. So this tests it both ways.
+   */
+   @Test
+   public void testSubProjectFromFullPath()
+   {
+     executer.inDirectory(javaprojectDir).withTasks('assemble').run();
+
+      File multiProjectDirectory = sample.getDir();
+      Assert.assertTrue(multiProjectDirectory.exists());
+
+      GradlePluginLord gradlePluginLord = new GradlePluginLord();
+      gradlePluginLord.setCurrentDirectory(multiProjectDirectory);
+      gradlePluginLord.setGradleHomeDirectory(dist.gradleHomeDir);
+
+      //refresh the projects and wait. This will throw an exception if it fails.
+      org.gradle.foundation.TestUtility.refreshProjectsBlocking(gradlePluginLord, 20);  //we'll give this 20 seconds to complete
+
+      //get the root project
+      List<ProjectView> projects = gradlePluginLord.getProjects();
+      Assert.assertNotNull(projects);
+      Assert.assertFalse( projects.isEmpty() );
+
+      ProjectView rootProject = projects.get(0)
+
+      //test it using no prefixed colon
+      ProjectView foundProject1 = rootProject.getSubProjectFromFullPath("services:webservice")
+      Assert.assertNotNull( foundProject1 )
+
+      //test it using a prefixed colon
+      ProjectView foundProject2 = rootProject.getSubProjectFromFullPath(":services:webservice")
+      Assert.assertNotNull( foundProject2 )
+
+      //should both the same project
+      Assert.assertEquals( foundProject1, foundProject2 )
+   }
+
+   /**
+   * This tests TaskView.getTaskFromFullPath. Specifically, the first character
+    * is optionally a colon. So this tests it both ways.
+   */
+   @Test
+   public void testGetTaskFromFullPath()
+   {
+     executer.inDirectory(javaprojectDir).withTasks('assemble').run();
+
+      File multiProjectDirectory = sample.getDir();
+      Assert.assertTrue(multiProjectDirectory.exists());
+
+      GradlePluginLord gradlePluginLord = new GradlePluginLord();
+      gradlePluginLord.setCurrentDirectory(multiProjectDirectory);
+      gradlePluginLord.setGradleHomeDirectory(dist.gradleHomeDir);
+
+      //refresh the projects and wait. This will throw an exception if it fails.
+      org.gradle.foundation.TestUtility.refreshProjectsBlocking(gradlePluginLord, 20);  //we'll give this 20 seconds to complete
+
+      //get the root project
+      List<ProjectView> projects = gradlePluginLord.getProjects();
+      Assert.assertNotNull(projects);
+      Assert.assertFalse( projects.isEmpty() );
+
+      ProjectView rootProject = projects.get(0)
+
+      //test it using no prefixed colon
+      TaskView foundTask1 = rootProject.getTaskFromFullPath("api:build")
+      Assert.assertNotNull( foundTask1 )
+
+      //test it using a prefixed colon
+      TaskView foundTask2 = rootProject.getTaskFromFullPath(":api:build")
+      Assert.assertNotNull( foundTask2 )
+
+      //should both the same project
+      Assert.assertEquals( foundTask1, foundTask2 )      
+   }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-ui/ui.gradle b/subprojects/gradle-ui/ui.gradle
new file mode 100644
index 0000000..acf4e76
--- /dev/null
+++ b/subprojects/gradle-ui/ui.gradle
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+dependencies {
+    compile project(':core')
+    compile project(':openApi')
+
+    groovy libraries.groovy_depends
+
+    compile libraries.dom4j,
+            libraries.commons_io,
+            libraries.slf4j_api,
+            libraries.jopt_simple
+
+    runtime libraries.jaxen
+
+    testCompile project(path: ':core', configuration: 'testFixtures')
+    testCompile project(path: ':core', configuration: 'integTestFixtures')
+    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+    testRuntime project(path: ':core', configuration: 'integTestFixturesRuntime')
+}
+
+task integTest(type: Test, dependsOn: [ rootProject.intTestImage ]) {
+    systemProperties['integTest.gradleHomeDir'] = rootProject.intTestImage.integTestGradleHome.absolutePath
+    systemProperties['integTest.srcDir'] = file('src').absolutePath
+    systemProperties['integTest.gradleUserHomeDir'] = rootProject.integTest.integTestUserDir.absolutePath
+    include 'org/gradle/integtests/**/*IntegrationTest.*'
+    testClassesDir = sourceSets.test.classesDir
+    classpath = sourceSets.test.runtimeClasspath
+
+    doFirst { task -> systemProperties['integTest.gradleHomeDir'] = rootProject.intTestImage.integTestGradleHome.absolutePath }
+}
+
+compileJava.options.depend()
+
+//this is just to exclude integration tests
+test {
+    jvmArgs '-Xms128m', '-Xmx256m', '-XX:MaxPermSize=128m', '-XX:+HeapDumpOnOutOfMemoryError'
+
+    exclude 'org/gradle/integtests/**/*.*'
+}
diff --git a/subprojects/gradle-wrapper/src/main/java/org/gradle/api/tasks/wrapper/Wrapper.java b/subprojects/gradle-wrapper/src/main/java/org/gradle/api/tasks/wrapper/Wrapper.java
new file mode 100644
index 0000000..c8ab291
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/main/java/org/gradle/api/tasks/wrapper/Wrapper.java
@@ -0,0 +1,345 @@
+/*
+ * 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.wrapper;
+
+import org.gradle.api.DefaultTask;
+import org.gradle.api.GradleException;
+import org.gradle.api.tasks.Input;
+import org.gradle.api.tasks.TaskAction;
+import org.gradle.util.GFileUtils;
+import org.gradle.util.GUtil;
+
+import java.io.File;
+import java.net.URL;
+import java.util.Properties;
+
+/**
+ * The wrapper task generates scripts (for *nix and windows) which enable to build your project with Gradle, without having to install Gradle.
+ * The scripts generated by this task are supposed to be committed to your version control system. This tasks also copies
+ * a gradle-wrapper.jar to your project dir which needs also be committed into your VCS.
+ * The scripts delegates to this jar. If a user execute a wrapper script the first time, the script downloads the gradle-distribution and
+ * runs the build against the downloaded distribution. Any installed Gradle distribution is ignored when using the wrapper scripts.
+ * Alternatively you can store the distribution for the wrapper in your version control system.
+ *
+ * @author Hans Dockter
+ */
+public class Wrapper extends DefaultTask {
+    // Properties used by the gradle-wrapper
+    static final String URL_ROOT_PROPERTY = "urlRoot";
+    static final String DISTRIBUTION_BASE_PROPERTY = "distributionBase";
+    static final String ZIP_STORE_BASE_PROPERTY = "zipStoreBase";
+    static final String DISTRIBUTION_PATH_PROPERTY = "distributionPath";
+    static final String DISTRIBUTION_VERSION_PROPERTY = "distributionVersion";
+    static final String ZIP_STORE_PATH_PROPERTY = "zipStorePath";
+    static final String DISTRIBUTION_NAME_PROPERTY = "distributionName";
+    static final String DISTRIBUTION_CLASSIFIER_PROPERTY = "distributionClassifier";
+    static final String WRAPPER_DIR = "gradle-wrapper";
+    static final String WRAPPER_JAR = WRAPPER_DIR + ".jar";
+    static final String WRAPPER_PROPERTIES = WRAPPER_DIR + ".properties";
+    
+    public static final String DEFAULT_URL_ROOT = "http://dist.codehaus.org/gradle";
+    public static final String WRAPPER_JAR_BASE_NAME = "gradle-wrapper";
+    public static final String DEFAULT_DISTRIBUTION_PARENT_NAME = "wrapper/dists";
+    public static final String DEFAULT_ARCHIVE_NAME = "gradle";
+    public static final String DEFAULT_ARCHIVE_CLASSIFIER = "bin";
+
+    public enum PathBase { PROJECT, GRADLE_USER_HOME }
+
+    @Input
+    private String scriptDestinationPath;
+
+    @Input
+    private String jarPath;
+
+    @Input
+    private String distributionPath;
+
+    @Input
+    private String archiveName;
+
+    @Input
+    private String archiveClassifier;
+
+    @Input
+    private PathBase distributionBase = PathBase.GRADLE_USER_HOME;
+
+    @Input
+    private String gradleVersion;
+
+    @Input
+    private String urlRoot;
+
+    @Input
+    private String archivePath;
+
+    @Input
+    private PathBase archiveBase = PathBase.GRADLE_USER_HOME;
+
+    private WrapperScriptGenerator wrapperScriptGenerator = new WrapperScriptGenerator();
+
+    public Wrapper() {
+        scriptDestinationPath = "";
+        jarPath = "";
+        distributionPath = DEFAULT_DISTRIBUTION_PARENT_NAME;
+        archiveName = DEFAULT_ARCHIVE_NAME;
+        archiveClassifier = DEFAULT_ARCHIVE_CLASSIFIER;
+        archivePath = DEFAULT_DISTRIBUTION_PARENT_NAME;
+        urlRoot = DEFAULT_URL_ROOT;
+    }
+
+    @TaskAction
+    void generate() {
+        String wrapperDir = GUtil.isTrue(jarPath) ? jarPath + "/" : "";
+        new File(getProject().getProjectDir(), wrapperDir).mkdirs();
+        String wrapperJar = wrapperDir + WRAPPER_JAR;
+        String wrapperPropertiesPath = wrapperDir + WRAPPER_PROPERTIES;
+        File jarFileDestination = new File(getProject().getProjectDir(), wrapperJar);
+        File propertiesFileDestination = new File(getProject().getProjectDir(), wrapperPropertiesPath);
+        URL jarFileSource = getClass().getResource("/" + WRAPPER_JAR_BASE_NAME + ".jar");
+        if (jarFileSource == null) {
+            throw new GradleException("Cannot locate wrapper JAR resource.");
+        }
+        propertiesFileDestination.delete();
+        jarFileDestination.delete();
+        writeProperties(propertiesFileDestination);
+        GFileUtils.copyURLToFile(jarFileSource, jarFileDestination);
+        File scriptDestinationDir = new File(getProject().getProjectDir(), scriptDestinationPath);
+        wrapperScriptGenerator.generate(wrapperJar, wrapperPropertiesPath, scriptDestinationDir);
+    }
+
+    private void writeProperties(File propertiesFileDestination) {
+        Properties wrapperProperties = new Properties();
+        wrapperProperties.put(URL_ROOT_PROPERTY, urlRoot);
+        wrapperProperties.put(DISTRIBUTION_BASE_PROPERTY, distributionBase.toString());
+        wrapperProperties.put(DISTRIBUTION_PATH_PROPERTY, distributionPath);
+        wrapperProperties.put(DISTRIBUTION_NAME_PROPERTY, archiveName);
+        wrapperProperties.put(DISTRIBUTION_CLASSIFIER_PROPERTY, archiveClassifier);
+        wrapperProperties.put(DISTRIBUTION_VERSION_PROPERTY, gradleVersion);
+        wrapperProperties.put(ZIP_STORE_BASE_PROPERTY, archiveBase.toString());
+        wrapperProperties.put(ZIP_STORE_PATH_PROPERTY, archivePath);
+        GUtil.saveProperties(wrapperProperties, propertiesFileDestination);
+    }
+
+    /**
+     * Returns the script destination path.
+     *
+     * @see #setScriptDestinationPath(String) 
+     */
+    public String getScriptDestinationPath() {
+        return scriptDestinationPath;
+    }
+
+    /**
+     * Specifies a path as the parent dir of the scripts which are generated when executing the wrapper task.
+     * This path specifies a directory <i>relative</i> to the project dir.  Defaults to empty string, i.e. the scripts
+     * are placed into the project root dir.
+     *
+     * @param scriptDestinationPath Any object which <code>toString</code> method specifies the path.
+     * Most likely a String or File object.
+     */
+    public void setScriptDestinationPath(String scriptDestinationPath) {
+        this.scriptDestinationPath = scriptDestinationPath;
+    }
+
+    /**
+     * Returns the jar path.
+     *
+     * @see #setJarPath(String) 
+     */
+    public String getJarPath() {
+        return jarPath.toString();
+    }
+
+    /**
+     * When executing the wrapper task, the jar path specifies the path where the gradle-wrapper.jar is copied to. The
+     * jar path must be a path relative to the project dir. The gradle-wrapper.jar must be submitted to your version
+     * control system. Defaults to empty string, i.e. the jar is placed into the project root dir.
+     *
+     * @param jarPath
+     */
+    public void setJarPath(String jarPath) {
+        this.jarPath = jarPath;
+    }
+
+    /**
+     * Returns the distribution path.
+     *
+     * @see #setDistributionPath(String) 
+     */
+    public String getDistributionPath() {
+        return distributionPath;
+    }
+
+    /**
+     * Set's the path where the gradle distributions needed by the wrapper are unzipped. The path is relative to the
+     * dir specified with {@link #distributionBase}.
+     *
+     * @param distributionPath  
+     */
+    public void setDistributionPath(String distributionPath) {
+        this.distributionPath = distributionPath;
+    }
+
+    /**
+     * Returns the gradle version for the wrapper.
+     *
+     * @see #setGradleVersion(String) 
+     */
+    public String getGradleVersion() {
+        return gradleVersion;
+    }
+
+    /**
+     * The version of the gradle distribution required by the wrapper. This is usually the same version of Gradle you use
+     * for building your project.
+     *
+     * @param gradleVersion
+     */
+    public void setGradleVersion(String gradleVersion) {
+        this.gradleVersion = gradleVersion;
+    }
+
+    public String getUrlRoot() {
+        return urlRoot;
+    }
+
+    /**
+     * A URL where to download the gradle distribution from. The pattern used by the wrapper for downloading is:
+     * <code>{@link #getUrlRoot()}</i>/{@link #getArchiveName()}-{@link #getArchiveClassifier()}-{@link #getGradleVersion()}.zip</code>.
+     * The default for the URL root is {@link #DEFAULT_URL_ROOT}.
+     *
+     * The wrapper downloads a certain distribution only ones and caches it.
+     * If your {@link #getDistributionBase()} is the project, you might submit the distribution to your version control system.
+     * That way no download is necessary at all. This might be in particular interesting, if you provide a custom gradle snapshot to the wrapper,
+     * because you don't need to provide a download server then.
+     *  
+     * @param urlRoot
+     */
+    public void setUrlRoot(String urlRoot) {
+        this.urlRoot = urlRoot;
+    }
+
+    /**
+     * Returns the distribution base.
+     *
+     * @see #setDistributionBase(org.gradle.api.tasks.wrapper.Wrapper.PathBase) 
+     */
+    public PathBase getDistributionBase() {
+        return distributionBase;
+    }
+
+    /**
+     * The distribution base specifies whether the unpacked wrapper distribution should be stored in the project or in
+     * the gradle user home dir. The path specified in {@link #distributionPath} is a relative path to either of those
+     * dirs.  
+     *
+     * @param distributionBase
+     */
+    public void setDistributionBase(PathBase distributionBase) {
+        this.distributionBase = distributionBase;
+    }
+
+    /**
+     * Returns the archive path.
+     *
+     * @see #setArchivePath(String) 
+     */
+    public String getArchivePath() {
+        return archivePath;
+    }
+
+    /**
+     * Set's the path where the gradle distributions archive should be saved (i.e. the parent dir). The path is relative to the
+     * parent dir specified with {@link #getArchiveBase()}.
+     *
+     * @param archivePath
+     */
+    public void setArchivePath(String archivePath) {
+        this.archivePath = archivePath;
+    }
+
+    /**
+     * Returns the archive base.
+     *
+     * @see #setArchiveBase(org.gradle.api.tasks.wrapper.Wrapper.PathBase) 
+     */
+    public PathBase getArchiveBase() {
+        return archiveBase;
+    }
+
+    /**
+     * The distribution base specifies whether the unpacked wrapper distribution should be stored in the project or in
+     * the gradle user home dir. The path specified in {@link #getArchivePath()} is a relative path to etiher of those dirs.
+     *
+     * @param archiveBase
+     */
+    public void setArchiveBase(PathBase archiveBase) {
+        this.archiveBase = archiveBase;
+    }
+
+    /**
+     * Returnes the archive name.
+     *
+     * @see #setArchiveName(String) 
+     */
+    public String getArchiveName() {
+        return archiveName;
+    }
+
+    /**
+     * Specifies the name of the archive as part of the download URL. The download URL is assembled by the pattern:
+     *
+     * <code>{@link #getUrlRoot()}</i>/{@link #getArchiveName()}-{@link #getArchiveClassifier()}-{@link #getGradleVersion()}.zip</code>
+     *
+     * The default for the archive name is {@link #DEFAULT_ARCHIVE_NAME}.
+     *
+     * @param archiveName
+     */
+    public void setArchiveName(String archiveName) {
+        this.archiveName = archiveName;
+    }
+
+    /**
+     * Returns the archive classifier.
+     *
+     * @see #setArchiveClassifier(String) 
+     */
+    public String getArchiveClassifier() {
+        return archiveClassifier;
+    }
+
+    /**
+     * Specifies the classifier of the archive as part of the download URL. The download URL is assembled by the pattern:
+     *
+     * <code>{@link #getUrlRoot()}</i>/{@link #getArchiveName()}-{@link #getArchiveClassifier()}-{@link #getGradleVersion()}.zip</code>
+     *
+     * The default for the archive classifier is {@link #DEFAULT_ARCHIVE_CLASSIFIER}.
+     *
+     * @param archiveClassifier
+     */
+    public void setArchiveClassifier(String archiveClassifier) {
+        this.archiveClassifier = archiveClassifier;
+    }
+
+    public WrapperScriptGenerator getUnixWrapperScriptGenerator() {
+        return wrapperScriptGenerator;
+    }
+
+    public void setUnixWrapperScriptGenerator(WrapperScriptGenerator wrapperScriptGenerator) {
+        this.wrapperScriptGenerator = wrapperScriptGenerator;
+    }
+}
diff --git a/subprojects/gradle-wrapper/src/main/java/org/gradle/api/tasks/wrapper/WrapperScriptGenerator.java b/subprojects/gradle-wrapper/src/main/java/org/gradle/api/tasks/wrapper/WrapperScriptGenerator.java
new file mode 100644
index 0000000..0b77574
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/main/java/org/gradle/api/tasks/wrapper/WrapperScriptGenerator.java
@@ -0,0 +1,96 @@
+/*
+ * 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.api.tasks.wrapper;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.tools.ant.taskdefs.Chmod;
+import org.gradle.api.UncheckedIOException;
+import org.gradle.util.AntUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+
+/**
+ * @author Hans Dockter
+ */
+public class WrapperScriptGenerator {
+    public static final String UNIX_NL = "\n";
+    public static final String CURRENT_DIR_UNIX = "`dirname \"$0\"`";
+    public static final String WINDOWS_NL = "\n";
+    public static final String CURRENT_DIR_WINDOWS = "%DIRNAME%";
+    private static final String FULLY_QUALIFIED_WRAPPER_NAME = "org.gradle.wrapper.GradleWrapperMain";
+
+    public void generate(String jarPath, String wrapperPropertiesPath, File scriptDestinationDir) {
+        try {
+            createUnixScript(jarPath, scriptDestinationDir, wrapperPropertiesPath);
+            createWindowsScript(jarPath, scriptDestinationDir, wrapperPropertiesPath);
+        } catch (IOException e) {
+            throw new UncheckedIOException(e);
+        }
+    }
+
+    private void createUnixScript(String jarPath, File scriptDestinationDir, String wrapperPropertiesPath) throws IOException {
+        String unixWrapperScriptHead = IOUtils.toString(Wrapper.class.getResourceAsStream("unixWrapperScriptHead.txt"));
+        String unixWrapperScriptTail = IOUtils.toString(Wrapper.class.getResourceAsStream("unixWrapperScriptTail.txt"));
+
+        String fillingUnix = "" + UNIX_NL +
+                "STARTER_MAIN_CLASS=" + FULLY_QUALIFIED_WRAPPER_NAME + UNIX_NL +
+                "CLASSPATH=" + CURRENT_DIR_UNIX + "/" + FilenameUtils.separatorsToUnix(jarPath) + UNIX_NL +
+                "WRAPPER_PROPERTIES=" + CURRENT_DIR_UNIX + "/" + FilenameUtils.separatorsToUnix(wrapperPropertiesPath) + UNIX_NL;
+
+        String unixScript = unixWrapperScriptHead + fillingUnix + unixWrapperScriptTail;
+        File unixScriptFile = new File(scriptDestinationDir, "gradlew");
+        FileUtils.writeStringToFile(unixScriptFile, unixScript);
+        createExecutablePermission(unixScriptFile);
+    }
+
+    private void createExecutablePermission(File unixScriptFile) {
+        Chmod chmod = new Chmod();
+        chmod.setProject(AntUtil.createProject());
+        chmod.setFile(unixScriptFile);
+        chmod.setPerm("ugo+rx");
+        chmod.execute();
+    }
+
+    private void createWindowsScript(String jarPath, File scriptDestinationDir, String wrapperPropertiesPath) throws IOException {
+        String windowsWrapperScriptHead = IOUtils.toString(Wrapper.class.getResourceAsStream("windowsWrapperScriptHead.txt"));
+        String windowsWrapperScriptTail = IOUtils.toString(Wrapper.class.getResourceAsStream("windowsWrapperScriptTail.txt"));
+        String fillingWindows = "" + WINDOWS_NL +
+                "set STARTER_MAIN_CLASS=" + FULLY_QUALIFIED_WRAPPER_NAME + WINDOWS_NL +
+                "set CLASSPATH=" + CURRENT_DIR_WINDOWS + "\\" + FilenameUtils.separatorsToWindows(jarPath) + WINDOWS_NL +
+                "set WRAPPER_PROPERTIES=" + CURRENT_DIR_WINDOWS + "\\" + FilenameUtils.separatorsToWindows(wrapperPropertiesPath) + WINDOWS_NL;
+        String windowsScript = windowsWrapperScriptHead + fillingWindows + windowsWrapperScriptTail;
+        File windowsScriptFile = new File(scriptDestinationDir, "gradlew.bat");
+        FileUtils.writeStringToFile(windowsScriptFile, transformIntoWindowsNewLines(windowsScript));
+    }
+
+    private String transformIntoWindowsNewLines(String s) {
+        StringWriter writer = new StringWriter();
+        for (char c : s.toCharArray()) {
+            if (c == '\n') {
+                writer.write('\r');
+                writer.write('\n');
+            } else if (c != '\r') {
+                writer.write(c);
+            }
+        }        
+        return writer.toString();
+    }
+}
diff --git a/subprojects/gradle-wrapper/src/main/java/org/gradle/api/tasks/wrapper/package-info.java b/subprojects/gradle-wrapper/src/main/java/org/gradle/api/tasks/wrapper/package-info.java
new file mode 100644
index 0000000..1ddac17
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/main/java/org/gradle/api/tasks/wrapper/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 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.
+ */
+
+/**
+ * The Gradle wrapper {@link org.gradle.api.Task}.
+ */
+package org.gradle.api.tasks.wrapper;
\ No newline at end of file
diff --git a/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/BootstrapMainStarter.java b/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/BootstrapMainStarter.java
new file mode 100644
index 0000000..6d4a68c
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/BootstrapMainStarter.java
@@ -0,0 +1,39 @@
+/*
+ * 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.wrapper;
+
+import java.io.File;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+/**
+ * @author Hans Dockter
+ */
+public class BootstrapMainStarter {
+    public void start(String[] args, String gradleHome, String version) throws Exception {
+        boolean debug = GradleWrapperMain.isDebug();
+        File gradleJar = new File(gradleHome, "lib/gradle-launcher-" + version + ".jar");
+        if (debug) {
+            System.out.println("gradleJar = " + gradleJar.getAbsolutePath());
+        }
+        URLClassLoader contextClassLoader = new URLClassLoader(new URL[] { gradleJar.toURI().toURL() });
+        Thread.currentThread().setContextClassLoader(contextClassLoader);
+        Class<?> mainClass = contextClassLoader.loadClass("org.gradle.launcher.GradleMain");
+        Method mainMethod = mainClass.getMethod("main", String[].class);
+        mainMethod.invoke(null, new Object[] {args});
+    }
+}
diff --git a/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/Download.java b/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/Download.java
new file mode 100644
index 0000000..00125af
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/Download.java
@@ -0,0 +1,72 @@
+/*
+ * 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.wrapper;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLConnection;
+
+/**
+ * @author Hans Dockter
+ */
+class Download implements IDownload {
+    private static final int PROGRESS_CHUNK = 20000;
+    private static final int BUFFER_SIZE = 10000;
+
+    public void download(String address, File destination) throws Exception {
+        if (destination.exists()) {
+            return;
+        }
+        destination.getParentFile().mkdirs();
+
+        downloadInternal(address, destination);
+    }
+
+    private void downloadInternal(String address, File destination) throws Exception {
+        OutputStream out = null;
+        URLConnection conn;
+        InputStream in = null;
+        try {
+            URL url = new URL(address);
+            out = new BufferedOutputStream(
+                    new FileOutputStream(destination));
+            conn = url.openConnection();
+            in = conn.getInputStream();
+            byte[] buffer = new byte[BUFFER_SIZE];
+            int numRead;
+            long progressCounter = 0;
+            while ((numRead = in.read(buffer)) != -1) {
+                progressCounter += numRead;
+                if (progressCounter / PROGRESS_CHUNK > 0) {
+                    System.out.print(".");
+                    progressCounter = progressCounter - PROGRESS_CHUNK;
+                }
+                out.write(buffer, 0, numRead);
+            }
+        } finally {
+            System.out.println("");
+            if (in != null) {
+                in.close();
+            }
+            if (out != null) {
+                out.close();
+            }
+        }
+    }
+
+
+}
diff --git a/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/GradleWrapperMain.java b/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/GradleWrapperMain.java
new file mode 100644
index 0000000..0656b20
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/GradleWrapperMain.java
@@ -0,0 +1,69 @@
+/*
+ * 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.wrapper;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public class GradleWrapperMain {
+    public static final String ALWAYS_UNPACK_ENV = "GRADLE_WRAPPER_ALWAYS_UNPACK";
+    public static final String ALWAYS_DOWNLOAD_ENV = "GRADLE_WRAPPER_ALWAYS_DOWNLOAD";
+    public static final String DEFAULT_GRADLE_USER_HOME = System.getProperty("user.home") + "/.gradle";
+    public static final String GRADLE_USER_HOME_PROPERTY_KEY = "gradle.user.home";
+    public static final String GRADLE_USER_HOME_ENV_KEY = "GRADLE_USER_HOME";
+    public static final String DEBUG_PROPERTY_KEY = "gradle.wrapper.debug";
+
+    public static void main(String[] args) throws Exception {
+        addSystemProperties(args);
+        
+        if (isDebug()) {
+            System.out.println(ALWAYS_UNPACK_ENV + " env variable: " + System.getenv(ALWAYS_UNPACK_ENV));
+            System.out.println(ALWAYS_DOWNLOAD_ENV + " env variable: " + System.getenv(ALWAYS_DOWNLOAD_ENV));
+        }
+        boolean alwaysDownload = Boolean.parseBoolean(System.getenv(ALWAYS_DOWNLOAD_ENV));
+        boolean alwaysUnpack = Boolean.parseBoolean(System.getenv(ALWAYS_UNPACK_ENV));
+
+        new Wrapper().execute(
+                args,
+                new Install(alwaysDownload, alwaysUnpack, new Download(), new PathAssembler(gradleUserHome())),
+                new BootstrapMainStarter());
+    }
+
+    private static void addSystemProperties(String[] args) {
+        System.getProperties().putAll(SystemPropertiesHandler.getSystemProperties(args));
+        System.getProperties().putAll(SystemPropertiesHandler.getSystemProperties(new File(gradleUserHome(), "gradle.properties")));
+        System.getProperties().putAll(SystemPropertiesHandler.getSystemProperties(new File("gradle.properties")));
+    }
+
+    private static String gradleUserHome() {
+        String gradleUserHome = System.getProperty(GRADLE_USER_HOME_PROPERTY_KEY);
+        if (gradleUserHome != null) {
+            return gradleUserHome;
+        } else if((gradleUserHome = System.getenv(GRADLE_USER_HOME_ENV_KEY)) != null) {
+            return gradleUserHome;
+        } else {
+            return DEFAULT_GRADLE_USER_HOME;
+        }
+    }
+
+    static boolean isDebug() {
+        String prop = System.getProperty(DEBUG_PROPERTY_KEY);
+        return prop != null && !prop.toUpperCase().equals("FALSE");
+    }
+}
diff --git a/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/IDownload.java b/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/IDownload.java
new file mode 100644
index 0000000..1280079
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/IDownload.java
@@ -0,0 +1,25 @@
+/*
+ * 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.wrapper;
+
+import java.io.File;
+
+/**
+ * @author Hans Dockter
+ */
+public interface IDownload {
+    void download(String address, File destination) throws Exception;
+}
diff --git a/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/Install.java b/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/Install.java
new file mode 100644
index 0000000..9caae3d
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/Install.java
@@ -0,0 +1,156 @@
+/*
+ * 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.wrapper;
+
+import java.io.*;
+import java.util.Enumeration;
+import java.util.Locale;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * @author Hans Dockter
+ */
+public class Install {
+    private IDownload download = new Download();
+
+    private boolean alwaysDownload;
+    private boolean alwaysUnpack;
+    private PathAssembler pathAssembler;
+
+    public Install(boolean alwaysDownload, boolean alwaysUnpack, IDownload download, PathAssembler pathAssembler) {
+        this.alwaysDownload = alwaysDownload;
+        this.alwaysUnpack = alwaysUnpack;
+        this.download = download;
+        this.pathAssembler = pathAssembler;
+    }
+
+    public String createDist(String urlRoot, String distBase, String distPath, String distName, String distVersion,
+                             String distClassifier, String zipBase, String zipPath) throws Exception {
+        String gradleHome = pathAssembler.gradleHome(distBase, distPath, distName, distVersion);
+        File gradleHomeFile = new File(gradleHome);
+        if (!alwaysDownload && !alwaysUnpack && gradleHomeFile.isDirectory()) {
+            return gradleHome;
+        }
+        File localZipFile = new File(pathAssembler.distZip(zipBase, zipPath, distName, distVersion, distClassifier));
+        if (alwaysDownload || !localZipFile.exists()) {
+            File tmpZipFile = new File(localZipFile.getParentFile(), localZipFile.getName() + ".part");
+            tmpZipFile.delete();
+            String downloadUrl = urlRoot + "/" + distName + "-" + distVersion + "-" + distClassifier + ".zip";
+            System.out.println("Downloading " + downloadUrl);
+            download.download(downloadUrl, tmpZipFile);
+            tmpZipFile.renameTo(localZipFile);
+        }
+        if (gradleHomeFile.isDirectory()) {
+            System.out.println("Deleting directory " + gradleHomeFile.getAbsolutePath());
+            deleteDir(gradleHomeFile);
+        }
+        File distDest = gradleHomeFile.getParentFile();
+        System.out.println("Unzipping " + localZipFile.getAbsolutePath() + " to " + distDest.getAbsolutePath());
+        unzip(localZipFile, distDest);
+        setExecutablePermissions(gradleHome);
+        return gradleHome;
+    }
+
+    private void setExecutablePermissions(String gradleHome) {
+        if (isWindows()) {
+            return;
+        }
+        File gradleCommand = new File(gradleHome, "bin/gradle");
+        String errorMessage = null;
+        try {
+            ProcessBuilder pb = new ProcessBuilder("chmod", "755", gradleCommand.getCanonicalPath());
+            Process p = pb.start();
+            if (p.waitFor() == 0) {
+                System.out.println("Set executable permissions for: " + gradleCommand.getAbsolutePath());
+            } else {
+                BufferedReader is = new BufferedReader(new InputStreamReader(p.getInputStream()));
+                errorMessage = "";
+                String line;
+                while ((line = is.readLine()) != null) {
+                    errorMessage += line + System.getProperty("line.separator");
+                }
+            }
+        } catch (IOException e) {
+            errorMessage = e.getMessage();
+        } catch (InterruptedException e) {
+            errorMessage = e.getMessage();
+        }
+        if (errorMessage != null) {
+            System.out.println("Could not set executable permissions for: " + gradleCommand.getAbsolutePath());
+            System.out.println("Please do this manually if you want to use the Gradle UI.");
+        }
+    }
+
+    private boolean isWindows() {
+        String osName = System.getProperty("os.name").toLowerCase(Locale.US);
+        if (osName.indexOf("windows") > -1) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean deleteDir(File dir) {
+        if (dir.isDirectory()) {
+            String[] children = dir.list();
+            for (int i = 0; i < children.length; i++) {
+                boolean success = deleteDir(new File(dir, children[i]));
+                if (!success) {
+                    return false;
+                }
+            }
+        }
+
+        // The directory is now empty so delete it
+        return dir.delete();
+    }
+
+    public void unzip(File zip, File dest) throws IOException {
+        Enumeration entries;
+        ZipFile zipFile;
+
+        zipFile = new ZipFile(zip);
+
+        entries = zipFile.entries();
+
+        while (entries.hasMoreElements()) {
+            ZipEntry entry = (ZipEntry) entries.nextElement();
+
+            if (entry.isDirectory()) {
+                (new File(dest, entry.getName())).mkdirs();
+                continue;
+            }
+
+            copyInputStream(zipFile.getInputStream(entry),
+                    new BufferedOutputStream(new FileOutputStream(new File(dest, entry.getName()))));
+        }
+        zipFile.close();
+    }
+
+    public void copyInputStream(InputStream in, OutputStream out) throws IOException {
+        byte[] buffer = new byte[1024];
+        int len;
+
+        while ((len = in.read(buffer)) >= 0) {
+            out.write(buffer, 0, len);
+        }
+
+        in.close();
+        out.close();
+    }
+
+}
diff --git a/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/PathAssembler.java b/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/PathAssembler.java
new file mode 100644
index 0000000..2d082f8
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/PathAssembler.java
@@ -0,0 +1,51 @@
+/*
+ * 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.wrapper;
+
+/**
+ * @author Hans Dockter
+ */
+public class PathAssembler {
+    public static final String GRADLE_USER_HOME_STRING = "GRADLE_USER_HOME";
+    public static final String PROJECT_STRING = "PROJECT";
+
+    public String gradleUserHome;
+
+    public PathAssembler() {
+    }
+
+    public PathAssembler(String gradleUserHome) {
+        this.gradleUserHome = gradleUserHome;
+    }
+
+    public String gradleHome(String distBase, String distPath, String distName, String distVersion) {
+        return getBaseDir(distBase) + "/" + distPath + "/" + distName + "-" + distVersion ;
+    }
+
+    public String distZip(String zipBase, String zipPath, String distName, String distVersion, String distClassifier) {
+        return getBaseDir(zipBase) + "/" + zipPath + "/" + distName + "-" + distVersion + "-" + distClassifier + ".zip";
+    }
+
+    private String getBaseDir(String base) {
+        if (base.equals(GRADLE_USER_HOME_STRING)) {
+            return gradleUserHome;
+        } else if (base.equals(PROJECT_STRING)) {
+            return System.getProperty("user.dir");
+        } else {
+            throw new RuntimeException("Base: " + base + " is unknown");
+        }
+    }
+}
diff --git a/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/SystemPropertiesHandler.java b/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/SystemPropertiesHandler.java
new file mode 100644
index 0000000..f7f8c5c
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/SystemPropertiesHandler.java
@@ -0,0 +1,71 @@
+/*
+ * 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.wrapper;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author Hans Dockter
+ */
+public class SystemPropertiesHandler {
+    public static Map<String, String> getSystemProperties(String[] arguments) {
+        Map<String, String> propertyMap = new HashMap<String, String>();
+        Pattern pattern = Pattern.compile("-D([^=]*)=?(.*)");
+        for (String argument : arguments) {
+            Matcher matcher = pattern.matcher(argument);
+            if (matcher.find()) {
+                String key = matcher.group(1);
+                String value = matcher.group(2);
+                if (key.length() > 0) {
+                    propertyMap.put(key, value);
+                }
+            }
+        }
+        return propertyMap;
+    }
+
+    public static Map<String, String> getSystemProperties(File propertiesFile) {
+        Map<String, String> propertyMap = new HashMap<String, String>();
+        if (!propertiesFile.isFile()) {
+            return propertyMap;
+        }
+        Properties properties = new Properties();
+        try {
+            properties.load(new FileInputStream(propertiesFile));
+        } catch (IOException e) {
+            throw new RuntimeException("Error when loading properties file=" + propertiesFile, e);
+        }
+
+        Pattern pattern = Pattern.compile("systemProp\\.(.*)");
+        for (Object argument : properties.keySet()) {
+            Matcher matcher = pattern.matcher(argument.toString());
+            if (matcher.find()) {
+                String key = matcher.group(1);
+                if (key.length() > 0) {
+                    propertyMap.put(key, properties.get(argument).toString());
+                }
+            }
+        }
+        return propertyMap;
+    }
+}
diff --git a/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/Wrapper.java b/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/Wrapper.java
new file mode 100644
index 0000000..ec8ee50
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/main/java/org/gradle/wrapper/Wrapper.java
@@ -0,0 +1,60 @@
+/*
+ * 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.wrapper;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.Arrays;
+import java.util.Properties;
+
+/**
+ * @author Hans Dockter
+ */
+public class Wrapper {
+    public static final String WRAPPER_PROPERTIES_PROPERTY = "org.gradle.wrapper.properties";
+    
+    public static final String URL_ROOT_PROPERTY = "urlRoot";
+    public static final String DISTRIBUTION_BASE_PROPERTY = "distributionBase";
+    public static final String ZIP_STORE_BASE_PROPERTY = "zipStoreBase";
+    public static final String DISTRIBUTION_PATH_PROPERTY = "distributionPath";
+    public static final String DISTRIBUTION_VERSION_PROPERTY = "distributionVersion";
+    public static final String ZIP_STORE_PATH_PROPERTY = "zipStorePath";
+    public static final String DISTRIBUTION_NAME_PROPERTY = "distributionName";
+    public static final String DISTRIBUTION_CLASSIFIER_PROPERTY = "distributionClassifier";
+
+    public void execute(String[] args, Install install, BootstrapMainStarter bootstrapMainStarter) throws Exception {
+        Properties wrapperProperties = new Properties();
+        wrapperProperties.load(new FileInputStream(new File(System.getProperty(WRAPPER_PROPERTIES_PROPERTY))));
+        if (GradleWrapperMain.isDebug()) {
+            System.out.println("wrapperProperties = " + wrapperProperties);
+        }
+        String version = (String) wrapperProperties.get(DISTRIBUTION_VERSION_PROPERTY);
+        String gradleHome = install.createDist(
+                (String) wrapperProperties.get(URL_ROOT_PROPERTY),
+                (String) wrapperProperties.get(DISTRIBUTION_BASE_PROPERTY),
+                (String) wrapperProperties.get(DISTRIBUTION_PATH_PROPERTY),
+                (String) wrapperProperties.get(DISTRIBUTION_NAME_PROPERTY),
+                version,
+                (String) wrapperProperties.get(DISTRIBUTION_CLASSIFIER_PROPERTY),
+                (String) wrapperProperties.get(ZIP_STORE_BASE_PROPERTY),
+                (String) wrapperProperties.get(ZIP_STORE_PATH_PROPERTY)
+        );
+        if (GradleWrapperMain.isDebug()) {
+            System.out.println("args = " + Arrays.asList(args));
+        }
+        bootstrapMainStarter.start(args, gradleHome, version);
+    }
+}
diff --git a/subprojects/gradle-wrapper/src/main/resources/org/gradle/api/tasks/wrapper/unixWrapperScriptHead.txt b/subprojects/gradle-wrapper/src/main/resources/org/gradle/api/tasks/wrapper/unixWrapperScriptHead.txt
new file mode 100644
index 0000000..4ff9ca8
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/main/resources/org/gradle/api/tasks/wrapper/unixWrapperScriptHead.txt
@@ -0,0 +1,63 @@
+#!/bin/bash
+
+##############################################################################
+##                                                                          ##
+##  Gradle wrapper script for UN*X                                         ##
+##                                                                          ##
+##############################################################################
+
+# Uncomment those lines to set JVM options. GRADLE_OPTS and JAVA_OPTS can be used together.
+# GRADLE_OPTS="$GRADLE_OPTS -Xmx512"
+# JAVA_OPTS="$JAVA_OPTS -Xmx512"
+
+GRADLE_APP_NAME=Gradle
+
+warn ( ) {
+    echo "${PROGNAME}: $*"
+}
+
+die ( ) {
+    warn "$*"
+    exit 1
+}
+
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set JAVA_HOME if it's not already set.
+if [ -z "$JAVA_HOME" ] ; then
+    if $darwin ; then
+        [ -z "$JAVA_HOME" -a -d "/Library/Java/Home" ] && export JAVA_HOME="/Library/Java/Home"
+        [ -z "$JAVA_HOME" -a -d "/System/Library/Frameworks/JavaVM.framework/Home" ] && export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Home"
+    else
+        javaExecutable="`which javac`"
+        [ -z "$javaExecutable" -o "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ] && die "JAVA_HOME not set and cannot find javac to deduce location, please set JAVA_HOME."
+        # readlink(1) is not available as standard on Solaris 10.
+        readLink=`which readlink`
+        [ `expr "$readLink" : '\([^ ]*\)'` = "no" ] && die "JAVA_HOME not set and readlink not available, please set JAVA_HOME."
+        javaExecutable="`readlink -f \"$javaExecutable\"`"
+        javaHome="`dirname \"$javaExecutable\"`"
+        javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+        export JAVA_HOME="$javaHome"
+    fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+    [ -n "$JAVACMD" ] && JAVACMD=`cygpath --unix "$JAVACMD"`
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
diff --git a/subprojects/gradle-wrapper/src/main/resources/org/gradle/api/tasks/wrapper/unixWrapperScriptTail.txt b/subprojects/gradle-wrapper/src/main/resources/org/gradle/api/tasks/wrapper/unixWrapperScriptTail.txt
new file mode 100644
index 0000000..d2349c9
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/main/resources/org/gradle/api/tasks/wrapper/unixWrapperScriptTail.txt
@@ -0,0 +1,75 @@
+# Determine the Java command to use to start the JVM.
+if [ -z "$JAVACMD" ] ; then
+    if [ -n "$JAVA_HOME" ] ; then
+        if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+            # IBM's JDK on AIX uses strange locations for the executables
+            JAVACMD="$JAVA_HOME/jre/sh/java"
+        else
+            JAVACMD="$JAVA_HOME/bin/java"
+        fi
+    else
+        JAVACMD="java"
+    fi
+fi
+if [ ! -x "$JAVACMD" ] ; then
+    die "JAVA_HOME is not defined correctly, can not execute: $JAVACMD"
+fi
+if [ -z "$JAVA_HOME" ] ; then
+    warn "JAVA_HOME environment variable is not set"
+fi
+
+# For Darwin, add GRADLE_APP_NAME to the JAVA_OPTS as -Xdock:name
+if $darwin; then
+    JAVA_OPTS="$JAVA_OPTS -Xdock:name=$GRADLE_APP_NAME"
+# we may also want to set -Xdock:image
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    JAVA_HOME=`cygpath --path --mixed "$JAVA_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done 
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+"$JAVACMD" $JAVA_OPTS $GRADLE_OPTS \
+        -classpath "$CLASSPATH" \
+        -Dorg.gradle.wrapper.properties="$WRAPPER_PROPERTIES" \
+        $STARTER_MAIN_CLASS \
+        "$@"
diff --git a/subprojects/gradle-wrapper/src/main/resources/org/gradle/api/tasks/wrapper/windowsWrapperScriptHead.txt b/subprojects/gradle-wrapper/src/main/resources/org/gradle/api/tasks/wrapper/windowsWrapperScriptHead.txt
new file mode 100644
index 0000000..879d397
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/main/resources/org/gradle/api/tasks/wrapper/windowsWrapperScriptHead.txt
@@ -0,0 +1,101 @@
+ at if "%DEBUG%" == "" @echo off
+ at rem ##########################################################################
+ at rem                                                                         ##
+ at rem  Gradle startup script for Windows                                      ##
+ at rem                                                                         ##
+ at rem ##########################################################################
+
+ at rem
+ at rem $Revision: 10602 $ $Date: 2008-01-25 02:49:54 +0100 (ven., 25 janv. 2008) $
+ at rem
+
+ at rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+ at rem Uncomment those lines to set JVM options. GRADLE_OPTS and JAVA_OPTS can be used together.
+ at rem set GRADLE_OPTS=%GRADLE_OPTS% -Xmx512
+ at rem set JAVA_OPTS=%JAVA_OPTS% -Xmx512
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.\
+
+ at rem Determine the command interpreter to execute the "CD" later
+set COMMAND_COM="cmd.exe"
+if exist "%SystemRoot%\system32\cmd.exe" set COMMAND_COM="%SystemRoot%\system32\cmd.exe"
+if exist "%SystemRoot%\command.com" set COMMAND_COM="%SystemRoot%\command.com"
+
+ at rem Use explicit find.exe to prevent cygwin and others find.exe from being used
+set FIND_EXE="find.exe"
+if exist "%SystemRoot%\system32\find.exe" set FIND_EXE="%SystemRoot%\system32\find.exe"
+if exist "%SystemRoot%\command\find.exe" set FIND_EXE="%SystemRoot%\command\find.exe"
+
+:check_JAVA_HOME
+ at rem Make sure we have a valid JAVA_HOME
+if not "%JAVA_HOME%" == "" goto have_JAVA_HOME
+
+echo.
+echo ERROR: Environment variable JAVA_HOME has not been set.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+echo.
+goto end
+
+:have_JAVA_HOME
+ at rem Validate JAVA_HOME
+%COMMAND_COM% /C DIR "%JAVA_HOME%" 2>&1 | %FIND_EXE% /I /C "%JAVA_HOME%" >nul
+if not errorlevel 1 goto init
+
+echo.
+echo ERROR: JAVA_HOME might be set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation if there are problems.
+echo.
+
+:init
+ at rem get name of script to launch with full path
+ at rem Get command-line arguments, handling Windowz variants
+SET _marker=%JAVA_HOME: =%
+ at rem IF NOT "%_marker%" == "%JAVA_HOME%" ECHO JAVA_HOME "%JAVA_HOME%" contains spaces. Please change to a location without spaces if this causes problems.
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%eval[2+2]" == "4" goto 4NT_args
+
+IF "%_marker%" == "%JAVA_HOME%" goto :win9xME_args
+
+set _FIXPATH=
+call :fixpath "%JAVA_HOME%"
+set JAVA_HOME=%_FIXPATH:~1%
+
+goto win9xME_args
+
+:fixpath
+if not %1.==. (
+for /f "tokens=1* delims=;" %%a in (%1) do (
+call :shortfilename "%%a" & call :fixpath "%%b"
+)
+)
+goto :EOF
+:shortfilename
+for %%i in (%1) do set _FIXPATH=%_FIXPATH%;%%~fsi
+goto :EOF
+
+
+:win9xME_args
+ at rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+ at rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+ at rem Setup the command line
diff --git a/subprojects/gradle-wrapper/src/main/resources/org/gradle/api/tasks/wrapper/windowsWrapperScriptTail.txt b/subprojects/gradle-wrapper/src/main/resources/org/gradle/api/tasks/wrapper/windowsWrapperScriptTail.txt
new file mode 100644
index 0000000..9311656
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/main/resources/org/gradle/api/tasks/wrapper/windowsWrapperScriptTail.txt
@@ -0,0 +1,21 @@
+set JAVA_EXE=%JAVA_HOME%\bin\java.exe
+
+set GRADLE_OPTS=%JAVA_OPTS% %GRADLE_OPTS% -Dorg.gradle.wrapper.properties="%WRAPPER_PROPERTIES%"
+
+"%JAVA_EXE%" %GRADLE_OPTS% -classpath "%CLASSPATH%" %STARTER_MAIN_CLASS% %CMD_LINE_ARGS%
+
+:end
+ at rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+if not "%OS%"=="Windows_NT" echo 1 > nul | choice /n /c:1
+
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit "%ERRORLEVEL%"
+exit /b "%ERRORLEVEL%"
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
\ No newline at end of file
diff --git a/subprojects/gradle-wrapper/src/test/groovy/org/gradle/api/tasks/wrapper/WrapperTest.java b/subprojects/gradle-wrapper/src/test/groovy/org/gradle/api/tasks/wrapper/WrapperTest.java
new file mode 100644
index 0000000..f9f0070
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/test/groovy/org/gradle/api/tasks/wrapper/WrapperTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.wrapper;
+
+import org.gradle.api.internal.AbstractTask;
+import org.gradle.api.tasks.AbstractTaskTest;
+import org.gradle.util.GUtil;
+import org.gradle.util.TemporaryFolder;
+import org.gradle.util.TestFile;
+import org.gradle.util.WrapUtil;
+import org.gradle.wrapper.GradleWrapperMain;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Properties;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(org.jmock.integration.junit4.JMock.class)
+public class WrapperTest extends AbstractTaskTest {
+    public static final String TEST_TEXT = "sometext";
+    public static final String TEST_FILE_NAME = "somefile";
+
+    private Wrapper wrapper;
+    private WrapperScriptGenerator wrapperScriptGeneratorMock;
+    private String distributionPath;
+    private String targetWrapperJarPath;
+    private Mockery context = new Mockery();
+    private TestFile expectedTargetWrapperJar;
+    private File expectedTargetWrapperProperties;
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    @Before
+    public void setUp() {
+        super.setUp();
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        wrapper = createTask(Wrapper.class);
+        wrapperScriptGeneratorMock = context.mock(WrapperScriptGenerator.class);
+        wrapper.setScriptDestinationPath("scriptDestination");
+        wrapper.setGradleVersion("1.0");
+        targetWrapperJarPath = "jarPath";
+        expectedTargetWrapperJar = new TestFile(getProject().getProjectDir(),
+                targetWrapperJarPath + "/" + Wrapper.WRAPPER_JAR);
+        expectedTargetWrapperProperties = new File(getProject().getProjectDir(),
+                targetWrapperJarPath + "/" + Wrapper.WRAPPER_PROPERTIES);
+        new File(getProject().getProjectDir(), targetWrapperJarPath).mkdirs();
+        distributionPath = "somepath";
+        wrapper.setJarPath(targetWrapperJarPath);
+        wrapper.setDistributionPath(distributionPath);
+        wrapper.setUnixWrapperScriptGenerator(wrapperScriptGeneratorMock);
+    }
+
+    public AbstractTask getTask() {
+        return wrapper;
+    }
+
+    @Test
+    public void testWrapper() {
+        wrapper = createTask(Wrapper.class);
+        assertEquals("", wrapper.getJarPath());
+        assertEquals("", wrapper.getScriptDestinationPath());
+        assertEquals(Wrapper.DEFAULT_DISTRIBUTION_PARENT_NAME, wrapper.getDistributionPath());
+        assertEquals(Wrapper.DEFAULT_ARCHIVE_NAME, wrapper.getArchiveName());
+        assertEquals(Wrapper.DEFAULT_ARCHIVE_CLASSIFIER, wrapper.getArchiveClassifier());
+        assertEquals(Wrapper.DEFAULT_DISTRIBUTION_PARENT_NAME, wrapper.getArchivePath());
+        assertEquals(Wrapper.DEFAULT_URL_ROOT, wrapper.getUrlRoot());
+        assertEquals(Wrapper.PathBase.GRADLE_USER_HOME, wrapper.getDistributionBase());
+        assertEquals(Wrapper.PathBase.GRADLE_USER_HOME, wrapper.getArchiveBase());
+    }
+
+    @Test
+    public void testExecuteWithNonExistingWrapperJarParentDir() throws IOException {
+        checkExecute();
+    }
+
+    @Test
+    public void testCheckInputs() throws IOException {
+        assertThat(wrapper.getInputs().getProperties().keySet(),
+                equalTo(WrapUtil.toSet("jarPath", "archiveClassifier", "distributionBase", "archiveBase",
+                        "distributionPath", "archiveName", "urlRoot", "scriptDestinationPath", "gradleVersion", "archivePath")));
+    }
+
+    @Test
+    public void testExecuteWithExistingWrapperJarParentDirAndExistingWrapperJar() throws IOException {
+        File jarDir = new File(getProject().getProjectDir(), "lib");
+        jarDir.mkdirs();
+        File wrapperJar = new File(getProject().getProjectDir(), targetWrapperJarPath);
+        File parentFile = expectedTargetWrapperJar.getParentFile();
+        assertTrue(parentFile.isDirectory() || parentFile.mkdirs());
+        try {
+            assertTrue(expectedTargetWrapperJar.createNewFile());
+        } catch (IOException e) {
+            throw new RuntimeException(String.format("Could not create %s.", wrapperJar), e);
+        }
+        checkExecute();
+    }
+
+    private void checkExecute() throws IOException {
+        context.checking(new Expectations() {
+            {
+                one(wrapperScriptGeneratorMock).generate(
+                        targetWrapperJarPath + "/" + Wrapper.WRAPPER_JAR,
+                        targetWrapperJarPath + "/" + Wrapper.WRAPPER_PROPERTIES,
+                        new File(getProject().getProjectDir(), wrapper.getScriptDestinationPath()));
+            }
+        });
+        wrapper.execute();
+        TestFile unjarDir = tmpDir.createDir("unjar");
+        expectedTargetWrapperJar.unzipTo(unjarDir);
+        unjarDir.file(GradleWrapperMain.class.getName().replace(".", "/") + ".class").assertIsFile();
+        Properties properties = GUtil.loadProperties(expectedTargetWrapperProperties);
+        assertEquals(properties.getProperty(Wrapper.URL_ROOT_PROPERTY), wrapper.getUrlRoot());
+        assertEquals(properties.getProperty(Wrapper.DISTRIBUTION_BASE_PROPERTY), wrapper.getDistributionBase().toString());
+        assertEquals(properties.getProperty(Wrapper.DISTRIBUTION_PATH_PROPERTY), wrapper.getDistributionPath());
+        assertEquals(properties.getProperty(Wrapper.DISTRIBUTION_NAME_PROPERTY), wrapper.getArchiveName());
+        assertEquals(properties.getProperty(Wrapper.DISTRIBUTION_CLASSIFIER_PROPERTY), wrapper.getArchiveClassifier());
+        assertEquals(properties.getProperty(Wrapper.DISTRIBUTION_VERSION_PROPERTY), wrapper.getGradleVersion());
+        assertEquals(properties.getProperty(Wrapper.ZIP_STORE_BASE_PROPERTY), wrapper.getArchiveBase().toString());
+        assertEquals(properties.getProperty(Wrapper.ZIP_STORE_PATH_PROPERTY), wrapper.getArchivePath());
+    }
+}
\ No newline at end of file
diff --git a/subprojects/gradle-wrapper/src/test/groovy/org/gradle/wrapper/DownloadTest.groovy b/subprojects/gradle-wrapper/src/test/groovy/org/gradle/wrapper/DownloadTest.groovy
new file mode 100644
index 0000000..019f112
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/test/groovy/org/gradle/wrapper/DownloadTest.groovy
@@ -0,0 +1,53 @@
+/*
+ * 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.wrapper
+
+import org.gradle.util.TemporaryFolder
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+class DownloadTest {
+    Download download
+    File testDir
+    File downloadFile
+    File rootDir
+    String sourceRoot
+    File remoteFile
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    @Before public void setUp()  {
+        download = new Download()
+        testDir = tmpDir.dir
+        rootDir = new File(testDir, 'root')
+        downloadFile = new File(rootDir, 'file')
+        (remoteFile = new File(testDir, 'remoteFile')).write('sometext')
+        sourceRoot = "file:///$remoteFile.canonicalPath"
+    }
+
+    @Test public void testDownload() {
+        assert !downloadFile.exists()
+        download.download(sourceRoot, downloadFile)
+        assert downloadFile.exists()
+        assertEquals('sometext', downloadFile.text)
+    }
+}
diff --git a/subprojects/gradle-wrapper/src/test/groovy/org/gradle/wrapper/InstallTest.groovy b/subprojects/gradle-wrapper/src/test/groovy/org/gradle/wrapper/InstallTest.groovy
new file mode 100644
index 0000000..c31aff7
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/test/groovy/org/gradle/wrapper/InstallTest.groovy
@@ -0,0 +1,164 @@
+/*
+ * 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.wrapper
+
+import org.gradle.api.tasks.wrapper.Wrapper.PathBase
+import org.gradle.util.TemporaryFolder
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import static org.junit.Assert.*
+
+/**
+ * @author Hans Dockter
+ */
+class InstallTest {
+    File testDir
+    Install install
+    String testZipBase
+    String testZipPath
+    String testDistBase
+    String testDistPath
+    String testDistName
+    String testDistVersion
+    String testDistClassifier
+
+    String urlRoot
+    IDownload downloadMock
+    PathAssembler pathAssemblerMock;
+    boolean downloadCalled
+    File zip
+    File distributionDir
+    File zipStore
+    File gradleScript
+    File gradleHomeDir
+    File zipDestination
+    @Rule
+    public TemporaryFolder tmpDir = new TemporaryFolder();
+
+    @Before public void setUp() {
+        downloadCalled = false
+        testDir = tmpDir.dir
+        testZipBase = PathBase.PROJECT.toString()
+        testZipPath = 'someZipPath'
+        testDistBase = PathBase.GRADLE_USER_HOME.toString()
+        testDistPath = 'someDistPath'
+        testDistName = 'gradle'
+        testDistVersion = '1.0'
+        testDistClassifier = 'clf'
+        distributionDir = new File(testDir, testDistPath)
+        gradleHomeDir = new File(distributionDir, "$testDistName-$testDistVersion")
+        zipStore = new File(testDir, 'zips');
+        zipDestination = new File(zipStore, "$testDistName-$testDistVersion-$testDistClassifier" + ".zip")
+        urlRoot = 'file://./tmpTest'
+        install = new Install(false, false, createDownloadMock(), createPathAssemblerMock())
+    }
+
+    IDownload createDownloadMock() {
+        [download: {String url, File destination ->
+            assertEquals("$urlRoot/$testDistName-${testDistVersion}-${testDistClassifier}.zip" as String, url)
+            assertEquals(zipDestination.getAbsolutePath() + '.part', destination.getAbsolutePath())
+            zip = createTestZip()
+            downloadCalled = true
+        }] as IDownload
+    }
+
+    PathAssembler createPathAssemblerMock() {
+        [gradleHome: {String distBase, String distPath, String distName, String distVersion ->
+            assertEquals(testDistBase, distBase)
+            assertEquals(testDistPath, distPath)
+            assertEquals(testDistName, distName)
+            assertEquals(testDistVersion, distVersion)
+            gradleHomeDir.getAbsolutePath()},
+         distZip: { String zipBase, String zipPath, String distName, String distVersion, String distClassifier ->
+            assertEquals(testZipBase, zipBase)
+            assertEquals(testZipPath, zipPath)
+            assertEquals(testDistName, distName)
+            assertEquals(testDistVersion, distVersion)
+            assertEquals(testDistClassifier, distClassifier)
+            zipDestination.getAbsolutePath()
+        }] as PathAssembler
+    }
+
+    @Test public void testInit() {
+        assert !install.alwaysDownload
+        assert !install.alwaysUnpack
+    }
+
+    File createTestZip() {
+        File explodedZipDir = new File(testDir, 'explodedZip')
+        File binDir = new File(explodedZipDir, 'bin')
+        binDir.mkdirs()
+        gradleScript = new File(binDir, 'gradle')
+        gradleScript.write('something')
+        zipStore.mkdirs()
+        AntBuilder antBuilder = new AntBuilder()
+        antBuilder.zip(destfile: zipDestination.absolutePath + '.part') {
+            zipfileset(dir: explodedZipDir, prefix: "$testDistName-$testDistVersion")
+        }
+        (zipDestination.absolutePath + '.part') as File
+    }
+
+    @Test public void testCreateDist() {
+        assertEquals(gradleHomeDir.absolutePath, install.createDist(urlRoot, testDistBase, testDistPath, testDistName, testDistVersion, testDistClassifier, testZipBase, testZipPath))
+        assert downloadCalled
+        assert distributionDir.isDirectory()
+        assert zipDestination.exists()
+        assert gradleScript.exists()
+//        assert new File(gradleHomeDir, "bin/gradle").canExecute()
+    }
+
+    @Test public void testCreateDistWithExistingRoot() {
+        distributionDir.mkdirs()
+        install.createDist(urlRoot, testDistBase, testDistPath, testDistName, testDistVersion, testDistClassifier, testZipBase, testZipPath)
+        assert downloadCalled
+        assert gradleHomeDir.isDirectory()
+        assert gradleScript.exists()
+    }
+
+    @Test public void testCreateDistWithExistingDist() {
+        gradleHomeDir.mkdirs()
+        long lastModified = gradleHomeDir.lastModified()
+        install.createDist(urlRoot, testDistBase, testDistPath, testDistName, testDistVersion, testDistClassifier, testZipBase, testZipPath)
+        assert !downloadCalled
+        assert lastModified == gradleHomeDir.lastModified()
+    }
+
+    @Test public void testCreateDistWithExistingDistAndZipAndAlwaysUnpackTrue() {
+        install = new Install(false, true, createDownloadMock(), createPathAssemblerMock())
+        createTestZip().renameTo(zipDestination)
+        gradleHomeDir.mkdirs()
+        File testFile = new File(gradleHomeDir, 'testfile')
+        install.createDist(urlRoot, testDistBase, testDistPath, testDistName, testDistVersion, testDistClassifier, testZipBase, testZipPath)
+        assert distributionDir.isDirectory()
+        assert gradleScript.exists()
+        assert !testFile.exists()
+        assert !downloadCalled
+    }
+
+    @Test public void testCreateDistWithExistingZipAndDistAndAlwaysDownloadTrue() {
+        install = new Install(true, false, createDownloadMock(), createPathAssemblerMock())
+        createTestZip().renameTo(zipDestination)
+        distributionDir.mkdirs()
+        File testFile = new File(gradleHomeDir, 'testfile')
+        install.createDist(urlRoot, testDistBase, testDistPath, testDistName, testDistVersion, testDistClassifier, testZipBase, testZipPath)
+        assert gradleHomeDir.isDirectory()
+        assert gradleScript.exists()
+        assert !testFile.exists()
+        assert downloadCalled
+    }
+}
diff --git a/subprojects/gradle-wrapper/src/test/groovy/org/gradle/wrapper/PathAssemblerTest.java b/subprojects/gradle-wrapper/src/test/groovy/org/gradle/wrapper/PathAssemblerTest.java
new file mode 100644
index 0000000..0b8cfee
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/test/groovy/org/gradle/wrapper/PathAssemblerTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.wrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Hans Dockter
+ */
+public class PathAssemblerTest {
+    public static final String TEST_GRADLE_USER_HOME = "someUserHome";
+    private PathAssembler pathAssembler;
+    private String testPath;
+    private String testName;
+    private String testVersion;
+    private String testClassifier;
+
+    @Before
+    public void setUp() {
+        pathAssembler = new PathAssembler(TEST_GRADLE_USER_HOME);
+        testPath = "somepath";
+        testName = "somename";
+        testVersion = "someversion";
+        testClassifier = "someclassifier";
+    }
+
+    @Test
+    public void gradleHomeWithGradleUserHomeBase() {
+        String gradleHome = pathAssembler.gradleHome(PathAssembler.GRADLE_USER_HOME_STRING, testPath, testName,
+                testVersion);
+        assertEquals(TEST_GRADLE_USER_HOME + "/" + testPath + "/" + testName + "-" + testVersion, gradleHome);
+    }
+
+    @Test
+    public void gradleHomeWithProjectBase() {
+        String gradleHome = pathAssembler.gradleHome(PathAssembler.PROJECT_STRING, testPath, testName, testVersion);
+        assertEquals(currentDirPath() + "/" + testPath + "/" + testName + "-" + testVersion, gradleHome);
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void gradleHomeWithUnknownBase() {
+        pathAssembler.gradleHome("unknownBase", testPath, testName, testVersion);
+    }
+
+    @Test
+    public void distZipWithGradleUserHomeBase() {
+        String gradleHome = pathAssembler.distZip(PathAssembler.GRADLE_USER_HOME_STRING, testPath, testName, testVersion, testClassifier);
+        assertEquals(TEST_GRADLE_USER_HOME + "/" + testPath + "/" + testName + "-" + testVersion + "-" + testClassifier + ".zip", gradleHome);
+    }
+
+    @Test
+    public void distZipWithProjectBase() {
+        String gradleHome = pathAssembler.distZip(PathAssembler.PROJECT_STRING, testPath, testName, testVersion, testClassifier);
+        assertEquals(currentDirPath() + "/" + testPath + "/" + testName + "-" + testVersion + "-" + testClassifier + ".zip", gradleHome);
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void distZipWithUnknownBase() {
+        pathAssembler.distZip("unknownBase", testPath, testName, testVersion, testClassifier);
+    }
+
+    private String currentDirPath() {
+        return System.getProperty("user.dir");
+    }
+}
diff --git a/subprojects/gradle-wrapper/src/test/groovy/org/gradle/wrapper/SystemPropertiesHandlerTest.groovy b/subprojects/gradle-wrapper/src/test/groovy/org/gradle/wrapper/SystemPropertiesHandlerTest.groovy
new file mode 100644
index 0000000..fc53d84
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/test/groovy/org/gradle/wrapper/SystemPropertiesHandlerTest.groovy
@@ -0,0 +1,48 @@
+/*
+ * 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.wrapper
+
+import org.gradle.util.TemporaryFolder
+import org.junit.Rule
+import spock.lang.Specification
+
+/**
+ * @author Hans Dockter
+ */
+class SystemPropertiesHandlerTest extends Specification {
+    @Rule
+    TemporaryFolder tmpDir = new TemporaryFolder()
+
+    def parsesCommandLineProperties() {
+        expect:
+        ['a.b': 'c', d: '', e: '', f: 'g'] == SystemPropertiesHandler.getSystemProperties(['-Da.b=c', 'arg', '-Pa=v', '-D', '-Dd', '-De=', '-Df=g'] as String[])
+    }
+
+    def parsesPropertiesFile() {
+        File propFile = tmpDir.file('props')
+        Properties props = new Properties()
+        props.putAll a: 'b', 'systemProp.c': 'd', 'systemProp.': 'e'
+        props.store(new FileOutputStream(propFile), "")
+        
+        expect:
+        [c: 'd'] == SystemPropertiesHandler.getSystemProperties(propFile)
+    }
+
+    def ifNoPropertyFileExistShouldReturnEmptyMap() {
+        expect:
+        [:] == SystemPropertiesHandler.getSystemProperties(tmpDir.file('unknown'))
+    }
+}
diff --git a/subprojects/gradle-wrapper/src/test/groovy/org/gradle/wrapper/WrapperTest.java b/subprojects/gradle-wrapper/src/test/groovy/org/gradle/wrapper/WrapperTest.java
new file mode 100644
index 0000000..2543666
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/test/groovy/org/gradle/wrapper/WrapperTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.wrapper;
+
+import org.jmock.Expectations;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.jmock.lib.legacy.ClassImposteriser;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * @author Hans Dockter
+ */
+ at RunWith(org.jmock.integration.junit4.JMock.class)
+public class WrapperTest {
+    Wrapper wrapper;
+    BootstrapMainStarter bootstrapMainStarterMock;
+    Install installMock;
+
+    JUnit4Mockery context = new JUnit4Mockery();
+
+    private File propertiesDir = new File("propertiesDir");
+    private File propertiesFile;
+
+    @Before
+    public void setUp() throws IOException {
+        context.setImposteriser(ClassImposteriser.INSTANCE);
+        wrapper = new Wrapper();
+        bootstrapMainStarterMock = context.mock(BootstrapMainStarter.class);
+        installMock = context.mock(Install.class);
+        propertiesDir.mkdirs();
+        propertiesFile = new File(propertiesDir, "wrapper.properties");
+        Properties testProperties = new Properties();
+        testProperties.load(WrapperTest.class.getResourceAsStream("/org/gradle/wrapper/wrapper.properties"));
+        testProperties.store(new FileOutputStream(propertiesFile), null);
+        System.setProperty(Wrapper.WRAPPER_PROPERTIES_PROPERTY, propertiesFile.getCanonicalPath());
+    }
+
+    @After
+    public void tearDown() {
+        propertiesFile.delete();
+        propertiesDir.delete();
+        System.getProperties().remove(Wrapper.WRAPPER_PROPERTIES_PROPERTY);
+    }
+
+    @Test
+    public void execute() throws Exception {
+        final String[] expectedArgs = { "arg1", "arg2" };
+        final int expectedExitValue = 5;
+        final String expectedGradleHome = "somepath";
+        context.checking(new Expectations() {{
+          one(installMock).createDist(
+                  "test" + Wrapper.URL_ROOT_PROPERTY,
+                  "test" + Wrapper.DISTRIBUTION_BASE_PROPERTY,
+                  "test" + Wrapper.DISTRIBUTION_PATH_PROPERTY,
+                  "test" + Wrapper.DISTRIBUTION_NAME_PROPERTY,
+                  "test" + Wrapper.DISTRIBUTION_VERSION_PROPERTY,
+                  "test" + Wrapper.DISTRIBUTION_CLASSIFIER_PROPERTY,
+                  "test" + Wrapper.ZIP_STORE_BASE_PROPERTY,
+                  "test" + Wrapper.ZIP_STORE_PATH_PROPERTY
+          ); will(returnValue(expectedGradleHome));
+          one(bootstrapMainStarterMock).start(expectedArgs, expectedGradleHome, "test" + Wrapper.DISTRIBUTION_VERSION_PROPERTY);
+        }});
+        wrapper.execute(expectedArgs, installMock, bootstrapMainStarterMock);
+    }
+}
diff --git a/subprojects/gradle-wrapper/src/test/resources/org/gradle/wrapper/wrapper.properties b/subprojects/gradle-wrapper/src/test/resources/org/gradle/wrapper/wrapper.properties
new file mode 100644
index 0000000..2abba00
--- /dev/null
+++ b/subprojects/gradle-wrapper/src/test/resources/org/gradle/wrapper/wrapper.properties
@@ -0,0 +1,8 @@
+urlRoot=testurlRoot
+distributionBase=testdistributionBase
+zipStoreBase=testzipStoreBase
+distributionPath=testdistributionPath
+zipStorePath=testzipStorePath
+distributionName=testdistributionName
+distributionClassifier=testdistributionClassifier
+distributionVersion=testdistributionVersion
diff --git a/subprojects/gradle-wrapper/wrapper.gradle b/subprojects/gradle-wrapper/wrapper.gradle
new file mode 100644
index 0000000..9ca7d06
--- /dev/null
+++ b/subprojects/gradle-wrapper/wrapper.gradle
@@ -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.
+ */
+apply plugin: 'groovy'
+
+dependencies {
+    groovy libraries.groovy_depends
+    compile project(':core')
+
+    compile libraries.commons_io, libraries.ant, libraries.ant_nodeps
+
+    testCompile project(path: ':core', configuration: 'testFixtures')
+    testRuntime project(path: ':core', configuration: 'testFixturesRuntime')
+}
+
+jar {
+    exclude 'org/gradle/wrapper/**'
+}
+
+task wrapperJar(type: Jar) {
+    dependsOn compileJava, processResources
+    from sourceSets.main.classesDir
+    include 'org/gradle/wrapper/**'
+    archiveName = 'gradle-wrapper.jar'
+    destinationDir = sourceSets.main.classesDir
+    manifest.mainAttributes("Main-Class": 'org.gradle.wrapper.GradleWrapperMain')
+}
+
+classes {
+    dependsOn wrapperJar
+}
diff --git a/wrapper/archetype.gradle b/wrapper/archetype.gradle
new file mode 100644
index 0000000..bfdcfad
--- /dev/null
+++ b/wrapper/archetype.gradle
@@ -0,0 +1,5 @@
+task archetype << {
+    sourceSets.all.each { sourceSet ->
+        allSource.sourceTrees.srcDirs.flatten().each { project.mkdir(it) }
+    }
+}
\ No newline at end of file
diff --git a/wrapper/gradle-wrapper.properties b/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..6ed33e0
--- /dev/null
+++ b/wrapper/gradle-wrapper.properties
@@ -0,0 +1,25 @@
+#
+# 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.
+#
+
+#Wed Nov 18 15:48:45 EST 2009
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+distributionVersion=0.9-20100727090849+0200
+zipStorePath=wrapper/dists
+urlRoot=http\://gradle.artifactoryonline.com/gradle/distributions/gradle-snapshots
+distributionName=gradle
+distributionClassifier=bin

-- 
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